From 80010e6d92515b5bde5b9bd84af2368048de4ca4 Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Thu, 10 Jun 2021 08:28:29 -0700 Subject: Validate Computer functions Bug: 186654390 Add a validation function to perform some basic checks on the structure of functions that are part of the Computer interface. The checks are: 1. Every function in Computer must have an @Live annotation. The annotation indicates if the function is locked or not. 2. If function is locked then there must be an override in ComputerLocked. 3. If the function is not locked then there may not be an override in ComputerLocked. 4. Every function in ComputerLocked must be an override of a function declared in Computer. Test: atest * PackageManagerServiceTests#testComputerStructure The test is run once on the candidate image - no failures are reported. The test is then run on modified builds, exercising the following five cases, to verify that errors are properly detected. Manual testing with the following three cases: 1. A function in Computer that does not have an @LiveImplementation annotation. 2. A function in Computer that has an invalid LiveImplementation.override annotation. 3. A locked function that is not overridden in ComputerLocked. 4. A not-locked function that is is overridden in ComputerLocked. 5. A function that is defined for the first time in ComputerLocked. Change-Id: Ic34aac67fe40aa4fec2d343fe4babdf1c565ce85 --- .../android/server/pm/PackageManagerService.java | 326 ++++++++++++++------- .../server/pm/PackageManagerServiceTest.java | 179 +++++++++++ 2 files changed, 391 insertions(+), 114 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 364ac42985f1..a70a1132360a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -436,8 +436,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.nio.charset.StandardCharsets; import java.security.DigestException; import java.security.DigestInputStream; @@ -1918,14 +1920,65 @@ public class PackageManagerService extends IPackageManager.Stub } /** - * A computer provides the functional interface to the snapshot. + * A {@link Computer} provides a set of functions that can operate on live data or snapshot + * data. At this time, the {@link Computer} is implemented by the + * {@link ComputerEngine}, which is in turn extended by {@link ComputerLocked}. + * + * New functions must be added carefully. + *
    + *
  1. New functions must be true functions with respect to data collected in a + * {@link Snapshot}. Such data may never be modified from inside a {@link Computer} + * function. + *
  2. + * + *
  3. A new function must be implemented in {@link ComputerEngine}. + *
  4. + * + *
  5. A new function must be overridden in {@link ComputerLocked} if the function + * cannot safely access live data without holding the PackageManagerService lock. The + * form of the {@link ComputerLocked} function must be a single call to the + * {@link ComputerEngine} implementation, wrapped in a synchronized + * block. Functions in {@link ComputerLocked} should never include any other code. + *
  6. + * + * Care must be taken when deciding if a function should be overridden in + * {@link ComputerLocked}. The complex lock relationships of PackageManagerService + * and other managers (like PermissionManager) mean deadlock is possible. On the + * other hand, not overriding in {@link ComputerLocked} may leave a function walking + * unstable data. + * + * To coax developers to consider such issues carefully, all methods in + * {@link Computer} must be annotated with @LiveImplementation(override = + * MANDATORY) or LiveImplementation(locked = NOT_ALLOWED). A unit + * test verifies the annotation and that the annotation corresponds to the code in + * {@link ComputerEngine} and {@link ComputerLocked}. */ - private interface Computer { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected interface Computer { + + /** + * Every method must be annotated. + */ + @Target({ ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface LiveImplementation { + // A Computer method must be annotated with one of the following values: + // MANDATORY - the method must be overridden in ComputerEngineLive. The + // format of the override is a call to the super method, wrapped in a + // synchronization block. + // NOT_ALLOWED - the method may not appear in the live computer. It must + // be final in the ComputerEngine. + int MANDATORY = 1; + int NOT_ALLOWED = 2; + int override() default MANDATORY; + String rationale() default ""; + } /** * Administrative statistics: record that the snapshot has been used. Every call * to use() increments the usage counter. */ + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) default void use() { } @@ -1933,95 +1986,154 @@ public class PackageManagerService extends IPackageManager.Stub * Fetch the snapshot usage counter. * @return The number of times this snapshot was used. */ + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) default int getUsed() { return 0; } + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) @NonNull List queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) @NonNull List queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) @NonNull List queryIntentServicesInternal(Intent intent, String resolvedType, int flags, int userId, int callingUid, boolean includeInstantApps); + @LiveImplementation(override = LiveImplementation.MANDATORY) @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(Intent intent, String resolvedType, int flags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits, String pkgName, String instantAppPkgName); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ActivityInfo getActivityInfo(ComponentName component, int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ActivityInfo getActivityInfoInternal(ComponentName component, int flags, int filterCallingUid, int userId); + @LiveImplementation(override = LiveImplementation.MANDATORY) AndroidPackage getPackage(String packageName); + @LiveImplementation(override = LiveImplementation.MANDATORY) AndroidPackage getPackage(int uid); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, int filterCallingUid, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ApplicationInfo getApplicationInfo(String packageName, int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ApplicationInfo getApplicationInfoInternal(String packageName, int flags, int filterCallingUid, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ComponentName getDefaultHomeActivity(int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ComponentName getHomeActivitiesAsUser(List allHomeCandidates, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, String resolvedType, int flags, int sourceUserId, int parentUserId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) Intent getHomeIntent(); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) List getMatchingCrossProfileIntentFilters(Intent intent, String resolvedType, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) List applyPostResolutionFilter(@NonNull List resolveInfos, String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid, boolean resolveForStart, int userId, Intent intent); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) PackageInfo getPackageInfo(String packageName, int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) PackageInfo getPackageInfoInternal(String packageName, long versionCode, int flags, int filterCallingUid, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) PackageSetting getPackageSetting(String packageName); + @LiveImplementation(override = LiveImplementation.MANDATORY) PackageSetting getPackageSettingInternal(String packageName, int callingUid); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ParceledListSlice getInstalledPackages(int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter, int sourceUserId, int targetUserId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) ServiceInfo getServiceInfo(ComponentName component, int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version); + @LiveImplementation(override = LiveImplementation.MANDATORY) String getInstantAppPackageName(int callingUid); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) String resolveExternalPackageNameLPr(AndroidPackage pkg); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) String resolveInternalPackageNameLPr(String packageName, long versionCode); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) String[] getPackagesForUid(int uid); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) UserInfo getProfileParent(int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean canViewInstantApps(int callingUid, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId, int flags); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean isCallerSameApp(String packageName, int uid); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean isComponentVisibleToInstantApp(@Nullable ComponentName component); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean isComponentVisibleToInstantApp(@Nullable ComponentName component, @ComponentType int type); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, String resolvedType, int flags); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean isInstantApp(String packageName, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid, @Nullable ComponentName component, @ComponentType int componentType, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) boolean shouldFilterApplicationLocked(@NonNull SharedUserSetting sus, int callingUid, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) int checkUidPermission(String permName, int uid); + @LiveImplementation(override = LiveImplementation.MANDATORY) int getPackageUidInternal(String packageName, int flags, int userId, int callingUid); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) int updateFlagsForApplication(int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) int updateFlagsForComponent(int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) int updateFlagsForPackage(int flags, int userId); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message); + @LiveImplementation(override = LiveImplementation.NOT_ALLOWED) void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, boolean requirePermissionWhenSameUser, String message); + @LiveImplementation(override = LiveImplementation.MANDATORY) SigningDetails getSigningDetails(@NonNull String packageName); + @LiveImplementation(override = LiveImplementation.MANDATORY) SigningDetails getSigningDetails(int uid); + @LiveImplementation(override = LiveImplementation.MANDATORY) boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId); + @LiveImplementation(override = LiveImplementation.MANDATORY) boolean filterAppAccess(String packageName, int callingUid, int userId); + @LiveImplementation(override = LiveImplementation.MANDATORY) void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState); } @@ -2030,7 +2142,8 @@ public class PackageManagerService extends IPackageManager.Stub * is entirely self-contained - it has no implicit access to * PackageManagerService. */ - private static class ComputerEngine implements Computer { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected static class ComputerEngine implements Computer { // The administrative use counter. private int mUsed = 0; @@ -2076,7 +2189,7 @@ public class PackageManagerService extends IPackageManager.Stub // PackageManagerService attributes that are primitives are referenced through the // pms object directly. Primitives are the only attributes so referenced. protected final PackageManagerService mService; - protected boolean safeMode() { + private boolean safeMode() { return mService.mSafeMode; } protected ComponentName resolveComponentName() { @@ -2130,18 +2243,18 @@ public class PackageManagerService extends IPackageManager.Stub /** * Record that the snapshot was used. */ - public void use() { + public final void use() { mUsed++; } /** * Return the usage counter. */ - public int getUsed() { + public final int getUsed() { return mUsed; } - public @NonNull List queryIntentActivitiesInternal(Intent intent, + public final @NonNull List queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { @@ -2240,14 +2353,14 @@ public class PackageManagerService extends IPackageManager.Stub resolveForStart, userId, intent); } - public @NonNull List queryIntentActivitiesInternal(Intent intent, + public final @NonNull List queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, int userId) { return queryIntentActivitiesInternal( intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(), userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); } - public @NonNull List queryIntentServicesInternal(Intent intent, + public final @NonNull List queryIntentServicesInternal(Intent intent, String resolvedType, int flags, int userId, int callingUid, boolean includeInstantApps) { if (!mUserManager.exists(userId)) return Collections.emptyList(); @@ -2471,7 +2584,7 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { + public final ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId); } @@ -2481,7 +2594,7 @@ public class PackageManagerService extends IPackageManager.Stub * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ - public ActivityInfo getActivityInfoInternal(ComponentName component, int flags, + public final ActivityInfo getActivityInfoInternal(ComponentName component, int flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForComponent(flags, userId); @@ -2535,8 +2648,8 @@ public class PackageManagerService extends IPackageManager.Stub return pkg; } - public ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, - int filterCallingUid, int userId) { + public final ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, + int flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps != null) { @@ -2563,7 +2676,7 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { + public final ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId); } @@ -2573,7 +2686,7 @@ public class PackageManagerService extends IPackageManager.Stub * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ - public ApplicationInfo getApplicationInfoInternal(String packageName, int flags, + public final ApplicationInfo getApplicationInfoInternal(String packageName, int flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForApplication(flags, userId); @@ -2765,7 +2878,7 @@ public class PackageManagerService extends IPackageManager.Stub * Report the 'Home' activity which is currently set as "always use this one". If non is set * then reports the most likely home activity or null if there are more than one. */ - public ComponentName getDefaultHomeActivity(int userId) { + public final ComponentName getDefaultHomeActivity(int userId) { List allHomeCandidates = new ArrayList<>(); ComponentName cn = getHomeActivitiesAsUser(allHomeCandidates, userId); if (cn != null) { @@ -2793,7 +2906,7 @@ public class PackageManagerService extends IPackageManager.Stub return lastComponent; } - public ComponentName getHomeActivitiesAsUser(List allHomeCandidates, + public final ComponentName getHomeActivitiesAsUser(List allHomeCandidates, int userId) { Intent intent = getHomeIntent(); List resolveInfos = queryIntentActivitiesInternal(intent, null, @@ -2822,7 +2935,7 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - public CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, + public final CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, String resolvedType, int flags, int sourceUserId, int parentUserId) { if (!mUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, sourceUserId)) { @@ -2868,15 +2981,15 @@ public class PackageManagerService extends IPackageManager.Stub return result; } - public Intent getHomeIntent() { + public final Intent getHomeIntent() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); intent.addCategory(Intent.CATEGORY_DEFAULT); return intent; } - public List getMatchingCrossProfileIntentFilters(Intent intent, - String resolvedType, int userId) { + public final List getMatchingCrossProfileIntentFilters( + Intent intent, String resolvedType, int userId) { CrossProfileIntentResolver resolver = mSettings.getCrossProfileIntentResolver(userId); if (resolver != null) { return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId); @@ -2895,7 +3008,8 @@ public class PackageManagerService extends IPackageManager.Stub * @param intent * @return A filtered list of resolved activities. */ - public List applyPostResolutionFilter(@NonNull List resolveInfos, + public final List applyPostResolutionFilter( + @NonNull List resolveInfos, String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid, boolean resolveForStart, int userId, Intent intent) { final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId); @@ -3041,7 +3155,7 @@ public class PackageManagerService extends IPackageManager.Stub return resolveInfos; } - public List filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, + private List filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, int matchFlags, List candidates, CrossProfileDomainInfo xpDomainInfo, int userId) { final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0; @@ -3188,7 +3302,7 @@ public class PackageManagerService extends IPackageManager.Stub return result; } - public PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) { + public final PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) { if (!mUserManager.exists(userId)) return null; if (ps == null) { return null; @@ -3258,7 +3372,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - public PackageInfo getPackageInfo(String packageName, int flags, int userId) { + public final PackageInfo getPackageInfo(String packageName, int flags, int userId) { return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST, flags, Binder.getCallingUid(), userId); } @@ -3269,7 +3383,7 @@ public class PackageManagerService extends IPackageManager.Stub * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ - public PackageInfo getPackageInfoInternal(String packageName, long versionCode, + public final PackageInfo getPackageInfoInternal(String packageName, long versionCode, int flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForPackage(flags, userId); @@ -3340,7 +3454,7 @@ public class PackageManagerService extends IPackageManager.Stub } @Nullable - public PackageSetting getPackageSetting(String packageName) { + public final PackageSetting getPackageSetting(String packageName) { return getPackageSettingInternal(packageName, Binder.getCallingUid()); } @@ -3350,7 +3464,7 @@ public class PackageManagerService extends IPackageManager.Stub return mSettings.getPackageLPr(packageName); } - public ParceledListSlice getInstalledPackages(int flags, int userId) { + public final ParceledListSlice getInstalledPackages(int flags, int userId) { final int callingUid = Binder.getCallingUid(); if (getInstantAppPackageName(callingUid) != null) { return ParceledListSlice.emptyList(); @@ -3489,7 +3603,7 @@ public class PackageManagerService extends IPackageManager.Stub return new CrossProfileDomainInfo(forwardingInfo, highestApprovalLevel); } - public ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter, + public final ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter, int sourceUserId, int targetUserId) { ResolveInfo forwardingResolveInfo = new ResolveInfo(); final long ident = Binder.clearCallingIdentity(); @@ -3601,7 +3715,7 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { + public final ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForComponent(flags, userId); @@ -3635,7 +3749,7 @@ public class PackageManagerService extends IPackageManager.Stub } @Nullable - public SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) { + public final SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) { return getSharedLibraryInfo(name, version, mSharedLibraries, null); } @@ -3671,7 +3785,7 @@ public class PackageManagerService extends IPackageManager.Stub return ownerUid; } - public String resolveExternalPackageNameLPr(AndroidPackage pkg) { + public final String resolveExternalPackageNameLPr(AndroidPackage pkg) { if (pkg.getStaticSharedLibName() != null) { return pkg.getManifestPackageName(); } @@ -3745,7 +3859,7 @@ public class PackageManagerService extends IPackageManager.Stub return packageName; } - public String resolveInternalPackageNameLPr(String packageName, long versionCode) { + public final String resolveInternalPackageNameLPr(String packageName, long versionCode) { final int callingUid = Binder.getCallingUid(); return resolveInternalPackageNameInternalLocked(packageName, versionCode, callingUid); @@ -3766,7 +3880,7 @@ public class PackageManagerService extends IPackageManager.Stub * calls to invalidateGetPackagesForUidCache() to locate the points at * which the cache is invalidated. */ - public String[] getPackagesForUid(int uid) { + public final String[] getPackagesForUid(int uid) { return getPackagesForUidInternal(uid, Binder.getCallingUid()); } @@ -3807,7 +3921,7 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - public UserInfo getProfileParent(int userId) { + public final UserInfo getProfileParent(int userId) { final long identity = Binder.clearCallingIdentity(); try { return mUserManager.getProfileParent(userId); @@ -3837,7 +3951,7 @@ public class PackageManagerService extends IPackageManager.Stub *
  7. The calling application is the default app prediction service.
  8. *
*/ - public boolean canViewInstantApps(int callingUid, int userId) { + public final boolean canViewInstantApps(int callingUid, int userId) { if (callingUid < Process.FIRST_APPLICATION_UID) { return true; } @@ -3861,8 +3975,8 @@ public class PackageManagerService extends IPackageManager.Stub return false; } - public boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId, - int flags) { + public final boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, + int userId, int flags) { // Callers can access only the libs they depend on, otherwise they need to explicitly // ask for the shared libraries given the caller is allowed to access all static libs. if ((flags & PackageManager.MATCH_STATIC_SHARED_LIBRARIES) != 0) { @@ -3945,13 +4059,13 @@ public class PackageManagerService extends IPackageManager.Stub == PackageManager.PERMISSION_GRANTED; } - public boolean isCallerSameApp(String packageName, int uid) { + public final boolean isCallerSameApp(String packageName, int uid) { AndroidPackage pkg = mPackages.get(packageName); return pkg != null && UserHandle.getAppId(uid) == pkg.getUid(); } - public boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) { + public final boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) { if (isComponentVisibleToInstantApp(component, TYPE_ACTIVITY)) { return true; } @@ -3964,7 +4078,7 @@ public class PackageManagerService extends IPackageManager.Stub return false; } - public boolean isComponentVisibleToInstantApp( + public final boolean isComponentVisibleToInstantApp( @Nullable ComponentName component, @ComponentType int type) { if (type == TYPE_ACTIVITY) { final ParsedActivity activity = mComponentResolver.getActivity(component); @@ -4012,13 +4126,13 @@ public class PackageManagerService extends IPackageManager.Stub * @return {@code true} if the intent is a camera intent and the persistent preferred * activity was not set by the DPC. */ - public boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, - String resolvedType, int flags) { + public final boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, + int userId, String resolvedType, int flags) { return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm( intent, userId, resolvedType, flags); } - public boolean isInstantApp(String packageName, int userId) { + public final boolean isInstantApp(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, false /* checkShell */, "isInstantApp"); @@ -4026,7 +4140,7 @@ public class PackageManagerService extends IPackageManager.Stub return isInstantAppInternal(packageName, userId, callingUid); } - public boolean isInstantAppInternal(String packageName, @UserIdInt int userId, + public final boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid) { if (HIDE_EPHEMERAL_APIS) { return false; @@ -4160,7 +4274,8 @@ public class PackageManagerService extends IPackageManager.Stub } } - public boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { + public final boolean isSameProfileGroup(@UserIdInt int callerUserId, + @UserIdInt int userId) { final long identity = Binder.clearCallingIdentity(); try { return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId); @@ -4187,7 +4302,8 @@ public class PackageManagerService extends IPackageManager.Stub * * @see #canViewInstantApps(int, int) */ - public boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, int callingUid, + public final boolean shouldFilterApplicationLocked(@Nullable PackageSetting ps, + int callingUid, @Nullable ComponentName component, @ComponentType int componentType, int userId) { // if we're in an isolated process, get the real calling UID if (Process.isIsolated(callingUid)) { @@ -4247,7 +4363,7 @@ public class PackageManagerService extends IPackageManager.Stub /** * @see #shouldFilterApplicationLocked(PackageSetting, int, ComponentName, int, int) */ - public boolean shouldFilterApplicationLocked( + public final boolean shouldFilterApplicationLocked( @Nullable PackageSetting ps, int callingUid, int userId) { return shouldFilterApplicationLocked(ps, callingUid, null, TYPE_UNKNOWN, userId); } @@ -4255,8 +4371,8 @@ public class PackageManagerService extends IPackageManager.Stub /** * @see #shouldFilterApplicationLocked(PackageSetting, int, ComponentName, int, int) */ - public boolean shouldFilterApplicationLocked(@NonNull SharedUserSetting sus, int callingUid, - int userId) { + public final boolean shouldFilterApplicationLocked(@NonNull SharedUserSetting sus, + int callingUid, int userId) { boolean filterApp = true; for (int index = sus.packages.size() - 1; index >= 0 && filterApp; index--) { filterApp &= shouldFilterApplicationLocked(sus.packages.valueAt(index), @@ -4280,7 +4396,7 @@ public class PackageManagerService extends IPackageManager.Stub } // NOTE: Can't remove without a major refactor. Keep around for now. - public int checkUidPermission(String permName, int uid) { + public final int checkUidPermission(String permName, int uid) { return mPermissionManager.checkUidPermission(uid, permName); } @@ -4330,21 +4446,21 @@ public class PackageManagerService extends IPackageManager.Stub /** * Update given flags when being used to request {@link ApplicationInfo}. */ - public int updateFlagsForApplication(int flags, int userId) { + public final int updateFlagsForApplication(int flags, int userId) { return updateFlagsForPackage(flags, userId); } /** * Update given flags when being used to request {@link ComponentInfo}. */ - public int updateFlagsForComponent(int flags, int userId) { + public final int updateFlagsForComponent(int flags, int userId) { return updateFlags(flags, userId); } /** * Update given flags when being used to request {@link PackageInfo}. */ - public int updateFlagsForPackage(int flags, int userId) { + public final int updateFlagsForPackage(int flags, int userId) { final boolean isCallerSystemUser = UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM; if ((flags & PackageManager.MATCH_ANY_USER) != 0) { @@ -4380,14 +4496,14 @@ public class PackageManagerService extends IPackageManager.Stub * action and a {@code android.intent.category.BROWSABLE} category * */ - public int updateFlagsForResolve(int flags, int userId, int callingUid, + public final int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { return updateFlagsForResolve(flags, userId, callingUid, wantInstantApps, false /*onlyExposedExplicitly*/, isImplicitImageCaptureIntentAndNotSetByDpc); } - public int updateFlagsForResolve(int flags, int userId, int callingUid, + public final int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { // Safe mode means we shouldn't match any third-party components @@ -4429,7 +4545,7 @@ public class PackageManagerService extends IPackageManager.Stub * @param checkShell whether to prevent shell from access if there's a debugging restriction * @param message the message to log on security exception */ - public void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, + public final void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message) { if (userId < 0) { throw new IllegalArgumentException("Invalid userId " + userId); @@ -4467,7 +4583,7 @@ public class PackageManagerService extends IPackageManager.Stub * @param checkShell whether to prevent shell from access if there's a debugging restriction * @param message the message to log on security exception */ - public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, + public final void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message) { enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, false, message); @@ -4484,7 +4600,7 @@ public class PackageManagerService extends IPackageManager.Stub * reference the same user. * @param message the message to log on security exception */ - public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, + public final void enforceCrossUserPermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, boolean requirePermissionWhenSameUser, String message) { if (userId < 0) { @@ -4736,31 +4852,14 @@ public class PackageManagerService extends IPackageManager.Stub } /** - * The live computer differs from the ComputerEngine in the methods that fetch data - * from PackageManagerService. - **/ - private static class ComputerEngineLive extends ComputerEngine { - ComputerEngineLive(Snapshot args) { - super(args); - } - protected ComponentName resolveComponentName() { - return mService.mResolveComponentName; - } - protected ActivityInfo instantAppInstallerActivity() { - return mService.mInstantAppInstallerActivity; - } - protected ApplicationInfo androidApplication() { - return mService.mAndroidApplication; - } - } - - /** - * This subclass is the external interface to the live computer. For each - * interface, it takes the PM lock and then delegates to the live - * computer engine. This is required because there are no locks taken in - * the engine itself. + * This subclass is the external interface to the live computer. Some internal helper + * methods are overridden to fetch live data instead of snapshot data. For each + * Computer interface that is overridden in this class, the override takes the PM lock + * and then delegates to the live computer engine. This is required because there are + * no locks taken in the engine itself. */ - private static class ComputerLocked extends ComputerEngineLive { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + protected static class ComputerLocked extends ComputerEngine { private final Object mLock; ComputerLocked(Snapshot args) { @@ -4768,18 +4867,17 @@ public class PackageManagerService extends IPackageManager.Stub mLock = mService.mLock; } - /** - * Explicilty snapshot {@link Settings#mPackages} for cases where the caller must not lock - * in order to get package data. It is expected that the caller locks itself to be able - * to block on changes to the package data and bring itself up to date once the change - * propagates to it. Use with heavy caution. - * @return - */ - private Map snapshotPackageSettings() { - return mSettings.snapshot().mPackages; + protected final ComponentName resolveComponentName() { + return mService.mResolveComponentName; + } + protected final ActivityInfo instantAppInstallerActivity() { + return mService.mInstantAppInstallerActivity; + } + protected final ApplicationInfo androidApplication() { + return mService.mAndroidApplication; } - public @NonNull List queryIntentServicesInternalBody(Intent intent, + public final @NonNull List queryIntentServicesInternalBody(Intent intent, String resolvedType, int flags, int userId, int callingUid, String instantAppPkgName) { synchronized (mLock) { @@ -4787,8 +4885,8 @@ public class PackageManagerService extends IPackageManager.Stub callingUid, instantAppPkgName); } } - public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(Intent intent, - String resolvedType, int flags, int filterCallingUid, int userId, + public final @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody( + Intent intent, String resolvedType, int flags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits, String pkgName, String instantAppPkgName) { synchronized (mLock) { @@ -4797,31 +4895,31 @@ public class PackageManagerService extends IPackageManager.Stub instantAppPkgName); } } - public ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags, + public final ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags, int filterCallingUid, int userId) { synchronized (mLock) { return super.getActivityInfoInternalBody(component, flags, filterCallingUid, userId); } } - public AndroidPackage getPackage(String packageName) { + public final AndroidPackage getPackage(String packageName) { synchronized (mLock) { return super.getPackage(packageName); } } - public AndroidPackage getPackage(int uid) { + public final AndroidPackage getPackage(int uid) { synchronized (mLock) { return super.getPackage(uid); } } - public ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags, + public final ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags, int filterCallingUid, int userId) { synchronized (mLock) { return super.getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId); } } - public ArrayList filterCandidatesWithDomainPreferredActivitiesLPrBody( + public final ArrayList filterCandidatesWithDomainPreferredActivitiesLPrBody( Intent intent, int matchFlags, List candidates, CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) { synchronized (mLock) { @@ -4829,49 +4927,49 @@ public class PackageManagerService extends IPackageManager.Stub matchFlags, candidates, xpDomainInfo, userId, debug); } } - public PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, + public final PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, int flags, int filterCallingUid, int userId) { synchronized (mLock) { return super.getPackageInfoInternalBody(packageName, versionCode, flags, filterCallingUid, userId); } } - public PackageSetting getPackageSettingInternal(String packageName, int callingUid) { + public final PackageSetting getPackageSettingInternal(String packageName, int callingUid) { synchronized (mLock) { return super.getPackageSettingInternal(packageName, callingUid); } } - public ParceledListSlice getInstalledPackagesBody(int flags, int userId, + public final ParceledListSlice getInstalledPackagesBody(int flags, int userId, int callingUid) { synchronized (mLock) { return super.getInstalledPackagesBody(flags, userId, callingUid); } } - public ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId, + public final ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId, int callingUid) { synchronized (mLock) { return super.getServiceInfoBody(component, flags, userId, callingUid); } } - public String getInstantAppPackageName(int callingUid) { + public final String getInstantAppPackageName(int callingUid) { synchronized (mLock) { return super.getInstantAppPackageName(callingUid); } } - public String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId, + public final String[] getPackagesForUidInternalBody(int callingUid, int userId, int appId, boolean isCallerInstantApp) { synchronized (mLock) { return super.getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp); } } - public boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, + public final boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, int callingUid) { synchronized (mLock) { return super.isInstantAppInternalBody(packageName, userId, callingUid); } } - public boolean isInstantAppResolutionAllowedBody(Intent intent, + public final boolean isInstantAppResolutionAllowedBody(Intent intent, List resolvedActivities, int userId, boolean skipPackageCheck, int flags) { synchronized (mLock) { @@ -4879,33 +4977,33 @@ public class PackageManagerService extends IPackageManager.Stub skipPackageCheck, flags); } } - public int getPackageUidInternal(String packageName, int flags, int userId, + public final int getPackageUidInternal(String packageName, int flags, int userId, int callingUid) { synchronized (mLock) { return super.getPackageUidInternal(packageName, flags, userId, callingUid); } } - public SigningDetails getSigningDetails(@NonNull String packageName) { + public final SigningDetails getSigningDetails(@NonNull String packageName) { synchronized (mLock) { return super.getSigningDetails(packageName); } } - public SigningDetails getSigningDetails(int uid) { + public final SigningDetails getSigningDetails(int uid) { synchronized (mLock) { return super.getSigningDetails(uid); } } - public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) { + public final boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) { synchronized (mLock) { return super.filterAppAccess(pkg, callingUid, userId); } } - public boolean filterAppAccess(String packageName, int callingUid, int userId) { + public final boolean filterAppAccess(String packageName, int callingUid, int userId) { synchronized (mLock) { return super.filterAppAccess(packageName, callingUid, userId); } } - public void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) { + public final void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) { synchronized (mLock) { super.dump(type, fd, pw, dumpState); } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index 558fb309ad98..976a588273a7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -18,7 +18,11 @@ package com.android.server.pm; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + import static java.lang.reflect.Modifier.isFinal; +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isProtected; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; @@ -44,9 +48,12 @@ import org.junit.runner.RunWith; import java.io.File; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; @@ -393,6 +400,178 @@ public class PackageManagerServiceTest { Assert.assertEquals(3600000003L, multiPackage[1].timeouts.maxPendingTimeUs); } + // Report an error from the Computer structure validation test. + private void flag(String name, String msg) { + fail(name + " " + msg); + } + + // Return a string that identifies a Method. This is not very efficient but it is not + // called very often. + private String displayName(Method m) { + String r = m.getName(); + String p = Arrays.toString(m.getGenericParameterTypes()) + .replaceAll("([a-zA-Z0-9]+\\.)+", "") + .replace("class ", "") + .replaceAll("^\\[", "(") + .replaceAll("\\]$", ")"); + return r + p; + } + + // Match a method to an array of Methods. Matching is on method signature: name and + // parameter types. If a method in the declared array matches, return it. Otherwise + // return null. + private Method matchMethod(Method m, Method[] declared) { + String n = m.getName(); + Type[] t = m.getGenericParameterTypes(); + for (int i = 0; i < declared.length; i++) { + Method l = declared[i]; + if (l != null && l.getName().equals(n) + && Arrays.equals(l.getGenericParameterTypes(), t)) { + Method result = l; + // Set the method to null since it has been visited already. + declared[i] = null; + return result; + } + } + return null; + } + + // Return the boolean locked value. A null return means the annotation was not + // found. This method will fail if the annotation is found but is not one of the + // known constants. + private Boolean getOverride(Method m) { + final String name = "Computer." + displayName(m); + final PackageManagerService.Computer.LiveImplementation annotation = + m.getAnnotation(PackageManagerService.Computer.LiveImplementation.class); + if (annotation == null) { + return null; + } + final int override = annotation.override(); + if (override == PackageManagerService.Computer.LiveImplementation.MANDATORY) { + return true; + } else if (override == PackageManagerService.Computer.LiveImplementation.NOT_ALLOWED) { + return false; + } else { + flag(name, "invalid Live value: " + override); + return null; + } + } + + @Test + public void testComputerStructure() { + // Verify that Copmuter methods are properly annotated and that ComputerLocked is + // properly populated per annotations. + // Call PackageManagerService.validateComputer(); + Class base = PackageManagerService.Computer.class; + + HashMap methodType = new HashMap<>(); + + // Verify that all Computer methods are annotated and that the annotation + // parameter locked() is valid. + for (Method m : base.getDeclaredMethods()) { + final String name = "Computer." + displayName(m); + Boolean override = getOverride(m); + if (override == null) { + flag(name, "missing required Live annotation"); + } + methodType.put(m, override); + } + + Class coreClass = PackageManagerService.ComputerEngine.class; + final Method[] coreMethods = coreClass.getDeclaredMethods(); + + // Examine every method in the core. If it inherits from a base method it must be + // "public final" if the base is NOT_ALLOWED or "public" if the base is MANDATORY. + // If the core method does not inherit from the base then it must be either + // private or protected. + for (Method m : base.getDeclaredMethods()) { + String name = "Computer." + displayName(m); + final boolean locked = methodType.get(m); + final Method core = matchMethod(m, coreMethods); + if (core == null) { + flag(name, "not overridden in ComputerEngine"); + continue; + } + name = "ComputerEngine." + displayName(m); + final int modifiers = core.getModifiers(); + if (!locked) { + if (!isPublic(modifiers)) { + flag(name, "is not public"); + } + if (!isFinal(modifiers)) { + flag(name, "is not final"); + } + } + } + // Any methods left in the coreMethods array must be private or protected. + // Protected methods must be overridden (and final) in the live list. + Method[] coreHelpers = new Method[coreMethods.length]; + int coreIndex = 0; + for (Method m : coreMethods) { + if (m != null) { + final String name = "ComputerEngine." + displayName(m); + final int modifiers = m.getModifiers(); + if (isPrivate(modifiers)) { + // Okay + } else if (isProtected(modifiers)) { + coreHelpers[coreIndex++] = m; + } else { + flag(name, "is neither private nor protected"); + } + } + } + + Class liveClass = PackageManagerService.ComputerLocked.class; + final Method[] liveMethods = liveClass.getDeclaredMethods(); + + // Examine every method in the live list. Every method must be final and must + // inherit either from base or core. If the method inherits from a base method + // then the base must be MANDATORY. + for (Method m : base.getDeclaredMethods()) { + String name = "Computer." + displayName(m); + final boolean locked = methodType.get(m); + final Method live = matchMethod(m, liveMethods); + if (live == null) { + if (locked) { + flag(name, "not overridden in ComputerLocked"); + } + continue; + } + if (!locked) { + flag(name, "improperly overridden in ComputerLocked"); + continue; + } + + name = "ComputerLocked." + displayName(m); + final int modifiers = live.getModifiers(); + if (!locked) { + if (!isPublic(modifiers)) { + flag(name, "is not public"); + } + if (!isFinal(modifiers)) { + flag(name, "is not final"); + } + } + } + for (Method m : coreHelpers) { + if (m == null) { + continue; + } + String name = "ComputerLocked." + displayName(m); + final Method live = matchMethod(m, liveMethods); + if (live == null) { + flag(name, "is not overridden in ComputerLocked"); + continue; + } + } + for (Method m : liveMethods) { + if (m != null) { + String name = "ComputerLocked." + displayName(m); + flag(name, "illegal local method"); + } + } + } + private static PerPackageReadTimeouts[] getPerPackageReadTimeouts(String knownDigestersList) { final String defaultTimeouts = "3600000001:3600000002:3600000003"; List result = PerPackageReadTimeouts.parseDigestersList( -- cgit v1.2.3-59-g8ed1b