【问题标题】:How would a thread created by an app be considered a different app from the app's ContentProvider?应用程序创建的线程如何被视为与应用程序的 ContentProvider 不同的应用程序?
【发布时间】:2015-11-02 23:28:59
【问题描述】:

我有一个应用程序,当ContentObserver 通知ContentProvider 发生更改时,它会尝试在后台线程上查询提供程序。这会导致SecurityException 被抛出:

8-10 15:54:29.577 3057-3200/com.xxxx.mobile.android.xxx W/Binder:从活页夹存根实现中捕获了一个 RuntimeException。 java.lang.SecurityException: Permission Denial: 读取 com.xxx.mobile.android.mdk.model.customer.ContentProvider uri content://com.xxx.mobile.android.consumer.xxx/vehicle from pid=0, uid= 1000 需要导出提供程序,或 grantUriPermission() 在 android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:539) 在 android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:452) 在 android.content.ContentProvider$Transport.query(ContentProvider.java:205) 在 android.content.ContentResolver.query(ContentResolver.java:478) 在 android.content.ContentResolver.query(ContentResolver.java:422)

应用创建的线程如何以与应用的 ContentProvider 不同的 UID 结束?

通过在android.content.ContentProvider 中放置一个异常断点,我看到UserHandle.isSameApp(uid, mMyUid)falseUserHandle.isSameUser(uid, mMyUid)true。我还看到提供者 UID 是 10087。

【问题讨论】:

  • 您问的是 uid=1000 - 这是 Android 系统用户 ID。请求很可能在内部被代理到系统进行处理。
  • @adelphus 是的。我认为这是安全异常的原因,但现在我不确定因为UserHandle.isSameUser 返回true
  • Android 用户与 App uid 值无关。不要把它们混在一起!应用程序 uid 值用于在应用程序之间强制执行沙盒,用户安全的实现方式不同。
  • 啊,好的。所以问题是为什么UserHandle.isSameApp返回false,即使线程是由应用程序创建的。
  • 当你说background thread时,你的意思是ThreadAsyncTask,还是别的什么?

标签: android android-contentprovider uid android-securityexception


【解决方案1】:

uid值1000属于Android系统。 Android 的许多功能都涉及将请求代理到系统线程进行处理。如果在此期间抛出异常,错误将包括系统的uid,而不是原始请求者。

关于其他点:

UserHandle.isSameApp(uid, mMyUid) is false

UserHandle.isSameUser(uid, mMyUid) is true

通过查看source,这些最容易解释。在支持多用户的 Android 设备上,每个用户都由一系列 UID 定义。 isSameApp 为假,因为 id 的模数不匹配:

 public static final boolean isSameApp(int uid1, int uid2) {
        return getAppId(uid1) == getAppId(uid2);
}

 public static final int getAppId(int uid) {
        return uid % PER_USER_RANGE;
}

同样,这两个 id 属于同一个用户,因为它们位于同一个范围内:

 public static final boolean isSameUser(int uid1, int uid2) {
        return getUserId(uid1) == getUserId(uid2);
 }

public static final int getUserId(int uid) {
        if (MU_ENABLED) {
            return uid / PER_USER_RANGE;
        } else {
            return 0;
        }
}

请注意,这个逻辑是有缺陷的,因为它意味着所有 Android 系统 uid (

另请注意,如果第二个用户安装了超过 1000 个应用程序(!),则应用程序可能会被误认为是系统应用程序(uid % PER_USER_RANGE 都将返回 1000)。不过这并不重要,因为强大的沙盒可以防止任何糟糕的事情发生。

【讨论】:

  • 这有助于我了解 ID 的工作原理。谢谢。您对我如何避免安全异常有什么建议吗?
  • 保护内容提供者的常规方法是定义并包含自定义权限like this。但我不完全确定你想要达到什么目标,所以也许这不是你需要的。
  • 该链接似乎对于将一个应用程序连接到另一个应用程序的内容提供者很有用。但就我而言,我只有一个应用程序。而且这个应用的内容提供者似乎认为这个应用产生的线程来自另一个应用,所以抛出了一个安全异常。
  • @Julian 抱歉 - 该链接的目的是证明即使在声明权限的清单中您也始终需要使用权限,否则您无法访问自己的 CP .但这里可能还有其他事情发生。
  • 不用担心。感谢您的尝试:)
【解决方案2】:

我在系统回调 (LeScanCallback) 中尝试与我的 ContentProvider 交互时遇到了同样的问题。问题是回调线程归Android系统所有,而不是我的应用程序,即使代码在我的应用程序中。

在尝试与我的 ContentProvider 交互之前将工作从回调传递到我的应用线程之一成功解决了问题。

为了减少线程创建和回收的样板(需要频繁回调以减少开销),我在我的委托方法上使用了AndroidAnnotation's @Background annotation(但今天将使用 Kotlin 协程)。

【讨论】:

    【解决方案3】:

    如果Thread 由具有提供程序的应用程序的任何组件启动,那么您可以在没有任何SecurityException 的情况下访问ContentProvider

    我在我的应用程序中使用ContentProvider 作为一个额外的抽象层,并且我没有将内容暴露给其他应用程序。我正在后台线程中访问ContentProvider(不是AsyncTask,而是一个简单的java.lang.Thread)。我没有收到任何SecurityException。以下是我的应用程序中的代码。

    AndroidManifest.xml

     <provider
        android:authorities="com.sample.provider"
        android:name="com.sample.MyProvider"
        android:exported="false" />
    

    MainActivity

    public void performContinue(Bundle extras){
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String AUTHORITY = "com.sample.provider";
                Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
                Uri currentUri = BASE_URI.buildUpon().appendPath("SAMPLE_COUNT").build();
                final Cursor query = InputActivity.this.getContentResolver().query(currentUri, null, null, null, null);
                if (query != null) {
                    final int count = query.getCount();
                    Log.d("DEBUG","CONTENT = " + count);
                }else{
                    Log.d("DEBUG","CONTENT = CURSOR NULL");
                }
            }
        });
        thread.setName("THREAD_1");
        thread.start();
    }
    


    我似乎没有得到任何SecurityException。理想情况下,我们需要使用AsyncQueryHandler 来访问ContentProvider,因为这允许您在后台线程中执行所有获取过程,并将使用 UI 线程将结果发布到 UI 中。但是在看到这篇文章之后,我只是想看看我是否可以使用Thread 并检查我是否仍然可以在没有任何异常的情况下访问它。它工作正常。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-30
      • 2014-02-15
      • 2019-08-07
      • 2021-08-16
      • 1970-01-01
      • 1970-01-01
      • 2019-06-14
      相关资源
      最近更新 更多