1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SuppressLint; 22 import android.app.job.JobInfo; 23 import android.app.job.JobParameters; 24 import android.app.job.JobScheduler; 25 import android.app.job.JobService; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.ModuleInfo; 30 import android.content.pm.PackageInfo; 31 import android.content.pm.PackageManager; 32 import android.os.Build; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.os.ResultReceiver; 36 import android.os.ServiceManager; 37 import android.os.ShellCallback; 38 import android.os.ShellCommand; 39 import android.os.SystemProperties; 40 import android.util.PackageUtils; 41 import android.util.Slog; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.os.IBinaryTransparencyService; 45 import com.android.internal.util.FrameworkStatsLog; 46 47 import java.io.FileDescriptor; 48 import java.io.PrintWriter; 49 import java.util.ArrayList; 50 import java.util.HashMap; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.concurrent.Executors; 54 import java.util.stream.Collectors; 55 56 /** 57 * @hide 58 */ 59 public class BinaryTransparencyService extends SystemService { 60 private static final String TAG = "TransparencyService"; 61 private static final String EXTRA_SERVICE = "service"; 62 63 @VisibleForTesting 64 static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized"; 65 @VisibleForTesting 66 static final String VBMETA_DIGEST_UNAVAILABLE = "vbmeta-digest-unavailable"; 67 @VisibleForTesting 68 static final String SYSPROP_NAME_VBETA_DIGEST = "ro.boot.vbmeta.digest"; 69 70 @VisibleForTesting 71 static final String BINARY_HASH_ERROR = "SHA256HashError"; 72 73 private final Context mContext; 74 private String mVbmetaDigest; 75 private HashMap<String, String> mBinaryHashes; 76 private HashMap<String, Long> mBinaryLastUpdateTimes; 77 78 final class BinaryTransparencyServiceImpl extends IBinaryTransparencyService.Stub { 79 80 @Override getSignedImageInfo()81 public String getSignedImageInfo() { 82 return mVbmetaDigest; 83 } 84 85 @Override getApexInfo()86 public Map getApexInfo() { 87 HashMap results = new HashMap(); 88 if (!updateBinaryMeasurements()) { 89 Slog.e(TAG, "Error refreshing APEX measurements."); 90 return results; 91 } 92 PackageManager pm = mContext.getPackageManager(); 93 if (pm == null) { 94 Slog.e(TAG, "Error obtaining an instance of PackageManager."); 95 return results; 96 } 97 98 for (PackageInfo packageInfo : getInstalledApexs()) { 99 results.put(packageInfo, mBinaryHashes.get(packageInfo.packageName)); 100 } 101 102 return results; 103 } 104 105 @Override onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver)106 public void onShellCommand(@Nullable FileDescriptor in, 107 @Nullable FileDescriptor out, 108 @Nullable FileDescriptor err, 109 @NonNull String[] args, 110 @Nullable ShellCallback callback, 111 @NonNull ResultReceiver resultReceiver) throws RemoteException { 112 (new ShellCommand() { 113 114 private int printSignedImageInfo() { 115 final PrintWriter pw = getOutPrintWriter(); 116 boolean listAllPartitions = false; 117 String opt; 118 119 while ((opt = getNextOption()) != null) { 120 switch (opt) { 121 case "-a": 122 listAllPartitions = true; 123 break; 124 default: 125 pw.println("ERROR: Unknown option: " + opt); 126 return 1; 127 } 128 } 129 130 final String signedImageInfo = getSignedImageInfo(); 131 pw.println("Image Info:"); 132 pw.println(Build.FINGERPRINT); 133 pw.println(signedImageInfo); 134 pw.println(""); 135 136 if (listAllPartitions) { 137 PackageManager pm = mContext.getPackageManager(); 138 if (pm == null) { 139 pw.println("ERROR: Failed to obtain an instance of package manager."); 140 return -1; 141 } 142 143 pw.println("Other partitions:"); 144 List<Build.Partition> buildPartitions = Build.getFingerprintedPartitions(); 145 for (Build.Partition buildPartition : buildPartitions) { 146 pw.println("Name: " + buildPartition.getName()); 147 pw.println("Fingerprint: " + buildPartition.getFingerprint()); 148 pw.println("Build time (ms): " + buildPartition.getBuildTimeMillis()); 149 } 150 } 151 return 0; 152 } 153 154 private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) { 155 pw.println("--- Module Details ---"); 156 pw.println("Module name: " + moduleInfo.getName()); 157 pw.println("Module visibility: " 158 + (moduleInfo.isHidden() ? "hidden" : "visible")); 159 } 160 161 private int printAllApexs() { 162 final PrintWriter pw = getOutPrintWriter(); 163 boolean verbose = false; 164 String opt; 165 166 // refresh cache to make sure info is most up-to-date 167 if (!updateBinaryMeasurements()) { 168 pw.println("ERROR: Failed to refresh info for APEXs."); 169 return -1; 170 } 171 if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) { 172 pw.println("ERROR: Unable to obtain apex_info at this time."); 173 return -1; 174 } 175 176 while ((opt = getNextOption()) != null) { 177 switch (opt) { 178 case "-v": 179 verbose = true; 180 break; 181 default: 182 pw.println("ERROR: Unknown option: " + opt); 183 return 1; 184 } 185 } 186 187 PackageManager pm = mContext.getPackageManager(); 188 if (pm == null) { 189 pw.println("ERROR: Failed to obtain an instance of package manager."); 190 return -1; 191 } 192 193 pw.println("APEX Info:"); 194 for (PackageInfo packageInfo : getInstalledApexs()) { 195 String packageName = packageInfo.packageName; 196 pw.println(packageName + ";" 197 + packageInfo.getLongVersionCode() + ":" 198 + mBinaryHashes.get(packageName).toLowerCase()); 199 200 if (verbose) { 201 pw.println("Install location: " 202 + packageInfo.applicationInfo.sourceDir); 203 pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime); 204 205 ModuleInfo moduleInfo; 206 try { 207 moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0); 208 } catch (PackageManager.NameNotFoundException e) { 209 pw.println("Is A Module: False"); 210 pw.println(""); 211 continue; 212 } 213 pw.println("Is A Module: True"); 214 printModuleDetails(moduleInfo, pw); 215 pw.println(""); 216 } 217 } 218 return 0; 219 } 220 221 private int printAllModules() { 222 final PrintWriter pw = getOutPrintWriter(); 223 boolean verbose = false; 224 String opt; 225 226 // refresh cache to make sure info is most up-to-date 227 if (!updateBinaryMeasurements()) { 228 pw.println("ERROR: Failed to refresh info for Modules."); 229 return -1; 230 } 231 if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) { 232 pw.println("ERROR: Unable to obtain module_info at this time."); 233 return -1; 234 } 235 236 while ((opt = getNextOption()) != null) { 237 switch (opt) { 238 case "-v": 239 verbose = true; 240 break; 241 default: 242 pw.println("ERROR: Unknown option: " + opt); 243 return 1; 244 } 245 } 246 247 PackageManager pm = mContext.getPackageManager(); 248 if (pm == null) { 249 pw.println("ERROR: Failed to obtain an instance of package manager."); 250 return -1; 251 } 252 253 pw.println("Module Info:"); 254 for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) { 255 String packageName = module.getPackageName(); 256 try { 257 PackageInfo packageInfo = pm.getPackageInfo(packageName, 258 PackageManager.MATCH_APEX); 259 pw.println(packageInfo.packageName + ";" 260 + packageInfo.getLongVersionCode() + ":" 261 + mBinaryHashes.get(packageName).toLowerCase()); 262 263 if (verbose) { 264 pw.println("Install location: " 265 + packageInfo.applicationInfo.sourceDir); 266 printModuleDetails(module, pw); 267 pw.println(""); 268 } 269 } catch (PackageManager.NameNotFoundException e) { 270 pw.println(packageName 271 + ";ERROR:Unable to find PackageInfo for this module."); 272 if (verbose) { 273 printModuleDetails(module, pw); 274 pw.println(""); 275 } 276 continue; 277 } 278 } 279 return 0; 280 } 281 282 @Override 283 public int onCommand(String cmd) { 284 if (cmd == null) { 285 return handleDefaultCommands(cmd); 286 } 287 288 final PrintWriter pw = getOutPrintWriter(); 289 switch (cmd) { 290 case "get": { 291 final String infoType = getNextArg(); 292 if (infoType == null) { 293 printHelpMenu(); 294 return -1; 295 } 296 297 switch (infoType) { 298 case "image_info": 299 return printSignedImageInfo(); 300 case "apex_info": 301 return printAllApexs(); 302 case "module_info": 303 return printAllModules(); 304 default: 305 pw.println(String.format("ERROR: Unknown info type '%s'", 306 infoType)); 307 return 1; 308 } 309 } 310 default: 311 return handleDefaultCommands(cmd); 312 } 313 } 314 315 private void printHelpMenu() { 316 final PrintWriter pw = getOutPrintWriter(); 317 pw.println("Transparency manager (transparency) commands:"); 318 pw.println(" help"); 319 pw.println(" Print this help text."); 320 pw.println(""); 321 pw.println(" get image_info [-a]"); 322 pw.println(" Print information about loaded image (firmware). Options:"); 323 pw.println(" -a: lists all other identifiable partitions."); 324 pw.println(""); 325 pw.println(" get apex_info [-v]"); 326 pw.println(" Print information about installed APEXs on device."); 327 pw.println(" -v: lists more verbose information about each APEX"); 328 pw.println(""); 329 pw.println(" get module_info [-v]"); 330 pw.println(" Print information about installed modules on device."); 331 pw.println(" -v: lists more verbose information about each module"); 332 pw.println(""); 333 } 334 335 @Override 336 public void onHelp() { 337 printHelpMenu(); 338 } 339 }).exec(this, in, out, err, args, callback, resultReceiver); 340 } 341 } 342 private final BinaryTransparencyServiceImpl mServiceImpl; 343 BinaryTransparencyService(Context context)344 public BinaryTransparencyService(Context context) { 345 super(context); 346 mContext = context; 347 mServiceImpl = new BinaryTransparencyServiceImpl(); 348 mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED; 349 mBinaryHashes = new HashMap<>(); 350 mBinaryLastUpdateTimes = new HashMap<>(); 351 } 352 353 /** 354 * Called when the system service should publish a binder service using 355 * {@link #publishBinderService(String, IBinder).} 356 */ 357 @Override onStart()358 public void onStart() { 359 try { 360 publishBinderService(Context.BINARY_TRANSPARENCY_SERVICE, mServiceImpl); 361 Slog.i(TAG, "Started BinaryTransparencyService"); 362 } catch (Throwable t) { 363 Slog.e(TAG, "Failed to start BinaryTransparencyService.", t); 364 } 365 } 366 367 /** 368 * Called on each phase of the boot process. Phases before the service's start phase 369 * (as defined in the @Service annotation) are never received. 370 * 371 * @param phase The current boot phase. 372 */ 373 @Override onBootPhase(int phase)374 public void onBootPhase(int phase) { 375 376 // we are only interested in doing things at PHASE_BOOT_COMPLETED 377 if (phase == PHASE_BOOT_COMPLETED) { 378 Slog.i(TAG, "Boot completed. Getting VBMeta Digest."); 379 getVBMetaDigestInformation(); 380 381 // due to potentially long computation that holds up boot time, computations for 382 // SHA256 digests of APEX and Module packages are scheduled here, 383 // but only executed when device is idle. 384 Slog.i(TAG, "Scheduling APEX and Module measurements to be updated."); 385 UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext, 386 BinaryTransparencyService.this); 387 } 388 } 389 390 /** 391 * JobService to update binary measurements and update internal cache. 392 */ 393 public static class UpdateMeasurementsJobService extends JobService { 394 private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID = 395 BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode(); 396 397 @Override onStartJob(JobParameters params)398 public boolean onStartJob(JobParameters params) { 399 Slog.d(TAG, "Job to update binary measurements started."); 400 if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) { 401 return false; 402 } 403 404 // we'll still update the measurements via threads to be mindful of low-end devices 405 // where this operation might take longer than expected, and so that we don't block 406 // system_server's main thread. 407 Executors.defaultThreadFactory().newThread(() -> { 408 // since we can't call updateBinaryMeasurements() directly, calling 409 // getApexInfo() achieves the same effect, and we simply discard the return 410 // value 411 412 IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE); 413 IBinaryTransparencyService iBtsService = 414 IBinaryTransparencyService.Stub.asInterface(b); 415 try { 416 iBtsService.getApexInfo(); 417 } catch (RemoteException e) { 418 Slog.e(TAG, "Updating binary measurements was interrupted.", e); 419 return; 420 } 421 jobFinished(params, false); 422 }).start(); 423 424 return true; 425 } 426 427 @Override onStopJob(JobParameters params)428 public boolean onStopJob(JobParameters params) { 429 return false; 430 } 431 432 @SuppressLint("DefaultLocale") scheduleBinaryMeasurements(Context context, BinaryTransparencyService service)433 static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) { 434 Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job"); 435 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 436 if (jobScheduler == null) { 437 Slog.e(TAG, "Failed to obtain an instance of JobScheduler."); 438 return; 439 } 440 441 final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID, 442 new ComponentName(context, UpdateMeasurementsJobService.class)) 443 .setRequiresDeviceIdle(true) 444 .setRequiresCharging(true) 445 .build(); 446 if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) { 447 Slog.e(TAG, "Failed to schedule job to update binary measurements."); 448 return; 449 } 450 Slog.d(TAG, String.format( 451 "Job %d to update binary measurements scheduled successfully.", 452 COMPUTE_APEX_MODULE_SHA256_JOB_ID)); 453 } 454 } 455 getVBMetaDigestInformation()456 private void getVBMetaDigestInformation() { 457 mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE); 458 Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest)); 459 FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest); 460 } 461 462 @NonNull getInstalledApexs()463 private List<PackageInfo> getInstalledApexs() { 464 List<PackageInfo> results = new ArrayList<>(); 465 PackageManager pm = mContext.getPackageManager(); 466 if (pm == null) { 467 Slog.e(TAG, "Error obtaining an instance of PackageManager."); 468 return results; 469 } 470 List<PackageInfo> allPackages = pm.getInstalledPackages( 471 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX)); 472 if (allPackages == null) { 473 Slog.e(TAG, "Error obtaining installed packages (including APEX)"); 474 return results; 475 } 476 477 results = allPackages.stream().filter(p -> p.isApex).collect(Collectors.toList()); 478 return results; 479 } 480 481 482 /** 483 * Updates the internal data structure with the most current APEX measurements. 484 * @return true if update is successful; false otherwise. 485 */ updateBinaryMeasurements()486 private boolean updateBinaryMeasurements() { 487 if (mBinaryHashes.size() == 0) { 488 Slog.d(TAG, "No apex in cache yet."); 489 doFreshBinaryMeasurements(); 490 return true; 491 } 492 493 PackageManager pm = mContext.getPackageManager(); 494 if (pm == null) { 495 Slog.e(TAG, "Failed to obtain a valid PackageManager instance."); 496 return false; 497 } 498 499 // We're assuming updates to existing modules and APEXs can happen, but not brand new 500 // ones appearing out of the blue. Thus, we're going to only go through our cache to check 501 // for changes, rather than freshly invoking `getInstalledPackages()` and 502 // `getInstalledModules()` 503 byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer(); 504 for (Map.Entry<String, Long> entry : mBinaryLastUpdateTimes.entrySet()) { 505 String packageName = entry.getKey(); 506 try { 507 PackageInfo packageInfo = pm.getPackageInfo(packageName, 508 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX)); 509 long cachedUpdateTime = entry.getValue(); 510 511 if (packageInfo.lastUpdateTime > cachedUpdateTime) { 512 Slog.d(TAG, packageName + " has been updated!"); 513 entry.setValue(packageInfo.lastUpdateTime); 514 515 // compute the digest for the updated package 516 String sha256digest = PackageUtils.computeSha256DigestForLargeFile( 517 packageInfo.applicationInfo.sourceDir, largeFileBuffer); 518 if (sha256digest == null) { 519 Slog.e(TAG, "Failed to compute SHA256sum for file at " 520 + packageInfo.applicationInfo.sourceDir); 521 mBinaryHashes.put(packageName, BINARY_HASH_ERROR); 522 } else { 523 mBinaryHashes.put(packageName, sha256digest); 524 } 525 526 if (packageInfo.isApex) { 527 FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, 528 packageInfo.packageName, 529 packageInfo.getLongVersionCode(), 530 mBinaryHashes.get(packageInfo.packageName)); 531 } 532 } 533 } catch (PackageManager.NameNotFoundException e) { 534 Slog.e(TAG, "Could not find package with name " + packageName); 535 continue; 536 } 537 } 538 539 return true; 540 } 541 doFreshBinaryMeasurements()542 private void doFreshBinaryMeasurements() { 543 PackageManager pm = mContext.getPackageManager(); 544 Slog.d(TAG, "Obtained package manager"); 545 546 // In general, we care about all APEXs, *and* all Modules, which may include some APKs. 547 548 // First, we deal with all installed APEXs. 549 byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer(); 550 for (PackageInfo packageInfo : getInstalledApexs()) { 551 ApplicationInfo appInfo = packageInfo.applicationInfo; 552 553 // compute SHA256 for these APEXs 554 String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir, 555 largeFileBuffer); 556 if (sha256digest == null) { 557 Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", 558 packageInfo.packageName)); 559 mBinaryHashes.put(packageInfo.packageName, BINARY_HASH_ERROR); 560 } else { 561 mBinaryHashes.put(packageInfo.packageName, sha256digest); 562 } 563 FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, packageInfo.packageName, 564 packageInfo.getLongVersionCode(), mBinaryHashes.get(packageInfo.packageName)); 565 Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName, 566 packageInfo.lastUpdateTime)); 567 mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime); 568 } 569 570 // Next, get all installed modules from PackageManager - skip over those APEXs we've 571 // processed above 572 for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) { 573 String packageName = module.getPackageName(); 574 if (packageName == null) { 575 Slog.e(TAG, "ERROR: Encountered null package name for module " 576 + module.getApexModuleName()); 577 continue; 578 } 579 if (mBinaryHashes.containsKey(module.getPackageName())) { 580 continue; 581 } 582 583 // get PackageInfo for this module 584 try { 585 PackageInfo packageInfo = pm.getPackageInfo(packageName, 586 PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX)); 587 ApplicationInfo appInfo = packageInfo.applicationInfo; 588 589 // compute SHA256 digest for these modules 590 String sha256digest = PackageUtils.computeSha256DigestForLargeFile( 591 appInfo.sourceDir, largeFileBuffer); 592 if (sha256digest == null) { 593 Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", 594 packageName)); 595 mBinaryHashes.put(packageName, BINARY_HASH_ERROR); 596 } else { 597 mBinaryHashes.put(packageName, sha256digest); 598 } 599 Slog.d(TAG, String.format("Last update time for %s: %d", packageName, 600 packageInfo.lastUpdateTime)); 601 mBinaryLastUpdateTimes.put(packageName, packageInfo.lastUpdateTime); 602 } catch (PackageManager.NameNotFoundException e) { 603 Slog.e(TAG, "ERROR: Could not obtain PackageInfo for package name: " 604 + packageName); 605 continue; 606 } 607 } 608 } 609 610 } 611