转载请标明出处:

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 crash08-24 18:14:43.989 E/AndroidRuntime( 2366): FATAL EXCEPTION: main08-24 18:14:43.989 E/AndroidRuntime( 2366): Process: com.android.messaging, PID: 236608-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.Throwable08-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(),这个是权限处理的核心函数。

其实,AMS中为应用需要确定Uri 权限,单独提供了一个public 接口函数checkGrantUriPermission(),通过code 会发现该函数最终也是会调用到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

1. Intent 的flags 要求设为 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION,不然不处理

2. 要求Uri 的scheme 是content,不然不处理

3. 如果provider 的grantUriPermissions属性为true,会接着确认grant-uri-permission,详细看 grant-uri-permission 文档

4. checkHoldingPermissionsLocked() 函数确定Provider的所需基本权限,详细看下面

5. checkUriPermissionLocked() 是在上面check fail 时,会进一步确认是否之前在provider 中已经grant,详细看下面。

 

先来看下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  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            //  below removes this ability            boolean allowDefaultRead = pi.readPermission == null;            boolean allowDefaultWrite = pi.writePermission == null;            // check if target holds any  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  read/write, if not blocked by            //  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 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>            mGrantedUriPermissions = new SparseArray>();

而这个mGrantedUriPermissions 是在函数findOrCreateUriPermissionLocked() 中创建:

    private UriPermission findOrCreateUriPermissionLocked(String sourcePkg,            String targetPkg, int targetUid, GrantUri grantUri) {        ArrayMap 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;    }

 

其实从 FileProvider 文档 中可以知道一般grant uri permission 有两种选择:

1. 通过接口grantUriPermission() 

Call the method Context.grantUriPermission(package, Uri, mode_flags) for the content:// Uri, using the desired mode flags. This grants temporary access permission for the content URI to the specified package, according to the value of the the mode_flags parameter, which you can set toFLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION or both. The permission remains in effect until you revoke it by calling revokeUriPermission() or until the device reboots.

 

2. 通过Intent 配置

  • Put the content URI in an Intent by calling setData().
  • Next, call the method Intent.setFlags() with either FLAG_GRANT_READ_URI_PERMISSION orFLAG_GRANT_WRITE_URI_PERMISSION or both.
  • Finally, send the Intent to another app. Most often, you do this by calling setResult().

Permissions granted in an Intent remain in effect while the stack of the receiving Activity is active. When the stack finishes, the permissions are automatically removed. Permissions granted to one Activity in a client app are automatically extended to other components of that app.

 

实例结论:

实例中出现的exception 问题,应该是Android 自身的问题,在分享到短信的时候,短信自身又启动了一个Activity,这样就导致了checkHoldingPermissionsLocked() 传入的 targetUid 和 callingUid都是短信自身的uid,所以最后导致check 都不能pass。在第一次startActivity的时候会进入checkGrantUriPermissionLocked(),但是碰到条件给过滤掉了,我们需要修改该过滤条件使其满足:

        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;            }        }

其实通过code 可以看到Android 源生也针对com.android.settings.files 进行了特殊的过滤。我们只需要参考这个即可。

 

 

参考文献:

https://developer.android.com/guide/topics/manifest/provider-element

https://developer.android.com/guide/topics/manifest/grant-uri-permission-element

https://developer.android.com/reference/android/support/v4/content/FileProvider#SpecifyFiles

更多相关文章

  1. android开机动画播放流程
  2. Android(安卓)访问权限设置
  3. Android(安卓)Service: 启动service, 停止service
  4. Android——调用系统摄像头拍照的问题
  5. Android(安卓)开发TCP、UdP客户端
  6. Android添加权限AndroidManifes.xml
  7. Android(安卓)NuPlayer播放框架
  8. Android新控件MotionLayout介绍(一)
  9. MPAndroidChart常见设置属性(一)——应用层

随机推荐

  1. vue3 父子组件传值详解
  2. 详解React 和 Redux的关系
  3. js-基础(五)classList对象、blur事件进行表
  4. Vue之Axios异步通信详解
  5. 属性重载与命名空间和类自动加载器
  6. kubeadm安装k8s 1.23.5
  7. 面向对象编程学习小结(一)
  8. js-基础(四)dom实战——留言板
  9. 文件实例与类操作
  10. vue中路由跳转的方式有哪些你知道吗