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
This commit is contained in:
Billy Lau
2023-01-18 23:29:18 -06:00
parent aadefe0a6a
commit 2294380462

View File

@@ -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: