Unable to boot due to installing an apk!

Posted by caramba on Wed, 19 Jan 2022 08:36:16 +0100

The beginning of the story

Today, the boss rushed over and said: xx, please help me see what's wrong with this mobile phone. Suddenly, it can't be turned on.

I thought to myself: I haven't mentioned the code recently. It shouldn't be my problem. (throw the pot ~. ~)

After plugging the computer into the mobile phone, I saw the following error report, which has been in the loop

12-31 16:08:49.603 21899 21899 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
12-31 16:08:49.603 21899 21899 E AndroidRuntime: java.lang.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}
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at com.android.server.pm.permission.PermissionManagerService.systemReady(PermissionManagerService.java:4688)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at com.android.server.pm.permission.PermissionManagerService.access$500(PermissionManagerService.java:181)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at com.android.server.pm.permission.PermissionManagerService$PermissionManagerServiceInternalImpl.systemReady(PermissionManagerService.java:4771)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  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(SystemServer.java:2305)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at com.android.server.SystemServer.run(SystemServer.java:624)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at com.android.server.SystemServer.main(SystemServer.java:440)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at java.lang.reflect.Method.invoke(Native Method)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
12-31 16:08:49.603 21899 21899 E AndroidRuntime:  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

After reading it, I immediately replied: boss, I know this. The students in the system application group must have forgotten it in privapp permissions platform The permission declaration is added under the XML file.

Boss: No, I've been using this mobile phone all the time, and suddenly it's like this.

At the moment, I look confused and forced: there is such a magical thing. This is the beginning of the story. Next, I began to investigate this magical phenomenon.

root of all evils

Well known

When the phone is turned on, check whether the priv app permissions are the same as / etc / permissions / privapp permissions platform xml (it may be in another folder, such as vendor/etc/permissions, or it may be called another name, because as long as the xml node is right, SystemConfig in pm will scan all xml in this kind of folder) whether the permissions declared are the same. If they are different, you cannot start the machine and will always crash on the loop.

Start with the PermissionManagerService#systemReady method in the log

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

As long as there is data in the mPrivappPermissionsViolations array, we will never be able to turn on. Let's see how the array is filled.

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 It is found that the perm is found in the blacklist or not in the whitelist
                        // All added to the boot firewall (my own name 0.0)
                        if (permissionViolation) {
                            Slog.w(TAG, "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;
                        }
                    }
             // ....          
                }
                }
            }
        }
}

After reading this, I have a question: how can an ordinary App have such a great impact (small body and great power?), You can see the above conditions before you can go to the code block of the verification permission statement. One of these conditions attracted my attention --- PKG Isprivileged(), which is true, indicates that this is a priv App, and priv apps are generally built into the system as system software and can be installed externally? (maybe I'm ashamed of my lack of knowledge ~ ~).

Associated with it is a scanFlag called SCAN_AS_PRIVILEGED, which is often used during startup scanning. Scan is added when scanning a specified system internal path_ AS_ Privileged, all packages under this path are priv app. Sure enough, when I look for the use of this flag, I find that other parts of PMS will attach this flag to a package, which is in the PMS#adjustScanFlags method.

// The shared user application of priv app should also be scanned like priv app
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()) {
        // Exempt SharedUsers signed with platform key. 
       // TODO (b / 72378145) fixed this exemption.
        // Force signing applications to whitelist their privileged permissions 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;
            }
        }
    }
}

In the PMS#adjustScanFlags method, scan the application append of priv App and shareduser_ AS_ Privileged, so that it should also be forcibly added to the white list of boot check, so google did it on purpose. Since it is a priv App, it is natural to check the power on permission. Therefore, when we preset the priv App to the system, we should determine whether there are other sharedUserId applications for the priv App, and if so, in the permission white list privapp permissions platform XML declares that this App does not have the permission to apply for the preset App to the system, otherwise the restart after installation will cause loop crash.

Why can SharedUser's applications share permissions

Well known * 2

The last step of checking permissions is to call getPermissionsState() in PackageSettings to get the permission status of the App. The code is as follows:

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

If the shareduser of PackageSettings of Package is not empty, the shareduser permission is used by default. Therefore, shareduser applications can share the existing permissions with each other, and a permission is granted. Other packages of shareduser will grant this permission by default. Students doing system application may know better, as long as they are in manifest If you declare android:sharedUserId="android.uid.system" in XML, you can get and use other Android. Uid uid. Additional permissions for the systemshareduser group.

How shareduser is assigned to PackageSettings, let's take a look at this place in the PMS#scanPackageNewLI method, that is, the place where the value shareduser is 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);
    }
}

We can see here according to parsedpackage Getshareduserid is the shareduserid of String type. Go to PM The SharedUserSetting of this package was found in settings, and it was not verified whether the shareduserid of this package met some conditions, such as signature.

Until the last PMS#commitPackageSettings (the step of persisting PackageSettings into packages.xml file, the installation package data will not change), this SharedUserSetting has not been re assigned, so I think it should be the problem of SharedUserId at this step. Tactfully, I put my idea on the assignment process of SharedUserId. After a search, I found the place where SharedUserId was assigned: ParsingPackageUtils#parseSharedUser

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)));                
} 

The above code is very simple, which roughly means reading r.styleable AndroidManifest_ SharedUserId is the content of this attribute, and then it is directly assigned to the SharedUserId of this String type. There is no compliance check. At this time, I can't help but have a question. Don't I just declare an android:sharedUserId="xxx" to get other permissions of SharedUser? At this time, I opened the test demo of the test and added the android:sharedUserId attribute to it, but the installation failed, and reported INSTALL_FAILED_SHARED_USER_INCOMPATIBLE exception. Search PMS and pop it, and you'll soon find spicy (but in fact, you're wrong, because the error report is not generated in PMS). Because I found the wrong reason, I knocked out the installation stack and found that the error was actually in PackageManagerServiceUtils#verifySignatures.

Hit the stack first

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)

This is also very logical. Other packages in SharedUser: you say you are my brother, so you are my brother? Compare your signature with mine. The same one is my brother. After all, in package management, the signature of a package is equivalent to the DNA of the package, and only through the same x509 PEM and pk8 file signature apk's signature will be the same.

You can see that if the verification signature does not match, throw new PackageManagerException is directly thrown to terminate the installation process, which does not give you a chance of successful installation at all. Therefore, it is not necessary to empty SharedUserSetting or SharedUserId. As long as the Package is successfully installed and their SharedUserSetting is not empty, 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;
    // ...
    // Check whether the signature of SharedUser matches
    if (pkgSetting.getSharedUser() != null
        && pkgSetting.getSharedUser().signatures.mSigningDetails
        != PackageParser.SigningDetails.UNKNOWN) {

        // A package that already exists. Make sure the signatures match. In case of signing certificate rotation,
        // Packages with newer certificates must be consistent with older versions of sharedUserId.
        // We check whether the new package is signed by an older certificate and can be signed by the current sharedUser,
        // Or signed by a newer certificate, and whether it is signed as sharedUser with an existing signing certificate.
        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 reject the sharedUserId function of the previous key in the 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;
        }
        // If it is not paired, throw an exception and 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;
}

last

Please pay attention to the confirmation of mobile phone backup and software source. If other mobile phone manufacturers "fix" this problem, you can tell it in the comment area. This problem will exist in both Android Q and R. as for the previous versions, they have not been confirmed one by one. I have damaged a Google Pixel (tear eye) with this apk. Ordinary users can only restore the factory settings. Advanced users can open USB debugging with serial port and uninstall this package.

For my inventory, please click My GitHub Free collection

Topics: Android Back-end