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对象中返回。

相关推荐
TeYiToKu5 分钟前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
dsywws7 分钟前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
yeyuningzi16 分钟前
Debian 12环境里部署nginx步骤记录
linux·运维·服务器
binishuaio24 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
上辈子杀猪这辈子学IT34 分钟前
【Zookeeper集群搭建】安装zookeeper、zookeeper集群配置、zookeeper启动与关闭、zookeeper的shell命令操作
linux·hadoop·zookeeper·centos·debian
minihuabei39 分钟前
linux centos 安装redis
linux·redis·centos
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
lldhsds2 小时前
书生大模型实战营第四期-入门岛-1. Linux前置基础
linux
Winston Wood2 小时前
Android Parcelable和Serializable的区别与联系
android·序列化
清风徐来辽2 小时前
Android 项目模型配置管理
android