Adding basic permissions to your app to allow it to, say, access the Internet, is fairly easy. However, the full permissions system has many capabilities beyond simply asking the user to let you do something. This chapter explores other uses of permissions, from securing your own components to using signature-level permissions (your own or Android’s).
Understanding this chapter requires that you have read the core chapters, particularly the chapter on permissions and the chapter on signing your app.
One of the sample apps uses RxJava and RxAndroid, which are introduced elsewhere in the book.
Principally, at least initially, permissions are there to allow the user to secure their device. They have to agree to allow you to do certain things, such as reading contacts, that they might not appreciate.
The other side of the coin, of course, is to secure your own application. If your application is mostly activities, security may be just an “outbound” thing, where you request the right to use resources of other applications. If, on the other hand, you put content providers or services in your application, you will want to implement “inbound” security to control which applications can do what with the data.
Note that the issue here is less about whether other applications might “mess up” your data, but rather about privacy of the user’s information or use of services that might incur expense. That is where the stock permissions for built-in Android applications are focused – can you read or modify contacts, can you send SMS, etc. If your application does not store information that might be considered private, security is less an issue. If, on the other hand, your application stores private data, such as medical information, security is much more important.
The first step to securing your own application using permissions is to
declare said permissions, once again in the AndroidManifest.xml
file.
In this case, instead of uses-permission
, you add permission
elements. Once again, you can have zero or more permission
elements,
all as direct children of the root manifest
element.
Declaring a permission is slightly more complicated than using a permission. There are three pieces of information you need to supply:
<permission
android:name="vnd.tlagency.sekrits.SEE_SEKRITS"
android:label="@string/see_sekrits_label"
android:description="@string/see_sekrits_description" />
This does not enforce the permission. Rather, it indicates that it is a possible permission; your application must still flag security violations as they occur.
There are two ways for your application to enforce permissions, dictating where and under what circumstances they are required. The easier one is to indicate in the manifest where permissions are required.
Activities, services, and receivers can all declare an attribute named
android:permission
, whose value is the name of the permission that is
required to access those items:
<activity
android:name=".SekritApp"
android:label="Top Sekrit"
android:permission="vnd.tlagency.sekrits.SEE_SEKRITS">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
Only applications that have requested your indicated permission will be able to access the secured component. In this case, “access” means:
sendBroadcast()
unless
the sender has the permissionIn your code, you have two additional ways to enforce permissions.
Your services can check permissions on a per-call basis via
checkCallingPermission()
. This returns PERMISSION_GRANTED
or
PERMISSION_DENIED
depending on whether the caller has the permission
you specified. For example, if your service implements separate read
and write methods, you could require separate read versus write
permissions in code by checking those methods for the permissions you
need from Java.
Also, you can include a permission when you call sendBroadcast()
.
This means that eligible broadcast receivers must hold that permission;
those without the permission are ineligible to receive it. We will
examine sendBroadcast()
in greater detail elsewhere in this book.
While normally you require your own custom permissions using the techniques described above, there is nothing stopping you from reusing a standard system permission, if it would fit your needs.
For example, suppose that you are writing YATC (Yet Another Twitter Client). You decide that in addition to YATC having its own UI, you will design YATC to be a “Twitter engine” for use by third party apps:
Intent
sContentProvider
You could, and perhaps should, implement your own custom permission.
However, since any app can get to Twitter just by having the INTERNET
permission, one could argue that a third-party app should just need
that same INTERNET
permission to use your API (rather than integrating
JTwitter or another third-party JAR).
Each permission in Android is assigned a protection level, via an
android:protectionLevel
attribute on the <permission>
element.
By default,
permissions are at a normal
level, but they can also be flagged as
dangerous
, signatureOrSystem
, or signature
. In the latter two cases,
“signature” means that the app requesting the permission and the app
requiring the permission should have be signed by the same signing key.
In the case of signatureOrSystem
— only used by the firmware –
the app requesting the permission either needs to be signed by the
firmware’s signing key or reside on the system partition (e.g., come
pre-installed with the device).
Most of Android’s permissions mentioned in this book are ones that
any SDK application can hold, if they ask for them and the user grants
them. INTERNET
, READ_CONTACTS
, ACCESS_FINE_LOCATION
, and kin
all are normal permissions.
BRICK
is not.
There was a permission in Android, named BRICK
, that, in theory, allows
an application to render a phone inoperable (a.k.a., “brick” the phone).
While there is no brickMe()
method in the Android SDK tied to this
permission, presumably there might be something deep in the firmware
that was protected by this permission. Though, since Android 6.0
removed the BRICK
permission from the SDK, it is clearly not something
Google expects us to use.
The BRICK
permission could not be held by ordinary Android SDK applications.
You could request it all you want, and it will not be granted.
However, applications that are signed with the same signing key that
signed the firmware could hold the BRICK
permission.
That is because
the system’s own manifest
used to have the following <permission>
element:
<permission android:name="android.permission.BRICK"
android:label="@string/permlab_brick"
android:description="@string/permdesc_brick"
android:protectionLevel="signature" />
You too can require signature
-level permissions. That will
restrict the holders of that permission to be other apps signed
by your signing key. This is particularly useful for inter-process
communication between apps in a suite — by using signature
permissions,
you ensure that only your apps will be able to participate in those
communications.
One nice thing about these sorts of signature-level permissions is that the user is not bothered with them. It is assumed that the user will agree to the communication between the apps signed by the same signing key. Hence, the user will not see signature-level permissions at install or upgrade time.
Since in some cases, you may not be sure which app will be installed first,
it is best to have all apps in the suite include the same <permission>
element, in addition to the corresponding <uses-permission>
element. That
way, no matter which app is installed first, it can declare the permission
that all will share.
Though, that has its own problems, as you will see in the next section.
(NOTE: Some of the material in this section originally appeared in material hosted in the CWAC-Security project repository. In addition, the author would like to thank Mark Carter and “Justin Case” for their contributions in this topic area).
Unfortunately, custom permissions have some undocumented limitations that make them intrinsically risky. Specifically, custom permissions can be defined by anyone, at any time, and “first one in wins”, which opens up the possibility of unexpected behavior.
Here, we will walk through some scenarios and show where the problems arise, plus discuss how to mitigate them as best we can.
All of the following scenarios focus on three major app profiles.
App A is an app that defines a custom permission in its manifest, such as:
<permission
android:name="com.commonsware.cwac.security.demo.OMG"
android:description="@string/perm_desc"
android:label="@string/perm_label"
android:protectionLevel="normal"/>
App A also defends a component using the android:permission
attribute, referencing the custom permission:
<provider
android:name="FileProvider"
android:authorities="com.commonsware.cwac.security.demo.files"
android:exported="true"
android:grantUriPermissions="false"
android:permission="com.commonsware.cwac.security.demo.OMG">
<grant-uri-permission android:path="/test.pdf"/>
</provider>
App B has a <uses-permission>
element to declare to the user that
it wishes to access components defended by that permission:
<uses-permission android:name="com.commonsware.cwac.security.demo.OMG"/>
App C has the same <uses-permission>
element. The difference is
that App B also has the <permission>
element, just as App A
does, albeit with different descriptive information (e.g.,
android:description
) and, at times, a different protection level.
All three apps are signed with different signing keys, because in the real world they would be from different developers.
So, to recap:
With all that in mind, let’s walk through some possible scenarios, focusing on two questions:
adb
), regarding this
permission?ContentProvider
from App A?Suppose the reason why App A has defined a custom permission is because it wants third-party apps to have the ability to access its secured components… but only with user approval. By defining a custom permission, and having third-party apps request that permission, the user should be informed about the requested permission and can make an informed decision.
Conversely, if an app tries to access a secured component but has not requested the permission, the access attempt should fail.
App C has requested the custom permission via the <uses-permission>
element. If the permission — defined by App A — has an
android:protectionLevel
of normal
or dangerous
, the user
will be informed about the requested permission at install time. If
the user continues with the installation, App C can access the
secured component.
If, however, the android:protectionLevel
is signature
, the user
is not informed about the requested permission at install time, as
the system can determine on its own whether or not the permission
should be granted. In this case, App A and App C are signed with
different signing keys, so Android silently ignores the permission
request. If the user continues with installation, then App C tries
to access App A’s secured component, App C crashes with a
SecurityException
.
In other words, this all works as expected.
However, in many cases, there is nothing forcing the user to install App A before App C. This is particularly true for publicly-distributed apps on common markets, like the Play Store.
When the user installs App C, the user is not informed about the request for the custom permission, presumably because that permission has not yet been defined. If the user later installs App A, App C is not retroactively granted the permission, and so App C’s attempts to use the secured component fail.
This works as expected, though it puts a bit of a damper on custom permissions. One way to work around this would be for the user to uninstall App C, then install it again (with App A already installed). This returns us to the original scenario from the preceding section. However, if the user has data in App C, losing that data may be a problem (as in a “let’s give App C, or perhaps App A, one-star ratings on the Play Store” sort of problem).
Suppose now we augment our SDK-consuming app (formerly App C) to
declare the same permission that App A does, in an attempt to allow
the two apps to be installed in either order. That is what App B is:
the same app as App C, but where it has the same <permission>
element as does App A in its manifest.
This scenario is particularly important where both apps could be of roughly equal importance to the user. In cases where App C is some sort of plugin for App A, it is not unreasonable for the author of App A to require App A to be installed first. But, if Twitter and Facebook wanted to access components of each others’ apps, it would be unreasonable for either of those firms to mandate that their app must be installed first. After all, if Twitter wants to be installed first, and Facebook wants to be installed first, one will be disappointed.
If the user installs App A (the app defending a component with the custom permission) before App B, the user will be notified at install time about App B’s request for this permission. Notably, the information shown on the installation security screen will contain App A’s description of the permission. And, if the user goes ahead and installs App B, App B can indeed access App A’s secured component, since it was granted permission by the user.
Once again, everything is working as expected. Going back to the two questions:
What happens if we reverse the order of installation? After all, if App A and App B are peers, from the standpoint of the user, there is roughly a 50% chance that the user will install App B before App A.
Here is where things go off the rails.
The user is not informed about App B’s request for the custom permission.
The user will be informed about any platform permissions that the
app requests via other <uses-permission>
elements. If there are none,
the user is told that App B requests no permissions… despite the
fact that it does.
When the user installs App A, the same thing occurs. Of course, since
App A does not have a <uses-permission>
element, this is not all
that surprising.
However, at this point, even though the user was not informed, App B holds the custom permission and can access the secured component.
This is bad enough when both parties are ethical. App B could be a piece of malware, though, designed to copy the data from App A, ideally without the user’s knowledge. And, if App B is installed before App A, that would happen.
So, going to the two questions:
You might think that the preceding problem would only be for
normal
or dangerous
protection levels. If App A defines
a permission as requiring a matching signature
, and App A marks a
component as being defended by that permission, Android must require
the signature match, right?
Wrong.
The behavior is identical to the preceding case. Android does
not use the defender’s protection level. It uses the definer’s
protection level, meaning the protection level of whoever was installed
first and had the <permission>
element.
So, if App A has the custom permission defined as signature
, and
App B has the custom permission defined as normal
, if App B is
installed first, the behavior is as shown in the preceding section:
What happens if we add App C back into the mix? Specifically, what if App B is installed first, then App A, then App C?
When App C eventually gets installed, the user is prompted for the
custom permission that App C requests via <uses-permission>
.
However, the description that the user sees is from App B, the one
that first defined the custom <permission>
. Moreover, the
protection level is whatever App B defined it to be. So if App B
downgraded the protection level from App A’s intended signature
to be normal
, App C can hold that permission and access the
secured App A component, even if it is signed by another signing key.
Not surprisingly, the same results occur if you install App B, then App C, then App A.
The behavior exhibited in these scenarios is consistent with two presumed implementation “features” of Android’s permission system:
<permission>
for a given android:name
gets to determine what
the description is and what the protection level is.<uses-permission>
element, the
permission was already defined by some other app, and the
protection level is not signature
.The “first one in wins” rule is a blessing and a curse. It is a
curse, insofar as it opens up the possibility for malware to hold
a custom permission without the user’s awareness of that, and even
to downgrade a signature
-level permission to normal
. However,
it is a blessing, in that the malware would have to be installed first;
if it is installed second, either its request to hold the permission
will be seen by the user (normal
or dangerous
) or the request to
hold the permission will be rejected (signature
).
This makes it somewhat unlikely for a piece of malware to try to sneakily make off with data. Eventually, if enough users start to ask publicly why App B needs access to App A’s data (for cases where App A was installed first and the user knows about the permission request), somebody in authority may eventually realize that this is a malware attack. Of course, “eventually” may be a rather long time.
However, there are some situations where Android’s custom permission behavior presents risk even greater than that. If the attacker has a means of being sure that their app was installed first, they can hold any permission from any third-party app they want to that was known at install time.
For example:
Android 5.0 now prevents two apps from defining the same <permission>
(“same” based on android:name
) unless they are signed by the same
signing key. First one in wins; the second app installation will fail.
On the plus side:
signature
-level <permission>
elements for their own app suite.However, it does pose significant limitations on legitimate public
uses of custom <permission>
elements. Only the defender should have
the <permission>
element now. Some client of the defender’s app
(C) should not have the <permission>
element and should simply rely
upon the fact that the defender should be installed first. If the
client were to define the <permission>
, then either the client or
the defender cannot be installed, which is pointless.
This has usability issues:
<permission>
element) exists. If not, the client
should alert the user to this fact and perhaps stop the app from
proceeding further. The user would have to uninstall the client,
install the defender, then reinstall the client, to get everything
working properly, and the more the user uses the client app, the more
painful the uninstall might be.<permission>
. If Facebook wanted to hold a custom Twitter permission,
and Twitter wanted to hold a custom Facebook permission, one of them
is out of luck — if Facebook is installed first, it cannot request
Twitter’s permission (as it does not yet exist) nor can it define
Twitter’s permission (as if it does, Twitter cannot be installed).
This might be able to be overcome for apps that are pre-loaded as
part of a ROM mod or other custom Android build.And, of course, this fix is only for Android 5.0 and above.
The “first one in wins” rule also leads us to a mitigation strategy: On first run of our app, see if any other app has defined permissions that we have defined. If that has happened, then we are at risk, and take appropriate steps. If, however, no other app has defined our custom permissions, then the Android permission system should work for us, and we can proceed as normal.
The CWAC-Security library
provides some helper code, in the form of the PermissionUtils
class, to detect
other apps defining the same custom permissions that you define.
The idea is that you call checkCustomPermissions()
— a static method
on PermissionUtils
— on the first run of your app. It will return details
about what other apps have already defined custom permissions that your app
defines. If checkCustomPermissions()
returns nothing, you know that everything
is fine, and you can move ahead. Otherwise, you can:
Android 6.0 introduced the concept of runtime permissions, where dangerous
permissions need to be requested at runtime in addition to being requested
in the manifest. This is covered back in the introductory chapter on permissions.
However, what happens if you define a custom dangerous
permission?
The good news is that it works. However, you will want to test it, in part to see what it looks like to users, so you can get the phrasing of your permission-related string resources correct.
The
Permissions/CustomDangerous
sample project contains two application modules:
app
is an app that defends an activity using a custom dangerous
permissionclient
is an app that wishes to request that permission and start that
protected activityThe <permission>
element is unremarkable, other than the protectionLevel
being set to dangerous
:
<permission
android:name="com.commonsware.android.perm.custdanger.SOMETHING"
android:description="@string/perm_desc"
android:label="@string/perm_label"
android:protectionLevel="dangerous" />
The label and description come from string resources:
<string name="perm_label">Custom Dangerous Permission</string>
<string name="perm_desc">This is a description. No, really.</string>
The client
module uses the same AbstractPermissionActivity
seen elsewhere
in this book to request that com.commonsware.android.perm.custdanger.SOMETHING
permission at runtime.
If you install the app
application, then install and run the client
application,
what you see is the description, not the label, appear in the runtime permission
dialog:
Figure 724: Custom Dangerous Permission, As Shown In Runtime Permission Dialog, on Android 6.0.1
If you go into Settings > Apps > Custom Dangerous Client > Permissions, the
custom dangerous
permission does not show up immediately, due to a poorly-designed
UI:
Figure 725: App Permissions in Settings, Showing Nothing Useful
Instead, the user needs to tap on the “Additional permissions” row to have
that be replaced by the custom dangerous
permission:
Figure 726: App Permissions in Settings, Showing Custom Dangerous Permission
Here, the label is what shows up, not the description.
Hence, you will want to tailor your phrasing of these string resources to make sense in their respective use cases.
On the one hand, developers should try to stick to documented permissions.
On the other hand, documentation is sometimes lacking. This is particularly true for permissions other than those defined by the OS itself, ones that come from other apps that change more frequently, including the Play Services SDK and framework.
You might find that you need to determine what permissions have been defined on a given device. Perhaps that need is at runtime — if you request a permission that does not exist, you cannot actually get it, and that may lead to problems in the future. Perhaps that need is just during development itself, to inspect some device and determine what it does and does not have in terms of permissions.
PackageManager
offers methods to allow you to examine the device’s
permissions and permission groups. The
Permissions/PermissionReporter
sample app uses these methods to build up a tabbed UI listing
the defined permissions, broken down by protection level.
getAllPermissionGroups()
on PackageManager
will return a list of
PermissionGroupInfo
objects. This method takes an int
value;
0
generally will be fine for your use cases.
On its own, PermissionGroupInfo
is
not especially useful. However, you can turn around and call
queryPermissionsByGroup()
on PackageManager
, passing in the
name
from the PermissionGroupInfo
, to get all of the permissions in that group.
This method also takes an int
value as the second parameter, where
once again 0
will be fine.
queryPermissionsByGroup()
returns a List
of PermissionInfo
objects. PermissionInfo
has a few interesting values:
name
, which is the fully-qualified name of the permissiondescriptionRes
, which is the string resource ID from the permission’s
android:description
attributeprotectionLevel
, which is a set of flags indicating the nature of the
permission’s securityNote that to get the actual text of the description, there is a
loadDescription()
method on PermissionInfo
that will do all the work
to find the actual string for the description, based upon the app that
defined the permission and the current locale.
To get the details of all the permissions defined on a device, we will
have to call queryPermissionsByGroup()
for each permission group. Each
of those calls will involve IPC, and so this might be slow enough to
warrant its own thread.
With that in mind, MainActivity
in the PermissionReporter
sample app has a
PermissionSource
that collects information about the permissions
on the system. That information is aggregated in a PermissionRoster
, which
the data emitted by this Observable
:
private static class PermissionSource implements ObservableOnSubscribe<PermissionRoster> {
private Context ctxt;
private PermissionSource(Context ctxt) {
this.ctxt=ctxt.getApplicationContext();
}
@Override
public void subscribe(ObservableEmitter<PermissionRoster> emitter)
throws Exception {
PackageManager pm=ctxt.getPackageManager();
final PermissionRoster result=new PermissionRoster();
addPermissionsFromGroup(pm, null, result);
for (PermissionGroupInfo group :
pm.getAllPermissionGroups(0)) {
addPermissionsFromGroup(pm, group.name, result);
}
emitter.onNext(result);
emitter.onComplete();
}
private void addPermissionsFromGroup(PackageManager pm,
String groupName,
PermissionRoster result)
throws PackageManager.NameNotFoundException {
for (PermissionInfo info :
pm.queryPermissionsByGroup(groupName, 0)) {
int coreBits=
info.protectionLevel &
PermissionInfo.PROTECTION_MASK_BASE;
switch (coreBits) {
case PermissionInfo.PROTECTION_NORMAL:
result.add(PermissionType.NORMAL, info);
break;
case PermissionInfo.PROTECTION_DANGEROUS:
result.add(PermissionType.DANGEROUS, info);
break;
case PermissionInfo.PROTECTION_SIGNATURE:
result.add(PermissionType.SIGNATURE, info);
break;
default:
result.add(PermissionType.OTHER, info);
break;
}
}
}
}
The subscribe()
method loops over the permission groups:
for (PermissionGroupInfo group :
pm.getAllPermissionGroups(0)) {
addPermissionsFromGroup(pm, group.name, result);
}
As it turns out, not all permissions are part of a group. To find out
the details of these un-grouped permissions, you need to call
queryPermissionsByGroup()
with a null
permission group name.
For each permission group (plus the magic null
group), we call
a private addPermissionsFromGroup()
method (shown above) to collect the details
of the permissions in that group.
The protectionLevel
field on a PermissionInfo
contains a number
of different sorts of flags. The PROTECTION_MASK_BASE
is a bitmask that
restricts the bits we are looking at to the ones for basic protections.
We then divide the permissions into four groups based on protection level:
system
or signatureOrSystem
permissions)Those PermissionInfo
objects are then poured into the PermissionRoster
object:
package com.commonsware.android.permreporter;
import android.content.pm.PermissionInfo;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.HashMap;
class PermissionRoster {
private HashMap<PermissionType, ArrayList<PermissionInfo>> roster=
new HashMap<PermissionType, ArrayList<PermissionInfo>>();
void add(PermissionType type, PermissionInfo info) {
ArrayList<PermissionInfo> list=roster.get(type);
if (list==null) {
list=new ArrayList<>();
roster.put(type, list);
}
list.add(info);
}
ArrayList<PermissionInfo> getListForType(PermissionType type) {
return(roster.get(type));
}
}
PermissionType
is an enum
defined by this project for the four groups
and includes some Java shenanigans for being able to convert back and
forth between integer values and the enum
values:
package com.commonsware.android.permreporter;
import android.util.SparseArray;
enum PermissionType {
NORMAL(0),
DANGEROUS(1),
SIGNATURE(2),
OTHER(3);
private static final SparseArray<PermissionType> BY_VALUE=
new SparseArray<PermissionType>(4);
static {
for (PermissionType type : PermissionType.values()) {
BY_VALUE.put(type.value, type);
}
}
private final int value;
PermissionType(int value) {
this.value=value;
}
static PermissionType forValue(int value) {
return(BY_VALUE.get(value));
}
}