The beginning of the story

Today the eldest brother rushed over to say: xx, you help me to see this mobile phone how to return a responsibility, suddenly can not open machine.

I thought to myself: I haven’t mentioned the code lately, so it’s probably not my problem. (Shake the pan ~ ~)

After plugging my computer into my phone, I saw that the following error message kept going in a loop

12-31 16:08:49.603 21899 21899 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: The main 12-31 16:08:49. 603 21899 21899 E AndroidRuntime: Java. Lang. An IllegalStateException: Signature|privileged permissions not in privapp-permissions whitelist: {com.xxx.xxx.xxxxx (/data/app/~~BR9Kz0rmscIpqqvqBf8jwg==/com.xxx.xxx.xxxxx-fLGzzHKkZaTB5_DLxgo_Fg==): android.permission.BACKUP, com.xxx.xxx.xxxxx (/data/app/~~BR9Kz0rmscIpqqvqBf8jwg==/com.xxx.xxx.xxxxx-fLGzzHKkZaTB5_DLxgo_Fg==): Android. Permission. UPDATE_DEVICE_STATS} - 31 16:08:49. 12, 603, 21899, 21899 E AndroidRuntime: at com.android.server.pm.permission.PermissionManagerService.systemReady(PermissionManagerService.java:4688) 12-31 16:08:49.603 21899 2189e at com.android.server.pm.permission.PermissionManagerService.access$500(PermissionManagerService.java:181) 12-31 16:08:49.603 21899 2189e at com.android.server.pm.permission.PermissionManagerService$PermissionManagerServiceInternalImpl.systemReady(PermissionMan Agerservice. Java :4771) 12-31 16:08:49.603 21899 2189e At com. Android. Server. PM. PackageManagerService. SystemReady (PackageManagerService. Java: 22183) 12-31 16:08:49. 603, 21899 21899 E AndroidRuntime: At com. Android. Server. SystemServer. StartOtherServices (2305) SystemServer. Java: 12-31 16:08:49. 603, 21899, 21899 E AndroidRuntime: At com. Android. Server. SystemServer. Run (624) SystemServer. Java: 12-31 16:08:49. 603, 21899, 21899 E AndroidRuntime: At com. Android. Server. SystemServer. Main (440) SystemServer. Java: 12-31 16:08:49. 603, 21899, 21899 E AndroidRuntime: The at Java. Lang. Reflect. Method. Invoke (Native Method) 12-31 16:08:49. 603, 21899, 21899 E AndroidRuntime: Ats com. Android. Internal. OS. RuntimeInit $MethodAndArgsCaller. Run (592) RuntimeInit. Java: 12-31 16:08:49. 603, 21899, 21899 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)Copy the code

After reading it, I immediately replied, “I know that, Sir. The system application group must have forgotten to add the permission declaration to the privApp-permissions -platform. XML file.

Eldest brother: can’t ah, this mobile phone I have been using, the back suddenly become so.

I was stunned at the moment: there is such a magical thing. That’s how the story started, and then I started investigating this amazing phenomenon.

The root of all evil

Known to all

The mobile phone will check whether the permissions of the priv-app and /etc/permissions/privapp-permissions-platform. XML are in a different folder, such as vendor/etc/permissions or another name. / / select * from SystemConfig; / / select * from SystemConfig; / / select * from SystemConfig; / / select * from SystemConfig; / / select * from SystemConfig;

Start with the PermissionManagerService#systemReady method in log

private void systemReady() { mSystemReady = true; / / the root of all evil if (mPrivappPermissionsViolations! = null) { throw new IllegalStateException("Signature|privileged permissions not in " + "privapp-permissions whitelist: " + mPrivappPermissionsViolations); } / /... }Copy the code

As long as mPrivappPermissionsViolations with data in the array, we will never be able to boot, see how the array is populated.

