安装应用的准备阶段是在PackageManagerService类中的preparePackageLI(InstallArgs args, PackageInstalledInfo res),代码有些长,分段阅读。
分段一
分段一:
@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异常。
分段二
分段二:
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异常。
分段三
分段三:
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方法如下:
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)方法。
重命名安装文件的目录
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()得到目标目录,看一下它的实现,
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
/** * 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代表两种模式:标准模式、遗留模式。看一下标准模式、遗留模式的条件:
/** 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),看一下它的代码:
/** * 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)方法:
/** * 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)的实现:
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类中:
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)方法
/** * 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的生成,
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 是签名块的大小。
先看下头数据的生成:
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数组。
先看下扩展数据的生成:
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)方法:
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)方法中,继续看代码
分段四
分段四:
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的值。
如果不是系统更新则不进行处理了。
分段五
分段五:
} 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对象中返回。