diff options
| author | 2024-10-21 23:51:13 +0000 | |
|---|---|---|
| committer | 2024-10-21 23:51:13 +0000 | |
| commit | 17db3e7b2cfebb1da96faf5af5977c16a0468cf8 (patch) | |
| tree | 3bf6da7cf8dc5c982d9b3f0b5b0e8a344446aa69 | |
| parent | 56ec45a3d9e33ea3385892f0af58336a18d50b80 (diff) | |
| parent | 7e28dff226b28edcf254cb6f6bdd4be9bc799280 (diff) | |
Merge "[Forensic] Add BackupTransportConnector" into main
8 files changed, 357 insertions, 2 deletions
diff --git a/core/java/android/security/forensic/ForensicEvent.aidl b/core/java/android/security/forensic/ForensicEvent.aidl new file mode 100644 index 000000000000..a321fb0cb939 --- /dev/null +++ b/core/java/android/security/forensic/ForensicEvent.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.forensic; + +/** {@hide} */ +parcelable ForensicEvent; diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java new file mode 100644 index 000000000000..9cbc5ecea962 --- /dev/null +++ b/core/java/android/security/forensic/ForensicEvent.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.forensic; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.security.Flags; +import android.util.ArrayMap; + +import java.util.Map; + +/** + * A class that represents a forensic event. + * @hide + */ +@FlaggedApi(Flags.FLAG_AFL_API) +public final class ForensicEvent implements Parcelable { + private static final String TAG = "ForensicEvent"; + + @NonNull + private final String mType; + + @NonNull + private final Map<String, String> mKeyValuePairs; + + public static final @NonNull Parcelable.Creator<ForensicEvent> CREATOR = + new Parcelable.Creator<>() { + public ForensicEvent createFromParcel(Parcel in) { + return new ForensicEvent(in); + } + + public ForensicEvent[] newArray(int size) { + return new ForensicEvent[size]; + } + }; + + public ForensicEvent(@NonNull String type, @NonNull Map<String, String> keyValuePairs) { + mType = type; + mKeyValuePairs = keyValuePairs; + } + + private ForensicEvent(@NonNull Parcel in) { + mType = in.readString(); + mKeyValuePairs = new ArrayMap<>(in.readInt()); + in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class); + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(mType); + out.writeInt(mKeyValuePairs.size()); + out.writeMap(mKeyValuePairs); + } + + @FlaggedApi(Flags.FLAG_AFL_API) + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "ForensicEvent{" + + "mType=" + mType + + ", mKeyValuePairs=" + mKeyValuePairs + + '}'; + } +} diff --git a/core/java/android/security/forensic/IBackupTransport.aidl b/core/java/android/security/forensic/IBackupTransport.aidl new file mode 100644 index 000000000000..c2cbc83ba1b3 --- /dev/null +++ b/core/java/android/security/forensic/IBackupTransport.aidl @@ -0,0 +1,41 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.forensic; +import android.security.forensic.ForensicEvent; + +import com.android.internal.infra.AndroidFuture; + +/** {@hide} */ +oneway interface IBackupTransport { + /** + * Initialize the server side. + */ + void initialize(in AndroidFuture<int> resultFuture); + + /** + * Send forensic logging data to the backup destination. + * The data is a list of ForensicEvent. + * The ForensicEvent is an abstract class that represents + * different type of events. + */ + void addData(in List<ForensicEvent> events, in AndroidFuture<int> resultFuture); + + /** + * Release the binder to the server. + */ + void release(in AndroidFuture<int> resultFuture); +} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 41981715a855..42ac90dd8066 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -7175,4 +7175,7 @@ <string name="identity_check_settings_action"></string> <!-- Package for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] --> <string name="identity_check_settings_package_name">com\u002eandroid\u002esettings</string> + + <!-- The name of the service for forensic backup transport. --> + <string name="config_forensicBackupTransport" translatable="false"></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 0b2b3453f20a..dfee85a3788d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5636,4 +5636,7 @@ <!-- Identity check strings --> <java-symbol type="string" name="identity_check_settings_action" /> <java-symbol type="string" name="identity_check_settings_package_name" /> + + <!-- Forensic backup transport --> + <java-symbol type="string" name="config_forensicBackupTransport" /> </resources> diff --git a/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java b/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java new file mode 100644 index 000000000000..caca011b6549 --- /dev/null +++ b/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.security.forensic; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.security.forensic.ForensicEvent; +import android.security.forensic.IBackupTransport; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.infra.AndroidFuture; + +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class BackupTransportConnection implements ServiceConnection { + private static final String TAG = "BackupTransportConnection"; + private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins + private final Context mContext; + private String mForensicBackupTransportConfig; + volatile IBackupTransport mService; + + public BackupTransportConnection(Context context) { + mContext = context; + mService = null; + } + + /** + * Initialize the BackupTransport binder service. + * @return Whether the initialization succeed. + */ + public boolean initialize() { + if (!bindService()) { + return false; + } + AndroidFuture<Integer> resultFuture = new AndroidFuture<>(); + try { + mService.initialize(resultFuture); + } catch (RemoteException e) { + Slog.e(TAG, "Remote Exception", e); + unbindService(); + return false; + } + Integer result = getFutureResult(resultFuture); + if (result != null && result == 0) { + return true; + } else { + unbindService(); + return false; + } + } + + /** + * Add data to the BackupTransport binder service. + * @param data List of ForensicEvent. + * @return Whether the data is added to the binder service. + */ + public boolean addData(List<ForensicEvent> data) { + AndroidFuture<Integer> resultFuture = new AndroidFuture<>(); + try { + mService.addData(data, resultFuture); + } catch (RemoteException e) { + Slog.e(TAG, "Remote Exception", e); + return false; + } + Integer result = getFutureResult(resultFuture); + return result != null && result == 0; + } + + /** + * Release the BackupTransport binder service. + */ + public void release() { + AndroidFuture<Integer> resultFuture = new AndroidFuture<>(); + try { + mService.release(resultFuture); + } catch (RemoteException e) { + Slog.e(TAG, "Remote Exception", e); + } finally { + unbindService(); + } + } + + private <T> T getFutureResult(AndroidFuture<T> future) { + try { + return future.get(FUTURE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException + | CancellationException e) { + Slog.w(TAG, "Failed to get result from transport:", e); + return null; + } + } + + private boolean bindService() { + mForensicBackupTransportConfig = mContext.getString( + com.android.internal.R.string.config_forensicBackupTransport); + if (TextUtils.isEmpty(mForensicBackupTransportConfig)) { + return false; + } + + ComponentName serviceComponent = + ComponentName.unflattenFromString(mForensicBackupTransportConfig); + if (serviceComponent == null) { + return false; + } + + Intent intent = new Intent().setComponent(serviceComponent); + boolean result = mContext.bindServiceAsUser( + intent, this, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM); + if (!result) { + unbindService(); + } + return result; + } + + private void unbindService() { + mContext.unbindService(this); + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mService = IBackupTransport.Stub.asInterface(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + } +} diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java index 07639d1a3945..20c648eb61c2 100644 --- a/services/core/java/com/android/server/security/forensic/ForensicService.java +++ b/services/core/java/com/android/server/security/forensic/ForensicService.java @@ -63,6 +63,7 @@ public class ForensicService extends SystemService { private final Context mContext; private final Handler mHandler; + private final BackupTransportConnection mBackupTransportConnection; private final BinderService mBinderService; private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>(); @@ -77,6 +78,7 @@ public class ForensicService extends SystemService { super(injector.getContext()); mContext = injector.getContext(); mHandler = new EventHandler(injector.getLooper(), this); + mBackupTransportConnection = injector.getBackupTransportConnection(); mBinderService = new BinderService(this); } @@ -221,6 +223,10 @@ public class ForensicService extends SystemService { private void enable(IForensicServiceCommandCallback callback) throws RemoteException { switch (mState) { case STATE_VISIBLE: + if (!mBackupTransportConnection.initialize()) { + callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE); + break; + } mState = STATE_ENABLED; notifyStateMonitors(); callback.onSuccess(); @@ -236,6 +242,7 @@ public class ForensicService extends SystemService { private void disable(IForensicServiceCommandCallback callback) throws RemoteException { switch (mState) { case STATE_ENABLED: + mBackupTransportConnection.release(); mState = STATE_VISIBLE; notifyStateMonitors(); callback.onSuccess(); @@ -266,6 +273,8 @@ public class ForensicService extends SystemService { Context getContext(); Looper getLooper(); + + BackupTransportConnection getBackupTransportConnection(); } private static final class InjectorImpl implements Injector { @@ -289,6 +298,11 @@ public class ForensicService extends SystemService { serviceThread.start(); return serviceThread.getLooper(); } + + @Override + public BackupTransportConnection getBackupTransportConnection() { + return new BackupTransportConnection(mContext); + } } } diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java index 7aa2e0f609a7..2b55303bd89f 100644 --- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java +++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java @@ -19,6 +19,9 @@ package com.android.server.security.forensic; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; import android.annotation.SuppressLint; import android.content.Context; @@ -50,7 +53,8 @@ public class ForensicServiceTest { IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; @Mock - private Context mContextSpy; + private Context mContext; + private BackupTransportConnection mBackupTransportConnection; private ForensicService mForensicService; private TestLooper mTestLooper; @@ -63,7 +67,7 @@ public class ForensicServiceTest { mTestLooper = new TestLooper(); mLooper = mTestLooper.getLooper(); - mForensicService = new ForensicService(new MockInjector(mContextSpy)); + mForensicService = new ForensicService(new MockInjector(mContext)); mForensicService.onStart(); } @@ -253,6 +257,8 @@ public class ForensicServiceTest { assertEquals(STATE_VISIBLE, scb1.mState); assertEquals(STATE_VISIBLE, scb2.mState); + doReturn(true).when(mBackupTransportConnection).initialize(); + CommandCallback ccb = new CommandCallback(); mForensicService.getBinderService().enable(ccb); mTestLooper.dispatchAll(); @@ -262,6 +268,29 @@ public class ForensicServiceTest { } @Test + public void testEnable_FromVisible_TwoMonitors_BackupTransportUnavailable() + throws RemoteException { + mForensicService.setState(STATE_VISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + + doReturn(false).when(mBackupTransportConnection).initialize(); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().enable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + assertNotNull(ccb.mErrorCode); + assertEquals(ERROR_BACKUP_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue()); + } + + @Test public void testEnable_FromEnabled_TwoMonitors() throws RemoteException { mForensicService.setState(STATE_ENABLED); StateCallback scb1 = new StateCallback(); @@ -330,6 +359,8 @@ public class ForensicServiceTest { assertEquals(STATE_ENABLED, scb1.mState); assertEquals(STATE_ENABLED, scb2.mState); + doNothing().when(mBackupTransportConnection).release(); + CommandCallback ccb = new CommandCallback(); mForensicService.getBinderService().disable(ccb); mTestLooper.dispatchAll(); @@ -356,6 +387,12 @@ public class ForensicServiceTest { return mLooper; } + @Override + public BackupTransportConnection getBackupTransportConnection() { + mBackupTransportConnection = spy(new BackupTransportConnection(mContext)); + return mBackupTransportConnection; + } + } private static class StateCallback extends IForensicServiceStateCallback.Stub { |