Add security policies for checking device owner/profile owner. (#9428)
diff --git a/binder/build.gradle b/binder/build.gradle
index d18fe5e..41c4bd4 100644
--- a/binder/build.gradle
+++ b/binder/build.gradle
@@ -23,14 +23,14 @@
}
}
}
- compileSdkVersion 29
+ compileSdkVersion 30
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
defaultConfig {
minSdkVersion 19
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
index 25f215b..653ae90 100644
--- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
+++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
@@ -17,11 +17,14 @@
package io.grpc.binder;
import android.annotation.SuppressLint;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
+import android.os.Build.VERSION;
import android.os.Process;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
@@ -173,6 +176,50 @@
};
}
+ /**
+ * Creates {@link SecurityPolicy} which checks if the app is a device owner app. See
+ * {@link DevicePolicyManager}.
+ */
+ public static SecurityPolicy isDeviceOwner(Context applicationContext) {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return anyPackageWithUidSatisfies(
+ applicationContext,
+ pkg -> VERSION.SDK_INT >= 18 && devicePolicyManager.isDeviceOwnerApp(pkg),
+ "Rejected by device owner policy. No packages found for UID.",
+ "Rejected by device owner policy");
+ }
+
+ /**
+ * Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See
+ * {@link DevicePolicyManager}.
+ */
+ public static SecurityPolicy isProfileOwner(Context applicationContext) {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return anyPackageWithUidSatisfies(
+ applicationContext,
+ pkg -> VERSION.SDK_INT >= 21 && devicePolicyManager.isProfileOwnerApp(pkg),
+ "Rejected by profile owner policy. No packages found for UID.",
+ "Rejected by profile owner policy");
+ }
+
+ /**
+ * Creates {@link SecurityPolicy} which checks if the app is a profile owner app on an
+ * organization-owned device. See {@link DevicePolicyManager}.
+ */
+ public static SecurityPolicy isProfileOwnerOnOrganizationOwnedDevice(Context applicationContext) {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return anyPackageWithUidSatisfies(
+ applicationContext,
+ pkg -> VERSION.SDK_INT >= 30
+ && devicePolicyManager.isProfileOwnerApp(pkg)
+ && devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(),
+ "Rejected by profile owner on organization-owned device policy. No packages found for UID.",
+ "Rejected by profile owner on organization-owned device policy");
+ }
+
private static Status checkUidSignature(
PackageManager packageManager,
int uid,
@@ -406,6 +453,29 @@
return Status.OK;
}
+ private static SecurityPolicy anyPackageWithUidSatisfies(
+ Context applicationContext,
+ Predicate<String> condition,
+ String errorMessageForNoPackages,
+ String errorMessageForDenied) {
+ return new SecurityPolicy() {
+ @Override
+ public Status checkAuthorization(int uid) {
+ String[] packages = applicationContext.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return Status.UNAUTHENTICATED.withDescription(errorMessageForNoPackages);
+ }
+
+ for (String pkg : packages) {
+ if (condition.apply(pkg)) {
+ return Status.OK;
+ }
+ }
+ return Status.PERMISSION_DENIED.withDescription(errorMessageForDenied);
+ }
+ };
+ }
+
/**
* Checks if the SHA-256 hash of the {@code signature} matches one of the {@code
* expectedSignatureSha256Hashes}.
diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
index a26d4dd..909547a 100644
--- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
+++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
@@ -24,10 +24,15 @@
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.Process;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
@@ -40,6 +45,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
public final class SecurityPoliciesTest {
@@ -59,6 +65,7 @@
private Context appContext;
private PackageManager packageManager;
+ private DevicePolicyManager devicePolicyManager;
private SecurityPolicy policy;
@@ -66,6 +73,8 @@
public void setUp() {
appContext = ApplicationProvider.getApplicationContext();
packageManager = appContext.getPackageManager();
+ devicePolicyManager =
+ (DevicePolicyManager) appContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}
@SuppressWarnings("deprecation")
@@ -323,6 +332,171 @@
.contains(OTHER_UID_PACKAGE_NAME);
}
+ @Test
+ @Config(sdk = 18)
+ public void testIsDeviceOwner_succeedsForDeviceOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setDeviceOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+ }
+
+ @Test
+ @Config(sdk = 18)
+ public void testIsDeviceOwner_failsForNotDeviceOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 18)
+ public void testIsDeviceOwner_failsWhenNoPackagesForUid() throws Exception {
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 17)
+ public void testIsDeviceOwner_failsForSdkLevelTooLow() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwner_succeedsForProfileOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwner_failsForNotProfileOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 19)
+ public void testIsProfileOwner_failsForSdkLevelTooLow() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 30)
+ public void testIsProfileOwnerOnOrgOwned_succeedsForProfileOwnerOnOrgOwned() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+ shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(true);
+
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+
+ }
+
+ @Test
+ @Config(sdk = 30)
+ public void testIsProfileOwnerOnOrgOwned_failsForProfileOwnerOnNonOrgOwned() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+ shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(false);
+
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 29)
+ public void testIsProfileOwnerOnOrgOwned_failsForSdkLevelTooLow() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
private static PackageInfoBuilder newBuilder() {
return new PackageInfoBuilder();
}