安装应用的准备阶段是在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对象中返回。