本文代碼部分基于Android 11.0(Android R)
故事的開(kāi)始
今天老大急沖沖的跑過(guò)來(lái)說(shuō):xx,你幫我看看這手機(jī)咋回事,突然開(kāi)不了機(jī)。
我心想:我最近也沒(méi)提過(guò)代碼,應(yīng)該不是我的問(wèn)題吧。(甩鍋.)
把電腦插上手機(jī)后,我看到下面這段報(bào)錯(cuò)一直在loop
12-31 16:08:49.603 21899 21899 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
12-31 16:08:49.603 21899 21899 E AndroidRuntime: java.lang.IllegalStateException: Signature|privileged permissions not in privapp-permissions whitelist: {com.xxx.xxx.xxxxx (/data/app/BR9Kz0rmscIpqqvqBf8jwg==/com.xxx.xxx.xxxxx-fLGzzHKkZaTB5_DLxgo_Fg==): android.permission.BACKUP, com.xxx.xxx.xxxxx (/data/app/BR9Kz0rmscIpqqvqBf8jwg==/com.xxx.xxx.xxxxx-fLGzzHKkZaTB5_DLxgo_Fg==): android.permission.UPDATE_DEVICE_STATS}
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.server.pm.permission.PermissionManagerService.systemReady(PermissionManagerService.java:4688)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.server.pm.permission.PermissionManagerService.access$500(PermissionManagerService.java:181)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.server.pm.permission.PermissionManagerService$PermissionManagerServiceInternalImpl.systemReady(PermissionManagerService.java:4771)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.server.pm.PackageManagerService.systemReady(PackageManagerService.java:22183)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.server.SystemServer.startOtherServices(SystemServer.java:2305)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.server.SystemServer.run(SystemServer.java:624)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.server.SystemServer.main(SystemServer.java:440)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
12-31 16:08:49.603 21899 21899 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
看完之后我立馬回答:老大,這個(gè)我知道,一定是系統(tǒng)應(yīng)用組的同學(xué)忘記在privapp-permissions-platform.xml文件下面加權(quán)限聲明了。
老大:不會(huì)呀,這個(gè)手機(jī)我一直在用,后面突然就變成這樣了。
此刻的我一臉懵逼:還有如此神奇之事。
故事的開(kāi)始便是這樣,接下來(lái)我就開(kāi)始調(diào)查這個(gè)神奇的現(xiàn)象。
萬(wàn)惡之源
《眾 所 周 知》
手機(jī)開(kāi)機(jī)會(huì)檢查priv-app的權(quán)限是否和/etc/permissions/privapp-permissions-platform.xml(有可能在別的文件夾下,例如vendor/etc/permissions,也有可能叫其他名字,因?yàn)橹灰獂ml的節(jié)點(diǎn)是對(duì)的就行,pm中的SystemConfig會(huì)對(duì)這類文件夾的所有xml進(jìn)行掃描)所聲明的權(quán)限是否一樣,不一樣則無(wú)法開(kāi)機(jī),就會(huì)一直loop上面的crash。
先從log中的PermissionManagerService#systemReady方法入手
private void systemReady() {
mSystemReady = true;
// 萬(wàn)惡之源
if (mPrivappPermissionsViolations != null) {
throw new IllegalStateException("Signature|privileged permissions not in "
+ "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
}
// ...
}
只要mPrivappPermissionsViolations這個(gè)數(shù)組中有數(shù)據(jù),我們就永遠(yuǎn)無(wú)法開(kāi)機(jī),看看這個(gè)數(shù)組是怎么填充的。
private boolean grantSignaturePermission(String perm, AndroidPackage pkg,
PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions) {
// ...
if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged()
&& !platformPackage && platformPermission) {
if (!hasPrivappWhitelistEntry(perm, pkg)) {
if (!mSystemReady
&& !pkgSetting.getPkgState().isUpdatedSystemApp()) {
ApexManager apexMgr = ApexManager.getInstance();
String apexContainingPkg = apexMgr.getActiveApexPackageNameContainingPackage(
pkg);
if (apexContainingPkg == null || apexMgr.isFactory(
apexMgr.getPackageInfo(apexContainingPkg, MATCH_ACTIVE_PACKAGE))) {
// ... 進(jìn)行和xml聲明的權(quán)限進(jìn)行對(duì)比... 發(fā)現(xiàn)在黑名單找到這個(gè)perm的或者在白名單找不到這個(gè)perm
// 一律加到開(kāi)機(jī)防火墻(我自己想的名字0.0)中
if (permissionViolation) {
Slog.w(TAG, "Privileged permission " + perm + " for package "
+ pkg.getPackageName() + " (" + pkg.getCodePath()
+ ") not in privapp-permissions whitelist");
if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
if (mPrivappPermissionsViolations == null) {
mPrivappPermissionsViolations = new ArraySet<>();
}
mPrivappPermissionsViolations.add(
pkg.getPackageName() + " (" + pkg.getCodePath() + "): "
+ perm);
}
} else {
return false;
}
}
// ....
}
}
}
}
}
看完這里,我有一個(gè)疑問(wèn),一個(gè)普通的App怎么會(huì)有如此大的影響(小身材大力量?),可以看到上面各種條件判斷才能走到這個(gè)校檢權(quán)限聲明的碼塊里。而這其中的一個(gè)條件引起了我的注意---pkg.isPrivileged(),這個(gè)為true則說(shuō)明這是一個(gè)priv-app,而priv-app一般都是內(nèi)置在系統(tǒng)內(nèi)作為系統(tǒng)軟件,還能在外部安裝?(可能是我見(jiàn)識(shí)太少了 丟人~~)。與之關(guān)聯(lián)的是一個(gè)scanFlag叫SCAN_AS_PRIVILEGED,這個(gè)在開(kāi)機(jī)掃描的時(shí)候就經(jīng)常使用,對(duì)一個(gè)指定的系統(tǒng)內(nèi)部路徑scan的時(shí)候加上SCAN_AS_PRIVILEGED,這個(gè)路徑下面所有的包都是priv-app。果不其然,當(dāng)我找這個(gè)flag的使用時(shí)候,發(fā)現(xiàn)PMS的其他地方也會(huì)給一個(gè)包附上這個(gè)flag,就在PMS#adjustScanFlags方法中。
// 對(duì)和priv-app進(jìn)行sharedUser的應(yīng)用也要像priv-app一樣掃描
final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0)
&& getVendorPartitionVersion() < 28;
if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
&& !pkg.isPrivileged()
&& (pkg.getSharedUserId() != null)
&& !skipVendorPrivilegeScan) {
SharedUserSetting sharedUserSetting = null;
try {
sharedUserSetting = mSettings.getSharedUserLPw(pkg.getSharedUserId(), 0,
0, false);
} catch (PackageManagerException ignore) {
}
if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
// 豁免使用平臺(tái)密鑰簽名的SharedUsers。
// TODO(b / 72378145)修復(fù)此豁免。
// 強(qiáng)制簽名應(yīng)用程序像其他priv-apps一樣將其特權(quán)許可列入白名單。
synchronized (mLock) {
PackageSetting platformPkgSetting = mSettings.mPackages.get("android");
if ((compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures,
pkg.getSigningDetails().signatures)
!= PackageManager.SIGNATURE_MATCH)) {
scanFlags |= SCAN_AS_PRIVILEGED;
}
}
}
}
而在PMS#adjustScanFlags方法中,給和priv-app sharedUser的應(yīng)用 append 上SCAN_AS_PRIVILEGED,讓其也要強(qiáng)制加入到開(kāi)機(jī)檢查的白名單列表,所以google這是故意而為之。既然作為一個(gè)priv-app,開(kāi)機(jī)檢查權(quán)限也就成了理所當(dāng)然了。所以我們?yōu)閜riv-app預(yù)置到系統(tǒng)的時(shí)候,要確定該priv-app有沒(méi)有其他的sharedUserId應(yīng)用,如果有的話,也要在權(quán)限白名單privapp-permissions-platform.xml中聲明這個(gè)沒(méi)有預(yù)置到系統(tǒng)的App申請(qǐng)的權(quán)限,否則裝上去之后重啟會(huì)造成loop crash。
為什么SharedUser的應(yīng)用能夠共享權(quán)限
《 眾 所 周 知》 * 2
所有檢查權(quán)限的最后步驟都是通過(guò)調(diào)用PackageSettings中的getPermissionsState()獲取App的權(quán)限獲取狀態(tài),代碼如下:
@Override
public PermissionsState getPermissionsState() {
return (sharedUser != null)
? sharedUser.getPermissionsState()
: super.getPermissionsState();
}
如果Package的PackageSettings的sharedUser不為空,則默認(rèn)用sharedUser的權(quán)限。所以SharedUser的應(yīng)用能互相共享大家已有的權(quán)限,且一個(gè)權(quán)限授予了,在這個(gè)SharedUser的其他包都會(huì)默認(rèn)授予這個(gè)權(quán)限。做系統(tǒng)應(yīng)用的同學(xué)可能比較了解,只要在manifest.xml中聲明android:sharedUserId="android.uid.system",就能獲取并使用其他同屬于android.uid.systemSharedUser組的其他權(quán)限。
SharedUser怎樣被賦值到PackageSettings上
我們先來(lái)看看PMS#scanPackageNewLI方法中的這個(gè)地方,也就值sharedUser賦值的地方。
SharedUserSetting sharedUserSetting = null;
if (parsedPackage.getSharedUserId() != null) {
// SIDE EFFECTS; may potentially allocate a new shared user
sharedUserSetting = mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
if (DEBUG_PACKAGE_SCANNING) {
if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
+ " (uid=" + sharedUserSetting.userId + "):"
+ " packages=" + sharedUserSetting.packages);
}
}
我們可以看到這里根據(jù)parsedPackage.getSharedUserId這個(gè)String類型的SharedUserId,就去pm.Settings中尋找屬于這個(gè)package的SharedUserSetting了,也沒(méi)有校驗(yàn)這個(gè)包的sharedUserId是否符合某些條件,例如簽名之類的條件。直到最后的PMS#commitPackageSettings(持久化PackageSettings到packages.xml文件中,安裝包數(shù)據(jù)不再發(fā)生變化步驟),也沒(méi)有對(duì)這個(gè)SharedUserSetting進(jìn)行重新賦值,所以到這步我覺(jué)得應(yīng)該是SharedUserId的問(wèn)題。機(jī)智的我把想法就放在了這個(gè)SharedUserId的賦值流程上面了,一通查找之下,我找到了SharedUserId賦值的地方:ParsingPackageUtils#parseSharedUser
private static ParseResult<ParsingPackage> parseSharedUser(ParseInput input,
ParsingPackage pkg, TypedArray sa) {
String str = nonConfigString(0, R.styleable.AndroidManifest_sharedUserId, sa);
if (TextUtils.isEmpty(str)) {
return input.success(pkg);
}
if (!"android".equals(pkg.getPackageName())) {
ParseResult<?> nameResult = validateName(input, str, true, true);
if (nameResult.isError()) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
"<manifest> specifies bad sharedUserId name \"" + str + "\": "
+ nameResult.getErrorMessage());
}
}
return input.success(pkg
.setSharedUserId(str.intern())
.setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
}
上面這段代碼很簡(jiǎn)單,大概的意思就是讀取R.styleable.AndroidManifest_sharedUserId這個(gè)attribute中的內(nèi)容,然后直接給它賦值到這個(gè)String類型的SharedUserId上面,也沒(méi)有做合規(guī)檢查。這時(shí)候不禁就有個(gè)疑問(wèn)了,那我豈不是隨便聲明一個(gè)android:sharedUserId="xxx"就能獲取到SharedUser的其他權(quán)限?這時(shí)候我打開(kāi)了測(cè)試的test demo,給它加上android:sharedUserId屬性,但沒(méi)有成功安裝成功,且報(bào)了INSTALL_FAILED_SHARED_USER_INCOMPATIBLE異常,往PMS那么一搜,啪的一下,很快就找到辣(但其實(shí)是找錯(cuò)了 因?yàn)閳?bào)錯(cuò)不是在PMS中產(chǎn)生)。因?yàn)檎义e(cuò)了的原因,我把安裝的堆棧打了出來(lái),發(fā)現(xiàn)報(bào)錯(cuò)其實(shí)是在PackageManagerServiceUtils#verifySignatures。
先把堆棧打出來(lái)
verifySignatures:689, PackageManagerServiceUtils (com.android.server.pm) reconcilePackagesLocked:16947, PackageManagerService (com.android.server.pm) installPackagesLI:17373, PackageManagerService (com.android.server.pm) installPackagesTracedLI:16696, PackageManagerService (com.android.server.pm) lambda$processInstallRequestsAsync$22$PackageManagerService:14802, PackageManagerService (com.android.server.pm) run:-1, -$$Lambda$PackageManagerService$9znobjOH7ab0F1jsW2oFdNipS-8 (com.android.server.pm) handleCallback:938, Handler (android.os) dispatchMessage:99, Handler (android.os) loop:223, Looper (android.os) run:67, HandlerThread (android.os) run:44, ServiceThread (com.android.server)
這也很符合邏輯,SharedUser中的其他包:你說(shuō)你是我兄弟,那你就是我兄弟?拿你的簽名跟我的對(duì)比一下,一樣的才算我兄弟。畢竟在包管理中,一個(gè)包的簽名相當(dāng)于這個(gè)包的DNA了,只有經(jīng)過(guò)同一個(gè)x509.pem和.pk8文件簽名的apk的簽名才會(huì)相同。
可以看到這里校驗(yàn)簽名不匹配直接拋出了throw new PackageManagerException,終止安裝流程,根本不給你安裝成功的機(jī)會(huì)。所以并不需要置空SharedUserSetting或者SharedUserId,只要安裝成功的Package且它們的SharedUserSetting不為空,都是合法的。
/**
* Verifies that signatures match.
* @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
* @throws PackageManagerException if the signatures did not match.
*/
public static boolean verifySignatures(PackageSetting pkgSetting,
PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
boolean compareCompat, boolean compareRecover)
throws PackageManagerException {
final String packageName = pkgSetting.name;
boolean compatMatch = false;
// ...
// 檢查是否匹配SharedUser的簽名
if (pkgSetting.getSharedUser() != null
&& pkgSetting.getSharedUser().signatures.mSigningDetails
!= PackageParser.SigningDetails.UNKNOWN) {
// 已經(jīng)存在的軟件包。確保簽名匹配。在簽署證書(shū)輪換的情況下,
// 帶有較新證書(shū)的軟件包必須與較舊版本的sharedUserId保持一致。
// 我們檢查是否新軟件包是由較舊的證書(shū)簽名的,可以使用當(dāng)前的sharedUser簽名,
// 還是由較新的證書(shū)簽名,以及是否與現(xiàn)有的簽名證書(shū)作為sharedUser簽名,則可以。
boolean match =
parsedSignatures.checkCapability(
pkgSetting.getSharedUser().signatures.mSigningDetails,
PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)
|| pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability(
parsedSignatures,
PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID);
// 如果sharedUserId功能檢查失敗,
// 則可能是由于這是sharedUserId中到目前為止唯一的包,
// 并且繼承被更新以拒絕繼承中先前密鑰的sharedUserId功能。
if (!match && pkgSetting.getSharedUser().packages.size() == 1
&& pkgSetting.getSharedUser().packages.valueAt(0).name.equals(packageName)) {
match = true;
}
if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, pkgSetting.getSharedUser().signatures, parsedSignatures);
}
if (!match && compareRecover) {
match =
matchSignaturesRecover(packageName,
pkgSetting.getSharedUser().signatures.mSigningDetails,
parsedSignatures,
PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)
|| matchSignaturesRecover(packageName,
parsedSignatures,
pkgSetting.getSharedUser().signatures.mSigningDetails,
PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID);
compatMatch |= match;
}
// 不配對(duì) 則拋異常 安裝失敗
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
"Package " + packageName
+ " has no signatures that match those in shared user "
+ pkgSetting.getSharedUser().name + "; ignoring!");
}
// ...
}
return compatMatch;
}
最后,請(qǐng)大家注意好手機(jī)備份和軟件來(lái)源的確認(rèn),如果有其他手機(jī)廠商“修復(fù)”了這個(gè)問(wèn)題可以在評(píng)論區(qū)告知,AndroidQ和R都會(huì)存在這個(gè)問(wèn)題,至于前面的版本就沒(méi)一一確認(rèn)了。我已經(jīng)用這個(gè)apk搞壞了一臺(tái)Google Pixel(淚目),普通用戶大概也只能恢復(fù)出廠設(shè)置了,高級(jí)用戶可以用串口打開(kāi)USB調(diào)試,然后把這個(gè)包卸載掉。