Provider 权限详解

版权声明:本文为博主原创文章,转载请务必注明作者与原文链接。 https://blog.csdn.net/jingerppp/article/details/82108549

来源:

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_PERMISSIONFLAG_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

猜你喜欢

转载自blog.csdn.net/jingerppp/article/details/82108549