• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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