private boolean grantSignaturePermission(String perm, AndroidPackage pkg, PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions) { // ... if (! privappPermissionsDisable && privilegedPermission && pkg.isPrivileged() && ! platformPackage && platformPermission) { if (! hasPrivappWhitelistEntry(perm, pkg)) { if (! mSystemReady && ! pkgSetting.getPkgState().isUpdatedSystemApp()) { ApexManager apexMgr = ApexManager.getInstance(); String apexContainingPkg = apexMgr.getActiveApexPackageNameContainingPackage( pkg); if (apexContainingPkg == null || apexMgr.isFactory( apexMgr.getPackageInfo(apexContainingPkg, MATCH_ACTIVE_PACKAGE))) { // ... Compare permissions with XML declarations... If (permissionViolation) {slog. w(TAG, init) {Slog. "Privileged permission " + perm + " for package " + pkg.getPackageName() + " (" + pkg.getCodePath() + ") not in privapp-permissions whitelist"); if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { if (mPrivappPermissionsViolations == null) { mPrivappPermissionsViolations = new ArraySet<>(); } mPrivappPermissionsViolations.add( pkg.getPackageName() + " (" + pkg.getCodePath() + "): " + perm); } } else { return false; }} / /... } } } } }Copy the code

After reading this, I have a question, how can an ordinary App have such a big influence (small size big power?). , you can see the above various criteria to go to the code block of the check permission declaration. And one of these conditions caught my attention — PKG.isprivileged (). If this is true, it means that this is a priv-App, and priv-App is generally built in the system as system software, and can be installed externally? (Maybe I see too little shame ~~).

It was associated with a scanFlag called SCAN_AS_PRIVILEGED, which was often used during startup scanning. SCAN_AS_PRIVILEGED was added during scan of a designated internal path of the system, and all packages under this path were priv-app. Sure enough, when I looked for the use of this flag, I found that other parts of the PMS also attach this flag to a package, in the PMS#adjustScanFlags method.

// Scan final Boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR)! = 0) && getVendorPartitionVersion() < 28; if (((scanFlags & SCAN_AS_PRIVILEGED) == 0) && ! pkg.isPrivileged() && (pkg.getSharedUserId() ! = null) && ! skipVendorPrivilegeScan) { SharedUserSetting sharedUserSetting = null; try { sharedUserSetting = mSettings.getSharedUserLPw(pkg.getSharedUserId(), 0, 0, false); } catch (PackageManagerException ignore) { } if (sharedUserSetting ! . = null && sharedUserSetting isPrivileged ()) {/ / exemption to use platform key signature SharedUsers. // TODO (B / 72378145) fixes this exemption. // Force signing applications to whitelist their privileges like other priv-apps. synchronized (mLock) { PackageSetting platformPkgSetting = mSettings.mPackages.get("android"); if ((compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures, pkg.getSigningDetails().signatures) ! = PackageManager.SIGNATURE_MATCH)) { scanFlags |= SCAN_AS_PRIVILEGED; }}}}Copy the code

In the PMS#adjustScanFlags method, it is forced to adjustscan_as_privileged in append for priv-app sharedUser to be in the whitelistfor boot check. Since as a priv-APP, the boot check permission has become a matter of course. So when we preload priv-App to the system, we need to make sure that the priv-App has any other sharedUserId applications. If so, You should also declare the permissions for the App that is not pre-installed to the system in the privApp-permissions -platform. XML whitelist, otherwise it will cause loop crash after reboot.

Why can SharedUser applications share permissions

All That is known * 2

The final step in all of these checks is to get the permission status of the App by calling getPermissionsState() in PackageSettings:

@Override public PermissionsState getPermissionsState() { return (sharedUser ! = null) ? sharedUser.getPermissionsState() : super.getPermissionsState(); }Copy the code

If the sharedUser of the Package’s PackageSettings is not empty, the sharedUser permission is used by default. So SharedUser applications can share existing permissions with each other, and if one permission is granted, all other packages in SharedUser will default to this permission. Android :sharedUserId=”android.uid. System” Can obtain and use the other belongs to android. The uid. Other permissions systemSharedUser group.

How SharedUser was assigned to PackageSettings let’s look at the place in the PMS#scanPackageNewLI method where SharedUser was assigned.

SharedUserSetting sharedUserSetting = null; if (parsedPackage.getSharedUserId() ! = null) { // SIDE EFFECTS; may potentially allocate a new shared user sharedUserSetting = mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(), 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/); if (DEBUG_PACKAGE_SCANNING) { if ((parseFlags & PackageParser.PARSE_CHATTY) ! = 0) Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId() + " (uid=" + sharedUserSetting.userId + "):" + " packages=" + sharedUserSetting.packages); }}Copy the code

. Here we can see according to parsedPackage getSharedUserId the SharedUserId of type String, will go to the PM. The Settings for SharedUserSetting belonging to this package, It also does not verify that the package’s sharedUserId meets certain conditions, such as signatures.

The SharedUserSetting was not reassigned until PMS#commitPackageSettings persisted into PackageSettings. XML. So at this point I think it’s a SharedUserId problem. The SharedUserId assignment is ParsingPackageUtils#parseSharedUser. The SharedUserId assignment is parsingpackageutils

private static ParseResult<ParsingPackage> parseSharedUser(ParseInput input, ParsingPackage pkg, TypedArray sa) { String str = nonConfigString(0, R.styleable.AndroidManifest_sharedUserId, sa); if (TextUtils.isEmpty(str)) { return input.success(pkg); } if (!" android".equals(pkg.getPackageName())) { ParseResult<? > nameResult = validateName(input, str, true, true); if (nameResult.isError()) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, "<manifest> specifies bad sharedUserId name \"" + str + "\": " + nameResult.getErrorMessage()); } } return input.success(pkg .setSharedUserId(str.intern()) .setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa))); }Copy the code

