Skip to content

Commit a66060f

Browse files
committed
add hasPermissions security policy and test
1 parent 0963f31 commit a66060f

2 files changed

Lines changed: 259 additions & 20 deletions

File tree

binder/src/main/java/io/grpc/binder/SecurityPolicies.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import android.os.Process;
2626
import com.google.common.base.Preconditions;
2727
import com.google.common.collect.ImmutableList;
28+
import com.google.common.collect.ImmutableSet;
2829
import io.grpc.ExperimentalApi;
2930
import io.grpc.Status;
3031
import java.util.Arrays;
@@ -187,4 +188,47 @@ private static boolean checkPackageSignature(
187188
}
188189
return false;
189190
}
191+
192+
/**
193+
* Creates a {@link SecurityPolicy} which checks if the caller has all of the given permissions
194+
* from {@code permissions}.
195+
*
196+
* @param permissions all permissions that the calling package needs to have
197+
* @throws NullPointerException if any of the inputs are {@code null}
198+
* @throws IllegalArgumentException if {@code permissions} is empty
199+
*/
200+
public static SecurityPolicy hasPermissions(
201+
PackageManager packageManager, ImmutableSet<String> permissions) {
202+
Preconditions.checkNotNull(packageManager, "packageManager");
203+
Preconditions.checkNotNull(permissions, "permissions");
204+
Preconditions.checkArgument(!permissions.isEmpty(), "permissions");
205+
return new SecurityPolicy() {
206+
@Override
207+
public Status checkAuthorization(int uid) {
208+
return checkPermissions(uid, packageManager, permissions);
209+
}
210+
};
211+
}
212+
213+
private static Status checkPermissions(
214+
int uid, PackageManager packageManager, ImmutableSet<String> permissions) {
215+
String[] packages = packageManager.getPackagesForUid(uid);
216+
if (packages == null) {
217+
return Status.UNAUTHENTICATED.withDescription(
218+
"Rejected by permission check security policy. No packages found for uid");
219+
}
220+
for (String pkg : packages) {
221+
for (String permission : permissions) {
222+
if (packageManager.checkPermission(permission, pkg) != PackageManager.PERMISSION_GRANTED) {
223+
return Status.PERMISSION_DENIED.withDescription(
224+
"Rejected by permission check security policy. "
225+
+ pkg
226+
+ " does not have permission "
227+
+ permission);
228+
}
229+
}
230+
}
231+
232+
return Status.OK;
233+
}
190234
}

binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java

Lines changed: 215 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
package io.grpc.binder;
1818

19+
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
20+
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
21+
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
22+
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
23+
import static com.google.common.base.Preconditions.checkState;
1924
import static com.google.common.truth.Truth.assertThat;
2025
import static org.robolectric.Shadows.shadowOf;
2126

