概述
权限检查
Resolver端访问Provider端的大致流程可以看做3个进程的通信,即:
Resolver端 -> system_server -> Provider端
而在system_server进程和Provider端进程中都要进行权限检查,看Resolver端是否有读写或其它权限,如果在system_server中权限检查不通过,则不会再走后续流程,必须system_server和Provider端权限检查都通过之后,Resolver端才能进行数据操作。
system_server端权限检查
总过程
CPH.getContentProviderImpl() ->
CPH.checkAssociationAndPermissionLocked() ->
CPH.checkContentProviderPermission() ->
Component权限检查:
AMS.checkComponentPermission() ->
ActivityManager.checkComponentPermission()
Uri权限检查:
UGMS.LocalService.checkAuthorityGrants() ->
UGMS.checkAuthorityGrantsLocked()
CPH.checkAssociationAndPermissionLocked()
在ContentProviderHelper.getContentProviderImpl()中被调用。
private void checkAssociationAndPermissionLocked(ProcessRecord callingApp, ProviderInfo cpi,
int callingUid, int userId, boolean checkUser, String cprName, long startTime) {
String msg;
if ((msg = checkContentProviderAssociation(callingApp, callingUid, cpi)) != null) {
throw new SecurityException("Content provider lookup " + cprName
+ " failed: association not allowed with package " + msg);
}
checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
if ((msg = checkContentProviderPermission(
cpi, Binder.getCallingPid(), Binder.getCallingUid(), userId, checkUser,
callingApp != null ? callingApp.toString() : null))
!= null) {
throw new SecurityException(msg);
}
checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
}
各个参数为:
- ProcessRecord callingApp:访问端进程
- ProviderInfo cpi:Provider端info
- int callingUid:访问端Uid
- String cprName:ContentProviderRecord对象的toString()
(1) checkContentProviderAssociation()
检查访问端package与Provider端package是否可连接,如果可连接则返回null
(2) checkContentProviderPermission()
在这里进行主要检查,大多数问题都出现在这里,所以主要看这里之后的调用栈。
if ((msg = checkContentProviderPermission(
cpi, Binder.getCallingPid(), Binder.getCallingUid(), userId, checkUser,
callingApp != null ? callingApp.toString() : null))
!= null) {
throw new SecurityException(msg);
}
如果msg=null,则表示允许访问。
CPH.checkContentProviderPermission()
(1) 检查用户权限
if (checkUser) {....}
(2) 检查访问端组件的读写权限
如果授权则返回null信息。
if (ActivityManagerService.checkComponentPermission(cpi.readPermission,
callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
if (ActivityManagerService.checkComponentPermission(cpi.writePermission,
callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
(3) 检查Uri权限
if (!checkedGrants
&& mService.mUgmInternal.checkAuthorityGrants(callingUid, cpi, userId, checkUser)) {
return null;
}
UriGrantsManagerService.checkAuthorityGrants() ->
UriGrantsManagerService.checkAuthorityGrantsLocked()
返回true表示拥有权限。
获取从系统保存的Uri权限记录信息mGrantedUriPermissions。然后逐个取出Resolver端进程的所有授权的Uri中的Auth,依次与本次访问的Provider的Auth比较,如果有匹配项就返回true。
private boolean checkAuthorityGrantsLocked(int callingUid, ProviderInfo cpi, int userId,
boolean checkUser) {
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
if (perms != null) {
for (int i = perms.size() - 1; i >= 0; i--) {
GrantUri grantUri = perms.keyAt(i);
if (grantUri.sourceUserId == userId || !checkUser) {
if (matchesProvider(grantUri.uri, cpi)) {
return true;
}
}
}
}
return false;
}
(4) 返回msg
如果以上所有流程都未return,则最后根据情况返回异常msg信息。
final String suffix;
if (!cpi.exported) {
suffix = " that is not exported from UID " + cpi.applicationInfo.uid;
} else if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(cpi.readPermission)) {
suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
} else {
suffix = " requires " + cpi.readPermission + " or " + cpi.writePermission;
}
final String msg = "Permission Denial: opening provider " + cpi.name
+ " from " + (appName != null ? appName : "(null)")
+ " (pid=" + callingPid + ", uid=" + callingUid + ")" + suffix;
Slog.w(TAG, msg);
return msg;
Component权限
AMS.checkComponentPermission()
在AMS.checkComponentPermission()中主要检查Resolver端进程是否拥有权限。
主要进行以下流程:
- Resolver端如果是system_server进程:if (pid == MY_PID),一律运行授权;
- if (permission != null):略;
- 进入到AM.checkComponentPermission()检查权限。
public static int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
// AMS和CPH运行在system_server进程中,MY_PID就是system_server进程的Pid
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
if (permission != null) {
synchronized (sActiveProcessInfoSelfLocked) {
ProcessInfo procInfo = sActiveProcessInfoSelfLocked.get(pid);
if (procInfo != null && procInfo.deniedPermissions != null
&& procInfo.deniedPermissions.contains(permission)) {
return PackageManager.PERMISSION_DENIED;
}
}
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
AM.checkComponentPermission()
之后进入到ActivityManager.checkComponentPermission()中检查权限:
- permission表示查询的是允许哪种权限
- uid表示访问端的UID
- owningUid表示Provider端的UID
- exported表示Provider端的android:exported属性
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
final int appId = UserHandle.getAppId(uid);
// 如果Resolver端uid=1000,默认拥有权限
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// If there is a uid that owns whatever is being accessed, it has
// blanket access to it regardless of the permissions it requires.
if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// If the target is not exported, then nobody else can get to it.
if (!exported) {
/*
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
here);
*/
return PackageManager.PERMISSION_DENIED;
}
//
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
几个比较重要的判断点需要注意:
(1) 系统进程
Process.ROOT_UID=0,也就是root进程默认拥有权限;
UID=1000的进程也默认授权;
最后都返回PM.PERMISSION_GRANTED.
(2) 相同Uid下的进程默认授权
(3) android:exported=”false”
这点通常是对FileProvider的,因为FileProvider的android:exported属性必须为false,如果请求的是FileProvider,则执行到这里返回PERMISSION_DENIED,及不授权。
之后回到CPH.checkContentProviderPermission()中会继续在Provider端中查询Resolver端是否拥有对FileProvider的权限。
(4) 无权限限制
对于普通的Provider请求(即非FileProvider,也无其它自定义权限),则在 if (permission == null)中返回允许授权。
大多数对Provider的授权检查都在这里返回。
(5) PMS方检查访问端uid的权限
最后是PMS查询Resolver端APP是否拥有这一权限。
Uri权限
UGMS=UriGrantsManagerService
UGMS.LocalService.checkAuthorityGrants()
LocalService实现了UriGrantsManagerInternal类。
public boolean checkAuthorityGrants(int callingUid, ProviderInfo cpi, int userId,
boolean checkUser) {
synchronized (mLock) {
return UriGrantsManagerService.this.checkAuthorityGrantsLocked(
callingUid, cpi, userId, checkUser);
}
}
UGMS.checkAuthorityGrantsLocked()
检查Resolver端进程是否拥有对该Authority的访问访问权限。返回true表示拥有权限。
逐个取出Resolver端进程的所有授权Uri中的Auth,依次与本次访问的Provider的Auth比较,如果有匹配项就返回true。
private boolean checkAuthorityGrantsLocked(int callingUid, ProviderInfo cpi, int userId,
boolean checkUser) {
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
if (perms != null) {
for (int i = perms.size() - 1; i >= 0; i--) {
GrantUri grantUri = perms.keyAt(i);
if (grantUri.sourceUserId == userId || !checkUser) {
if (matchesProvider(grantUri.uri, cpi)) {
return true;
}
}
}
}
return false;
}
/** Returns true if the uri authority is one of the authorities specified in the provider. */
private boolean matchesProvider(Uri uri, ProviderInfo cpi) {
String uriAuth = uri.getAuthority();
String cpiAuth = cpi.authority;
if (cpiAuth.indexOf(';') == -1) {
return cpiAuth.equals(uriAuth);
}
String[] cpiAuths = cpiAuth.split(";");
int length = cpiAuths.length;
for (int i = 0; i < length; i++) {
if (cpiAuths[i].equals(uriAuth)) return true;
}
return false;
}
最后返回到checkAssociationAndPermissionLocked(),如果msg==null,表示无异常信息,检验通过。
if ((msg = checkContentProviderPermission(
cpi, Binder.getCallingPid(), Binder.getCallingUid(), userId, checkUser,
callingApp != null ? callingApp.toString() : null))
!= null) {
throw new SecurityException(msg);
}
Provider端权限检查
总过程
通过Binder调用到Provider端之后,Provider端在执行数据操作前,也需要对来自Resolver端的请求作权限检查,如果不通过则不会进行数据操作。
以下过程以读取权限为例。
(1) Provider端调用栈
Binder Native ->
Binder.execTransact() ->
Binder.execTransactInternal()
ContentProviderNative.onTransact() ->
读取FileProvider的权限检查流程:
ContentProvider.Transport.openTypedAssetFile() ->
ContentProvider.Transport.enforceFilePermission() ->
ContentProvider.Transport.enforceReadPermission() ->
ContentProvider.enforceReadPermissionInner()
查询普通Provider的权限检查流程:
ContentProvider.Transport.query() ->
ContentProvider.Transport.enforceReadPermission() ->
ContentProvider.enforceReadPermissionInner(){
//此处返回
if (allowDefaultRead) return PermissionChecker.PERMISSION_GRANTED;
}
(2) system_server端
Provider端检查Uri权限(FileProvider)时会调用到系统中。
ContextImpl.checkUriPermission() ->
AMS.checkUriPermission() ->
UriGrantsManagerService.checkUriPermission() ->
UriGrantsManagerService.checkAuthorityGrantsLocked()
CP.enforceReadPermissionInner()
CP=ContentProvider
主要进行以下检查:
(1) Uid是否相同
如果Resolver端与Provider端的Uid相同,则授权允许;
(2) if (mExported && checkUser(pid, uid, context))
- FileProvider不会走此流程,因为mExported == false。
- 对普通Provider:即非FileProvider和带有额外权限的Provider,默认拥有权限:
boolean allowDefaultRead = (componentPerm == null);
if (allowDefaultRead) return PermissionChecker.PERMISSION_GRANTED;
- 检查路径权限
(3) 检查Uri权限
通过context.checkUriPermission调用到AMS,最后调用到UGMS以检查Resolver端是否拥有Uri授权。
调用栈:
ContextImpl.checkUriPermission() ->
AMS.checkUriPermission() ->
UriGrantsManagerService.checkUriPermission() ->
UriGrantsManagerService.checkAuthorityGrantsLocked()
详情参考system_server端权限检查中的UGMS.checkAuthorityGrantsLocked()。
protected int enforceReadPermissionInner(Uri uri,
@NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
String missingPerm = null;
int strongestResult = PermissionChecker.PERMISSION_GRANTED;
//如果是相同Uid则授权允许
if (UserHandle.isSameApp(uid, mMyUid)) {
return PermissionChecker.PERMISSION_GRANTED;
}
//这部分暂未验证
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getReadPermission();
if (componentPerm != null) {
final int result = checkPermission(componentPerm, attributionSource);
if (result == PermissionChecker.PERMISSION_GRANTED) {
return PermissionChecker.PERMISSION_GRANTED;
} else {
missingPerm = componentPerm;
strongestResult = Math.max(strongestResult, result);
}
}
// 对未添加额外权限的普通Provider默认拥有权限
boolean allowDefaultRead = (componentPerm == null);
final PathPermission[] pps = getPathPermissions();
if (pps != null) {
final String path = uri.getPath();
for (PathPermission pp : pps) {
final String pathPerm = pp.getReadPermission();
if (pathPerm != null && pp.match(path)) {
final int result = checkPermission(pathPerm, attributionSource);
if (result == PermissionChecker.PERMISSION_GRANTED) {
return PermissionChecker.PERMISSION_GRANTED;
} else {
// any denied <path-permission> means we lose
// default <provider> access.
allowDefaultRead = false;
missingPerm = pathPerm;
strongestResult = Math.max(strongestResult, result);
}
}
}
}
// if we passed <path-permission> checks above, and no default
// <provider> permission, then allow access.
//普通Provider在这里返回
if (allowDefaultRead) return PermissionChecker.PERMISSION_GRANTED;
}
// 检查Uri授权,FileProvider需要走到这一步
final int callingUserId = UserHandle.getUserId(uid);
final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
? maybeAddUserId(uri, callingUserId) : uri;
if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
return PermissionChecker.PERMISSION_GRANTED;
}
// If the worst denial we found above was ignored, then pass that
// ignored through; otherwise we assume it should be a real error below.
if (strongestResult == PermissionChecker.PERMISSION_SOFT_DENIED) {
return PermissionChecker.PERMISSION_SOFT_DENIED;
}
//最后权限授权失败,打印失败日志
final String suffix;
if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(mReadPermission)) {
suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
} else if (mExported) {
suffix = " requires " + missingPerm + ", or grantUriPermission()";
} else {
suffix = " requires the provider be exported, or grantUriPermission()";
}
throw new SecurityException("Permission Denial: reading "
+ ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+ ", uid=" + uid + suffix);
}