Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add hasPermissions security policy and test #9117

Merged
merged 5 commits into from May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions binder/src/main/java/io/grpc/binder/SecurityPolicies.java
Expand Up @@ -25,6 +25,7 @@
import android.os.Process;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CheckReturnValue;
import io.grpc.ExperimentalApi;
import io.grpc.Status;
Expand Down Expand Up @@ -218,4 +219,47 @@ public Status checkAuthorization(int uid) {
}
};
}

/**
* Creates a {@link SecurityPolicy} which checks if the caller has all of the given permissions
* from {@code permissions}.
*
* @param permissions all permissions that the calling package needs to have
* @throws NullPointerException if any of the inputs are {@code null}
* @throws IllegalArgumentException if {@code permissions} is empty
*/
public static SecurityPolicy hasPermissions(
PackageManager packageManager, ImmutableSet<String> permissions) {
Preconditions.checkNotNull(packageManager, "packageManager");
Preconditions.checkNotNull(permissions, "permissions");
Preconditions.checkArgument(!permissions.isEmpty(), "permissions");
return new SecurityPolicy() {
@Override
public Status checkAuthorization(int uid) {
return checkPermissions(uid, packageManager, permissions);
}
};
}

private static Status checkPermissions(
int uid, PackageManager packageManager, ImmutableSet<String> permissions) {
String[] packages = packageManager.getPackagesForUid(uid);
if (packages == null || packages.length == 0) {
return Status.UNAUTHENTICATED.withDescription(
"Rejected by permission check security policy. No packages found for uid");
}
for (String pkg : packages) {
for (String permission : permissions) {
if (packageManager.checkPermission(permission, pkg) != PackageManager.PERMISSION_GRANTED) {
return Status.PERMISSION_DENIED.withDescription(
"Rejected by permission check security policy. "
+ pkg
+ " does not have permission "
+ permission);
}
}
}

return Status.OK;
}
}
241 changes: 221 additions & 20 deletions binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
Expand Up @@ -16,6 +16,11 @@

package io.grpc.binder;

import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;

