Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ab6f2e2 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

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
parent 774d8669
Loading
Loading
Loading
Loading
+20 −4
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import android.net.wifi.WifiInfo;
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 class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        }
    }

    public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle) {
    public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle,
            boolean throwForMissiongPermission) {
        if (!mHasFeature) {
            return null;
        }
@@ -1736,8 +1738,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        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);
        }
+8 −0
Original line number Diff line number Diff line
@@ -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
+30 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.content.BroadcastReceiver;
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 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
        setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
        setUpPackageManagerForAdmin(adminNoPerm, DpmMockContext.CALLER_UID);

        setUpUserManager();
    }
@@ -336,6 +339,33 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        }
    }

    /**
     * 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}
+30 −18
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ public abstract class DpmTestBase extends AndroidTestCase {
    public ComponentName admin1;
    public ComponentName admin2;
    public ComponentName admin3;
    public ComponentName adminNoPerm;

    @Override
    protected void setUp() throws Exception {
@@ -56,6 +57,7 @@ public abstract class DpmTestBase extends AndroidTestCase {
        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 @@ public abstract class DpmTestBase extends AndroidTestCase {
    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 @@ public abstract class DpmTestBase extends AndroidTestCase {
        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 @@ public abstract class DpmTestBase extends AndroidTestCase {
                        | 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 @@ public abstract class DpmTestBase extends AndroidTestCase {
                        admin.getPackageName(), 0));
        assertTrue(pi.applicationInfo.flags != 0);

        pi.applicationInfo.uid = packageUid;
        pi.applicationInfo = ai;

        doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo(
                eq(admin.getPackageName()),
+2 −0
Original line number Diff line number Diff line
@@ -24,4 +24,6 @@ public class DummyDeviceAdmins {
    }
    public static class Admin3 extends DeviceAdminReceiver {
    }
    public static class AdminNoPerm extends DeviceAdminReceiver {
    }
}