From 229438046209a898a8371f309b1244149efcde9d Mon Sep 17 00:00:00 2001 From: Billy Lau Date: Wed, 18 Jan 2023 23:29:18 -0600 Subject: [PATCH] BinaryTransparencyService: Add package update observers. Previously, an observer for APK updates was added/registered. However, there are several challenges that we have to overcome in order to cover updates to non-staged APEXs. In the end we are making use of broadcast receivers to listen to `Intent.ACTION_PACKAGE_ADDED` and further filtering out updates from new installation, and only focusing on packages that we are interested in. This helps us cover non-staged APEXs. Additionally, we are adding a new observer to hook into staged APEXs so that our coverage is now complete. Other minor adjustments: - refactor all package update hooks into helper methods and change its call from `onStart` to `onBootPhase`. Bug: 264428429 Test: Manual. Pull and re-install preloaded app and APEXs to simulate updates and watch logcat for corresponding entry. Change-Id: Iaf41ae53976ee9df03f8631168bef1c4ceebe192 --- .../server/BinaryTransparencyService.java | 154 +++++++++++++++--- 1 file changed, 129 insertions(+), 25 deletions(-) diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index faa69381d8afc..8f594a5a378a2 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -27,15 +27,20 @@ import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.compat.annotation.ChangeId; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApexStagedEvent; import android.content.pm.ApplicationInfo; import android.content.pm.IBackgroundInstallControlService; +import android.content.pm.IPackageManagerNative; +import android.content.pm.IStagedApexObserver; import android.content.pm.InstallSourceInfo; import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; @@ -51,6 +56,7 @@ import android.hardware.face.FaceSensorProperties; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -142,7 +148,6 @@ public class BinaryTransparencyService extends SystemService { private String mVbmetaDigest; // the system time (in ms) the last measurement was taken private long mMeasurementsLastRecordedMs; - private PackageManagerInternal mPackageManagerInternal; private BiometricLogger mBiometricLogger; /** @@ -1103,7 +1108,6 @@ public class BinaryTransparencyService extends SystemService { mServiceImpl = new BinaryTransparencyServiceImpl(); mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED; mMeasurementsLastRecordedMs = 0; - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mBiometricLogger = biometricLogger; } @@ -1119,28 +1123,6 @@ public class BinaryTransparencyService extends SystemService { } catch (Throwable t) { Slog.e(TAG, "Failed to start BinaryTransparencyService.", t); } - - // register a package observer to detect updates to preloads - mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageChanged(String packageName, int uid) { - // check if the updated package is a preloaded app. - PackageManager pm = mContext.getPackageManager(); - try { - pm.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of( - PackageManager.MATCH_FACTORY_ONLY)); - } catch (PackageManager.NameNotFoundException e) { - // this means that this package is not a preloaded app - return; - } - - Slog.d(TAG, "Preload " + packageName + " was updated. Scheduling measurement..."); - UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext, - BinaryTransparencyService.this); - } - }); - - // TODO(b/264428429): Register observer for updates to APEXs. } /** @@ -1172,6 +1154,8 @@ public class BinaryTransparencyService extends SystemService { Slog.i(TAG, "Scheduling measurements to be taken."); UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext, BinaryTransparencyService.this); + + registerAllPackageUpdateObservers(); } } @@ -1415,6 +1399,126 @@ public class BinaryTransparencyService extends SystemService { FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest); } + /** + * Listen for APK updates. + * + * There are two ways available to us to do this: + * 1. Register an observer using + * {@link PackageManagerInternal#getPackageList(PackageManagerInternal.PackageListObserver)}. + * 2. Register broadcast receivers, listening to either {@code ACTION_PACKAGE_ADDED} or + * {@code ACTION_PACKAGE_REPLACED}. + * + * After experimentation, we found that Option #1 does not catch updates to non-staged APEXs. + * Thus, we are implementing Option #2 here. More specifically, listening to + * {@link Intent#ACTION_PACKAGE_ADDED} allows us to capture all events we care about. + * + * We did not use {@link Intent#ACTION_PACKAGE_REPLACED} because it unfortunately does not + * detect updates to non-staged APEXs. Thus, we rely on {@link Intent#EXTRA_REPLACING} to + * filter out new installation from updates instead. + */ + private void registerApkAndNonStagedApexUpdateListener() { + Slog.d(TAG, "Registering APK & Non-Staged APEX updates..."); + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addDataScheme("package"); // this is somehow necessary + mContext.registerReceiver(new PackageUpdatedReceiver(), filter); + } + + /** + * Listen for staged-APEX updates. + * + * This method basically covers cases that are not caught by + * {@link #registerApkAndNonStagedApexUpdateListener()}, namely updates to APEXs that are staged + * for the subsequent reboot. + */ + private void registerStagedApexUpdateObserver() { + Slog.d(TAG, "Registering APEX updates..."); + IPackageManagerNative iPackageManagerNative = IPackageManagerNative.Stub.asInterface( + ServiceManager.getService("package_native")); + if (iPackageManagerNative == null) { + Slog.e(TAG, "IPackageManagerNative is null"); + return; + } + + try { + iPackageManagerNative.registerStagedApexObserver(new IStagedApexObserver.Stub() { + @Override + public void onApexStaged(ApexStagedEvent event) throws RemoteException { + Slog.d(TAG, "A new APEX has been staged for update. There are currently " + + event.stagedApexModuleNames.length + " APEX(s) staged for update. " + + "Scheduling measurement..."); + UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext, + BinaryTransparencyService.this); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register a StagedApexObserver."); + } + } + + private boolean isPackagePreloaded(String packageName) { + PackageManager pm = mContext.getPackageManager(); + try { + pm.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of( + PackageManager.MATCH_FACTORY_ONLY)); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return true; + } + + private boolean isPackageAnApex(String packageName) { + PackageManager pm = mContext.getPackageManager(); + try { + PackageInfo packageInfo = pm.getPackageInfo(packageName, + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX)); + return packageInfo.isApex; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private class PackageUpdatedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) { + return; + } + + Uri data = intent.getData(); + if (data == null) { + Slog.e(TAG, "Shouldn't happen: intent data is null!"); + return; + } + + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + Slog.d(TAG, "Not an update. Skipping..."); + return; + } + + String packageName = data.getSchemeSpecificPart(); + // now we've got to check what package is this + if (isPackagePreloaded(packageName) || isPackageAnApex(packageName)) { + Slog.d(TAG, packageName + " was updated. Scheduling measurement..."); + UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext, + BinaryTransparencyService.this); + } + } + } + + /** + * Register observers for APK and APEX updates. The current implementation breaks this process + * into 2 cases/methods because PackageManager does not offer a unified interface to register + * for all package updates in a universal and comprehensive manner. + * Thus, the observers will be invoked when either + * i) APK or non-staged APEX update; or + * ii) APEX staging happens. + * This will then be used as signals to schedule measurement for the relevant binaries. + */ + private void registerAllPackageUpdateObservers() { + registerApkAndNonStagedApexUpdateListener(); + registerStagedApexUpdateObserver(); + } + private String translateContentDigestAlgorithmIdToString(int algorithmId) { switch (algorithmId) { case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256: