Android 安装应用-准备阶段

安装应用的准备阶段是在PackageManagerService类中的preparePackageLI(InstallArgs args, PackageInstalledInfo res),代码有些长,分段阅读。

分段一

分段一:

java 复制代码
    @GuardedBy("mInstallLock")
    private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
            throws PrepareFailure {
        final int installFlags = args.installFlags;
        final File tmpPackageFile = new File(args.getCodePath());
        final boolean onExternal = args.volumeUuid != null;
        final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
        final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
        final boolean virtualPreload =
                ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
        final boolean isRollback = args.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
        @ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
        if (args.move != null) {
            // moving a complete application; perform an initial scan on the new install location
            scanFlags |= SCAN_INITIAL;
        }
        if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
            scanFlags |= SCAN_DONT_KILL_APP;
        }
        if (instantApp) {
            scanFlags |= SCAN_AS_INSTANT_APP;
        }
        if (fullApp) {
            scanFlags |= SCAN_AS_FULL_APP;
        }
        if (virtualPreload) {
            scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
        }

        if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);

        // Validity check
        if (instantApp && onExternal) {
            Slog.i(TAG, "Incompatible ephemeral install; external=" + onExternal);
            throw new PrepareFailure(PackageManager.INSTALL_FAILED_SESSION_INVALID);
        }

        // Retrieve PackageSettings and parse package
        @ParseFlags final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY
                | ParsingPackageUtils.PARSE_ENFORCE_CODE
                | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);

        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
        final ParsedPackage parsedPackage;
        try (PackageParser2 pp = mInjector.getPreparingPackageParser()) {
            parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
            AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
        } catch (PackageParserException e) {
            throw new PrepareFailure("Failed parse during installPackageLI", e);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }

        // Instant apps have several additional install-time checks.
        if (instantApp) {
            if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) {
                Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
                                + " does not target at least O");
                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
                        "Instant app package must target at least O");
            }
            if (parsedPackage.getSharedUserId() != null) {
                Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
                        + " may not declare sharedUserId.");
                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
                        "Instant app package may not declare a sharedUserId");
            }
        }

        if (parsedPackage.isStaticSharedLibrary()) {
            // Static shared libraries have synthetic package names
            renameStaticSharedLibraryPackage(parsedPackage);

            // No static shared libs on external storage
            if (onExternal) {
                Slog.i(TAG, "Static shared libs can only be installed on internal storage.");
                throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
                        "Packages declaring static-shared libs cannot be updated");
            }
        }

        String pkgName = res.name = parsedPackage.getPackageName();
        if (parsedPackage.isTestOnly()) {
            if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
                throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
            }
        }

        try {
            // either use what we've been given or parse directly from the APK
            if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
                parsedPackage.setSigningDetails(args.signingDetails);
            } else {
                parsedPackage.setSigningDetails(ParsingPackageUtils.getSigningDetails(
                        parsedPackage, false /* skipVerify */));
            }
        } catch (PackageParserException e) {
            throw new PrepareFailure("Failed collect during installPackageLI", e);
        }

        if (instantApp && parsedPackage.getSigningDetails().signatureSchemeVersion
                < SignatureSchemeVersion.SIGNING_BLOCK_V2) {
            Slog.w(TAG, "Instant app package " + parsedPackage.getPackageName()
                    + " is not signed with at least APK Signature Scheme v2");
            throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
                    "Instant app package must be signed with APK Signature Scheme v2 or greater");
        }        

先设置一些变量。installFlags为安装表示,tmpPackageFile为安装apk文件目录,onExternal是安装在外部空间,instantApp是代表INSTANT_APP。在这安装的例子中,args是FileInstallArgs类对象,它的.getCodePath()的值是来自InstallParams对象的成员origin的成员file,而构造InstallParams对象时,参数就是安装apk文件的目录。scanFlags开始时,就有SCAN_NEW_INSTALL和SCAN_UPDATE_SIGNATURE标识,它的值是在浏览那个步骤,需要使用,它还根据状态,是否增加几个标识。

在INSTANT_APP并且是装在外部的情况下,会扔出PrepareFailure异常。

下面是要解析包,pp是PackageParser2类对象,tmpPackageFile是目录,解析出来的parsedPackage实际类型是PackageImpl。

AndroidPackageUtils.validatePackageDexMetadata(parsedPackage)是如果存在安装包的dex元数据文件,是去做验证。

如果是INSTANT_APP,目标sdk不能低于Build.VERSION_CODES.O。并且不能声明sharedUserId属性值。不然会报异常。

如果解析包是静态分享库,需要将它的包名改成静态分享库的名字格式,它的格式为packageName + "_" + libraryVersion。并且静态分享库不能放在外部存储中。

接着会为参数res.name赋值为解析包的包名。

如果解析包是配置了android:testOnly="true",但是安装标识里面没有INSTALL_ALLOW_TEST,也会报PrepareFailure异常。

如果参数args.signingDetails不为PackageParser.SigningDetails.UNKNOWN,说明它已经有签名信息了。所以直接将它设置在解析包对象parsedPackage中。如果没有,这时需要调用ParsingPackageUtils.getSigningDetails()得到对应的签名信息,然后设置到解析包对象parsedPackage中。

如果是INSTANT_APP,并且签名方式版本比SIGNING_BLOCK_V2小,则会报PrepareFailure异常。

分段二

分段二:

java 复制代码
        boolean systemApp = false;
        boolean replace = false;
        synchronized (mLock) {
            // Check if installing already existing package
            if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
                String oldName = mSettings.getRenamedPackageLPr(pkgName);
                if (parsedPackage.getOriginalPackages().contains(oldName)
                        && mPackages.containsKey(oldName)) {
                    // This package is derived from an original package,
                    // and this device has been updating from that original
                    // name.  We must continue using the original name, so
                    // rename the new package here.
                    parsedPackage.setPackageName(oldName);
                    pkgName = parsedPackage.getPackageName();
                    replace = true;
                    if (DEBUG_INSTALL) {
                        Slog.d(TAG, "Replacing existing renamed package: oldName="
                                + oldName + " pkgName=" + pkgName);
                    }
                } else if (mPackages.containsKey(pkgName)) {
                    // This package, under its official name, already exists
                    // on the device; we should replace it.
                    replace = true;
                    if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
                }

                if (replace) {
                    // Prevent apps opting out from runtime permissions
                    AndroidPackage oldPackage = mPackages.get(pkgName);
                    final int oldTargetSdk = oldPackage.getTargetSdkVersion();
                    final int newTargetSdk = parsedPackage.getTargetSdkVersion();
                    if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
                            && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
                        throw new PrepareFailure(
                                PackageManager.INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE,
                                "Package " + parsedPackage.getPackageName()
                                        + " new target SDK " + newTargetSdk
                                        + " doesn't support runtime permissions but the old"
                                        + " target SDK " + oldTargetSdk + " does.");
                    }
                    // Prevent persistent apps from being updated
                    if (oldPackage.isPersistent()
                            && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
                                "Package " + oldPackage.getPackageName() + " is a persistent app. "
                                        + "Persistent apps are not updateable.");
                    }
                }
            }

            PackageSetting ps = mSettings.getPackageLPr(pkgName);
            if (ps != null) {
                if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);

                // Static shared libs have same package with different versions where
                // we internally use a synthetic package name to allow multiple versions
                // of the same package, therefore we need to compare signatures against
                // the package setting for the latest library version.
                PackageSetting signatureCheckPs = ps;
                if (parsedPackage.isStaticSharedLibrary()) {
                    SharedLibraryInfo libraryInfo = getLatestSharedLibraVersionLPr(parsedPackage);
                    if (libraryInfo != null) {
                        signatureCheckPs = mSettings.getPackageLPr(libraryInfo.getPackageName());
                    }
                }

                // Quick validity check that we're signed correctly if updating;
                // we'll check this again later when scanning, but we want to
                // bail early here before tripping over redefined permissions.
                final KeySetManagerService ksms = mSettings.getKeySetManagerService();
                if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
                    if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
                        throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                                + parsedPackage.getPackageName() + " upgrade keys do not match the "
                                + "previously installed version");
                    }
                } else {
                    try {
                        final boolean compareCompat = isCompatSignatureUpdateNeeded(parsedPackage);
                        final boolean compareRecover = isRecoverSignatureUpdateNeeded(
                                parsedPackage);
                        // We don't care about disabledPkgSetting on install for now.
                        final boolean compatMatch = verifySignatures(signatureCheckPs, null,
                                parsedPackage.getSigningDetails(), compareCompat, compareRecover,
                                isRollback);
                        // The new KeySets will be re-added later in the scanning process.
                        if (compatMatch) {
                            synchronized (mLock) {
                                ksms.removeAppKeySetDataLPw(parsedPackage.getPackageName());
                            }
                        }
                    } catch (PackageManagerException e) {
                        throw new PrepareFailure(e.error, e.getMessage());
                    }
                }

                if (ps.pkg != null) {
                    systemApp = ps.pkg.isSystem();
                }
                res.origUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
            }

            final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups());
            for (int groupNum = 0; groupNum < numGroups; groupNum++) {
                final ParsedPermissionGroup group =
                        parsedPackage.getPermissionGroups().get(groupNum);
                final PermissionGroupInfo sourceGroup = getPermissionGroupInfo(group.getName(), 0);

                if (sourceGroup != null
                        && cannotInstallWithBadPermissionGroups(parsedPackage)) {
                    final String sourcePackageName = sourceGroup.packageName;

                    if ((replace || !parsedPackage.getPackageName().equals(sourcePackageName))
                            && !doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
                            scanFlags)) {
                        EventLog.writeEvent(0x534e4554, "146211400", -1,
                                parsedPackage.getPackageName());

                        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP,
                                "Package "
                                        + parsedPackage.getPackageName()
                                        + " attempting to redeclare permission group "
                                        + group.getName() + " already owned by "
                                        + sourcePackageName);
                    }
                }
            }

           // TODO: Move logic for checking permission compatibility into PermissionManagerService
            final int N = ArrayUtils.size(parsedPackage.getPermissions());
            for (int i = N - 1; i >= 0; i--) {
                final ParsedPermission perm = parsedPackage.getPermissions().get(i);
                final Permission bp = mPermissionManager.getPermissionTEMP(perm.getName());

                // Don't allow anyone but the system to define ephemeral permissions.
                if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
                        && !systemApp) {
                    Slog.w(TAG, "Non-System package " + parsedPackage.getPackageName()
                            + " attempting to delcare ephemeral permission "
                            + perm.getName() + "; Removing ephemeral.");
                    perm.setProtectionLevel(perm.getProtectionLevel() & ~PermissionInfo.PROTECTION_FLAG_INSTANT);
                }

                // Check whether the newly-scanned package wants to define an already-defined perm
                if (bp != null) {
                    final String sourcePackageName = bp.getPackageName();

                    if (!doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
                            scanFlags)) {
                        // If the owning package is the system itself, we log but allow
                        // install to proceed; we fail the install on all other permission
                        // redefinitions.
                        if (!sourcePackageName.equals("android")) {
                            throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
                                    + parsedPackage.getPackageName()
                                    + " attempting to redeclare permission "
                                    + perm.getName() + " already owned by "
                                    + sourcePackageName)
                                    .conflictsWithExistingPermission(perm.getName(),
                                            sourcePackageName);
                        } else {
                            Slog.w(TAG, "Package " + parsedPackage.getPackageName()
                                    + " attempting to redeclare system permission "
                                    + perm.getName() + "; ignoring new declaration");
                            parsedPackage.removePermission(i);
                        }
                    } else if (!PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())) {
                        // Prevent apps to change protection level to dangerous from any other
                        // type as this would allow a privilege escalation where an app adds a
                        // normal/signature permission in other app's group and later redefines
                        // it as dangerous leading to the group auto-grant.
                        if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE)
                                == PermissionInfo.PROTECTION_DANGEROUS) {
                            if (bp != null && !bp.isRuntime()) {
                                Slog.w(TAG, "Package " + parsedPackage.getPackageName()
                                        + " trying to change a non-runtime permission "
                                        + perm.getName()
                                        + " to runtime; keeping old protection level");
                                perm.setProtectionLevel(bp.getProtectionLevel());
                            }
                        }
                    }
                }

                if (perm.getGroup() != null
                        && cannotInstallWithBadPermissionGroups(parsedPackage)) {
                    boolean isPermGroupDefinedByPackage = false;
                    for (int groupNum = 0; groupNum < numGroups; groupNum++) {
                        if (parsedPackage.getPermissionGroups().get(groupNum).getName()
                                .equals(perm.getGroup())) {
                            isPermGroupDefinedByPackage = true;
                            break;
                        }
                    }

                    if (!isPermGroupDefinedByPackage) {
                        final PermissionGroupInfo sourceGroup =
                                getPermissionGroupInfo(perm.getGroup(), 0);

                        if (sourceGroup == null) {
                            EventLog.writeEvent(0x534e4554, "146211400", -1,
                                    parsedPackage.getPackageName());

                            throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP,
                                    "Package "
                                            + parsedPackage.getPackageName()
                                            + " attempting to declare permission "
                                            + perm.getName() + " in non-existing group "
                                            + perm.getGroup());
                        } else {
                            String groupSourcePackageName = sourceGroup.packageName;

                            if (!PLATFORM_PACKAGE_NAME.equals(groupSourcePackageName)
                                    && !doesSignatureMatchForPermissions(groupSourcePackageName,
                                    parsedPackage, scanFlags)) {
                                EventLog.writeEvent(0x534e4554, "146211400", -1,
                                        parsedPackage.getPackageName());

                                throw new PrepareFailure(INSTALL_FAILED_BAD_PERMISSION_GROUP,
                                        "Package "
                                                + parsedPackage.getPackageName()
                                                + " attempting to declare permission "
                                                + perm.getName() + " in group "
                                                + perm.getGroup() + " owned by package "
                                                + groupSourcePackageName
                                                + " with incompatible certificate");
                            }
                        }
                    }
                }
            }
        }

下面要进入一段同步锁的代码段,变量systemApp代表是系统APP,replace代表是要替换安装。

我们在文章Android安装过程二 系统进程中PackageInstallerSession对象的创建 中代码分段二 中可以看到,会将PackageManager.INSTALL_REPLACE_EXISTING添加到SessionParams 对象中成员变量installFlags中,而我们这里FileInstallArgs对象成员变量installFlags的值就是来自SessionParams 对象中成员变量installFlags。所以,这里会进入这个if语句中。

先看一下,应用包名变更的一种情况。如果包名变更,需要在配置文件(Manifest文件)中,配置application同级别original-package标签 的属性 "name"的值。变更之后,再更新安装时,会将包名变更的关系保存在PackageManagerService对象成员mSettings(Settings类对象)的成员变量mRenamedPackages中,它是WatchedArrayMap类型,key是变更之后包名,值为变更之前的包名。

所以在这里,它先检查是否符合包名变更的情况,如果是,将解析包对象的包名设置为它的原来的包名。并且将临时变量pkgName设置为原来的包名,replace = true,代表它是替换更新。

如果不是包名变更,但是正常包更新安装的情况,就是mPackages.containsKey(pkgName)为true的情况。mPackages中包括所有安装成功应用的包信息。如果是这种情况,将replace = true,代表它是替换更新。

其他的就不是替换安装的情况,replace的值还是为false。

接下来就是处理替换安装的情况下,如果旧安装包的目标sdk版本大于Build.VERSION_CODES.LOLLIPOP_MR1,但是新包的目标sdk版本小于等于它,则报异常。如果旧包时持久包,并且安装标识没有PackageManager.INSTALL_STAGED,则也会报异常。

下面是从成员mSettings中通过包名取得PackageSetting对象,在安装成功之后,每个应用在mSettings中也会通过包名维持PackageSetting对象。如果它存在,下面主要会是去检查签名匹配。

如果解析包是静态分享库,它会通过名字存在多个版本,这里是去找一个最近版本的库信息。

之后拿到KeySetManagerService对象ksms。它里面维护这签名公钥和对应的key的关系。

ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)这种是对于APK里面Manifest文件中配置了"key-sets"标签的应用来说的。一般的应用不会配置它。

其他的会走else这种情况,它需要验证签名。变量compareCompat代表它会兼容签名升级之前和之后的格式。变量compareRecover代表它会考虑签名有可能会编码格式稍有错误的情况。这俩都代表一种回退的验证,如果正常验证没有通过,满足这俩条件,会进行回退验证。像compareCompat为true的情况下,这里需要理解一下PackageParser.SigningDetails这个类,它有一个成员变量Signature[] signatures,以前旧版本,如果多个证书,都会存在它里面。而现在,它们也会存在Signature类的成员变量Certificate[] mCertificateChain中,所以像这种情况,需要将mCertificateChain中的证书都取出来和之前的signatures进行比较。

这里调用verifySignatures()方法,是得验证通过的(和返回值compatMatch为true不完全一样),如果没通过,会扔出PackageManagerException异常。

如果compatMatch为true,需要删除该表对应的公钥相关的信息。

如果ps.pkg != null,会检查它是否是系统app。

res.origUsers则是已经安装的用户id。

接下来会检查解析包的PermissionGroup,它来自Manifest中的"permission-group"标签(和uses-permission同级)。如果某个PermissionGroup在被其他的应用声明过,或者该应用自己本身升级使用。如果它们的证书不相互符合,会报PrepareFailure异常。

下面是用来检查解析包的Permission,它来自Manifest中的"permission"标签(和uses-permission同级)。

如果声明的权限等级有PermissionInfo.PROTECTION_FLAG_INSTANT,但是它不是系统APP,需要将该等级去除。

如果该权限已经声明过(可能是应用本身声明也可能是其他应用),如果证书验证不能通过,加入原来声明权限的包名不是系统自己,会报异常。如果是系统自己声明的权限,需要将解析包的权限去除。

权限声明过,并且证书验证通过,但是解析包不是平台系统包名,如果它声明的权限是危险级,并且该权限之前不是运行时,需要将解析包的保护等级设置成它之前的等级。

下面接着处理的是,如果权限所属的权限组不是该解析包声明的权限组会做什么。isPermGroupDefinedByPackage为true,则说明它的权限组为解析包声明的,反之,则不是。

在它不是解析包声明的权限组的情况下。如果找不到权限组,则报PrepareFailure异常,是说该权限没有对应的权限组。如果存在权限组,但是权限组所属的应用包不为系统,并且它和解析包的证书验证不通过的情况下,也会报不一致签名证书的PrepareFailure异常。

分段三

分段三:

java 复制代码
        if (systemApp) {
            if (onExternal) {
                // Abort update; system app can't be replaced with app on sdcard
                throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
                        "Cannot install updates to system apps on sdcard");
            } else if (instantApp) {
                // Abort update; system app can't be replaced with an instant app
                throw new PrepareFailure(INSTALL_FAILED_SESSION_INVALID,
                        "Cannot update a system app with an instant app");
            }
        }

        if (args.move != null) {
            // We did an in-place move, so dex is ready to roll
            scanFlags |= SCAN_NO_DEX;
            scanFlags |= SCAN_MOVE;

            synchronized (mLock) {
                final PackageSetting ps = mSettings.getPackageLPr(pkgName);
                if (ps == null) {
                    res.setError(INSTALL_FAILED_INTERNAL_ERROR,
                            "Missing settings for moved package " + pkgName);
                }

                // We moved the entire application as-is, so bring over the
                // previously derived ABI information.
                parsedPackage.setPrimaryCpuAbi(ps.primaryCpuAbiString)
                        .setSecondaryCpuAbi(ps.secondaryCpuAbiString);
            }

        } else {
            // Enable SCAN_NO_DEX flag to skip dexopt at a later stage
            scanFlags |= SCAN_NO_DEX;

            try {
                PackageSetting pkgSetting;
                synchronized (mLock) {
                    pkgSetting = mSettings.getPackageLPr(pkgName);
                }
                boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
                        && pkgSetting.getPkgState().isUpdatedSystemApp();
                final String abiOverride = deriveAbiOverride(args.abiOverride);
                AndroidPackage oldPackage = mPackages.get(pkgName);
                boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
                        derivedAbi = mInjector.getAbiHelper().derivePackageAbi(parsedPackage,
                        isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
                        abiOverride, mAppLib32InstallDir);
                derivedAbi.first.applyTo(parsedPackage);
                derivedAbi.second.applyTo(parsedPackage);
            } catch (PackageManagerException pme) {
                Slog.e(TAG, "Error deriving application ABI", pme);
                throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
                        "Error deriving application ABI: " + pme.getMessage());
            }
        }

        if (!args.doRename(res.returnCode, parsedPackage)) {
            throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
        }

        try {
            setUpFsVerityIfPossible(parsedPackage);
        } catch (InstallerException | IOException | DigestException | NoSuchAlgorithmException e) {
            throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR,
                    "Failed to set up verity: " + e);
        }

        final PackageFreezer freezer =
                freezePackageForInstall(pkgName, installFlags, "installPackageLI");
        boolean shouldCloseFreezerBeforeReturn = true;

如果是系统app,不能更新到外部空间,不能使用instant app方式更新。

args.move != null代表是需要移动应用包,这里先不说。

接着将scanFlags添加上SCAN_NO_DEX标识。

再接下来就是处理本地库和确定使用的指令集。mInjector.getAbiHelper()在这里是PackageAbiHelperImpl对象,调用它的derivePackageAbi()方法,就是确定使用的指令abi,还会将对应的so包文件拷贝出来,放到对应的文件夹中。这块参看 Android 提取出Apk的本地库 该篇文章。使用的指令abi就在结果derivedAbi的first中,复制出来的so文件所在文件信息在derivedAbi.second中,所以将它们设置到解析包对象parsedPackage中。看下对应的类的apply方法如下:

java 复制代码
    final class Abis {
        public void applyTo(ParsedPackage pkg) {
            pkg.setPrimaryCpuAbi(primary)
                    .setSecondaryCpuAbi(secondary);
        }   
    }
	............
    final class NativeLibraryPaths {
        public void applyTo(ParsedPackage pkg) {
            pkg.setNativeLibraryRootDir(nativeLibraryRootDir)
                    .setNativeLibraryRootRequiresIsa(nativeLibraryRootRequiresIsa)
                    .setNativeLibraryDir(nativeLibraryDir)
                    .setSecondaryNativeLibraryDir(secondaryNativeLibraryDir);
        }
    }             

可见这里是将对应值设置到pkg对象(实际是PackageImpl对象)的primaryCpuAbi、secondaryCpuAbi、nativeLibraryRootDir、nativeLibraryRootRequiresIsa、nativeLibraryDir、secondaryNativeLibraryDir中。

接下来调用args.doRename(res.returnCode, parsedPackage),这里args是FileInstallArgs对象,所以会调用FileInstallArgs类的doRename(int status, ParsedPackage parsedPackage)方法,这里是重命名安装文件的目录。

接下来就是处理安装文件,如果可能,将它enable fs-verity。

下面会调用freezePackageForInstall(pkgName, installFlags, "installPackageLI")创建一个PackageFreezer。如果installFlags没有INSTALL_DONT_KILL_APP标识,并且之前已经安装过该应用,在创建PackageFreezer时,会将该应用杀掉,以防止它继续运行,导致混乱。

shouldCloseFreezerBeforeReturn变量是说该方法执行完返回时,是否应该关闭刚才创建的PackageFreezer对象。

下面先看看重命名安装文件的目录,也即FileInstallArgs类的doRename()方法,再看看文件使能fs-verity,也即setUpFsVerityIfPossible(parsedPackage)方法。

重命名安装文件的目录

java 复制代码
    class FileInstallArgs extends InstallArgs {
    	......
        boolean doRename(int status, ParsedPackage parsedPackage) {
            if (status != PackageManager.INSTALL_SUCCEEDED) {
                cleanUp();
                return false;
            }

            final File targetDir = resolveTargetDir();
            final File beforeCodeFile = codeFile;
            final File afterCodeFile = getNextCodePath(targetDir, parsedPackage.getPackageName());

            if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
            final boolean onIncremental = mIncrementalManager != null
                    && isIncrementalPath(beforeCodeFile.getAbsolutePath());
            try {
                makeDirRecursive(afterCodeFile.getParentFile(), 0775);
                if (onIncremental) {
                    // Just link files here. The stage dir will be removed when the installation
                    // session is completed.
                    mIncrementalManager.linkCodePath(beforeCodeFile, afterCodeFile);
                } else {
                    Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
                }
            } catch (IOException | ErrnoException e) {
                Slog.w(TAG, "Failed to rename", e);
                return false;
            }

            if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) {
                Slog.w(TAG, "Failed to restorecon");
                return false;
            }

            // Reflect the rename internally
            codeFile = afterCodeFile;

            // Reflect the rename in scanned details
            try {
                parsedPackage.setCodePath(afterCodeFile.getCanonicalPath());
            } catch (IOException e) {
                Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
                return false;
            }
            parsedPackage.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
                    afterCodeFile, parsedPackage.getBaseApkPath()));
            parsedPackage.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
                    afterCodeFile, parsedPackage.getSplitCodePaths()));

            return true;
        } 
        ......
    }           	

如果当前结果已经是失败了,就调用cleanUp()执行清理。

紧接着是调用resolveTargetDir()得到目标目录,看一下它的实现,

java 复制代码
        private File resolveTargetDir() {
            boolean isStagedInstall = (installFlags & INSTALL_STAGED) != 0;
            if (isStagedInstall) {
                return Environment.getDataAppDirectory(null);
            } else {
                return codeFile.getParentFile();
            }
        }

如果是staged安装,则目录为"/data/app"。如果是正常安装,得到成员变量codeFile的父目录。我们知道我们这里codeFile也是安装文件的目录,它里面包含安装文件。像我们的例子中如果是内置存储位置,codeFile为"/data/app/vmdl" + sessionId + ".tmp",里面有安装文件为"base.apk"。所以这里得到的目标目录为"/data/app/"。

回到doRename()中,将beforeCodeFile为之前的安装目录。

再通过getNextCodePath()得到之后安装文件的目录文件,赋值给afterCodeFile。它的格式为 targetDir/~~[randomStrA]/[packageName]-[randomStrB],其中randomStrA、randomStrB都是随机数。

接着会创建新生成目录。之后,会调用Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath())将之前的安装目录重命名为新生成的目录。像我们的例子即将"/data/app/vmdl" + sessionId + ".tmp"目录重命名为 /data/app/~~[randomStrA]/[packageName]-[randomStrB]。

之后,会将codeFile指向新生成的目录。

再之后,会将解析包对象的path设置为新生成的目录。

下面也是设置mBaseApkPath为新生成的目录+安装包的名字。splitCodePaths为新生成的目录+其他安装包的名字。

文件设置fs-verity

java 复制代码
    /**
     * Set up fs-verity for the given package if possible.  This requires a feature flag of system
     * property to be enabled only if the kernel supports fs-verity.
     *
     * <p>When the feature flag is set to legacy mode, only APK is supported (with some experimental
     * kernel patches). In normal mode, all file format can be supported.
     */
    private void setUpFsVerityIfPossible(AndroidPackage pkg) throws InstallerException,
            PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
        final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
        final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
        if (!standardMode && !legacyMode) {
            return;
        }

        if (isIncrementalPath(pkg.getPath()) && IncrementalManager.getVersion()
                < IncrementalManager.MIN_VERSION_TO_SUPPORT_FSVERITY) {
            return;
        }

        // Collect files we care for fs-verity setup.
        ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
        if (legacyMode) {
            synchronized (mLock) {
                final PackageSetting ps = mSettings.getPackageLPr(pkg.getPackageName());
                if (ps != null && ps.isPrivileged()) {
                    fsverityCandidates.put(pkg.getBaseApkPath(), null);
                    if (pkg.getSplitCodePaths() != null) {
                        for (String splitPath : pkg.getSplitCodePaths()) {
                            fsverityCandidates.put(splitPath, null);
                        }
                    }
                }
            }
        } else {
            // NB: These files will become only accessible if the signing key is loaded in kernel's
            // .fs-verity keyring.
            fsverityCandidates.put(pkg.getBaseApkPath(),
                    VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));

            final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
                    pkg.getBaseApkPath());
            if (new File(dmPath).exists()) {
                fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
            }

            if (pkg.getSplitCodePaths() != null) {
                for (String path : pkg.getSplitCodePaths()) {
                    fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));

                    final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
                    if (new File(splitDmPath).exists()) {
                        fsverityCandidates.put(splitDmPath,
                                VerityUtils.getFsveritySignatureFilePath(splitDmPath));
                    }
                }
            }
        }

        for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
            final String filePath = entry.getKey();
            final String signaturePath = entry.getValue();

            if (!legacyMode) {
                // fs-verity is optional for now.  Only set up if signature is provided.
                if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
                    try {
                        VerityUtils.setUpFsverity(filePath, signaturePath);
                    } catch (IOException e) {
                        throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
                                "Failed to enable fs-verity: " + e);
                    }
                }
                continue;
            }

            // In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
            final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(filePath);
            if (result.isOk()) {
                if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling verity to " + filePath);
                final FileDescriptor fd = result.getUnownedFileDescriptor();
                try {
                    final byte[] rootHash = VerityUtils.generateApkVerityRootHash(filePath);
                    try {
                        // A file may already have fs-verity, e.g. when reused during a split
                        // install. If the measurement succeeds, no need to attempt to set up.
                        mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
                    } catch (InstallerException e) {
                        mInstaller.installApkVerity(filePath, fd, result.getContentSize());
                        mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
                    }
                } finally {
                    IoUtils.closeQuietly(fd);
                }
            } else if (result.isFailed()) {
                throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
                        "Failed to generate verity");
            }
        }
    }

变量standardMode、legacyMode代表两种模式:标准模式、遗留模式。看一下标准模式、遗留模式的条件:

java 复制代码
    /** Returns true if standard APK Verity is enabled. */
    static boolean isApkVerityEnabled() {
        return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R
                || SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED)
                        == FSVERITY_ENABLED;
    }

    static boolean isLegacyApkVerityEnabled() {
        return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_LEGACY;
    }

可见和系统SDK版本和系统属性"ro.apk_verity.mode"的值有关。如果SDK版本大于等于R时或者"ro.apk_verity.mode"的值为FSVERITY_ENABLED时,就为标准模式。如果"ro.apk_verity.mode"的值为FSVERITY_LEGACY时,为遗留模式。所谓标准模式,就是以后新的都使用这种模式,而遗留模式则是之前实现的,现在需要考虑兼容的。

如果这两种模式都不满足,就结束执行。

下面就要收集哪些文件需要建立fs-verity。fsverityCandidates就是收集的文件集合。

如果在遗留模式下,当前解析包的应用是特权应用,会将它的基本包和分包安装文件收集到fsverityCandidates中的key。fsverityCandidates的value值是文件对应使用的证书文件,之前是不用的,所以设置为null。

标准模式fsverity

如果是在标准模式下,会将解析包的基本安装文件路径和证书文件路径放到fsverityCandidates中。证书文件是安装文件名+".fsv_sig"的文件,它们在一个文件夹中。

如果对应安装文件的dex元数据文件(它的名字是安装文件名字的".apk"替换成".dm")存在,也会将它放到fsverityCandidates集合中。

同样如果存在分包,也和基础安装文件是同样的处理。

再往下就是循环fsverityCandidates集合中的文件,进行设置fsverity。

可以看到,如果是在标准模式下,fsverity也不是必定进行的,也得提供证书的情况下,才会调用VerityUtils.setUpFsverity(filePath, signaturePath)设置fsverity。并且使用的这个公钥需要在.fs-verity内核密钥环中。

遗留模式fsverity

如果是在遗留模式下,看一下如何设置fsverity。

VerityUtils.generateApkVeritySetupData(filePath)方法,它会生成设置fsverity的数据,并且它里面会进行一些验证。如果数据生成成功了,它生成设置fsverity的数据会放在一个共享内存中,result.getUnownedFileDescriptor()得到文件描述符即指向共享内存。

VerityUtils.generateApkVerityRootHash(filePath)是生成摘要hash。它是用来判断文件是否已经设置fsverity。mInstaller.assertFsverityRootHashMatches(filePath, rootHash)就是为了验证文件是否已经设置fsverity,因为有的文件可能已经设置过fsverity,所以如果验证通过,接着就关闭共享内存。如果文件没有设置fsverity,则会调用mInstaller.installApkVerity(filePath, fd, result.getContentSize())是执行设置fsverity,设置之后,再调用一遍mInstaller.assertFsverityRootHashMatches(filePath, rootHash)执行验证。

如果生成设置fsverity的数据的方法返回失败了,会扔出PrepareFailure异常。

下面来看看生成设置fsverity的数据,然后再看看生成验证摘要数据,之后看它如何设置fsverity。

生成设置fsverity的数据

它的实现是VerityUtils.generateApkVeritySetupData(filePath),看一下它的代码:

java 复制代码
    /**
     * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
     *
     * @deprecated This is only used for previous fs-verity implementation, and should never be used
     *             on new devices.
     * @return {@code SetupResult} that contains the result code, and when success, the
     *         {@code FileDescriptor} to read all the data from.
     */
    @Deprecated
    public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
        if (DEBUG) {
            Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
        }
        SharedMemory shm = null;
        try {
            final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
            if (signedVerityHash == null) {
                if (DEBUG) {
                    Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
                }
                return SetupResult.skipped();
            }

            Pair<SharedMemory, Integer> result =
                    generateFsVerityIntoSharedMemory(apkPath, signedVerityHash);
            shm = result.first;
            int contentSize = result.second;
            FileDescriptor rfd = shm.getFileDescriptor();
            if (rfd == null || !rfd.valid()) {
                return SetupResult.failed();
            }
            return SetupResult.ok(Os.dup(rfd), contentSize);
        } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException
                | SignatureNotFoundException | ErrnoException e) {
            Slog.e(TAG, "Failed to set up apk verity: ", e);
            return SetupResult.failed();
        } finally {
            if (shm != null) {
                shm.close();
            }
        }
    }

ApkSignatureVerifier.getVerityRootHash(apkPath)主要就是从Apk文件中的签名分块中找到验证的根hash。这些内容可以参考一下该篇博客 Android APK文件的签名V2查找、验证。如果没有该验证摘要,就返回SetupResult.skipped(),代表跳过。

接着是调用generateFsVerityIntoSharedMemory(apkPath, signedVerityHash)来生成建立FsVerity的数据并将它们放入共享内存中。他返回的结果是一个Pair对象result,result.first是一个共享内存对象,result.second则代表其中数据的长度。如果没有问题,会返回SetupResult.ok(Os.dup(rfd), contentSize)。其中rfd是共享内存的文件描述符,contentSize是数据的长度。

看一下generateFsVerityIntoSharedMemory(apkPath, signedVerityHash)方法:

java 复制代码
    /**
     * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
     * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
     * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
     * length equals to the returned {@code Integer}.
     */
    private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath,
            @NonNull byte[] expectedRootHash)
            throws IOException, DigestException, NoSuchAlgorithmException,
                   SignatureNotFoundException {
        TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
        byte[] generatedRootHash =
                ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
        // We only generate Merkle tree once here, so it's important to make sure the root hash
        // matches the signed one in the apk.
        if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
            throw new SecurityException("verity hash mismatch: "
                    + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash));
        }

        int contentSize = shmBufferFactory.getBufferLimit();
        SharedMemory shm = shmBufferFactory.releaseSharedMemory();
        if (shm == null) {
            throw new IllegalStateException("Failed to generate verity tree into shared memory");
        }
        if (!shm.setProtect(OsConstants.PROT_READ)) {
            throw new SecurityException("Failed to set up shared memory correctly");
        }
        return Pair.create(shm, contentSize);
    }

TrackedShmBufferFactory是用来创建共享内存的类。ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory)是用来生成Merkle树根的摘要值,同时生成设置fsverity的数据也在刚生成的参数shmBufferFactory中。

接着拿生成Merkle树根的摘要值和参数中的摘要值expectedRootHash相比,如果不相等,会报异常。

shmBufferFactory.getBufferLimit()是设置fsverity的数据的长度,shmBufferFactory.releaseSharedMemory()是得到共享内存对象shm 。最后是构造成Pair对象返回。

再看一下ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory)的实现:

java 复制代码
    public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
            NoSuchAlgorithmException {
        // first try v3
        try {
            return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory);
        } catch (SignatureNotFoundException e) {
            // try older version
        }
        return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory);
    }

它还是先按照V3签名的方式处理,如果没找到V3签名就会去按照V2签名的方式生成根的hash。这里就按照V2签名的方式来说,它的实现在ApkSignatureSchemeV2Verifier类中:

java 复制代码
    static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)
            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
                   NoSuchAlgorithmException {
        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
            SignatureInfo signatureInfo = findSignature(apk);
            return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo);
        }
    }

这里是找到签名的信息对象signatureInfo,然后调用VerityBuilder类的generateApkVerity(apkPath, bufferFactory, signatureInfo)方法

java 复制代码
    /**
     * Generates the apk-verity header and hash tree to be used by kernel for the given apk. This
     * method does not check whether the root hash exists in the Signing Block or not.
     *
     * <p>The output is stored in the {@link ByteBuffer} created by the given {@link
     * ByteBufferFactory}.
     *
     * @return the root hash of the generated hash tree.
     */
    @NonNull
    static byte[] generateApkVerity(@NonNull String apkPath,
            @NonNull ByteBufferFactory bufferFactory, @NonNull SignatureInfo signatureInfo)
            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
                   NoSuchAlgorithmException {
        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
            VerityResult result = generateVerityTreeInternal(apk, bufferFactory, signatureInfo);
            ByteBuffer footer = slice(result.verityData, result.merkleTreeSize,
                    result.verityData.limit());
            generateApkVerityFooter(apk, signatureInfo, footer);
            // Put the reverse offset to apk-verity header at the end.
            footer.putInt(footer.position() + 4);
            result.verityData.limit(result.merkleTreeSize + footer.position());
            return result.rootHash;
        }
    }

generateVerityTreeInternal(apk, bufferFactory, signatureInfo)是通过构建merkle树来计算出来树根的摘要值。它还会计算出来merkle树的大小和对应的数据。这块参考一下该篇博客 Android APK文件完整性验证

接下来会处理设置fsverity的所需数据的尾部数据footer。它是通过generateApkVerityFooter(apk, signatureInfo, footer)实现的,接着会在尾部数据添加上数据的位置footer.position() + 4。

VerityResult类型result是上面构造merkle树方法计算出来的结果,result.verityData即是设置fsverity的所需数据。它在这里会将它的数据所在的位置设置为result.merkleTreeSize + footer.position()。result.merkleTreeSize即为merkle树的大小,footer.position()为数据尾部数据大小。

result.rootHash即为计算出来树根的摘要值,将它返回。

fsverity的所需数据的尾部数据的生成

咱们看下尾部数据footer的生成,

java 复制代码
    static void generateApkVerityFooter(@NonNull RandomAccessFile apk,
            @NonNull SignatureInfo signatureInfo, @NonNull ByteBuffer footerOutput)
            throws IOException {
        footerOutput.order(ByteOrder.LITTLE_ENDIAN);
        generateApkVerityHeader(footerOutput, apk.length(), DEFAULT_SALT);
        long signingBlockSize =
                signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
        generateApkVerityExtensions(footerOutput, signatureInfo.apkSigningBlockOffset,
                signingBlockSize, signatureInfo.eocdOffset);
    }

先是调用generateApkVerityHeader(footerOutput, apk.length(), DEFAULT_SALT)生成尾部数据的头数据,之后调用generateApkVerityExtensions(footerOutput, signatureInfo.apkSigningBlockOffset, signingBlockSize, signatureInfo.eocdOffset)生成尾部数据的扩展数据。signingBlockSize 是签名块的大小。

先看下头数据的生成:

java 复制代码
    private static ByteBuffer generateApkVerityHeader(ByteBuffer buffer, long fileSize,
            byte[] salt) {
        if (salt.length != 8) {
            throw new IllegalArgumentException("salt is not 8 bytes long");
        }

        // TODO(b/30972906): update the reference when there is a better one in public.
        buffer.put("TrueBrew".getBytes());  // magic

        buffer.put((byte) 1);               // major version
        buffer.put((byte) 0);               // minor version
        buffer.put((byte) 12);              // log2(block-size): log2(4096)
        buffer.put((byte) 7);               // log2(leaves-per-node): log2(4096 / 32)

        buffer.putShort((short) 1);         // meta algorithm, SHA256 == 1
        buffer.putShort((short) 1);         // data algorithm, SHA256 == 1

        buffer.putInt(0);                   // flags
        buffer.putInt(0);                   // reserved

        buffer.putLong(fileSize);           // original file size

        buffer.put((byte) 2);               // authenticated extension count
        buffer.put((byte) 0);               // unauthenticated extension count
        buffer.put(salt);                   // salt (8 bytes)
        skip(buffer, 22);                   // reserved

        return buffer;
    }

可以根据注释看一下每个字段的意思,总共64个字节的数据。魔数为"TrueBrew",相关版本、对应算法、文件大小、盐等数据。这里的盐是8个数值为0的byte数组。

先看下扩展数据的生成:

java 复制代码
    private static ByteBuffer generateApkVerityExtensions(ByteBuffer buffer,
            long signingBlockOffset, long signingBlockSize, long eocdOffset) {
        // Snapshot of the experimental fs-verity structs (different from upstream).
        //
        // struct fsverity_extension_elide {
        //   __le64 offset;
        //   __le64 length;
        // }
        //
        // struct fsverity_extension_patch {
        //   __le64 offset;
        //   u8 databytes[];
        // };

        final int kSizeOfFsverityExtensionHeader = 8;
        final int kExtensionSizeAlignment = 8;

        {
            // struct fsverity_extension #1
            final int kSizeOfFsverityElidedExtension = 16;

            // First field is total size of extension, padded to 64-bit alignment
            buffer.putInt(kSizeOfFsverityExtensionHeader + kSizeOfFsverityElidedExtension);
            buffer.putShort((short) 1);  // ID of elide extension
            skip(buffer, 2);             // reserved

            // struct fsverity_extension_elide
            buffer.putLong(signingBlockOffset);
            buffer.putLong(signingBlockSize);
        }

        {
            // struct fsverity_extension #2
            final int kTotalSize = kSizeOfFsverityExtensionHeader
                    + 8 // offset size
                    + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;

            buffer.putInt(kTotalSize);   // Total size of extension, padded to 64-bit alignment
            buffer.putShort((short) 2);  // ID of patch extension
            skip(buffer, 2);             // reserved

            // struct fsverity_extension_patch
            buffer.putLong(eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);  // offset
            buffer.putInt(Math.toIntExact(signingBlockOffset));  // databytes

            // The extension needs to be 0-padded at the end, since the length may not be multiple
            // of 8.
            int kPadding = kExtensionSizeAlignment - kTotalSize % kExtensionSizeAlignment;
            if (kPadding == kExtensionSizeAlignment) {
                kPadding = 0;
            }
            skip(buffer, kPadding);      // padding
        }

        return buffer;
    }

看这注释,它这数据是按照fsverity_extension_elide、fsverity_extension_patch结构来进行填充的。

其中方法参数signingBlockOffset是签名块的偏移值、signingBlockSize是签名块的大小、eocdOffset是中央目录尾部的数据在apk文件中的偏移量。

这样我们就看完了fsverity的所需数据。前面是Merkle树的数据,后面是尾部数据,尾部数据包括头和扩展数据。头数据是一些魔数为"TrueBrew",相关版本、对应算法、文件大小、盐等数据。扩展数据是和fsverity_extension_elide、fsverity_extension_patch结构相关的数据。

设置文件fsverity

设置文件fsverity是Installer对象的installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)实现的,Installer对象又通过AIDL与installd进程进行交互。它最终会调用到InstalldNativeService.cpp文件中的installApkVerity(const std::string& filePath, android::base::unique_fd verityInputAshmem, int32_t contentSize)方法:

java 复制代码
binder::Status InstalldNativeService::installApkVerity(const std::string& filePath,
        android::base::unique_fd verityInputAshmem, int32_t contentSize) {
    ENFORCE_UID(AID_SYSTEM);
    CHECK_ARGUMENT_PATH(filePath);
    std::lock_guard<std::recursive_mutex> lock(mLock);

    if (!android::base::GetBoolProperty(kPropApkVerityMode, false)) {
        return ok();
    }
#ifndef NDEBUG
    ASSERT_PAGE_SIZE_4K();
#endif
    // TODO: also check fsverity support in the current file system if compiled with DEBUG.
    // TODO: change ashmem to some temporary file to support huge apk.
    if (!ashmem_valid(verityInputAshmem.get())) {
        return error("FD is not an ashmem");
    }

    // 1. Seek to the next page boundary beyond the end of the file.
    ::android::base::unique_fd wfd(open(filePath.c_str(), O_WRONLY));
    if (wfd.get() < 0) {
        return error("Failed to open " + filePath);
    }
    struct stat st;
    if (fstat(wfd.get(), &st) < 0) {
        return error("Failed to stat " + filePath);
    }
    // fsverity starts from the block boundary.
    off_t padding = kVerityPageSize - st.st_size % kVerityPageSize;
    if (padding == kVerityPageSize) {
        padding = 0;
    }
    if (lseek(wfd.get(), st.st_size + padding, SEEK_SET) < 0) {
        return error("Failed to lseek " + filePath);
    }

    // 2. Write everything in the ashmem to the file.  Note that allocated
    //    ashmem size is multiple of page size, which is different from the
    //    actual content size.
    int shmSize = ashmem_get_size_region(verityInputAshmem.get());
    if (shmSize < 0) {
        return error("Failed to get ashmem size: " + std::to_string(shmSize));
    }
    if (contentSize < 0) {
        return error("Invalid content size: " + std::to_string(contentSize));
    }
    if (contentSize > shmSize) {
        return error("Content size overflow: " + std::to_string(contentSize) + " > " +
                     std::to_string(shmSize));
    }
    auto data = std::unique_ptr<void, std::function<void (void *)>>(
        mmap(nullptr, contentSize, PROT_READ, MAP_SHARED, verityInputAshmem.get(), 0),
        [contentSize] (void* ptr) {
          if (ptr != MAP_FAILED) {
            munmap(ptr, contentSize);
          }
        });

    if (data.get() == MAP_FAILED) {
        return error("Failed to mmap the ashmem");
    }
    char* cursor = reinterpret_cast<char*>(data.get());
    int remaining = contentSize;
    while (remaining > 0) {
        int ret = TEMP_FAILURE_RETRY(write(wfd.get(), cursor, remaining));
        if (ret < 0) {
            return error("Failed to write to " + filePath + " (" + std::to_string(remaining) +
                         + "/" + std::to_string(contentSize) + ")");
        }
        cursor += ret;
        remaining -= ret;
    }
    wfd.reset();

    // 3. Enable fsverity (needs readonly fd. Once it's done, the file becomes immutable.
    ::android::base::unique_fd rfd(open(filePath.c_str(), O_RDONLY));
    if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) {
        return error("Failed to enable fsverity on " + filePath);
    }
    return ok();
}

