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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user