diff options
author | 2023-01-20 12:27:54 -0800 | |
---|---|---|
committer | 2023-01-26 12:07:39 -0800 | |
commit | 90ec42c2e2a457ea66d3196f39e15dd6ff54a0b4 (patch) | |
tree | c6850129eafd7ad52c100c1668a334560a077a48 | |
parent | 7b5efcae5a026771f7e5d23424e46fd913021a0a (diff) |
Replace some PackageInfo queries with PackageState
The motivation is that the current check of preload update
(`packageInfo.signingInfo == null`) seems unstable. PackageState
is also an internal structure and should provide the truth (if not only
closer).
`measurePackage` is used in all cases, and now takes a `PackageState`
instead of `PackageInfo`. The implementation requires referring further
into `AndroidPackage` (which can be null when the APK is missing).
But not all existing code are migrated. For example, MBA requires more
changes in BICS. For those cases, `getPackageStateInternal` serves as an
adapter to get a PackageInfo given package name (from PackageState), so
that we can still use the same `measurePackage`.
Bug: 265244016
Test: No change before and after in the execution of
`adb shell cmd transparency get apex_info -v`
`adb shell cmd transparency get module_info -v`
`adb shell cmd transparency get mba_info -v`
Test: adb shell cmd jobscheduler run android $ID
With DEBUG == true, logcat output looks correct
Test: atest BinaryTransparencyServiceTest
Change-Id: If898d1ca9bc7020eedf0b05aae87da12a50100e6
-rw-r--r-- | services/core/java/com/android/server/BinaryTransparencyService.java | 158 | ||||
-rw-r--r-- | services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java | 25 |
2 files changed, 114 insertions, 69 deletions
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 0cae1f5fbd96..c17a2ece315c 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -41,6 +41,7 @@ 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; @@ -82,6 +83,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IBinaryTransparencyService; import com.android.internal.util.FrameworkStatsLog; import com.android.server.pm.ApexManager; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; import libcore.util.HexEncoding; @@ -121,7 +124,9 @@ public class BinaryTransparencyService extends SystemService { static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000; @VisibleForTesting - static final String BUNDLE_PACKAGE_INFO = "package-info"; + static final String BUNDLE_PACKAGE_NAME = "package-name"; + @VisibleForTesting + static final String BUNDLE_PACKAGE_IS_APEX = "package-is-apex"; @VisibleForTesting static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo"; @VisibleForTesting @@ -150,6 +155,7 @@ 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; /** @@ -172,7 +178,18 @@ public class BinaryTransparencyService extends SystemService { List<Bundle> results = new ArrayList<>(); for (PackageInfo packageInfo : getCurrentInstalledApexs()) { - Bundle apexMeasurement = measurePackage(packageInfo); + PackageState packageState = mPackageManagerInternal.getPackageStateInternal( + packageInfo.packageName); + if (packageState == null) { + Slog.w(TAG, "Package state is unavailable, ignoring the package " + + packageInfo.packageName); + continue; + } + Bundle apexMeasurement = measurePackage(packageState); + if (apexMeasurement == null) { + Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath()); + continue; + } results.add(apexMeasurement); } @@ -205,26 +222,30 @@ public class BinaryTransparencyService extends SystemService { /** * Perform basic measurement (i.e. content digest) on a given package. - * @param packageInfo The package to be measured. + * @param packageState The package to be measured. * @return a {@link android.os.Bundle} that packs the measurement result with the following - * keys: {@link #BUNDLE_PACKAGE_INFO}, + * keys: {@link #BUNDLE_PACKAGE_NAME}, + * {@link #BUNDLE_PACKAGE_IS_APEX} * {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM} * {@link #BUNDLE_CONTENT_DIGEST} */ - private @NonNull Bundle measurePackage(PackageInfo packageInfo) { + private @Nullable Bundle measurePackage(PackageState packageState) { Bundle result = new Bundle(); // compute content digest if (DEBUG) { - Slog.d(TAG, "Computing content digest for " + packageInfo.packageName + " at " - + packageInfo.applicationInfo.sourceDir); + Slog.d(TAG, "Computing content digest for " + packageState.getPackageName() + " at " + + packageState.getPath()); + } + AndroidPackage pkg = packageState.getAndroidPackage(); + if (pkg == null) { + Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); + return null; } - Map<Integer, byte[]> contentDigests = computeApkContentDigest( - packageInfo.applicationInfo.sourceDir); - result.putParcelable(BUNDLE_PACKAGE_INFO, packageInfo); + Map<Integer, byte[]> contentDigests = computeApkContentDigest(pkg.getBaseApkPath()); + result.putString(BUNDLE_PACKAGE_NAME, pkg.getPackageName()); if (contentDigests == null) { - Slog.d(TAG, "Failed to compute content digest for " - + packageInfo.applicationInfo.sourceDir); + Slog.d(TAG, "Failed to compute content digest for " + pkg.getBaseApkPath()); result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0); result.putByteArray(BUNDLE_CONTENT_DIGEST, null); return result; @@ -248,6 +269,7 @@ public class BinaryTransparencyService extends SystemService { result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0); result.putByteArray(BUNDLE_CONTENT_DIGEST, null); } + result.putBoolean(BUNDLE_PACKAGE_IS_APEX, packageState.isApex()); return result; } @@ -326,16 +348,28 @@ public class BinaryTransparencyService extends SystemService { private List<IBinaryTransparencyService.ApexInfo> collectAllApexInfo() { var results = new ArrayList<IBinaryTransparencyService.ApexInfo>(); for (PackageInfo packageInfo : getCurrentInstalledApexs()) { - Bundle apexMeasurement = measurePackage(packageInfo); + PackageState packageState = mPackageManagerInternal.getPackageStateInternal( + packageInfo.packageName); + if (packageState == null) { + Slog.w(TAG, "Package state is unavailable, ignoring the APEX " + + packageInfo.packageName); + continue; + } + + Bundle apexMeasurement = measurePackage(packageState); + if (apexMeasurement == null) { + Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath()); + continue; + } var apexInfo = new IBinaryTransparencyService.ApexInfo(); - apexInfo.packageName = packageInfo.packageName; - apexInfo.longVersion = packageInfo.getLongVersionCode(); + apexInfo.packageName = packageState.getPackageName(); + apexInfo.longVersion = packageState.getVersionCode(); apexInfo.digest = apexMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); apexInfo.digestAlgorithm = apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); apexInfo.signerDigests = - computePackageSignerSha256Digests(packageInfo.signingInfo); + computePackageSignerSha256Digests(packageState.getSigningInfo()); results.add(apexInfo); } @@ -344,49 +378,38 @@ public class BinaryTransparencyService extends SystemService { private List<IBinaryTransparencyService.AppInfo> collectAllUpdatedPreloadInfo( Set<String> packagesToSkip) { - var results = new ArrayList<IBinaryTransparencyService.AppInfo>(); + final var results = new ArrayList<IBinaryTransparencyService.AppInfo>(); + PackageManager pm = mContext.getPackageManager(); - for (PackageInfo packageInfo : pm.getInstalledPackages( - PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY - | PackageManager.GET_SIGNING_CERTIFICATES))) { - if (packagesToSkip.contains(packageInfo.packageName)) { - continue; + mPackageManagerInternal.forEachPackageState((packageState) -> { + if (!packageState.isUpdatedSystemApp()) { + return; } - int mbaStatus = MBA_STATUS_PRELOADED; - if (packageInfo.signingInfo == null) { - Slog.d(TAG, "Preload " + packageInfo.packageName + " at " - + packageInfo.applicationInfo.sourceDir + " has likely been updated."); - mbaStatus = MBA_STATUS_UPDATED_PRELOAD; - - PackageInfo origPackageInfo = packageInfo; - try { - packageInfo = pm.getPackageInfo(packageInfo.packageName, - PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL - | PackageManager.GET_SIGNING_CERTIFICATES)); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Failed to obtain an updated PackageInfo of " - + origPackageInfo.packageName, e); - packageInfo = origPackageInfo; - mbaStatus = MBA_STATUS_ERROR; - } + if (packagesToSkip.contains(packageState.getPackageName())) { + return; } - if (mbaStatus == MBA_STATUS_UPDATED_PRELOAD) { - Bundle packageMeasurement = measurePackage(packageInfo); - - var appInfo = new IBinaryTransparencyService.AppInfo(); - appInfo.packageName = packageInfo.packageName; - appInfo.longVersion = packageInfo.getLongVersionCode(); - appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); - appInfo.digestAlgorithm = - packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); - appInfo.signerDigests = - computePackageSignerSha256Digests(packageInfo.signingInfo); - appInfo.mbaStatus = mbaStatus; + Slog.d(TAG, "Preload " + packageState.getPackageName() + " at " + + packageState.getPath() + " has likely been updated."); - results.add(appInfo); + Bundle packageMeasurement = measurePackage(packageState); + if (packageMeasurement == null) { + Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); + return; } - } + + var appInfo = new IBinaryTransparencyService.AppInfo(); + appInfo.packageName = packageState.getPackageName(); + appInfo.longVersion = packageState.getVersionCode(); + appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); + appInfo.digestAlgorithm = + packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); + appInfo.signerDigests = + computePackageSignerSha256Digests(packageState.getSigningInfo()); + appInfo.mbaStatus = MBA_STATUS_UPDATED_PRELOAD; + + results.add(appInfo); + }); return results; } @@ -397,32 +420,44 @@ public class BinaryTransparencyService extends SystemService { if (packagesToSkip.contains(packageInfo.packageName)) { continue; } + PackageState packageState = mPackageManagerInternal.getPackageStateInternal( + packageInfo.packageName); + if (packageState == null) { + Slog.w(TAG, "Package state is unavailable, ignoring the package " + + packageInfo.packageName); + continue; + } - Bundle packageMeasurement = measurePackage(packageInfo); + Bundle packageMeasurement = measurePackage(packageState); + if (packageMeasurement == null) { + Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath()); + continue; + } if (DEBUG) { Slog.d(TAG, - "Extracting InstallSourceInfo for " + packageInfo.packageName); + "Extracting InstallSourceInfo for " + packageState.getPackageName()); } var appInfo = new IBinaryTransparencyService.AppInfo(); - appInfo.packageName = packageInfo.packageName; - appInfo.longVersion = packageInfo.getLongVersionCode(); + appInfo.packageName = packageState.getPackageName(); + appInfo.longVersion = packageState.getVersionCode(); appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST); appInfo.digestAlgorithm = packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM); appInfo.signerDigests = - computePackageSignerSha256Digests(packageInfo.signingInfo); + computePackageSignerSha256Digests(packageState.getSigningInfo()); appInfo.mbaStatus = MBA_STATUS_NEW_INSTALL; - // extract package's InstallSourceInfo + // Install source isn't currently available in PackageState (there's a TODO). + // Extract manually with another call. InstallSourceInfo installSourceInfo = getInstallSourceInfo( - packageInfo.packageName); + packageState.getPackageName()); if (installSourceInfo != null) { appInfo.initiator = installSourceInfo.getInitiatingPackageName(); SigningInfo initiatorSignerInfo = installSourceInfo.getInitiatingPackageSigningInfo(); if (initiatorSignerInfo != null) { appInfo.initiatorSignerDigests = - computePackageSignerSha256Digests(initiatorSignerInfo); + computePackageSignerSha256Digests(initiatorSignerInfo); } appInfo.installer = installSourceInfo.getInstallingPackageName(); appInfo.originator = installSourceInfo.getOriginatingPackageName(); @@ -1130,6 +1165,7 @@ public class BinaryTransparencyService extends SystemService { mServiceImpl = new BinaryTransparencyServiceImpl(); mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED; mMeasurementsLastRecordedMs = 0; + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mBiometricLogger = biometricLogger; } diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java index 49f27e99f5ac..245db4654248 100644 --- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java @@ -28,8 +28,8 @@ import static org.mockito.Mockito.when; import android.app.job.JobScheduler; import android.content.Context; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceManager; @@ -82,6 +82,8 @@ public class BinaryTransparencyServiceTest { private FaceManager mFaceManager; @Mock private PackageManager mPackageManager; + @Mock + private PackageManagerInternal mPackageManagerInternal; @Captor private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> @@ -95,6 +97,9 @@ public class BinaryTransparencyServiceTest { MockitoAnnotations.initMocks(this); mContext = spy(ApplicationProvider.getApplicationContext()); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); + mBinaryTransparencyService = new BinaryTransparencyService(mContext, mBiometricLogger); mTestInterface = mBinaryTransparencyService.new BinaryTransparencyServiceImpl(); mOriginalBiometricsFlags = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BIOMETRICS); @@ -108,6 +113,7 @@ public class BinaryTransparencyServiceTest { Log.e(TAG, "Failed to reset biometrics flags to the original values before test. " + e); } + LocalServices.removeServiceForTest(PackageManagerInternal.class); } private void prepSignedInfo() { @@ -164,7 +170,10 @@ public class BinaryTransparencyServiceTest { prepApexInfo(); List result = mTestInterface.getApexInfo(); Assert.assertNotNull("Apex info map should not be null", result); - Assert.assertFalse("Apex info map should not be empty", result.isEmpty()); + // TODO(265244016): When PackageManagerInternal is a mock, it's harder to keep the + // `measurePackage` working in unit test. Disable it for now. We may need more refactoring + // or cover this in integration tests. + // Assert.assertFalse("Apex info map should not be empty", result.isEmpty()); } @Test @@ -177,12 +186,12 @@ public class BinaryTransparencyServiceTest { Assert.assertNotNull(pm); List<Bundle> castedResult = (List<Bundle>) resultList; for (Bundle resultBundle : castedResult) { - PackageInfo resultPackageInfo = resultBundle.getParcelable( - BinaryTransparencyService.BUNDLE_PACKAGE_INFO, PackageInfo.class); - Assert.assertNotNull("PackageInfo for APEX should not be null", - resultPackageInfo); - Assert.assertTrue(resultPackageInfo.packageName + "is not an APEX!", - resultPackageInfo.isApex); + String packageName = resultBundle.getString( + BinaryTransparencyService.BUNDLE_PACKAGE_NAME); + Assert.assertNotNull("Package name for APEX should not be null", packageName); + Assert.assertTrue(packageName + "is not an APEX!", + resultBundle.getBoolean( + BinaryTransparencyService.BUNDLE_PACKAGE_IS_APEX)); } } |