背景
android 11版本(api level 30)上新增了隐私查看api。运用运用相应的api来查看自己的运用中是否有超出预料外的隐私访问(比方接入的第三方sdk有未奉告的隐私访问行为)。
api阐明详见谷歌官方文档:
developer.android.google.cn/guide/topic…
从文档中咱们能够得知,相应的功用完成是在AppOpsManager完成的。在这篇文档中咱们将讨论AppOpsManager在这块代码上的更新。
以下代码剖析根据Android 10和11版本源码。关于android 11,个人在本地运用的是lineageOS 18.1的源码,或许和aosp略有出入。
首要结构
AppOps在android 4.3版本就已经引进,关于运用开发者来说一向不可见。Ops,为Opereations的简写,意为“运用的操作、行为”。在android 6.0引进运行时权限之前,AppOps机制一向作为隐藏权限办理机制存在,国内各大手机厂商定制rom基本都会根据AppOps进行二次开发,以完成自己的权限办理。AppOps中界说的“行为”是“权限”的超集,所以这篇文章我将多用“行为办理”而不是“权限办理”来描述AppOps的操作。
AppOpsService完成了首要的行为校验机制。和其他所有的相似服务相同,在开机时发动并注入到体系服务会集。AppOpsManager首要完成了对外的接口,可供开发者及其他体系服务调用。
流程剖析
咱们以比较简单的Clipboard(剪贴板)服务调用来作为示例进行剖析。
运用获取到ClipboardManager之后,经过ClipboardManager.getPrimaryClip接口获取粘帖板中信息。ClipboardManager经过绑定的信息获取调用者的packageName和uid,然后调用ClipboardService.getPrimaryClip办法。
在ClipboardService.getPrimaryClip办法中,能够看到调用了clipboardAccessAllowed的办法。这个办法便是验证权限和行为的办法。在这儿我把要害代码展示一下:
private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
@UserIdInt int userId, boolean shouldNoteOp) {
boolean allowed = false;
// First, verify package ownership to ensure use below is safe.
mAppOps.checkPackage(uid, callingPackage);
// Shell can access the clipboard for testing purposes.
if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
callingPackage) == PackageManager.PERMISSION_GRANTED) {
allowed = true;
}
// The default IME is always allowed to access the clipboard.
String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD, userId);
if (!TextUtils.isEmpty(defaultIme)) {
final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
if (imePkg.equals(callingPackage)) {
allowed = true;
}
}
switch (op) {
case AppOpsManager.OP_READ_CLIPBOARD:
// Clipboard can only be read by applications with focus..
// or the application have the INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL
// at the same time. e.x. SystemUI. It needs to check the window focus of
// Binder.getCallingUid(). Without checking, the user X can't copy any thing from
// INTERNAL_SYSTEM_WINDOW to the other applications.
if (!allowed) {
allowed = mWm.isUidFocused(uid)
|| isInternalSysWindowAppWithWindowFocus(callingPackage);
}
if (!allowed && mContentCaptureInternal != null) {
// ...or the Content Capture Service
// The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
// is used to check if the uid has the permission BIND_CONTENT_CAPTURE_SERVICE.
// if the application has the permission, let it to access user's clipboard.
// To passed synthesized uid user#10_app#systemui may not tell the real uid.
// userId must pass intending userId. i.e. user#10.
allowed = mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId);
}
if (!allowed && mAutofillInternal != null) {
// ...or the Augmented Autofill Service
// The uid parameter of mAutofillInternal.isAugmentedAutofillServiceForUser
// is used to check if the uid has the permission BIND_AUTOFILL_SERVICE.
// if the application has the permission, let it to access user's clipboard.
// To passed synthesized uid user#10_app#systemui may not tell the real uid.
// userId must pass intending userId. i.e. user#10.
allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
}
break;
case AppOpsManager.OP_WRITE_CLIPBOARD:
// Writing is allowed without focus.
allowed = true;
break;
default:
throw new IllegalArgumentException("Unknown clipboard appop " + op);
}
if (!allowed) {
Slog.e(TAG, "Denying clipboard access to " + callingPackage
+ ", application is not in focus nor is it a system service for "
+ "user " + userId);
return false;
}
// Finally, check the app op.
int appOpsResult;
if (shouldNoteOp) {
appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
} else {
appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
}
return appOpsResult == AppOpsManager.MODE_ALLOWED;
}
首先咱们看到调用了mAppOps.checkPackage(uid, callingPackage);,这儿首要是为了保证调用者的包名和uid是归于同一个运用的。个人了解首要是为了躲避跨运用的攻击行为,增强安全性。
然后是调用了PackageManager的鉴权办法。mPm.checkPermission,校验经过会设置标志位为true。
另外默认的IME(输入法)能够设置一直答应运用获取剪切板信息。这儿经过查看了同样会设置标志位为true。
然后便是校验op(operation,行为)。这儿依旧是查看是否有操作的权限,比方一般运用假如未获取焦点,是不答应读取剪切板的。
经过了上述所有查看之后,在最终,会调用AppOpsManager来进行行为查看。
noteOp和checkOp的首要表现是相同的,最大的不同点是noteOp会在校验行为的一起,对行为进行记载。这个办法的回来值介绍如下:
-
MODE_ALLOWED:故名思议,答应操作。
-
MODE_IGNORED:“忽略”,运用未具有相应操作的权限,可是此刻会静默拒绝,不会抛出反常导致运用crash。
-
MODE_ERRORED:和MODE_IGNORED相似,同样是运用不具有相应权限。可是此刻framework会抛出一个SecurityException反常。
-
MODE_DEFAULT:不常见
-
MODE_FOREGROUND:故名意思,只有运用在前台的时分才被答应相应操作。
咱们从noteOp这个办法持续往下的剖析。能够发现在最终会调用到AppOpsManager.noteOpNoThrow办法中。这个办法在android 10上的完成极为简单,可是在android 11上做了很大的改变。咱们对比一下前后的改变:
android 10
public int noteOpNoThrow(int op, int uid, String packageName) {
try {
return mService.noteOperation(op, uid, packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
android 11
public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message) {
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false;
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
// Set stack trace as default message
message = getFormattedStackTrace();
shouldCollectMessage = true;
}
}
int mode = mService.noteOperation(op, uid, packageName, attributionTag,
collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
collectNotedOpForSelf(op, attributionTag);
} else if (collectionMode == COLLECT_SYNC) {
collectNotedOpSync(op, attributionTag);
}
}
return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
能够看到android 11上首要是增加了搜集调用者信息的相关办法。
首先经过collectNoteOpCallsForValidation进行信息搜集。这个办法内部如下:
private void collectNoteOpCallsForValidation(int op) {
if (NOTE_OP_COLLECTION_ENABLED) {
try {
mService.collectNoteOpCallsForValidation(getFormattedStackTrace(),
op, mContext.getOpPackageName(), mContext.getApplicationInfo().longVersionCode);
} catch (RemoteException e) {
// Swallow error, only meant for logging ops, should not affect flow of the code
}
}
}
经过getFormattedStackTrace获取现在的调用栈信息,然后将信息传入AppOpsService中,以供存入文件。由于AppOpsService归于跨进程调用,无法直接获取调用者的仓库信息,而AppOpsManager能够做到,所以才将这部分逻辑代码放置在AppOpsManager中完成。
咱们持续跟随noteOpNoThrow中的代码。调用了getNotedOpCollectionMode办法来判别当前是否要进行信息搜集。这部分判别会决定最终是否调用collectNotedOpForSelf或者collectNotedOpSync。从源码中咱们能够得知,这两块的信息搜集是传递给OnOpNotedCallback的,用户经过AppOpsManager设置的隐私代码审计便是经过这个接口。
getNotedOpCollectionMode办法会从sAppOpsToNote数组中取得判别结果,这个数组是一个简单的缓存结构,在没有缓存的时分会从AppOpsService.shouldCollectNotes中取得结果。这个办法的代码如下:
public boolean shouldCollectNotes(int opCode) {
Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
String perm = AppOpsManager.opToPermission(opCode);
if (perm == null) {
return false;
}
PermissionInfo permInfo;
try {
permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
return permInfo.getProtection() == PROTECTION_DANGEROUS
|| (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0;
}
最要害的判别其实是最终一行。前一句permInfo.getProtection() == PROTECTION_DANGEROUS比较好了解,当权限是PROTECTION_DANGEROUS等级的时分,回来true。后一句涉及到一个额外的标志,appop。这儿我用举例的方式解说,咱们定位到framework/base/core/res/AndroidManifest.xml,查看ACCESS_NOTIFICATIONS这个权限:
<permission android:name="android.permission.ACCESS_NOTIFICATIONS"
android:protectionLevel="signature|privileged|appop" />
像这种在protectionLevel标签中附加appop的权限,哪怕不是PROTECTION_DANGEROUS等级,同样会回来为true。可是咱们能够看到,附加了这种标签的权限非常少,只有寥寥几个。所以大部分非敏感权限,都是不会触发OnOpNotedCallback的回调的。可是不扫除google将来收紧的或许。
总结
能够看到,android中的“权限校验”不止permissionManager,还混杂了AppOps在其间。两者共同完成了权限校验。可是AppOps的查看更为广泛,很多没有被界说为“权限”的行为,也会由AppOps来进行查看。
AppOps在android 11上有了很大更新,添加了隐私查看机制,记载的信息也更加全面。关于运用开发者来说,能够经过新api来查看自己的运用是否有预料之外的隐私访问,躲避法令危险。关于framework开发者来说,能够经过新的api来更轻松地完成相似隐私访问统计的功用(如MIUI照明弹);