Expand All @@ -26,8 +31,9 @@
import android.os.Process;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import io.grpc.Status;
import io.grpc.binder.SecurityPolicy;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -39,7 +45,6 @@ public final class SecurityPoliciesTest {
private static final int MY_UID = Process.myUid();
private static final int OTHER_UID = MY_UID + 1;
private static final int OTHER_UID_SAME_SIGNATURE = MY_UID + 2;
private static final int OTHER_UID_NO_SIGNATURE = MY_UID + 3;
private static final int OTHER_UID_UNKNOWN = MY_UID + 4;

private static final String PERMISSION_DENIED_REASONS = "some reasons";
Expand All @@ -49,7 +54,6 @@ public final class SecurityPoliciesTest {

private static final String OTHER_UID_PACKAGE_NAME = "other.package";
private static final String OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME = "other.package.samesignature";
private static final String OTHER_UID_NO_SIGNATURE_PACKAGE_NAME = "other.package.nosignature";

private Context appContext;
private PackageManager packageManager;
Expand All @@ -60,24 +64,22 @@ public final class SecurityPoliciesTest {
public void setUp() {
appContext = ApplicationProvider.getApplicationContext();
packageManager = appContext.getPackageManager();
installPackage(MY_UID, appContext.getPackageName(), SIG1);
installPackage(OTHER_UID, OTHER_UID_PACKAGE_NAME, SIG2);
installPackage(OTHER_UID_SAME_SIGNATURE, OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME, SIG1);
installPackage(OTHER_UID_NO_SIGNATURE, OTHER_UID_NO_SIGNATURE_PACKAGE_NAME);
}

@SuppressWarnings("deprecation")
private void installPackage(int uid, String packageName, Signature... signatures) {
PackageInfo info = new PackageInfo();
info.packageName = packageName;
info.signatures = signatures;
shadowOf(packageManager).installPackage(info);
shadowOf(packageManager).setPackagesForUid(uid, packageName);
private void installPackages(int uid, PackageInfo... packageInfo) {
String[] packageNames = new String[packageInfo.length];
for (int i = 0; i < packageInfo.length; i++) {
shadowOf(packageManager).installPackage(packageInfo[i]);
packageNames[i] = packageInfo[i].packageName;
}
shadowOf(packageManager).setPackagesForUid(uid, packageNames);
}

@Test
public void testInternalOnly() throws Exception {
policy = SecurityPolicies.internalOnly();

assertThat(policy.checkAuthorization(MY_UID).getCode()).isEqualTo(Status.OK.getCode());
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
.isEqualTo(Status.PERMISSION_DENIED.getCode());
Expand All @@ -99,6 +101,11 @@ public void testPermissionDenied() throws Exception {
@Test
public void testHasSignature_succeedsIfPackageNameAndSignaturesMatch()
throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.hasSignature(packageManager, OTHER_UID_PACKAGE_NAME, SIG2);

// THEN UID for package that has SIG2 will be authorized
Expand All @@ -107,6 +114,14 @@ public void testHasSignature_succeedsIfPackageNameAndSignaturesMatch()

@Test
public void testHasSignature_failsIfPackageNameDoesNotMatch() throws Exception {
PackageInfo info =
newBuilder()
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
.setSignatures(SIG1)
.build();

installPackages(OTHER_UID_SAME_SIGNATURE, info);

policy = SecurityPolicies.hasSignature(packageManager, appContext.getPackageName(), SIG1);

// THEN UID for package that has SIG1 but different package name will not be authorized
Expand All @@ -116,6 +131,11 @@ public void testHasSignature_failsIfPackageNameDoesNotMatch() throws Exception {

@Test
public void testHasSignature_failsIfSignatureDoesNotMatch() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.hasSignature(packageManager, OTHER_UID_PACKAGE_NAME, SIG1);

// THEN UID for package that doesn't have SIG1 will not be authorized
Expand All @@ -126,6 +146,11 @@ public void testHasSignature_failsIfSignatureDoesNotMatch() throws Exception {
@Test
public void testOneOfSignatures_succeedsIfPackageNameAndSignaturesMatch()
throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy =
SecurityPolicies.oneOfSignatures(
packageManager, OTHER_UID_PACKAGE_NAME, ImmutableList.of(SIG2));
Expand All @@ -136,6 +161,14 @@ public void testOneOfSignatures_succeedsIfPackageNameAndSignaturesMatch()

@Test
public void testOneOfSignature_failsIfAllSignaturesDoNotMatch() throws Exception {
PackageInfo info =
newBuilder()
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
.setSignatures(SIG1)
.build();

installPackages(OTHER_UID_SAME_SIGNATURE, info);

policy =
SecurityPolicies.oneOfSignatures(
packageManager,
Expand All @@ -150,11 +183,14 @@ public void testOneOfSignature_failsIfAllSignaturesDoNotMatch() throws Exception
@Test
public void testOneOfSignature_succeedsIfPackageNameAndOneOfSignaturesMatch()
throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy =
SecurityPolicies.oneOfSignatures(
packageManager,
OTHER_UID_PACKAGE_NAME,
ImmutableList.of(SIG1, SIG2));
packageManager, OTHER_UID_PACKAGE_NAME, ImmutableList.of(SIG1, SIG2));

// THEN UID for package that has SIG2 will be authorized
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
Expand All @@ -164,12 +200,177 @@ public void testOneOfSignature_succeedsIfPackageNameAndOneOfSignaturesMatch()
public void testHasSignature_failsIfUidUnknown() throws Exception {
policy =
SecurityPolicies.hasSignature(
packageManager,
appContext.getPackageName(),
SIG1);
packageManager,
appContext.getPackageName(),
SIG1);

assertThat(policy.checkAuthorization(OTHER_UID_UNKNOWN).getCode())
.isEqualTo(Status.UNAUTHENTICATED.getCode());
.isEqualTo(Status.UNAUTHENTICATED.getCode());
}

@Test
public void testHasPermissions_sharedUserId_succeedsIfAllPackageHavePermissions()
throws Exception {
PackageInfo info =
newBuilder()
.setPackageName(OTHER_UID_PACKAGE_NAME)
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.build();

PackageInfo infoSamePerms =
newBuilder()
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.build();

installPackages(OTHER_UID, info, infoSamePerms);

policy =
SecurityPolicies.hasPermissions(
packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION));
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}

@Test
public void testHasPermissions_sharedUserId_failsIfOnePackageHasNoPermissions() throws Exception {
PackageInfo info =
newBuilder()
.setPackageName(OTHER_UID_PACKAGE_NAME)
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.build();

PackageInfo infoNoPerms =
newBuilder()
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
.setPermission(ACCESS_FINE_LOCATION, 0)
.build();

installPackages(OTHER_UID, info, infoNoPerms);

policy = SecurityPolicies.hasPermissions(packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION));
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
.isEqualTo(Status.PERMISSION_DENIED.getCode());
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
.contains(ACCESS_FINE_LOCATION);
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
.contains(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME);
}

@Test
public void testHasPermissions_succeedsIfPackageHasPermissions() throws Exception {
PackageInfo info =
newBuilder()
.setPackageName(OTHER_UID_PACKAGE_NAME)
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(WRITE_EXTERNAL_STORAGE, 0)
.build();

installPackages(OTHER_UID, info);

policy =
SecurityPolicies.hasPermissions(
packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION));
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}

@Test
public void testHasPermissions_failsIfPackageDoesNotHaveOnePermission() throws Exception {
PackageInfo info =
newBuilder()
.setPackageName(OTHER_UID_PACKAGE_NAME)
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(WRITE_EXTERNAL_STORAGE, 0)
.build();

installPackages(OTHER_UID, info);

policy =
SecurityPolicies.hasPermissions(
packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION, WRITE_EXTERNAL_STORAGE));
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
.isEqualTo(Status.PERMISSION_DENIED.getCode());
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
.contains(WRITE_EXTERNAL_STORAGE);
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
.contains(OTHER_UID_PACKAGE_NAME);
}

@Test
public void testHasPermissions_failsIfPackageDoesNotHavePermissions() throws Exception {
PackageInfo info =
newBuilder()
.setPackageName(OTHER_UID_PACKAGE_NAME)
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
.setPermission(WRITE_EXTERNAL_STORAGE, 0)
.build();

installPackages(OTHER_UID, info);

policy =
SecurityPolicies.hasPermissions(packageManager, ImmutableSet.of(WRITE_EXTERNAL_STORAGE));
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
.isEqualTo(Status.PERMISSION_DENIED.getCode());
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
.contains(WRITE_EXTERNAL_STORAGE);
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
.contains(OTHER_UID_PACKAGE_NAME);
}

private static PackageInfoBuilder newBuilder() {
return new PackageInfoBuilder();
}

private static class PackageInfoBuilder {
private String packageName;
private Signature[] signatures;
private final HashMap<String, Integer> permissions = new HashMap<>();

public PackageInfoBuilder setPackageName(String packageName) {
this.packageName = packageName;
return this;
}

public PackageInfoBuilder setPermission(String permissionName, int permissionFlag) {
this.permissions.put(permissionName, permissionFlag);
return this;
}

public PackageInfoBuilder setSignatures(Signature... signatures) {
this.signatures = signatures;
return this;
}

public PackageInfo build() {
checkState(this.packageName != null, "packageName is a mandatory field");

PackageInfo packageInfo = new PackageInfo();

packageInfo.packageName = this.packageName;

if (this.signatures != null) {
packageInfo.signatures = this.signatures;
}

if (!this.permissions.isEmpty()) {
String[] requestedPermissions =
this.permissions.keySet().toArray(new String[this.permissions.size()]);
int[] requestedPermissionsFlags = new int[requestedPermissions.length];

for (int i = 0; i < requestedPermissions.length; i++) {
requestedPermissionsFlags[i] = this.permissions.get(requestedPermissions[i]);
}

packageInfo.requestedPermissions = requestedPermissions;
packageInfo.requestedPermissionsFlags = requestedPermissionsFlags;
}

return packageInfo;
}
}

@Test
Expand Down