@@ -26,8 +31,9 @@
2631
import android.os.Process;
2732
import androidx.test.core.app.ApplicationProvider;
2833
import com.google.common.collect.ImmutableList;
34+
import com.google.common.collect.ImmutableSet;
2935
import io.grpc.Status;
30-
import io.grpc.binder.SecurityPolicy;
36+
import java.util.HashMap;
3137
import org.junit.Before;
3238
import org.junit.Test;
3339
import org.junit.runner.RunWith;
@@ -39,7 +45,6 @@ public final class SecurityPoliciesTest {
3945
private static final int MY_UID = Process.myUid();
4046
private static final int OTHER_UID = MY_UID + 1;
4147
private static final int OTHER_UID_SAME_SIGNATURE = MY_UID + 2;
42-
private static final int OTHER_UID_NO_SIGNATURE = MY_UID + 3;
4348
private static final int OTHER_UID_UNKNOWN = MY_UID + 4;
4449

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

5055
private static final String OTHER_UID_PACKAGE_NAME = "other.package";
5156
private static final String OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME = "other.package.samesignature";
52-
private static final String OTHER_UID_NO_SIGNATURE_PACKAGE_NAME = "other.package.nosignature";
5357

5458
private Context appContext;
5559
private PackageManager packageManager;
@@ -60,24 +64,22 @@ public final class SecurityPoliciesTest {
6064
public void setUp() {
6165
appContext = ApplicationProvider.getApplicationContext();
6266
packageManager = appContext.getPackageManager();
63-
installPackage(MY_UID, appContext.getPackageName(), SIG1);
64-
installPackage(OTHER_UID, OTHER_UID_PACKAGE_NAME, SIG2);
65-
installPackage(OTHER_UID_SAME_SIGNATURE, OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME, SIG1);
66-
installPackage(OTHER_UID_NO_SIGNATURE, OTHER_UID_NO_SIGNATURE_PACKAGE_NAME);
6767
}
6868

6969
@SuppressWarnings("deprecation")
70-
private void installPackage(int uid, String packageName, Signature... signatures) {
71-
PackageInfo info = new PackageInfo();
72-
info.packageName = packageName;
73-
info.signatures = signatures;
74-
shadowOf(packageManager).installPackage(info);
75-
shadowOf(packageManager).setPackagesForUid(uid, packageName);
70+
private void installPackages(int uid, PackageInfo... packageInfo) {
71+
String[] packageNames = new String[packageInfo.length];
72+
for (int i = 0; i < packageInfo.length; i++) {
73+
shadowOf(packageManager).installPackage(packageInfo[i]);
74+
packageNames[i] = packageInfo[i].packageName;
75+
}
76+
shadowOf(packageManager).setPackagesForUid(uid, packageNames);
7677
}
7778

7879
@Test
7980
public void testInternalOnly() throws Exception {
8081
policy = SecurityPolicies.internalOnly();
82+
8183
assertThat(policy.checkAuthorization(MY_UID).getCode()).isEqualTo(Status.OK.getCode());
8284
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
8385
.isEqualTo(Status.PERMISSION_DENIED.getCode());
@@ -99,6 +101,11 @@ public void testPermissionDenied() throws Exception {
99101
@Test
100102
public void testHasSignature_succeedsIfPackageNameAndSignaturesMatch()
101103
throws Exception {
104+
PackageInfo info =
105+
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
106+
107+
installPackages(OTHER_UID, info);
108+
102109
policy = SecurityPolicies.hasSignature(packageManager, OTHER_UID_PACKAGE_NAME, SIG2);
103110

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

108115
@Test
109116
public void testHasSignature_failsIfPackageNameDoesNotMatch() throws Exception {
117+
PackageInfo info =
118+
newBuilder()
119+
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
120+
.setSignatures(SIG1)
121+
.build();
122+
123+
installPackages(OTHER_UID_SAME_SIGNATURE, info);
124+
110125
policy = SecurityPolicies.hasSignature(packageManager, appContext.getPackageName(), SIG1);
111126

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

117132
@Test
118133
public void testHasSignature_failsIfSignatureDoesNotMatch() throws Exception {
134+
PackageInfo info =
135+
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
136+
137+
installPackages(OTHER_UID, info);
138+
119139
policy = SecurityPolicies.hasSignature(packageManager, OTHER_UID_PACKAGE_NAME, SIG1);
120140

121141
// THEN UID for package that doesn't have SIG1 will not be authorized
@@ -126,6 +146,11 @@ public void testHasSignature_failsIfSignatureDoesNotMatch() throws Exception {
126146
@Test
127147
public void testOneOfSignatures_succeedsIfPackageNameAndSignaturesMatch()
128148
throws Exception {
149+
PackageInfo info =
150+
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
151+
152+
installPackages(OTHER_UID, info);
153+
129154
policy =
130155
SecurityPolicies.oneOfSignatures(
131156
packageManager, OTHER_UID_PACKAGE_NAME, ImmutableList.of(SIG2));
@@ -136,6 +161,14 @@ public void testOneOfSignatures_succeedsIfPackageNameAndSignaturesMatch()
136161

137162
@Test
138163
public void testOneOfSignature_failsIfAllSignaturesDoNotMatch() throws Exception {
164+
PackageInfo info =
165+
newBuilder()
166+
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
167+
.setSignatures(SIG1)
168+
.build();
169+
170+
installPackages(OTHER_UID_SAME_SIGNATURE, info);
171+
139172
policy =
140173
SecurityPolicies.oneOfSignatures(
141174
packageManager,
@@ -150,11 +183,14 @@ public void testOneOfSignature_failsIfAllSignaturesDoNotMatch() throws Exception
150183
@Test
151184
public void testOneOfSignature_succeedsIfPackageNameAndOneOfSignaturesMatch()
152185
throws Exception {
186+
PackageInfo info =
187+
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
188+
189+
installPackages(OTHER_UID, info);
190+
153191
policy =
154192
SecurityPolicies.oneOfSignatures(
155-
packageManager,
156-
OTHER_UID_PACKAGE_NAME,
157-
ImmutableList.of(SIG1, SIG2));
193+
packageManager, OTHER_UID_PACKAGE_NAME, ImmutableList.of(SIG1, SIG2));
158194

159195
// THEN UID for package that has SIG2 will be authorized
160196
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
@@ -164,11 +200,170 @@ public void testOneOfSignature_succeedsIfPackageNameAndOneOfSignaturesMatch()
164200
public void testHasSignature_failsIfUidUnknown() throws Exception {
165201
policy =
166202
SecurityPolicies.hasSignature(
167-
packageManager,
168-
appContext.getPackageName(),
169-
SIG1);
203+
packageManager,
204+
appContext.getPackageName(),
205+
SIG1);
170206

171207
assertThat(policy.checkAuthorization(OTHER_UID_UNKNOWN).getCode())
172-
.isEqualTo(Status.UNAUTHENTICATED.getCode());
208+
.isEqualTo(Status.UNAUTHENTICATED.getCode());
209+
}
210+
211+
@Test
212+
public void testHasPermissions_sharedUserId_succeedsIfAllPackageHavePermissions()
213+
throws Exception {
214+
PackageInfo info =
215+
newBuilder()
216+
.setPackageName(OTHER_UID_PACKAGE_NAME)
217+
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
218+
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
219+
.build();
220+
221+
PackageInfo infoSamePerms =
222+
newBuilder()
223+
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
224+
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
225+
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
226+
.build();
227+
228+
installPackages(OTHER_UID, info, infoSamePerms);
229+
230+
policy =
231+
SecurityPolicies.hasPermissions(
232+
packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION));
233+
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
234+
}
235+
236+
@Test
237+
public void testHasPermissions_sharedUserId_failsIfOnePackageHasNoPermissions() throws Exception {
238+
PackageInfo info =
239+
newBuilder()
240+
.setPackageName(OTHER_UID_PACKAGE_NAME)
241+
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
242+
.build();
243+
244+
PackageInfo infoNoPerms =
245+
newBuilder()
246+
.setPackageName(OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME)
247+
.setPermission(ACCESS_FINE_LOCATION, 0)
248+
.build();
249+
250+
installPackages(OTHER_UID, info, infoNoPerms);
251+
252+
policy = SecurityPolicies.hasPermissions(packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION));
253+
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
254+
.isEqualTo(Status.PERMISSION_DENIED.getCode());
255+
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
256+
.contains("does not have permission");
257+
}
258+
259+
@Test
260+
public void testHasPermissions_succeedsIfPackageHasPermissions() throws Exception {
261+
PackageInfo info =
262+
newBuilder()
263+
.setPackageName(OTHER_UID_PACKAGE_NAME)
264+
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
265+
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
266+
.setPermission(WRITE_EXTERNAL_STORAGE, 0)
267+
.build();
268+
269+
installPackages(OTHER_UID, info);
270+
271+
policy =
272+
SecurityPolicies.hasPermissions(
273+
packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION));
274+
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
275+
}
276+
277+
@Test
278+
public void testHasPermissions_failsIfPackageDoesNotHaveOnePermission() throws Exception {
279+
PackageInfo info =
280+
newBuilder()
281+
.setPackageName(OTHER_UID_PACKAGE_NAME)
282+
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
283+
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
284+
.setPermission(WRITE_EXTERNAL_STORAGE, 0)
285+
.build();
286+
287+
installPackages(OTHER_UID, info);
288+
289+
policy =
290+
SecurityPolicies.hasPermissions(
291+
packageManager, ImmutableSet.of(ACCESS_FINE_LOCATION, WRITE_EXTERNAL_STORAGE));
292+
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
293+
.isEqualTo(Status.PERMISSION_DENIED.getCode());
294+
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
295+
.contains("does not have permission");
296+
}
297+
298+
@Test
299+
public void testHasPermissions_failsIfPackageDoesNotHavePermissions() throws Exception {
300+
PackageInfo info =
301+
newBuilder()
302+
.setPackageName(OTHER_UID_PACKAGE_NAME)
303+
.setPermission(ACCESS_FINE_LOCATION, REQUESTED_PERMISSION_GRANTED)
304+
.setPermission(ACCESS_COARSE_LOCATION, REQUESTED_PERMISSION_GRANTED)
305+
.setPermission(WRITE_EXTERNAL_STORAGE, 0)
306+
.build();
307+
308+
installPackages(OTHER_UID, info);
309+
310+
policy =
311+
SecurityPolicies.hasPermissions(packageManager, ImmutableSet.of(WRITE_EXTERNAL_STORAGE));
312+
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
313+
.isEqualTo(Status.PERMISSION_DENIED.getCode());
314+
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
315+
.contains("does not have permission");
316+
}
317+
318+
private static PackageInfoBuilder newBuilder() {
319+
return new PackageInfoBuilder();
320+
}
321+
322+
private static class PackageInfoBuilder {
323+
private String packageName;
324+
private Signature[] signatures;
325+
private final HashMap<String, Integer> permissions = new HashMap<>();
326+
327+
public PackageInfoBuilder setPackageName(String packageName) {
328+
this.packageName = packageName;
329+
return this;
330+
}
331+
332+
public PackageInfoBuilder setPermission(String permissionName, int permissionFlag) {
333+
this.permissions.put(permissionName, permissionFlag);
334+
return this;
335+
}
336+
337+
public PackageInfoBuilder setSignatures(Signature... signatures) {
338+
this.signatures = signatures;
339+
return this;
340+
}
341+
342+
public PackageInfo build() {
343+
checkState(this.packageName != null, "packageName is a mandatory field");
344+
345+
PackageInfo packageInfo = new PackageInfo();
346+
347+
packageInfo.packageName = this.packageName;
348+
349+
if (this.signatures != null) {
350+
packageInfo.signatures = this.signatures;
351+
}
352+
353+
if (!this.permissions.isEmpty()) {
354+
String[] requestedPermissions =
355+
this.permissions.keySet().toArray(new String[this.permissions.size()]);
356+
int[] requestedPermissionsFlags = new int[requestedPermissions.length];
357+
358+
for (int i = 0; i < requestedPermissions.length; i++) {
359+
requestedPermissionsFlags[i] = this.permissions.get(requestedPermissions[i]);
360+
}
361+
362+
packageInfo.requestedPermissions = requestedPermissions;
363+
packageInfo.requestedPermissionsFlags = requestedPermissionsFlags;
364+
}
365+
366+
return packageInfo;
367+
}
173368
}
174369
}

0 commit comments

Comments
 (0)