FileProvider权限检查过程
相关进程
Resolver端访问Provider端的大致流程可以看做3个进程的通信,即:
Resolver端 -> system_server -> Provider端
system_server系统进程涉及到Provider的类主要是AMS和ContentProviderHelper(CPH)。
而在system_server进程和Provider端进程中都要进行权限检查,看Resolver端是否有读写或其它权限,如果在system_server中权限检查不通过,则不会再走后续流程,必须system_server和Provider端权限检查都通过之后,Resolver端才能进行数据操作。
过程概述
(1) system_server中的权限检查过程
CPH.getContentProviderImpl() ->
CPH.checkAssociationAndPermissionLocked() ->
CPH.checkContentProviderPermission() ->
Component权限检查:
AMS.checkComponentPermission() ->
ActivityManager.checkComponentPermission()
Uri权限检查:
UGMS.LocalService.checkAuthorityGrants() ->
UGMS.checkAuthorityGrantsLocked()
(2) Provider端权限检查过程
Binder Native ->
Binder.execTransact() ->
Binder.execTransactInternal()
ContentProviderNative.onTransact() ->
读取FileProvider的权限检查流程:
ContentProvider.Transport.openTypedAssetFile() ->
ContentProvider.Transport.enforceFilePermission() ->
ContentProvider.Transport.enforceReadPermission() ->
ContentProvider.enforceReadPermissionInner()
Provider端检查Uri权限时会调用到系统中:
ContextImpl.checkUriPermission() ->
AMS.checkUriPermission() ->
UriGrantsManagerService.checkUriPermission() ->
UriGrantsManagerService.checkAuthorityGrantsLocked()
普通APP-拥有权限
条件:
- APP端进程为普通用户(非Uid=100及system_server);
- FileProvider端已授予Resolver端读或写权限;
- 当前是Resolver端与FileProvider端第1次建立连接;
- FileProvider端已启动。
结果:
- system_server端:在UriGrantsManagerService.checkAuthorityGrantsLocked()中返回允许访问的授权;
- Provider端:在ContentProvider.enforceReadPermissionInner()返回允许访问的授权;
- 最终成功访问。
注:只有Resolver端启动后第1次访问Provider时才会走到AMS.getContentProvider()的流程,这是才会询问授权情况。第2次时就会走acquireExistingProvider(),直接通过本地已有记录访问。相关逻辑在ActivityThread.acquireProvider()中。
system_server端权限检查-granted
system_server端返回允许访问的权限的全过程堆栈:
CPH.getContentProviderImpl() ->
CPH.checkContentProviderPermission() ->
UriGrantsManagerService.LocalSercvice.checkAuthorityGrants() ->
UriGrantsManagerService.checkAuthorityGrantsLocked()
(1) 进程权限检查(即ComponentPermission)
调用栈:
CPH.getContentProviderImpl() ->
CPH.checkAssociationAndPermissionLocked() ->
CPH.checkContentProviderPermission() ->
AMS.checkComponentPermission() ->
ActivityManager.checkComponentPermission()
在ActivityManager.checkComponentPermission()中检查Resolver端进程是否拥有权限:
if (!exported) {
return PackageManager.PERMISSION_DENIED;
}
返回否定,因为FileProvider端的android:exported属性只能为true,所以这里不是FileProvider的授权结果。
堆栈回到checkContentProviderPermission()中继续向下。
(2) Uri权限
UGMS=UriGrantsManagerService
调用栈:
CPH.getContentProviderImpl() ->
CPH.checkAssociationAndPermissionLocked() ->
CPH.checkContentProviderPermission(){
if (!checkedGrants
&& mService.mUgmInternal.checkAuthorityGrants(callingUid, cpi, userId, checkUser)) {
return null;
}
} ->
UGMS.LocalService.checkAuthorityGrants() ->
UGMS.checkAuthorityGrantsLocked()
在UGMS.checkAuthorityGrants()中检查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;
}
最后返回到checkAssociationAndPermissionLocked(),如果msg==null,表示无异常信息,检验通过。
if ((msg = checkContentProviderPermission(
cpi, Binder.getCallingPid(), Binder.getCallingUid(), userId, checkUser,
callingApp != null ? callingApp.toString() : null))
!= null) {
throw new SecurityException(msg);
}
Provider端权限检查-granted
正常情况下获取允许授权结果在ContentProvider.enforceReadPermissionInner()中:
// last chance, check against any uri grants
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;
}
普通APP-没有权限
条件:
- APP端进程为普通用户(非Uid=100及system_process);
- Resolver端进程未获得FileProvider端授权;
- Resolver端无法访问到FileProvider端的文件内容;
- FileProvider端已启动。
结果:会在UriGrantsManagerService.checkAuthorityGrantsLocked()返回拒绝授权。
system_server权限检查-denied
逐个取出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;
}
返回到CPH.checkContentProviderPermission()中打印信息:
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;
system_server进程访问
条件:
- 访问端进程为system_server或与system_server运行在同一进程;
- system_server进程未提前获得FileProvider端的Uri授权;
- 当前是Resolver端与FileProvider端第1次建立连接;
- FileProvider端已启动。
结果:
- system_server:默认具有访问权限。
- Provider端:权限校验通过
system_server权限检查-granted
返回授权结果代码处:
public static int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
//...
}
此处由于Resolver端是system_server,所以Pid相同,允许授权。
Provider端权限检查-granted
if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
return PermissionChecker.PERMISSION_GRANTED;
}
这里返回允许权限,最终成功访问。
Uid=1000,非system_server进程访问
条件:
- Resolver端与system_server的Uid相同但Pid不同;
- Resolver端进程未提前获得FileProvider端的Uri授权;
- 当前是Resolver端与FileProvider端第1次建立连接;
- FileProvider端已启动。
结果:
- system_server:默认具有访问权限。
- Provider端:权限校验通过
system_server检查权限-granted
在以下处返回:
CPH.getContentProviderImpl() ->
CPH.checkAssociationAndPermissionLocked() ->
CPH.checkContentProviderPermission() ->
AMS.checkComponentPermission() ->
ActivityManager.checkComponentPermission()
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);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
//...
}
判断appId == Process.SYSTEM_UID符合条件,返回允许访问。之后Resolver端会获得Provider端的引用,通过该引用可以调用到Provider端。
Provider端权限检查-denied
但实际上即使在system_server授权成功了,在没有Provider端的授权下,最终也访问失败。
关键信息:
java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.demoapp.filedemo.fileprovider/share_name/myfile.txt from pid=14563, uid=1000 requires the provider be exported, or grantUriPermission()
打印于ContentProvider.enforceReadPermissionInner()中:
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);
}