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
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 0cae1f5..c17a2ec 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.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.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 @@
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 @@
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 @@
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 @@
/**
* 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());
}
- Map<Integer, byte[]> contentDigests = computeApkContentDigest(
- packageInfo.applicationInfo.sourceDir);
- result.putParcelable(BUNDLE_PACKAGE_INFO, packageInfo);
+ AndroidPackage pkg = packageState.getAndroidPackage();
+ if (pkg == null) {
+ Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
+ return null;
+ }
+ 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 @@
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 @@
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 @@
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);
+ Slog.d(TAG, "Preload " + packageState.getPackageName() + " at "
+ + packageState.getPath() + " has likely been updated.");
- 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;
-
- 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 @@
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 @@
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 49f27e9..245db46 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 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 @@
private FaceManager mFaceManager;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
@@ -95,6 +97,9 @@
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 @@
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 @@
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 @@
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));
}
}