来源:
https://blog.csdn.net/shift_wwx/article/details/82108549
前言:
之前有篇博文 《Android基础总结之八:ContentProvider》大概说明provider 的基础知识。对于AndroidManifest.xml中provider 的解析可以看下博文《android PMS 如何解析 APK》,本文主要对provider 权限进行详细解析。如果provider 会被其他程序使用时,需要将export 属性设为true,还需要进行readPermission 和writePermission 设置。当然,对于SDK 22以后出现的FileProvider 又是另一种情况,本文会结合source code 详细分析Provider 在使用中权限管理。
本文代码基于版本Android O。
实例:
之前出现一个exception 的log,本文结合这个实例进行解析。
--------- beginning of crash
08-24 18:14:43.989 E/AndroidRuntime( 2366): FATAL EXCEPTION: main
08-24 18:14:43.989 E/AndroidRuntime( 2366): Process: com.android.messaging, PID: 2366
08-24 18:14:43.989 E/AndroidRuntime( 2366): java.lang.SecurityException: UID 10098 does not have permission to content://com.android.dialer.files/my_cache/my_cache/%2B12543365555_08-11-18_0442AM.amr [user 0]
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.os.Parcel.readException(Parcel.java:2005)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.os.Parcel.readException(Parcel.java:1951)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.app.IActivityManager$Stub$Proxy.startActivity(IActivityManager.java:4352)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.app.Instrumentation.execStartActivity(Instrumentation.java:1613)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.app.Activity.startActivityForResult(Activity.java:4501)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.app.Activity.startActivityForResult(Activity.java:4459)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.app.Activity.startActivity(Activity.java:4820)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.app.Activity.startActivity(Activity.java:4788)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at com.android.messaging.ui.Q.wT(SourceFile:200)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at com.android.messaging.ui.conversationlist.ShareIntentActivity.pR(SourceFile:179)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at com.android.messaging.ui.conversationlist.o.onClick(SourceFile:98)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:166)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.os.Handler.dispatchMessage(Handler.java:106)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.os.Looper.loop(Looper.java:164)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at android.app.ActivityThread.main(ActivityThread.java:6518)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at java.lang.reflect.Method.invoke(Native Method)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
08-24 18:14:43.989 E/AndroidRuntime( 2366): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
08-24 18:14:43.989 D/RecurrenceRule( 599): Resolving using anchor 2018-08-24T18:14:43.989+08:00[Asia/Shanghai]
Log 提示uid 为10098 的进程没有权限使用一个provider。
源码分析:
debug 函数startActivity(),最终会调用到AMS 中,下面将堆栈打印出来:
08-24 18:14:43.946 E/ActivityManager( 599): WJ Stack:java.lang.Throwable
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityManagerService.checkGrantUriPermissionLocked(ActivityManagerService.java:8975)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityManagerService.checkGrantUriPermissionFromIntentLocked(ActivityManagerService.java:9264)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityManagerService.grantUriPermissionFromIntentLocked(ActivityManagerService.java:9304)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityStarter.startActivityUnchecked(ActivityStarter.java:1203)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityStarter.startActivity(ActivityStarter.java:1000)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityStarter.startActivity(ActivityStarter.java:577)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityStarter.startActivityLocked(ActivityStarter.java:283)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityStarter.startActivityMayWait(ActivityStarter.java:822)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:4616)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityManagerService.startActivity(ActivityManagerService.java:4603)
08-24 18:14:43.946 E/ActivityManager( 599): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:121)
08-24 18:14:43.946 E/ActivityManager( 599): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971)
08-24 18:14:43.946 E/ActivityManager( 599): at android.os.Binder.execTransact(Binder.java:697)
在AMS 中会调用grantUriPermissionFromIntentLocked():
void grantUriPermissionFromIntentLocked(int callingUid,
String targetPkg, Intent intent, UriPermissionOwner owner, int targetUserId) {
NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg,
intent, intent != null ? intent.getFlags() : 0, null, targetUserId);
if (needed == null) {
return;
}
grantUriPermissionUncheckedFromIntentLocked(needed, owner);
}
这里参数intent、targetPkg、owner、targetUserId 都是ActivityStarter 传进来。
最终函数会调用到checkGrantUriPermissionLocked(),这个是权限处理的核心函数,应用需要确定Uri 权限会通过系统的public 接口函数checkGrantUriPermission(),最终也是会调用到checkGrantUriPermissionLocked()。
public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri,
final int modeFlags, int userId) {
enforceNotIsolatedCaller("checkGrantUriPermission");
synchronized(this) {
return checkGrantUriPermissionLocked(callingUid, targetPkg,
new GrantUri(userId, uri, false), modeFlags, -1);
}
}
来看下函数checkGrantUriPermissionLocked():
int checkGrantUriPermissionLocked(int callingUid, String targetPkg, GrantUri grantUri,
final int modeFlags, int lastTargetUid) {
// 要求Intent 的flags 设为FLAG_GRANT_READ_URI_PERMISSION 或FLAG_GRANT_WRITE_URI_PERMISSION
if (!Intent.isAccessUriMode(modeFlags)) {
return -1;
}
...
...
// 要求scheme 是content
if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Can't grant URI permission for non-content URI: " + grantUri);
return -1;
}
// Bail early if system is trying to hand out permissions directly; it
// must always grant permissions on behalf of someone explicit.
final int callingAppId = UserHandle.getAppId(callingUid);
if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {
// Exempted authority for cropping user photos in Settings app
} else {
Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
+ " grant to " + grantUri + "; use startActivityAsCaller() instead");
return -1;
}
}
...
...
// 确定targetUid 是否有该permission
if (targetUid >= 0) {
// First... does the target actually need this permission?
if (checkHoldingPermissionsLocked(pm, pi, grantUri, targetUid, modeFlags)) {
// No need to grant the target this permission.
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Target " + targetPkg + " already has full permission to " + grantUri);
return -1;
}
}
...
...
/* There is a special cross user grant if:
* - The target is on another user.
* - Apps on the current user can access the uri without any uid permissions.
* In this case, we grant a uri permission, even if the ContentProvider does not normally
* grant uri permissions.
*/
boolean specialCrossUserGrant = UserHandle.getUserId(targetUid) != grantUri.sourceUserId
&& checkHoldingPermissionsInternalLocked(pm, pi, grantUri, callingUid,
modeFlags, false /*without considering the uid permissions*/);
// Second... is the provider allowing granting of URI permissions?
if (!specialCrossUserGrant) {
if (!pi.grantUriPermissions) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
+ " does not allow granting of Uri permissions (uri "
+ grantUri + ")");
}
if (pi.uriPermissionPatterns != null) {
final int N = pi.uriPermissionPatterns.length;
boolean allowed = false;
for (int i=0; i<N; i++) {
if (pi.uriPermissionPatterns[i] != null
&& pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
allowed = true;
break;
}
}
if (!allowed) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
+ " does not allow granting of permission to path of Uri "
+ grantUri);
}
}
}
// 确认callingUid 是否有该permission
if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) {
// 确认是否之前已经存在该grant uri permission
if (!checkUriPermissionLocked(grantUri, callingUid, modeFlags)) {
if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(pi.readPermission)) {
throw new SecurityException(
"UID " + callingUid + " does not have permission to " + grantUri
+ "; you could obtain access using ACTION_OPEN_DOCUMENT "
+ "or related APIs");
} else {
throw new SecurityException(
"UID " + callingUid + " does not have permission to " + grantUri);
}
}
}
return targetUid;
}
1. Intent 的flags 要求设为 FLAG_GRANT_READ_URI_PERMISSION
, FLAG_GRANT_WRITE_URI_PERMISSION
,不然不处理
2. 要求Uri 的scheme 是content,不然不处理
3. 如果provider 的grantUriPermissions属性为true,需要确认grant-uri-permission,详细看 grant-uri-permission 文档
4. checkHoldingPermissionsLocked() 函数确定Provider的所需权限,详细看下面
5. checkUriPermissionLocked() 是在上面确认fail 时,还提供了其他check 方式,详细看下面。
来看下checkHoldingPermissionsLocked():
private final boolean checkHoldingPermissionsLocked(
IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"checkHoldingPermissionsLocked: uri=" + grantUri + " uid=" + uid);
if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true)
!= PERMISSION_GRANTED) {
return false;
}
}
Slog.v(TAG_URI_PERMISSION,
"enter checkHoldingPermissionsInternalLocked");
return checkHoldingPermissionsInternalLocked(pm, pi, grantUri, uid, modeFlags, true);
}
对于多用户需要最开始check INTERACT_ACROSS_USERS 权限,这里最终调用checkHoldingPermissionsInternalLocked():
private final boolean checkHoldingPermissionsInternalLocked(IPackageManager pm, ProviderInfo pi,
GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) {
if (pi.applicationInfo.uid == uid) {
return true;
} else if (!pi.exported) {
return false;
}
boolean readMet = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0;
boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
try {
// check if target holds top-level <provider> permissions
if (!readMet && pi.readPermission != null && considerUidPermissions
&& (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
readMet = true;
}
if (!writeMet && pi.writePermission != null && considerUidPermissions
&& (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
writeMet = true;
}
// track if unprotected read/write is allowed; any denied
// <path-permission> below removes this ability
boolean allowDefaultRead = pi.readPermission == null;
boolean allowDefaultWrite = pi.writePermission == null;
// check if target holds any <path-permission> that match uri
final PathPermission[] pps = pi.pathPermissions;
if (pps != null) {
final String path = grantUri.uri.getPath();
int i = pps.length;
while (i > 0 && (!readMet || !writeMet)) {
i--;
PathPermission pp = pps[i];
if (pp.match(path)) {
if (!readMet) {
final String pprperm = pp.getReadPermission();
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Checking read perm for " + pprperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(pprperm, uid));
if (pprperm != null) {
if (considerUidPermissions && pm.checkUidPermission(pprperm, uid)
== PERMISSION_GRANTED) {
readMet = true;
} else {
allowDefaultRead = false;
}
}
}
if (!writeMet) {
final String ppwperm = pp.getWritePermission();
if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Checking write perm " + ppwperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(ppwperm, uid));
if (ppwperm != null) {
if (considerUidPermissions && pm.checkUidPermission(ppwperm, uid)
== PERMISSION_GRANTED) {
writeMet = true;
} else {
allowDefaultWrite = false;
}
}
}
}
}
}
// grant unprotected <provider> read/write, if not blocked by
// <path-permission> above
if (allowDefaultRead) readMet = true;
if (allowDefaultWrite) writeMet = true;
} catch (RemoteException e) {
return false;
}
return readMet && writeMet;
}
1. 应用uid 和provider uid 相同时,check 通过
2. provider 的exported 属性如果为false,check 直接不通过
3. 在provider 的exported 属性为true 时,为了保护provider 有时候需要加上read permission 和write permission,如果provider 设定了这两个permission,应用在使用过的时候需要保证有这两个权限,如code 中会通过函数checkUidPermission()。
当然,如果provider 没有加这两个权限的保护,系统认为read 和write 都是允许的,如code:
if (allowDefaultRead) readMet = true;
if (allowDefaultWrite) writeMet = true;
结论:
如果希望checkHoldingPermissionsLocked() 通过,必须满足下面其中一点:
1. 应用的uid 和provider uid 相同
2. provider 的exported 设为true,而且应用必须同时拥有read permission 和write permission,如果provider 没有加这个保护,默认情况下应用是有这两个权限
第二种情况比较特殊,如果是FileProvider 那么exported 必须是false,code 如下:(详细看 FileProvider文档)
public void attachInfo(Context context, ProviderInfo info) {
super.attachInfo(context, info);
// Sanity check our security
if (info.exported) {
throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grant uri permissions");
}
mStrategy = getPathStrategy(context, info.authority);
}
上面checkGrantUriPermissionLocked() 函数我们知道最终需要两个条件中一个满足就可以check 通过:
1. checkHoldingPermissionsLocked() 函数能check pass
2. checkUriPermissionLocked() 函数能check pass
上面解析了checkHoldingPermissionsLocked() 的check 过程,详细看该函数后面的结论。对于FileProvider 比较特殊,要求exported 属性必须为false,函数check 肯定是fail,如果该函数check 不过,只需要保证checkUriPermissionLocked() 函数能check pass 就可以,也就是说即使provider 的exported 的属性值为false,也有可能check pass的。下面来详细解析checkUriPermissionLocked() 函数:
private final boolean checkUriPermissionLocked(GrantUri grantUri, int uid,
final int modeFlags) {
final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
: UriPermission.STRENGTH_OWNED;
// Root gets to do everything.
if (uid == 0) {
return true;
}
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
if (perms == null) return false;
// First look for exact match
final UriPermission exactPerm = perms.get(grantUri);
if (exactPerm != null && exactPerm.getStrength(modeFlags) >= minStrength) {
return true;
}
// No exact match, look for prefixes
final int N = perms.size();
for (int i = 0; i < N; i++) {
final UriPermission perm = perms.valueAt(i);
if (perm.uri.prefix && grantUri.uri.isPathPrefixMatch(perm.uri.uri)
&& perm.getStrength(modeFlags) >= minStrength) {
return true;
}
}
return false;
函数主要确认mGrantedUriPermissions 是否有对应uid 额ArrayMap 存在,也就是说之前必须要创建这样的ArrayMap 并且是将其add 到mGrantedUriPermissions 中。
private final SparseArray<ArrayMap<GrantUri, UriPermission>>
mGrantedUriPermissions = new SparseArray<ArrayMap<GrantUri, UriPermission>>();
而这个mGrantedUriPermissions 是在函数findOrCreateUriPermissionLocked() 中创建:
private UriPermission findOrCreateUriPermissionLocked(String sourcePkg,
String targetPkg, int targetUid, GrantUri grantUri) {
ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris == null) {
targetUris = Maps.newArrayMap();
mGrantedUriPermissions.put(targetUid, targetUris);
}
UriPermission perm = targetUris.get(grantUri);
if (perm == null) {
perm = new UriPermission(sourcePkg, targetPkg, targetUid, grantUri);
targetUris.put(grantUri, perm);
}
return perm;
}
参考文献:
https://developer.android.com/guide/topics/manifest/provider-element
https://developer.android.com/reference/android/support/v4/content/FileProvider#SpecifyFiles