参数verityInputAshmem是共享内存的文件描述符,contentSize是设置fsverity数据的长度,filePath是文件的路径。

先打开文件filePath,得到wfd文件描述符。然后调用fstat,得到文件信息,如果文件长度不是kVerityPageSize(4KB)的整数倍,需要得到填充数据的数量padding。

接着调用lseek,将文件设置到文件长度+padding的位置。

下面shmSize是共享内存的大小,如果数据大小contentSize大于共享内存大小会报溢出错误。

接着data是通过mmap映射到共享内存的数据。

然后通过while循环将数据写入文件的文件长度+padding的位置之后。

再打开文件,通过ioctl调用FS_IOC_ENABLE_VERITY命令,将文件使能fsverity。

这样我们就将遗留模式的文件使能fsverity说完了。

下面我们接着返回到preparePackageLI(InstallArgs args, PackageInstalledInfo res)方法中,继续看代码

分段四

分段四:

java 复制代码
        try {
            final AndroidPackage existingPackage;
            String renamedPackage = null;
            boolean sysPkg = false;
            int targetScanFlags = scanFlags;
            int targetParseFlags = parseFlags;
            final PackageSetting ps;
            final PackageSetting disabledPs;
            if (replace) {
                if (parsedPackage.isStaticSharedLibrary()) {
                    // Static libs have a synthetic package name containing the version
                    // and cannot be updated as an update would get a new package name,
                    // unless this is installed from adb which is useful for development.
                    AndroidPackage existingPkg = mPackages.get(parsedPackage.getPackageName());
                    if (existingPkg != null
                            && (installFlags & PackageManager.INSTALL_FROM_ADB) == 0) {
                        throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PACKAGE,
                                "Packages declaring "
                                        + "static-shared libs cannot be updated");
                    }
                }

                final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;

                final AndroidPackage oldPackage;
                final String pkgName11 = parsedPackage.getPackageName();
                final int[] allUsers;
                final int[] installedUsers;
                final int[] uninstalledUsers;

                synchronized (mLock) {
                    oldPackage = mPackages.get(pkgName11);
                    existingPackage = oldPackage;
                    if (DEBUG_INSTALL) {
                        Slog.d(TAG,
                                "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage);
                    }

                    ps = mSettings.getPackageLPr(pkgName11);
                    disabledPs = mSettings.getDisabledSystemPkgLPr(ps);

                    // verify signatures are valid
                    final KeySetManagerService ksms = mSettings.getKeySetManagerService();
                    if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) {
                        if (!ksms.checkUpgradeKeySetLocked(ps, parsedPackage)) {
                            throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                    "New package not signed by keys specified by upgrade-keysets: "
                                            + pkgName11);
                        }
                    } else {
                        SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails();
                        SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails();
                        // default to original signature matching
                        if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails,
                                SigningDetails.CertCapabilities.INSTALLED_DATA)
                                && !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails,
                                SigningDetails.CertCapabilities.ROLLBACK)) {
                            // Allow the update to proceed if this is a rollback and the parsed
                            // package's current signing key is the current signer or in the lineage
                            // of the old package; this allows a rollback to a previously installed
                            // version after an app's signing key has been rotated without requiring
                            // the rollback capability on the previous signing key.
                            if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
                                    parsedPkgSigningDetails)) {
                                throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                        "New package has a different signature: " + pkgName11);
                            }
                        }
                    }

                    // don't allow a system upgrade unless the upgrade hash matches
                    if (oldPackage.getRestrictUpdateHash() != null && oldPackage.isSystem()) {
                        final byte[] digestBytes;
                        try {
                            final MessageDigest digest = MessageDigest.getInstance("SHA-512");
                            updateDigest(digest, new File(parsedPackage.getBaseApkPath()));
                            if (!ArrayUtils.isEmpty(parsedPackage.getSplitCodePaths())) {
                                for (String path : parsedPackage.getSplitCodePaths()) {
                                    updateDigest(digest, new File(path));
                                }
                            }
                            digestBytes = digest.digest();
                        } catch (NoSuchAlgorithmException | IOException e) {
                            throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
                                    "Could not compute hash: " + pkgName11);
                        }
                        if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
                            throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
                                    "New package fails restrict-update check: " + pkgName11);
                        }
                        // retain upgrade restriction
                        parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
                    }

                    // Check for shared user id changes
                    String invalidPackageName = null;
                    if (!Objects.equals(oldPackage.getSharedUserId(),
                            parsedPackage.getSharedUserId())) {
                        invalidPackageName = parsedPackage.getPackageName();
                    }

                    if (invalidPackageName != null) {
                        throw new PrepareFailure(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                                "Package " + invalidPackageName + " tried to change user "
                                        + oldPackage.getSharedUserId());
                    }

                    // In case of rollback, remember per-user/profile install state
                    allUsers = mUserManager.getUserIds();
                    installedUsers = ps.queryInstalledUsers(allUsers, true);
                    uninstalledUsers = ps.queryInstalledUsers(allUsers, false);


                    // don't allow an upgrade from full to ephemeral
                    if (isInstantApp) {
                        if (args.user == null || args.user.getIdentifier() == UserHandle.USER_ALL) {
                            for (int currentUser : allUsers) {
                                if (!ps.getInstantApp(currentUser)) {
                                    // can't downgrade from full to instant
                                    Slog.w(TAG,
                                            "Can't replace full app with instant app: " + pkgName11
                                                    + " for user: " + currentUser);
                                    throw new PrepareFailure(
                                            PackageManager.INSTALL_FAILED_SESSION_INVALID);
                                }
                            }
                        } else if (!ps.getInstantApp(args.user.getIdentifier())) {
                            // can't downgrade from full to instant
                            Slog.w(TAG, "Can't replace full app with instant app: " + pkgName11
                                    + " for user: " + args.user.getIdentifier());
                            throw new PrepareFailure(
                                    PackageManager.INSTALL_FAILED_SESSION_INVALID);
                        }
                    }
                }

                // Update what is removed
                res.removedInfo = new PackageRemovedInfo(this);
                res.removedInfo.uid = oldPackage.getUid();
                res.removedInfo.removedPackage = oldPackage.getPackageName();
                res.removedInfo.installerPackageName = ps.installSource.installerPackageName;
                res.removedInfo.isStaticSharedLib = parsedPackage.getStaticSharedLibName() != null;
                res.removedInfo.isUpdate = true;
                res.removedInfo.origUsers = installedUsers;
                res.removedInfo.installReasons = new SparseArray<>(installedUsers.length);
                for (int i = 0; i < installedUsers.length; i++) {
                    final int userId = installedUsers[i];
                    res.removedInfo.installReasons.put(userId, ps.getInstallReason(userId));
                }
                res.removedInfo.uninstallReasons = new SparseArray<>(uninstalledUsers.length);
                for (int i = 0; i < uninstalledUsers.length; i++) {
                    final int userId = uninstalledUsers[i];
                    res.removedInfo.uninstallReasons.put(userId, ps.getUninstallReason(userId));
                }

                sysPkg = oldPackage.isSystem();
                if (sysPkg) {
                    // Set the system/privileged/oem/vendor/product flags as needed
                    final boolean privileged = oldPackage.isPrivileged();
                    final boolean oem = oldPackage.isOem();
                    final boolean vendor = oldPackage.isVendor();
                    final boolean product = oldPackage.isProduct();
                    final boolean odm = oldPackage.isOdm();
                    final boolean systemExt = oldPackage.isSystemExt();
                    final @ParseFlags int systemParseFlags = parseFlags;
                    final @ScanFlags int systemScanFlags = scanFlags
                            | SCAN_AS_SYSTEM
                            | (privileged ? SCAN_AS_PRIVILEGED : 0)
                            | (oem ? SCAN_AS_OEM : 0)
                            | (vendor ? SCAN_AS_VENDOR : 0)
                            | (product ? SCAN_AS_PRODUCT : 0)
                            | (odm ? SCAN_AS_ODM : 0)
                            | (systemExt ? SCAN_AS_SYSTEM_EXT : 0);

                    if (DEBUG_INSTALL) {
                        Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
                                + ", old=" + oldPackage);
                    }
                    res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
                    targetParseFlags = systemParseFlags;
                    targetScanFlags = systemScanFlags;
                } else { // non system replace
                    replace = true;
                    if (DEBUG_INSTALL) {
                        Slog.d(TAG,
                                "replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
                                        + oldPackage);
                    }
                }
            } else { // new package install

如果replace为true,代表是一个更新安装。下面这段代码都是处理更新安装的情况。

如果安装包是一个静态分享库,它的名字会包含版本号,如果允许更新会生成一个新的包名,这是不允许,除非是使用adb安装形式,是可以的。

ps是旧的PackageSetting对象,代码分段二中说过了KeySetManagerService对象,ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)这种是对于APK里面Manifest文件中配置了"key-sets"标签的应用来说的。一般的应用不会配置它。

