The Limits of ContentProvider Security

ContentProvider has a fairly robust integration with the Android permissions system. You can make a ContentProvider publicly available, yet limit access via android:readPermission and android:writePermission manifest attributes. Or, you can make a ContentProvider private, yet “poke pinholes in the firewall” via android:grantUriPermissions to allow third-party access to your content in limited cases. An example would be an email client making email attachments available to third-party file viewers (e.g., Adobe Reader for PDFs), but only on user demand, rather than making all attachments available at all times.

The dirty little secret with all of this is that once your data is in the hands of another app, that app can do whatever it wants with the data, including making that data available to yet other apps. Permissions are not transitive: if A gives data to B (where B has specific permission to get this data), and B gives that data to C, C has no direct relationship with A and does not necessarily need any permissions that A requires of B. From Android’s standpoint, the fact that B happens to hand the same data over to C that it got from A is immaterial.

Now, your reaction to this description may be that B is some form of malware. However, that is not necessarily the case.

For example, suppose B is an email client. Reasonable attention is paid to the scenario where B receives emails with attachments. At the same time, though, B probably allows users to send emails with attachments. Those attachments are not going to be written in B itself, but rather will represent photos, documents, or other files that exist somewhere on the Android device. B can use ACTION_GET_CONTENT or the Storage Access Framework (ACTION_OPEN_DOCUMENT) to allow the user to pick something to attach. However, not only does B have to have the right to send that attachment to the mail server, but B also needs to be able to show that attachment to the user via B’s “Sent Items” or equivalent roster of past sent emails. If the user attached a photo to an email, B probably should have the feature whereby the sender can view that photo. But, we have two problems:

  1. At the time the email is being crafted and the user picks the photo, the Uri that B gets may be good only for B. B may not be able to pass that Uri along to an image viewer. And while it could be argued that B could implement an image viewer itself, there are an arbitrary number of possible MIME types for attachments, and B cannot implement all of them, realistically.

  2. What happens if the user deletes, moves, or otherwise modifies the photo, such that the original Uri is no longer valid? Now, even B cannot get to the image again, let alone pass that image off to a third-party app.

To address both #1 and #2, B could make a local copy of the photo. However, not only does this represent duplication of data, but now any protections that A had for who could access that photo are lost with respect to B’s copy, and it is now B’s protections that matter.

The security around a ContentProvider is securing an API. It is not securing the data. The same holds true for any application of the Android permission system, such as for securing a service.

The fact that clients of your ContentProvider can make local copies of data and share that with others does not mean that you should abandon the Android permission system, or that you should abandon ContentProvider. However, you do need to think through what data you are exposing through your app’s APIs (including a ContentProvider) and whether the benefits for the user of making that data available are worth the risk of the data being distributed more widely than your permissions might imply.

Learn second-generation Android app development — with Kotlin and the Android Jetpack — through CommonsWare’s Android app development training!