Add BinaryTransparencyService as new SystemService.

Bug: 197684182

Created a new system service that aggregates information about:
1) signed partitions on device
2) APEXs installed on device
3) Modules installed on device

Introduces new adb shell commands as below:
adb shell cmd transparency get image_info [-a]
adb shell cmd transparency get apex_info [-v]
adb shell cmd transparency get module_info [-v]

Test: Built and tested locally on a bramble - the device boots.
Issuing adb shell commands as above provides correct outputs.

Change-Id: I2cae1bd794456688779c70c65b3f5ec8bcd7c6b3
This commit is contained in:
Billy Lau
2022-01-20 15:42:17 -08:00
parent 5a6ec22676
commit d1fb08be0a
10 changed files with 676 additions and 0 deletions

View File

@@ -28,6 +28,11 @@ filegroup {
srcs: ["com/android/internal/os/IDropBoxManagerService.aidl"],
}
filegroup {
name: "IBinaryTransparencyService.aidl",
srcs: ["com/android/internal/os/IBinaryTransparencyService.aidl"],
}
filegroup {
name: "ITracingServiceProxy.aidl",
srcs: ["android/tracing/ITracingServiceProxy.aidl"],

View File

@@ -212,6 +212,7 @@ import android.telecom.TelecomManager;
import android.telephony.MmsManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyRegistryManager;
import android.transparency.BinaryTransparencyManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -242,6 +243,7 @@ import com.android.internal.app.ISoundTriggerService;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.graphics.fonts.IFontManager;
import com.android.internal.net.INetworkWatchlistManager;
import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
import com.android.internal.util.Preconditions;
@@ -500,6 +502,17 @@ public final class SystemServiceRegistry {
return new DropBoxManager(ctx, service);
}});
registerService(Context.BINARY_TRANSPARENCY_SERVICE, BinaryTransparencyManager.class,
new CachedServiceFetcher<BinaryTransparencyManager>() {
@Override
public BinaryTransparencyManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(
Context.BINARY_TRANSPARENCY_SERVICE);
IBinaryTransparencyService service = IBinaryTransparencyService.Stub.asInterface(b);
return new BinaryTransparencyManager(ctx, service);
}});
registerService(Context.INPUT_SERVICE, InputManager.class,
new StaticServiceFetcher<InputManager>() {
@Override

View File

@@ -5075,6 +5075,16 @@ public abstract class Context {
*/
public static final String DROPBOX_SERVICE = "dropbox";
/**
* System service name for BinaryTransparencyService. This is used to retrieve measurements
* pertaining to various pre-installed and system binaries on device for the purposes of
* providing transparency to the user.
*
* @hide
*/
@SuppressLint("ServiceName")
public static final String BINARY_TRANSPARENCY_SERVICE = "transparency";
/**
* System service name for the DeviceIdleManager.
* @see #getSystemService(String)

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.transparency;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.os.IBinaryTransparencyService;
import java.util.Map;
/**
* BinaryTransparencyManager defines a number of system interfaces that other system apps or
* services can make use of, when trying to get more information about the various binaries
* that are installed on this device.
* @hide
*/
@SystemService(Context.BINARY_TRANSPARENCY_SERVICE)
public class BinaryTransparencyManager {
private static final String TAG = "TransparencyManager";
private final Context mContext;
private final IBinaryTransparencyService mService;
/**
* Constructor
* @param context The calling context.
* @param service A valid instance of IBinaryTransparencyService.
* @hide
*/
public BinaryTransparencyManager(Context context, IBinaryTransparencyService service) {
mContext = context;
mService = service;
}
/**
* Obtains a string containing information that describes the signed images that are installed
* on this device. Currently, this piece of information is identified as the VBMeta digest.
* @return A String containing the VBMeta Digest of the signed partitions loaded on this device.
*/
@NonNull
public String getSignedImageInfo() {
try {
return mService.getSignedImageInfo();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns a map of all installed APEXs consisting of package name to SHA256 hash of the
* package.
* @return A Map with the following entries: {apex package name : sha256 digest of package}
*/
@NonNull
public Map getApexInfo() {
try {
Slog.d(TAG, "Calling backend's getApexInfo()");
return mService.getApexInfo();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -0,0 +1,4 @@
# Bug component: 36824
billylau@google.com
vishwath@google.com
mpgroover@google.com

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
/**
* "Backend" interface used by {@link android.os.BinaryTransparencyManager} to talk to the
* BinaryTransparencyService that actually implements the measurement and information aggregation
* functionality.
*
* @see BinaryTransparencyManager
*/
interface IBinaryTransparencyService {
String getSignedImageInfo();
Map getApexInfo();
}

View File

@@ -2,6 +2,7 @@ per-file *Power* = file:/services/core/java/com/android/server/power/OWNERS
per-file *Zygote* = file:/ZYGOTE_OWNERS
per-file *Cpu* = file:CPU_OWNERS
per-file *Binder* = file:BINDER_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
# BatteryStats
per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS

View File

@@ -0,0 +1,525 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemProperties;
import android.util.PackageUtils;
import android.util.Slog;
import com.android.internal.os.IBinaryTransparencyService;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @hide
*/
public class BinaryTransparencyService extends SystemService {
private static final String TAG = "TransparencyService";
private static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized";
private static final String VBMETA_DIGEST_UNAVAILABLE = "vbmeta-digest-unavailable";
private static final String SYSPROP_NAME_VBETA_DIGEST = "ro.boot.vbmeta.digest";
private static final String BINARY_HASH_ERROR = "SHA256HashError";
private final Context mContext;
private String mVbmetaDigest;
private HashMap<String, String> mBinaryHashes;
private HashMap<String, Long> mBinaryLastUpdateTimes;
final class BinaryTransparencyServiceImpl extends IBinaryTransparencyService.Stub {
@Override
public String getSignedImageInfo() {
return mVbmetaDigest;
}
@Override
public Map getApexInfo() {
HashMap results = new HashMap();
if (!updateBinaryMeasurements()) {
Slog.e(TAG, "Error refreshing APEX measurements.");
return results;
}
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "Error obtaining an instance of PackageManager.");
return results;
}
for (PackageInfo packageInfo : getInstalledApexs()) {
results.put(packageInfo, mBinaryHashes.get(packageInfo.packageName));
}
return results;
}
@Override
public void onShellCommand(@Nullable FileDescriptor in,
@Nullable FileDescriptor out,
@Nullable FileDescriptor err,
@NonNull String[] args,
@Nullable ShellCallback callback,
@NonNull ResultReceiver resultReceiver) throws RemoteException {
(new ShellCommand() {
private int printSignedImageInfo() {
final PrintWriter pw = getOutPrintWriter();
boolean listAllPartitions = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-a":
listAllPartitions = true;
break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
final String signedImageInfo = getSignedImageInfo();
pw.println("Image Info:");
pw.println(Build.FINGERPRINT);
pw.println(signedImageInfo);
pw.println("");
if (listAllPartitions) {
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
pw.println("ERROR: Failed to obtain an instance of package manager.");
return -1;
}
pw.println("Other partitions:");
List<Build.Partition> buildPartitions = Build.getFingerprintedPartitions();
for (Build.Partition buildPartition : buildPartitions) {
pw.println("Name: " + buildPartition.getName());
pw.println("Fingerprint: " + buildPartition.getFingerprint());
pw.println("Build time (ms): " + buildPartition.getBuildTimeMillis());
}
}
return 0;
}
private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
pw.println("--- Module Details ---");
pw.println("Module name: " + moduleInfo.getName());
pw.println("Module visibility: "
+ (moduleInfo.isHidden() ? "hidden" : "visible"));
}
private int printAllApexs() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
// refresh cache to make sure info is most up-to-date
if (!updateBinaryMeasurements()) {
pw.println("ERROR: Failed to refresh info for APEXs.");
return -1;
}
if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
pw.println("ERROR: Unable to obtain apex_info at this time.");
return -1;
}
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
verbose = true;
break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
pw.println("ERROR: Failed to obtain an instance of package manager.");
return -1;
}
pw.println("APEX Info:");
for (PackageInfo packageInfo : getInstalledApexs()) {
String packageName = packageInfo.packageName;
pw.println(packageName + ";"
+ packageInfo.getLongVersionCode() + ":"
+ mBinaryHashes.get(packageName).toLowerCase());
if (verbose) {
pw.println("Install location: "
+ packageInfo.applicationInfo.sourceDir);
pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime);
ModuleInfo moduleInfo;
try {
moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
pw.println("Is A Module: False");
pw.println("");
continue;
}
pw.println("Is A Module: True");
printModuleDetails(moduleInfo, pw);
pw.println("");
}
}
return 0;
}
private int printAllModules() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
// refresh cache to make sure info is most up-to-date
if (!updateBinaryMeasurements()) {
pw.println("ERROR: Failed to refresh info for Modules.");
return -1;
}
if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
pw.println("ERROR: Unable to obtain module_info at this time.");
return -1;
}
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
verbose = true;
break;
default:
pw.println("ERROR: Unknown option: " + opt);
return 1;
}
}
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
pw.println("ERROR: Failed to obtain an instance of package manager.");
return -1;
}
pw.println("Module Info:");
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.MATCH_APEX);
pw.println(packageInfo.packageName + ";"
+ packageInfo.getLongVersionCode() + ":"
+ mBinaryHashes.get(packageName).toLowerCase());
if (verbose) {
pw.println("Install location: "
+ packageInfo.applicationInfo.sourceDir);
printModuleDetails(module, pw);
pw.println("");
}
} catch (PackageManager.NameNotFoundException e) {
pw.println(packageName);
pw.println(packageName
+ ";ERROR:Unable to find PackageInfo for this module.");
if (verbose) {
printModuleDetails(module, pw);
pw.println("");
}
continue;
}
}
return 0;
}
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
final PrintWriter pw = getOutPrintWriter();
switch (cmd) {
case "get": {
final String infoType = getNextArg();
if (infoType == null) {
printHelpMenu();
return -1;
}
switch (infoType) {
case "image_info":
return printSignedImageInfo();
case "apex_info":
return printAllApexs();
case "module_info":
return printAllModules();
default:
pw.println(String.format("ERROR: Unknown info type '%s'",
infoType));
return 1;
}
}
default:
return handleDefaultCommands(cmd);
}
}
private void printHelpMenu() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Transparency manager (transparency) commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
pw.println(" get image_info [-a]");
pw.println(" Print information about loaded image (firmware). Options:");
pw.println(" -a: lists all other identifiable partitions.");
pw.println("");
pw.println(" get apex_info [-v]");
pw.println(" Print information about installed APEXs on device.");
pw.println(" -v: lists more verbose information about each APEX");
pw.println("");
pw.println(" get module_info [-v]");
pw.println(" Print information about installed modules on device.");
pw.println(" -v: lists more verbose information about each module");
pw.println("");
}
@Override
public void onHelp() {
printHelpMenu();
}
}).exec(this, in, out, err, args, callback, resultReceiver);
}
}
private final BinaryTransparencyServiceImpl mServiceImpl;
public BinaryTransparencyService(Context context) {
super(context);
mContext = context;
mServiceImpl = new BinaryTransparencyServiceImpl();
mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
mBinaryHashes = new HashMap<>();
mBinaryLastUpdateTimes = new HashMap<>();
}
/**
* Called when the system service should publish a binder service using
* {@link #publishBinderService(String, IBinder).}
*/
@Override
public void onStart() {
try {
publishBinderService(Context.BINARY_TRANSPARENCY_SERVICE, mServiceImpl);
Slog.i(TAG, "Started BinaryTransparencyService");
} catch (Throwable t) {
Slog.e(TAG, "Failed to start BinaryTransparencyService.", t);
}
}
/**
* Called on each phase of the boot process. Phases before the service's start phase
* (as defined in the @Service annotation) are never received.
*
* @param phase The current boot phase.
*/
@Override
public void onBootPhase(int phase) {
// we are only interested in doing things at PHASE_BOOT_COMPLETED
if (phase == PHASE_BOOT_COMPLETED) {
// due to potentially long computation that holds up boot time, apex sha computations
// are deferred to first call
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
}
}
private void getVBMetaDigestInformation() {
mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
}
@NonNull
private List<PackageInfo> getInstalledApexs() {
List<PackageInfo> results = new ArrayList<PackageInfo>();
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "Error obtaining an instance of PackageManager.");
return results;
}
List<PackageInfo> allPackages = pm.getInstalledPackages(
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
if (allPackages == null) {
Slog.e(TAG, "Error obtaining installed packages (including APEX)");
return results;
}
results = allPackages.stream().filter(p -> p.isApex).collect(Collectors.toList());
return results;
}
/**
* Updates the internal data structure with the most current APEX measurements.
* @return true if update is successful; false otherwise.
*/
private boolean updateBinaryMeasurements() {
if (mBinaryHashes.size() == 0) {
Slog.d(TAG, "No apex in cache yet.");
doFreshBinaryMeasurements();
return true;
}
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "Failed to obtain a valid PackageManager instance.");
return false;
}
// We're assuming updates to existing modules and APEXs can happen, but not brand new
// ones appearing out of the blue. Thus, we're going to only go through our cache to check
// for changes, rather than freshly invoking `getInstalledPackages()` and
// `getInstalledModules()`
for (Map.Entry<String, Long> entry : mBinaryLastUpdateTimes.entrySet()) {
String packageName = entry.getKey();
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
long cachedUpdateTime = entry.getValue();
if (packageInfo.lastUpdateTime > cachedUpdateTime) {
Slog.d(TAG, packageName + " has been updated!");
entry.setValue(packageInfo.lastUpdateTime);
// compute the digest for the updated package
String sha256digest = computeSha256DigestOfFile(
packageInfo.applicationInfo.sourceDir);
if (sha256digest == null) {
Slog.e(TAG, "Failed to compute SHA256sum for file at "
+ packageInfo.applicationInfo.sourceDir);
mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
} else {
mBinaryHashes.put(packageName, sha256digest);
}
}
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Could not find package with name " + packageName);
continue;
}
}
return true;
}
private void doFreshBinaryMeasurements() {
PackageManager pm = mContext.getPackageManager();
Slog.d(TAG, "Obtained package manager");
// In general, we care about all APEXs, *and* all Modules, which may include some APKs.
// First, we deal with all installed APEXs.
for (PackageInfo packageInfo : getInstalledApexs()) {
ApplicationInfo appInfo = packageInfo.applicationInfo;
// compute SHA256 for these APEXs
String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir);
if (sha256digest == null) {
Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
packageInfo.packageName));
mBinaryHashes.put(packageInfo.packageName, BINARY_HASH_ERROR);
} else {
mBinaryHashes.put(packageInfo.packageName, sha256digest);
}
Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName,
packageInfo.lastUpdateTime));
mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
}
// Next, get all installed modules from PackageManager - skip over those APEXs we've
// processed above
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
if (packageName == null) {
Slog.e(TAG, "ERROR: Encountered null package name for module "
+ module.getApexModuleName());
continue;
}
if (mBinaryHashes.containsKey(module.getPackageName())) {
continue;
}
// get PackageInfo for this module
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
ApplicationInfo appInfo = packageInfo.applicationInfo;
// compute SHA256 digest for these modules
String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir);
if (sha256digest == null) {
Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
packageName));
mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
} else {
mBinaryHashes.put(packageName, sha256digest);
}
Slog.d(TAG, String.format("Last update time for %s: %d", packageName,
packageInfo.lastUpdateTime));
mBinaryLastUpdateTimes.put(packageName, packageInfo.lastUpdateTime);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "ERROR: Could not obtain PackageInfo for package name: "
+ packageName);
continue;
}
}
}
@Nullable
private String computeSha256DigestOfFile(@NonNull String pathToFile) {
File apexFile = new File(pathToFile);
try {
byte[] apexFileBytes = Files.readAllBytes(apexFile.toPath());
return PackageUtils.computeSha256Digest(apexFileBytes);
} catch (IOException e) {
Slog.e(TAG, String.format("I/O error occurs when reading from %s", pathToFile));
return null;
}
}
}

View File

@@ -22,6 +22,7 @@ per-file BatteryService.java = file:platform/hardware/interfaces:/health/aidl/OW
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppOp* = file:/core/java/android/permission/OWNERS
per-file *Battery* = file:/BATTERY_STATS_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS

View File

@@ -1456,6 +1456,10 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(KeyChainSystemService.class);
t.traceEnd();
t.traceBegin("StartBinaryTransparencyService");
mSystemServiceManager.startService(BinaryTransparencyService.class);
t.traceEnd();
t.traceBegin("StartSchedulingPolicyService");
ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
t.traceEnd();