Above this code is very simple, probably means read R.s. Tyleable AndroidManifest_sharedUserId the attribute of the content, and then directly to assign it to the SharedUserId of type String, nor for a compliance test. Android :sharedUserId=” XXX” At this time, I opened the test demo and added the Android :sharedUserId attribute to it, but the installation failed, and the INSTALL_FAILED_SHARED_USER_INCOMPATIBLE was reported. I searched the PMS and snapped. It was soon found (but it was a mistake because the error was not generated in PMS). Because of the wrong reasons, I put the installation of the stack to play out, found an error is actually PackageManagerServiceUtils# verifySignatures.

Let’s just type out the stack

verifySignatures:689, PackageManagerServiceUtils (com.android.server.pm)
reconcilePackagesLocked:16947, PackageManagerService (com.android.server.pm)
installPackagesLI:17373, PackageManagerService (com.android.server.pm)
installPackagesTracedLI:16696, PackageManagerService (com.android.server.pm)
lambda$processInstallRequestsAsync$22$PackageManagerService:14802, PackageManagerService (com.android.server.pm)
run:-1, -$$Lambda$PackageManagerService$9znobjOH7ab0F1jsW2oFdNipS-8 (com.android.server.pm)
handleCallback:938, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loop:223, Looper (android.os)
run:67, HandlerThread (android.os)
run:44, ServiceThread (com.android.server)
Copy the code

It also makes sense to the other packages in SharedUser: If you say you’re my brother, then you’re my brother? Compare your signature with mine. The same is my brother. After all, in package management, the signature of a package is the DNA of the package, and only the signature of apK signed by the same x509.pem and.pk8 files will be the same.

Throw New PackageManagerException, which terminates the installation process without giving you a chance of success. So there is no need to null SharedUserSetting or SharedUserId, as long as the Package is successfully installed and their SharedUserSetting is not null, it is legal.

/** * Verifies that signatures match. * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}. * @throws PackageManagerException if the signatures did not match. */ public static boolean verifySignatures(PackageSetting pkgSetting, PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures, boolean compareCompat, boolean compareRecover) throws PackageManagerException { final String packageName = pkgSetting.name; boolean compatMatch = false; / /... If (pkgsetting.getSharedUser ()! = null && pkgSetting.getSharedUser().signatures.mSigningDetails ! = PackageParser. SigningDetails. UNKNOWN) {/ / existing packages. Make sure the signatures match. In the case of signing certificate rotation, // The package with the newer certificate must be consistent with the older version of sharedUserId. // We check if the new package is signed by an older certificate and can be signed with the current sharedUser certificate, or if it is signed by a newer certificate and is signed as sharedUser with an existing signing certificate, yes. boolean match = parsedSignatures.checkCapability( pkgSetting.getSharedUser().signatures.mSigningDetails, PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) || pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability( parsedSignatures, PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); // If the sharedUserId function check fails, // it may be because this is the only package in sharedUserId so far, and the inheritance is updated to deny the sharedUserId function in the previous key inheritance. if (! match && pkgSetting.getSharedUser().packages.size() == 1 && pkgSetting.getSharedUser().packages.valueAt(0).name.equals(packageName)) { match = true; } if (! match && compareCompat) { match = matchSignaturesCompat( packageName, pkgSetting.getSharedUser().signatures, parsedSignatures); } if (! match && compareRecover) { match = matchSignaturesRecover(packageName, pkgSetting.getSharedUser().signatures.mSigningDetails, parsedSignatures, PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) || matchSignaturesRecover(packageName, parsedSignatures, pkgSetting.getSharedUser().signatures.mSigningDetails, PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); compatMatch |= match; } // The installation fails if (! match) { throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, "Package " + packageName + " has no signatures that match those in shared user " + pkgSetting.getSharedUser().name + "; ignoring!" ); } / /... } return compatMatch; }Copy the code

The last

Please pay attention to the phone backup and confirm the source of the software, if other phone manufacturers “fix” this problem can be informed in the comment section, AndroidQ and R will have this problem, as for the previous version has not been confirmed. I’ve already broken a Google Pixel with this apK, and ordinary users can probably only restore the factory Settings, but advanced users can use the serial port to open the USB debugging, and then uninstall the package.

My inventory, need small partners please clickMy lotFree to receive