Skip to content

Commit 1a59ce3

Browse files
committed
Merge remote-tracking branch 'upstream/master' into rlseagerinit
2 parents 8fb30c9 + 94532a6 commit 1a59ce3

File tree

28 files changed

+919
-87
lines changed

28 files changed

+919
-87
lines changed

binder/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ dependencies {
7272
androidTestImplementation testFixtures(project(':grpc-core'))
7373

7474
testFixturesImplementation libraries.guava.testlib
75+
testFixturesImplementation testFixtures(project(':grpc-core'))
7576
}
7677

7778
import net.ltgt.gradle.errorprone.CheckSeverity

binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ public BinderClientTransportBuilder setReadyTimeoutMillis(int timeoutMillis) {
172172
return this;
173173
}
174174

175+
@CanIgnoreReturnValue
176+
public BinderClientTransportBuilder setPreAuthorizeServer(boolean preAuthorizeServer) {
177+
factoryBuilder.setPreAuthorizeServers(preAuthorizeServer);
178+
return this;
179+
}
180+
175181
public BinderTransport.BinderClientTransport build() {
176182
return factoryBuilder
177183
.buildClientTransportFactory()
@@ -372,11 +378,12 @@ public void testBlackHoleEndpointConnectTimeout() throws Exception {
372378
}
373379

374380
@Test
375-
public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
381+
public void testBlackHoleSecurityPolicyAuthTimeout() throws Exception {
376382
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
377383
transport =
378384
new BinderClientTransportBuilder()
379385
.setSecurityPolicy(securityPolicy)
386+
.setPreAuthorizeServer(false)
380387
.setReadyTimeoutMillis(1_234)
381388
.build();
382389
transport.start(transportListener).run();
@@ -387,15 +394,39 @@ public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
387394
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
388395
assertThat(transportStatus.getDescription()).contains("1234");
389396
transportListener.awaitTermination();
390-
391397
// If the transport gave up waiting on auth, it should cancel its request.
392398
assertThat(authRequest.isCancelled()).isTrue();
393399
}
394400

395401
@Test
396-
public void testAsyncSecurityPolicyFailure() throws Exception {
402+
public void testBlackHoleSecurityPolicyPreAuthTimeout() throws Exception {
397403
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
398-
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
404+
transport =
405+
new BinderClientTransportBuilder()
406+
.setSecurityPolicy(securityPolicy)
407+
.setPreAuthorizeServer(true)
408+
.setReadyTimeoutMillis(1_234)
409+
.build();
410+
transport.start(transportListener).run();
411+
// Take the next authRequest but don't respond to it, in order to trigger the ready timeout.
412+
AuthRequest preAuthRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
413+
414+
Status transportStatus = transportListener.awaitShutdown();
415+
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
416+
assertThat(transportStatus.getDescription()).contains("1234");
417+
transportListener.awaitTermination();
418+
// If the transport gave up waiting on auth, it should cancel its request.
419+
assertThat(preAuthRequest.isCancelled()).isTrue();
420+
}
421+
422+
@Test
423+
public void testAsyncSecurityPolicyAuthFailure() throws Exception {
424+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
425+
transport =
426+
new BinderClientTransportBuilder()
427+
.setPreAuthorizeServer(false)
428+
.setSecurityPolicy(securityPolicy)
429+
.build();
399430
RuntimeException exception = new NullPointerException();
400431
transport.start(transportListener).run();
401432
securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS).setResult(exception);
@@ -406,15 +437,55 @@ public void testAsyncSecurityPolicyFailure() throws Exception {
406437
}
407438

408439
@Test
409-
public void testAsyncSecurityPolicySuccess() throws Exception {
440+
public void testAsyncSecurityPolicyPreAuthFailure() throws Exception {
410441
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
411-
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
442+
transport =
443+
new BinderClientTransportBuilder()
444+
.setPreAuthorizeServer(true)
445+
.setSecurityPolicy(securityPolicy)
446+
.build();
447+
RuntimeException exception = new NullPointerException();
448+
transport.start(transportListener).run();
449+
securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS).setResult(exception);
450+
Status transportStatus = transportListener.awaitShutdown();
451+
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
452+
assertThat(transportStatus.getCause()).isEqualTo(exception);
453+
transportListener.awaitTermination();
454+
}
455+
456+
@Test
457+
public void testAsyncSecurityPolicyAuthSuccess() throws Exception {
458+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
459+
transport =
460+
new BinderClientTransportBuilder()
461+
.setPreAuthorizeServer(false)
462+
.setSecurityPolicy(securityPolicy)
463+
.build();
464+
transport.start(transportListener).run();
465+
securityPolicy
466+
.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS)
467+
.setResult(Status.PERMISSION_DENIED.withDescription("xyzzy"));
468+
Status transportStatus = transportListener.awaitShutdown();
469+
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
470+
assertThat(transportStatus.getDescription()).contains("xyzzy");
471+
transportListener.awaitTermination();
472+
}
473+
474+
@Test
475+
public void testAsyncSecurityPolicyPreAuthSuccess() throws Exception {
476+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
477+
transport =
478+
new BinderClientTransportBuilder()
479+
.setPreAuthorizeServer(true)
480+
.setSecurityPolicy(securityPolicy)
481+
.build();
412482
transport.start(transportListener).run();
413483
securityPolicy
414484
.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS)
415-
.setResult(Status.PERMISSION_DENIED);
485+
.setResult(Status.PERMISSION_DENIED.withDescription("xyzzy"));
416486
Status transportStatus = transportListener.awaitShutdown();
417487
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
488+
assertThat(transportStatus.getDescription()).contains("xyzzy");
418489
transportListener.awaitTermination();
419490
}
420491

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public final class AndroidComponentAddress extends SocketAddress {
5858
@Nullable
5959
private final UserHandle targetUser; // null means the same user that hosts this process.
6060

61-
protected AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
61+
private AndroidComponentAddress(Intent bindIntent, @Nullable UserHandle targetUser) {
6262
checkArgument(
6363
bindIntent.getComponent() != null || bindIntent.getPackage() != null,
6464
"'bindIntent' must be explicit. Specify either a package or ComponentName.");
@@ -250,7 +250,22 @@ public Builder setBindIntentFromComponent(ComponentName component) {
250250
return this;
251251
}
252252

253-
/** See {@link AndroidComponentAddress#getTargetUser()}. */
253+
/**
254+
* Specifies the Android user in which the built Address' bind Intent will be evaluated.
255+
*
256+
* <p>Connecting to a server in a different Android user is uncommon and requires the client app
257+
* have runtime visibility of &#064;SystemApi's and hold certain &#064;SystemApi permissions.
258+
* The device must also be running Android SDK version 30 or higher.
259+
*
260+
* <p>See https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces
261+
* for details on which apps can call the underlying &#064;SystemApi's needed to make this type
262+
* of connection.
263+
*
264+
* <p>One of the "android.permission.INTERACT_ACROSS_XXX" permissions is required. The exact one
265+
* depends on the calling user's relationship to the target user, whether client and server are
266+
* in the same or different apps, and the version of Android in use. See {@link
267+
* Context#bindServiceAsUser}, the essential underlying Android API, for details.
268+
*/
254269
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
255270
public Builder setTargetUser(@Nullable UserHandle targetUser) {
256271
this.targetUser = targetUser;

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import android.content.Intent;
2020
import android.os.UserHandle;
21+
import io.grpc.Attributes;
22+
import io.grpc.EquivalentAddressGroup;
2123
import io.grpc.ExperimentalApi;
2224
import io.grpc.NameResolver;
2325

@@ -35,12 +37,27 @@ private ApiConstants() {}
3537
/**
3638
* Specifies the Android user in which target URIs should be resolved.
3739
*
38-
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all
39-
* {@link io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let
40-
* clients address servers in another Android user using this argument.
40+
* <p>{@link UserHandle} can't reasonably be encoded in a target URI string. Instead, all {@link
41+
* io.grpc.NameResolverProvider}s producing {@link AndroidComponentAddress}es should let clients
42+
* address servers in another Android user using this argument.
4143
*
42-
* <p>See also {@link AndroidComponentAddress#getTargetUser()}.
44+
* <p>Connecting to a server in a different Android user is uncommon and can only be done by a
45+
* "system app" client with special permissions. See {@link
46+
* AndroidComponentAddress.Builder#setTargetUser(UserHandle)} for details.
4347
*/
48+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
4449
public static final NameResolver.Args.Key<UserHandle> TARGET_ANDROID_USER =
4550
NameResolver.Args.Key.create("target-android-user");
51+
52+
/**
53+
* Lets you override a Channel's pre-auth configuration (see {@link
54+
* BinderChannelBuilder#preAuthorizeServers(boolean)}) for a given {@link EquivalentAddressGroup}.
55+
*
56+
* <p>A {@link NameResolver} that discovers servers from an untrusted source like PackageManager
57+
* can use this to force server pre-auth and prevent abuse.
58+
*/
59+
@EquivalentAddressGroup.Attr
60+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12191")
61+
public static final Attributes.Key<Boolean> PRE_AUTH_SERVER_OVERRIDE =
62+
Attributes.Key.create("pre-auth-server-override");
4663
}

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

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ public BinderChannelBuilder securityPolicy(SecurityPolicy securityPolicy) {
242242
* specify a {@link UserHandle}. If neither the Channel nor the {@link AndroidComponentAddress}
243243
* specifies a target user, the {@link UserHandle} of the current process will be used.
244244
*
245-
* <p>Targeting a Service in a different Android user is uncommon and requires special permissions
246-
* normally reserved for system apps. See {@link android.content.Context#bindServiceAsUser} for
247-
* details.
245+
* <p>Connecting to a server in a different Android user is uncommon and can only be done by a
246+
* "system app" client with special permissions. See {@link
247+
* AndroidComponentAddress.Builder#setTargetUser(UserHandle)} for details.
248248
*
249249
* @deprecated This method's name is misleading because it implies an impersonated client identity
250250
* when it's actually specifying part of the server's location. It's also no longer necessary
@@ -279,6 +279,35 @@ public BinderChannelBuilder strictLifecycleManagement() {
279279
return this;
280280
}
281281

282+
/**
283+
* Checks servers against this Channel's {@link SecurityPolicy} *before* binding.
284+
*
285+
* <p>Android users can be tricked into installing a malicious app with the same package name as a
286+
* legitimate server. That's why we don't send calls to a server until it has been authorized by
287+
* an appropriate {@link SecurityPolicy}. But merely binding to a malicious server can enable
288+
* "keep-alive" and "background activity launch" abuse, even if it's ultimately unauthorized.
289+
* Pre-authorization mitigates these threats by performing a preliminary {@link SecurityPolicy}
290+
* check against a server app's PackageManager-registered identity without actually creating an
291+
* instance of it. This is especially important for security when the server's direct address
292+
* isn't known in advance but rather resolved via target URI or discovered by other means.
293+
*
294+
* <p>Note that, unlike ordinary authorization, pre-authorization is performed against the server
295+
* app's UID, not the UID of the process hosting the bound Service. These can be different, most
296+
* commonly due to services that set `android:isolatedProcess=true`.
297+
*
298+
* <p>Pre-authorization is strongly recommended but it remains optional for now because of this
299+
* behavior change and the small performance cost.
300+
*
301+
* <p>The default value of this property is false but it will become true in a future release.
302+
* Clients that require a particular behavior should configure it explicitly using this method
303+
* rather than relying on the default.
304+
*/
305+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12191")
306+
public BinderChannelBuilder preAuthorizeServers(boolean preAuthorize) {
307+
transportFactoryBuilder.setPreAuthorizeServers(preAuthorize);
308+
return this;
309+
}
310+
282311
@Override
283312
public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) {
284313
checkState(

binder/src/main/java/io/grpc/binder/internal/Bindable.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package io.grpc.binder.internal;
1818

19+
import android.content.pm.ServiceInfo;
1920
import android.os.IBinder;
2021
import androidx.annotation.AnyThread;
2122
import androidx.annotation.MainThread;
2223
import io.grpc.Status;
24+
import io.grpc.StatusException;
2325

2426
/** An interface for managing a {@code Binder} connection. */
2527
interface Bindable {
@@ -45,6 +47,19 @@ interface Observer {
4547
void onUnbound(Status reason);
4648
}
4749

50+
/**
51+
* Fetches details about the remote Service from PackageManager without binding to it.
52+
*
53+
* <p>Resolving an untrusted address before binding to it lets you screen out problematic servers
54+
* before giving them a chance to run. However, note that the identity/existence of the resolved
55+
* Service can change between the time this method returns and the time you actually bind/connect
56+
* to it. For example, suppose the target package gets uninstalled or upgraded right after this
57+
* method returns. In {@link Observer#onBound}, you should verify that the server you resolved is
58+
* the same one you connected to.
59+
*/
60+
@AnyThread
61+
ServiceInfo resolve() throws StatusException;
62+
4863
/**
4964
* Attempt to bind with the remote service.
5065
*

binder/src/main/java/io/grpc/binder/internal/BinderClientTransportFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public final class BinderClientTransportFactory implements ClientTransportFactor
5555
final InboundParcelablePolicy inboundParcelablePolicy;
5656
final OneWayBinderProxy.Decorator binderDecorator;
5757
final long readyTimeoutMillis;
58+
final boolean preAuthorizeServers; // TODO(jdcormie): Default to true.
5859

5960
ScheduledExecutorService executorService;
6061
Executor offloadExecutor;
@@ -75,6 +76,7 @@ private BinderClientTransportFactory(Builder builder) {
7576
inboundParcelablePolicy = checkNotNull(builder.inboundParcelablePolicy);
7677
binderDecorator = checkNotNull(builder.binderDecorator);
7778
readyTimeoutMillis = builder.readyTimeoutMillis;
79+
preAuthorizeServers = builder.preAuthorizeServers;
7880

7981
executorService = scheduledExecutorPool.getObject();
8082
offloadExecutor = offloadExecutorPool.getObject();
@@ -128,6 +130,7 @@ public static final class Builder implements ClientTransportFactoryBuilder {
128130
InboundParcelablePolicy inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT;
129131
OneWayBinderProxy.Decorator binderDecorator = OneWayBinderProxy.IDENTITY_DECORATOR;
130132
long readyTimeoutMillis = 60_000;
133+
boolean preAuthorizeServers;
131134

132135
@Override
133136
public BinderClientTransportFactory buildClientTransportFactory() {
@@ -216,5 +219,11 @@ public Builder setReadyTimeoutMillis(long readyTimeoutMillis) {
216219
this.readyTimeoutMillis = readyTimeoutMillis;
217220
return this;
218221
}
222+
223+
/** Whether to check server addresses against the SecurityPolicy *before* binding to them. */
224+
public Builder setPreAuthorizeServers(boolean preAuthorizeServers) {
225+
this.preAuthorizeServers = preAuthorizeServers;
226+
return this;
227+
}
219228
}
220229
}

0 commit comments

Comments
 (0)