博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android ContentProvider调用报错"Bad call:..."及相关Binder权限问题分析
阅读量:6604 次
发布时间:2019-06-24

本文共 11079 字,大约阅读时间需要 36 分钟。

问题:

项目中有一下情况:进程A调用另一进程的B ContentProvider,B在该此次query中需要在query另一个 C ContentProvider:

class BContentProvider extends ContentProvider {        Context mContext;        ...        @Override        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {            ...            try {                // query C ContentProvider:                Cursor cursor = mContext.getContentResolver().query(...);                if (cursor != null) {                    try {                        //do something;                    } finally {                        cursor.close();                    }                }                Cursor cursor = mContext.getContentResolver().query(...);            ...        ...            }        }    }复制代码

在这种情况下,系统抛出Exception如下:

1-11 16:04:51.867  2633  3557 W AppOps  : Bad call: specified package com.providers.xxx under uid 10032 but it is really 1000101-11 16:04:51.867  2633  3557 W AppOps  : java.lang.RuntimeException: here01-11 16:04:51.867  2633  3557 W AppOps  : 	at com.android.server.AppOpsService.getOpsRawLocked(AppOpsService.java:1399)01-11 16:04:51.867  2633  3557 W AppOps  : 	at com.android.server.AppOpsService.noteOperationUnchecked(AppOpsService.java:1115)01-11 16:04:51.867  2633  3557 W AppOps  : 	at com.android.server.AppOpsService.noteProxyOperation(AppOpsService.java:1093)01-11 16:04:51.867  2633  3557 W AppOps  : 	at com.android.internal.app.IAppOpsService$Stub.onTransact(IAppOpsService.java:157)01-11 16:04:51.867  2633  3557 W AppOps  : 	at android.os.BinderInjector.onTransact(BinderInjector.java:30)01-11 16:04:51.867  2633  3557 W AppOps  : 	at android.os.Binder.execTransact(Binder.java:569)01-11 16:04:51.868  4659  6791 E DatabaseUtils: Writing exception to parcel01-11 16:04:51.868  4659  6791 E DatabaseUtils: java.lang.SecurityException: Proxy package com.providers.xxx from uid 10001 or calling package com.providers.xxx from uid 10032 not allowed to perform READ_PROVIDER_C01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.app.AppOpsManager.noteProxyOp(AppOpsManager.java:1834)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentProvider.checkPermissionAndAppOp(ContentProvider.java:538)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:560)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:483)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentProvider$Transport.query(ContentProvider.java:212)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentResolver.query(ContentResolver.java:532)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentResolver.query(ContentResolver.java:473)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at com.android.providers.xxx.BDatabaseHelper.query(BDatabaseHelper.java:7238)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentProvider$Transport.query(ContentProvider.java:239)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.os.BinderInjector.onTransact(BinderInjector.java:30)01-11 16:04:51.868  4659  6791 E DatabaseUtils: 	at android.os.Binder.execTransact(Binder.java:569)复制代码

分析:

由于错误log首先反应了没有C ContentProvider的权限,但检查A应用是有C的读写权限的。所以排除了A的权限问题。 继续分析: 通过log可以看到确实是ContentProvider在做权限检查时出错。通过log中对应的源码进行分析: 首先可以看到ContentProvider.query()的时候做了权限检查,注意,传入的enforceReadPermission()的callingPkg是调用方的包名,以上面为例,就是B的包名。

ContentProvider.query():

@Override        public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,                @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {            validateIncomingUri(uri);            uri = maybeGetUriWithoutUserId(uri);            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {复制代码

enforceReadPermission()调用了.checkPermissionAndAppOp()方法,ContentProvider.checkPermissionAndAppOp()调用了AppOpsManager.noteProxyOp()去做检查出了异常。

AppOpsManager.noteProxyOp():

public int noteProxyOp(int op, String proxiedPackageName) {        int mode = noteProxyOpNoThrow(op, proxiedPackageName);        if (mode == MODE_ERRORED) {            throw new SecurityException("Proxy package " + mContext.getOpPackageName()                    + " from uid " + Process.myUid() + " or calling package "                    + proxiedPackageName + " from uid " + Binder.getCallingUid()                    + " not allowed to perform " + sOpNames[op]);        }        return mode;    }复制代码

noteProxyOpNoThrow()又做了什么呢? AppOpsManager.noteProxyOpNoThrow():

/**     * Like {
@link #noteProxyOp(int, String)} but instead * of throwing a {
@link SecurityException} it returns {
@link #MODE_ERRORED}. * @hide */ public int noteProxyOpNoThrow(int op, String proxiedPackageName) { try { return mService.noteProxyOperation(op, mContext.getOpPackageName(), Binder.getCallingUid(), proxiedPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }复制代码

可见noteProxyOpNoThrow()是通过binder调用到了AppOpsService.noteProxyOperation()方法,注意,这里传入的是AppOpsService.noteProxyOperation()的后两个参数为Binder.getCallingUid()和之前层层传入的调用方的包名,也就是上面例子的B的包名。

下面,继续看binder另一侧的AppOpsService.noteProxyOperation()方法,我们结合log中AppOps的输出log:

AppOpsService.noteProxyOperation():

@Override    public int noteProxyOperation(int code, String proxyPackageName,            int proxiedUid, String proxiedPackageName) {        verifyIncomingOp(code);        final int proxyUid = Binder.getCallingUid();        String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);        if (resolveProxyPackageName == null) {            return AppOpsManager.MODE_IGNORED;        }        final int proxyMode = noteOperationUnchecked(code, proxyUid,                resolveProxyPackageName, -1, null);        if (proxyMode != AppOpsManager.MODE_ALLOWED || Binder.getCallingUid() == proxiedUid) {            return proxyMode;        }        String resolveProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);        if (resolveProxiedPackageName == null) {            return AppOpsManager.MODE_IGNORED;        }        return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,                proxyMode, resolveProxyPackageName);    }复制代码

AppOpsService.noteOperationUnchecked():

private int noteOperationUnchecked(int code, int uid, String packageName,            int proxyUid, String proxyPackageName) {        Op op = null;        Op switchOp = null;        int switchCode;        int resultMode = AppOpsManager.MODE_ALLOWED;        synchronized (this) {            Ops ops = getOpsRawLocked(uid, packageName, true);          ...         }    ...}复制代码

AppOpsService.getOpsRawLocked():

private Ops getOpsRawLocked(int uid, String packageName, boolean edit) {        ...        Ops ops = uidState.pkgOps.get(packageName);        if (ops == null) {            if (!edit) {                return null;            }            boolean isPrivileged = false;            // This is the first time we have seen this package name under this uid,            // so let's make sure it is valid.            if (uid != 0) {                final long ident = Binder.clearCallingIdentity();                try {                    int pkgUid = -1;                    try {                        ApplicationInfo appInfo = ActivityThread.getPackageManager()                                .getApplicationInfo(packageName,                                        PackageManager.MATCH_DEBUG_TRIAGED_MISSING,                                        UserHandle.getUserId(uid));                        if (appInfo != null) {                            pkgUid = appInfo.uid;                            isPrivileged = (appInfo.privateFlags                                    & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;                        }                        ...                    }                    ...                    if (pkgUid != uid) {                        // Oops!  The package name is not valid for the uid they are calling                        // under.  Abort.                        RuntimeException ex = new RuntimeException("here");                        ex.fillInStackTrace();                        Slog.w(TAG, "Bad call: specified package " + packageName                                + " under uid " + uid + " but it is really " + pkgUid, ex);                        return null;                    }                } finally {                    Binder.restoreCallingIdentity(ident);                }            }            ops = new Ops(packageName, uidState, isPrivileged);            uidState.pkgOps.put(packageName, ops);        }        return ops;    }复制代码

这里主要的操作就是将传入的uid和包名进行判断:比对该包对应的uid和传入的uid比较,如果不一致就报错。错误信息和log中的一致:

Bad call: specified package com.providers.xxx under uid 10032 but it is really 10001复制代码

上文提到了,这个包名是传入的ContentProvider的调用方的包名,也就是例子中的B的包名。而uid是在AppOpsManager中通过Binder.getCallingUid()获得的。log中显示,此uid并不是B的uid,而是其上游调用者A的uid。 为什么在C中调用Binder.getCallingUid()得到的是A进程的呢?我找到了袁辉辉大神的一片博客:

“线程B通过Binder调用当前线程的某个组件:此时线程B是线程B某个组件的调用端,则mCallingUid和mCallingPid应该保存当前线程B的PID和UID,故需要调用clearCallingIdentity()方法完成这个功能。当线程B调用完某个组件,由于线程B仍然处于线程A的被调用端,因此mCallingUid和mCallingPid需要恢复成线程A的UID和PID,这是调用restoreCallingIdentity()即可完成。”

Binder的机制就是这么设计的,所以需要在B进行下一次Binder调用(也就是query ContentProvider)之前调用clearCallingIdentity()来将B的 PID和UID附给mCallingUid和mCallingPid。Binder调用结束后在restoreCallingIdentity()来将其恢复成其原本调用方的PID和UID。这样在C里就会用B的相关信息进行权限校验,在AppOpsService.getOpsRawLocked(),UID和包名都是B的,是一致的,就不会报错。

解决办法

其实上文也已经提到了,参考 ,在B进行Query前后分别调用clearCallingIdentity() //作用是清空远程调用端的uid和pid,用当前本地进程的uid和pid替代,这样在之后的调用方去进行权限校验时会以B的信息为主,不会出现包名和UID不一致的情况。 最后修改过的调用方式如下:

long token = Binder.clearCallingIdentity();        try {            Cursor cursor = mContext.getContentResolver().query(...);            if (cursor != null) {                try {                    //do something;                } finally {                    cursor.close();                }            }        } finally {            Binder.restoreCallingIdentity(token);        }复制代码

总结:

  1. ContentProvider是用Binder实现的,查询的过程其实就是一次Binder调用,所以想深入了解ContentProvider一定要会一些Binder相关的知识。

  2. ContentProvider在接受一次查询前会调用AppOpsManager(其会通过Binder再由AppOpsService完成)进行权限校验,其中会校验调用方的UID和包名是否一致,其相关功能可见文章: 。

  3. Binder调用时候可以通过Binder.getCallingPid()和Binder.getCallingUid()来获取调用方的PID和UID,而如果A通过Binder调用B,B又Binder调用了C,那么在C中Binder.getCallingPid()和Binder.getCallingUid()得到的是A的PID和UID,这种情况下需要在B调用C的前后用Binder.clearCallingIdentity()和Binder.restoreCallingIdentity()使其带上B的PID和UID,从而在C中进行权限校验时候用B的信息进行校验,当然这也符合逻辑,B调用的C,应该B需要有相应权限。

  4. Binder.clearCallingIdentity()和Binder.restoreCallingIdentity()的实现原理 也有介绍,是通过移位实现的。

转载地址:http://mwbso.baihongyu.com/

你可能感兴趣的文章
double,失去精度
查看>>
php intval函数
查看>>
windows环境下生成ssh keys
查看>>
python使用dbutils的PooledDB连接池,操作数据库
查看>>
他山之石,可以攻玉--回顾我的微服务之旅(转)
查看>>
java8中的stream().filter()的使用和Optional()
查看>>
Mysql按数字大小排序String字段
查看>>
python练习笔记——组合恒等式
查看>>
Qt封装QTcpServer参考资料--QT4中构建多线程的服务器
查看>>
MySQL DDL--ghost执行模板和参数
查看>>
开源一个Android自定义图表库
查看>>
IPV6地址格式分析
查看>>
DTLS-PSK算法抓包解析***
查看>>
ROS-RouterOS hAP ac2+usb 4G上网卡+小米新推的无线上网卡是绝配
查看>>
Eclipse常用快捷键
查看>>
nginx brotli 压缩试用
查看>>
Guava学习笔记(三):集合
查看>>
[转]Java中BigDecimal的使用
查看>>
ORA-30377 MV_CAPABILITIES_TABLE not found
查看>>
排序题如何进行数据分析
查看>>