DA receiver should be protected with BIND_DEVICE_ADMIN.
- DPM.setActiveAdmin() will not accept DAs without BIND_DEVICE_ADMIN
when it's targeting NYC or above.
- DAs without BIND_DEVICE_ADMIN targeting MNC or below will still be
accepted. (with a logcat warning)
- DAs that are already set on a device without BIND_DEVICE_ADMIN
will still be accepted regardless of the target API level, even when
it's upgraded to a version targeting NYC.
Bug 24168653
Change-Id: I1914c2ec99135d9dd8cbac3f6914f9e43bafacc8
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5c3a55d..f80a611 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -75,6 +75,7 @@
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
@@ -1721,7 +1722,8 @@
}
}
- public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle) {
+ public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle,
+ boolean throwForMissiongPermission) {
if (!mHasFeature) {
return null;
}
@@ -1736,8 +1738,20 @@
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
+ final ResolveInfo ri = infos.get(0);
+
+ if (!permission.BIND_DEVICE_ADMIN.equals(ri.activityInfo.permission)) {
+ final String message = "DeviceAdminReceiver " + adminName + " must be protected with"
+ + permission.BIND_DEVICE_ADMIN;
+ Slog.w(LOG_TAG, message);
+ if (throwForMissiongPermission &&
+ ri.activityInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.M) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
try {
- return new DeviceAdminInfo(mContext, infos.get(0));
+ return new DeviceAdminInfo(mContext, ri);
} catch (XmlPullParserException e) {
Slog.w(LOG_TAG, "Bad device admin requested for user=" + userHandle + ": " + adminName,
e);
@@ -1928,7 +1942,8 @@
String name = parser.getAttributeValue(null, "name");
try {
DeviceAdminInfo dai = findAdmin(
- ComponentName.unflattenFromString(name), userHandle);
+ ComponentName.unflattenFromString(name), userHandle,
+ /* throwForMissionPermission= */ false);
if (VERBOSE_LOG
&& (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
!= userHandle)) {
@@ -2319,7 +2334,8 @@
enforceCrossUserPermission(userHandle);
DevicePolicyData policy = getUserData(userHandle);
- DeviceAdminInfo info = findAdmin(adminReceiver, userHandle);
+ DeviceAdminInfo info = findAdmin(adminReceiver, userHandle,
+ /* throwForMissionPermission= */ true);
if (info == null) {
throw new IllegalArgumentException("Bad admin: " + adminReceiver);
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index c147bcc..eed326e 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -94,6 +94,14 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.devicepolicy.DummyDeviceAdmins$AdminNoPerm">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin_sample" />
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ </intent-filter>
+ </receiver>
+
</application>
<instrumentation
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0159356..565ef4b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -27,6 +27,8 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.net.wifi.WifiInfo;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
@@ -85,6 +87,7 @@
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
+ setUpPackageManagerForAdmin(adminNoPerm, DpmMockContext.CALLER_UID);
setUpUserManager();
}
@@ -338,6 +341,33 @@
/**
* Test for:
+ * {@link DevicePolicyManager#setActiveAdmin} when the admin isn't protected with
+ * BIND_DEVICE_ADMIN.
+ */
+ public void testSetActiveAdmin_permissionCheck() throws Exception {
+ // 1. Make sure the caller has proper permissions.
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+
+ try {
+ dpm.setActiveAdmin(adminNoPerm, /* replace =*/ false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertTrue(expected.getMessage().contains(permission.BIND_DEVICE_ADMIN));
+ }
+ assertFalse(dpm.isAdminActive(adminNoPerm));
+
+ // Change the target API level to MNC. Now it can be set as DA.
+ setUpPackageManagerForAdmin(adminNoPerm, DpmMockContext.CALLER_UID, null,
+ VERSION_CODES.M);
+ dpm.setActiveAdmin(adminNoPerm, /* replace =*/ false);
+ assertTrue(dpm.isAdminActive(adminNoPerm));
+
+ // TODO Test the "load from the file" case where DA will still be loaded even without
+ // BIND_DEVICE_ADMIN and target API is N.
+ }
+
+ /**
+ * Test for:
* {@link DevicePolicyManager#removeActiveAdmin}
*/
public void testRemoveActiveAdmin_SecurityException() {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index e11f3fb..5b33e4d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -43,6 +43,7 @@
public ComponentName admin1;
public ComponentName admin2;
public ComponentName admin3;
+ public ComponentName adminNoPerm;
@Override
protected void setUp() throws Exception {
@@ -56,6 +57,7 @@
admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
+ adminNoPerm = new ComponentName(mRealTestContext, DummyDeviceAdmins.AdminNoPerm.class);
}
@Override
@@ -67,11 +69,36 @@
protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid)
throws Exception {
setUpPackageManagerForAdmin(admin, packageUid,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+ /* enabledSetting =*/ null, /* appTargetSdk = */ null);
}
protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
int enabledSetting) throws Exception {
+ setUpPackageManagerForAdmin(admin, packageUid, enabledSetting, /* appTargetSdk = */ null);
+ }
+
+ protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
+ Integer enabledSetting, Integer appTargetSdk) throws Exception {
+
+ // Set up getApplicationInfo().
+
+ final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
+ mRealTestContext.getPackageManager().getApplicationInfo(
+ admin.getPackageName(),
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
+
+ ai.enabledSetting = enabledSetting == null
+ ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ : enabledSetting;
+ if (appTargetSdk != null) {
+ ai.targetSdkVersion = appTargetSdk;
+ }
+ ai.uid = packageUid;
+
+ doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
+ eq(admin.getPackageName()),
+ eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
+ eq(UserHandle.getUserId(packageUid)));
// Set up queryBroadcastReceivers().
@@ -88,7 +115,7 @@
realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
// We need to rewrite the UID in the activity info.
- realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
+ realResolveInfo.get(0).activityInfo.applicationInfo = ai;
doReturn(realResolveInfo).when(mMockContext.packageManager).queryBroadcastReceivers(
MockUtils.checkIntentComponent(admin),
@@ -96,21 +123,6 @@
| PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
eq(UserHandle.getUserId(packageUid)));
- // Set up getApplicationInfo().
-
- final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
- mRealTestContext.getPackageManager().getApplicationInfo(
- admin.getPackageName(),
- PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
-
- ai.enabledSetting = enabledSetting;
- ai.uid = packageUid;
-
- doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
- eq(admin.getPackageName()),
- eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
- eq(UserHandle.getUserId(packageUid)));
-
// Set up getPackageInfo().
final PackageInfo pi = DpmTestUtils.cloneParcelable(
@@ -118,7 +130,7 @@
admin.getPackageName(), 0));
assertTrue(pi.applicationInfo.flags != 0);
- pi.applicationInfo.uid = packageUid;
+ pi.applicationInfo = ai;
doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo(
eq(admin.getPackageName()),
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DummyDeviceAdmins.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DummyDeviceAdmins.java
index 08293a2..a0f4d97 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DummyDeviceAdmins.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DummyDeviceAdmins.java
@@ -24,4 +24,6 @@
}
public static class Admin3 extends DeviceAdminReceiver {
}
+ public static class AdminNoPerm extends DeviceAdminReceiver {
+ }
}