进入到else分支后,是检查签名验证的。如果parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails, SigningDetails.CertCapabilities.INSTALLED_DATA)和oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails, SigningDetails.CertCapabilities.ROLLBACK)都不满足的情况下,isRollback为true并且oldPkgSigningDetails.hasAncestorOrSelf( parsedPkgSigningDetails)为true才会不报PrepareFailure异常。

系统包一般不允许更新,除非更新文件的hash摘要也旧包的相同。而新文件的hash摘要是使用文件的内容进行"SHA-512"摘要算法得到的。

如果解析包和旧包的SharedUserId不同,会报INSTALL_FAILED_SHARED_USER_INCOMPATIBLE PrepareFailure异常。

installedUsers变量是旧包的安装用户,uninstalledUsers是旧包的卸载用户。

如果现在是InstantApp安装,而之前不是,则会报INSTALL_FAILED_SESSION_INVALID PrepareFailure异常。它不允许从完全安装向instant安装降级。

接着会更新删除信息,它是一个PackageRemovedInfo对象。它包含uid,删除包名、安装者的包名、静态共享库是否、安装用户、还有安装用户的原因、卸载用户的原因。

再接下来,如果是系统包更新,会根据旧包的状态,来更新浏览标识,这些标识包括SCAN_AS_PRIVILEGED、SCAN_AS_OEM、SCAN_AS_VENDOR、SCAN_AS_PRODUCT、SCAN_AS_ODM、SCAN_AS_SYSTEM_EXT。

接着会设置res的返回code为PackageManager.INSTALL_SUCCEEDED。

接着会更新变量targetParseFlags、targetScanFlags的值。

如果不是系统更新则不进行处理了。

分段五

分段五:

java 复制代码
            } else { // new package install
                ps = null;
                disabledPs = null;
                replace = false;
                existingPackage = null;
                // Remember this for later, in case we need to rollback this install
                String pkgName1 = parsedPackage.getPackageName();

                if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + parsedPackage);

                // TODO(patb): MOVE TO RECONCILE
                synchronized (mLock) {
                    renamedPackage = mSettings.getRenamedPackageLPr(pkgName1);
                    if (renamedPackage != null) {
                        // A package with the same name is already installed, though
                        // it has been renamed to an older name.  The package we
                        // are trying to install should be installed as an update to
                        // the existing one, but that has not been requested, so bail.
                        throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS,
                                "Attempt to re-install " + pkgName1
                                        + " without first uninstalling package running as "
                                        + renamedPackage);
                    }
                    if (mPackages.containsKey(pkgName1)) {
                        // Don't allow installation over an existing package with the same name.
                        throw new PrepareFailure(INSTALL_FAILED_ALREADY_EXISTS,
                                "Attempt to re-install " + pkgName1
                                        + " without first uninstalling.");
                    }
                }
            }
            // we're passing the freezer back to be closed in a later phase of install
            shouldCloseFreezerBeforeReturn = false;

            return new PrepareResult(replace, targetScanFlags, targetParseFlags,
                    existingPackage, parsedPackage, replace /* clearCodeCache */, sysPkg,
                    ps, disabledPs);
        } finally {
            res.freezer = freezer;
            if (shouldCloseFreezerBeforeReturn) {
                freezer.close();
            }
        }
    }

这个else里面的处理是针对初次安装的应用来说的。

可见ps、disabledPs、existingPackage都设置为null,因为现在还没有旧包。replace自然为false。

接着是处理,包改过名字,但是不是按照正常的改包的步骤来做的(配置文件(Manifest文件)中,配置application同级别original-package标签 的属性 "name"的值),这时renamedPackage != null,所以会报异常INSTALL_FAILED_ALREADY_EXISTS异常。如果mPackages.containsKey(pkgName1)为true,也代表不是首次安装,也会报异常。

shouldCloseFreezerBeforeReturn = false,说明关闭freezer在之后的某个安装阶段执行。 如果 shouldCloseFreezerBeforeReturn 为true,代表需要关闭freezer,直接执行freezer.close()关闭它。

最后将是否替换包、浏览标识、解析标识、存在的旧解析包、新的安装解析包、清除代码缓存、是否系统包、旧的PackSetting对象,替换的系统PackSetting对象封装到PrepareResult对象中,返回它。

总结

准备阶段做的事情,咱们在这里做一下总结:

1、解析安装包,并且这里是以Cluster方式解析的。

2、如果是更新安装,会检查签名。还会检查安装文件中声明的权限和权限组。

3、解析包的so包复制到对应目录中,并将使用的ABI和对应的so包路径设置到解析包对象中。

4、修改解析包的安装路径和安装位置。

5、如果可能,会将安装文件设置FsVerity

6、如果更新安装,还会检查签名,这次比上次严格。

7、如果更新安装,会设置删除包的信息。

8、最后将信息封装到PrepareResult对象中返回。

相关推荐
天机️灵韵1 小时前
云效DevOps vs Gitee vs 自建GitLab的技术选型
git·开源项目
每次的天空1 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
眠修2 小时前
Kuberrnetes 服务发布
linux·运维·服务器
恋猫de小郭2 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日3 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安3 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑3 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
即将头秃的程序媛4 小时前
centos 7.9安装tomcat,并实现开机自启
linux·运维·centos
fangeqin5 小时前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
爱奥尼欧6 小时前
【Linux 系统】基础IO——Linux中对文件的理解
linux·服务器·microsoft