Skip to content

Commit 454eebb

Browse files
committed
Merge branch 'keep-alive' of https://github.com/jdcormie/grpc-java into keep-alive
2 parents efe9ccc + 9ec685b commit 454eebb

8 files changed

Lines changed: 256 additions & 40 deletions

File tree

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

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,8 @@
5858
import java.io.InputStream;
5959
import java.util.ArrayDeque;
6060
import java.util.Deque;
61-
import java.util.concurrent.BlockingQueue;
6261
import java.util.concurrent.ExecutorService;
6362
import java.util.concurrent.Executors;
64-
import java.util.concurrent.LinkedBlockingQueue;
6563
import java.util.concurrent.ScheduledExecutorService;
6664
import java.util.concurrent.TimeUnit;
6765
import javax.annotation.Nullable;
@@ -102,7 +100,7 @@ public final class BinderClientTransportTest {
102100

103101
AndroidComponentAddress serverAddress;
104102
BinderTransport.BinderClientTransport transport;
105-
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
103+
SettableAsyncSecurityPolicy blockingSecurityPolicy = new SettableAsyncSecurityPolicy();
106104

107105
private final ObjectPool<ScheduledExecutorService> executorServicePool =
108106
new FixedObjectPool<>(Executors.newScheduledThreadPool(1));
@@ -170,6 +168,11 @@ public BinderClientTransportBuilder setReadyTimeoutMillis(int timeoutMillis) {
170168
return this;
171169
}
172170

171+
public BinderClientTransportBuilder setPreAuthorizeServer(boolean preAuthorizeServer) {
172+
factoryBuilder.setPreAuthorizeServers(preAuthorizeServer);
173+
return this;
174+
}
175+
173176
public BinderTransport.BinderClientTransport build() {
174177
return factoryBuilder
175178
.buildClientTransportFactory()
@@ -179,7 +182,7 @@ public BinderTransport.BinderClientTransport build() {
179182

180183
@After
181184
public void tearDown() throws Exception {
182-
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.ABORTED);
185+
blockingSecurityPolicy.setAuthorizationResult(Status.ABORTED);
183186
transport.shutdownNow(Status.OK);
184187
HostServices.awaitServiceShutdown();
185188
shutdownAndTerminate(executorServicePool.getObject());
@@ -285,16 +288,16 @@ public void testMessageProducerClosedAfterStream_b169313545() throws Exception {
285288
@Test
286289
public void testNewStreamBeforeTransportReadyFails() throws Exception {
287290
// Use a special SecurityPolicy that lets us act before the transport is setup/ready.
288-
transport =
289-
new BinderClientTransportBuilder().setSecurityPolicy(blockingSecurityPolicy).build();
291+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
292+
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
290293
transport.start(transportListener).run();
291294
ClientStream stream =
292295
transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT, tracers);
293296
stream.start(streamListener);
294297
assertThat(streamListener.awaitClose().getCode()).isEqualTo(Code.INTERNAL);
295298

296299
// Unblock the SETUP_TRANSPORT handshake and make sure it becomes ready in the usual way.
297-
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
300+
securityPolicy.setAuthorizationResult(Status.OK);
298301
transportListener.awaitReady();
299302
}
300303

@@ -370,24 +373,60 @@ public void testBlackHoleEndpointConnectTimeout() throws Exception {
370373
}
371374

372375
@Test
373-
public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
376+
public void testBlackHoleSecurityPolicyAuthTimeout() throws Exception {
377+
transport =
378+
new BinderClientTransportBuilder()
379+
.setSecurityPolicy(blockingSecurityPolicy)
380+
.setPreAuthorizeServer(false)
381+
.setReadyTimeoutMillis(1_234)
382+
.build();
383+
transport.start(transportListener).run();
384+
Status transportStatus = transportListener.awaitShutdown();
385+
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
386+
assertThat(transportStatus.getDescription()).contains("1234");
387+
transportListener.awaitTermination();
388+
}
389+
390+
@Test
391+
public void testBlackHoleSecurityPolicyPreAuthTimeout() throws Exception {
374392
transport =
375393
new BinderClientTransportBuilder()
376394
.setSecurityPolicy(blockingSecurityPolicy)
395+
.setPreAuthorizeServer(true)
377396
.setReadyTimeoutMillis(1_234)
378397
.build();
379398
transport.start(transportListener).run();
380399
Status transportStatus = transportListener.awaitShutdown();
381400
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
382401
assertThat(transportStatus.getDescription()).contains("1234");
383402
transportListener.awaitTermination();
384-
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
385403
}
386404

387405
@Test
388-
public void testAsyncSecurityPolicyFailure() throws Exception {
406+
public void testAsyncSecurityPolicyAuthFailure() throws Exception {
389407
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
390-
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
408+
transport =
409+
new BinderClientTransportBuilder()
410+
.setPreAuthorizeServer(false)
411+
.setSecurityPolicy(securityPolicy)
412+
.build();
413+
RuntimeException exception = new NullPointerException();
414+
securityPolicy.setAuthorizationException(exception);
415+
transport.start(transportListener).run();
416+
Status transportStatus = transportListener.awaitShutdown();
417+
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
418+
assertThat(transportStatus.getCause()).isEqualTo(exception);
419+
transportListener.awaitTermination();
420+
}
421+
422+
@Test
423+
public void testAsyncSecurityPolicyPreAuthFailure() throws Exception {
424+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
425+
transport =
426+
new BinderClientTransportBuilder()
427+
.setPreAuthorizeServer(true)
428+
.setSecurityPolicy(securityPolicy)
429+
.build();
391430
RuntimeException exception = new NullPointerException();
392431
securityPolicy.setAuthorizationException(exception);
393432
transport.start(transportListener).run();
@@ -400,11 +439,32 @@ public void testAsyncSecurityPolicyFailure() throws Exception {
400439
@Test
401440
public void testAsyncSecurityPolicySuccess() throws Exception {
402441
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
403-
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
404-
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED);
442+
transport =
443+
new BinderClientTransportBuilder()
444+
.setPreAuthorizeServer(false)
445+
.setSecurityPolicy(securityPolicy)
446+
.build();
447+
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED.withDescription("xyzzy"));
448+
transport.start(transportListener).run();
449+
Status transportStatus = transportListener.awaitShutdown();
450+
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
451+
assertThat(transportStatus.getDescription()).contains("xyzzy");
452+
transportListener.awaitTermination();
453+
}
454+
455+
@Test
456+
public void testAsyncSecurityPolicyPreAuthSuccess() throws Exception {
457+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
458+
transport =
459+
new BinderClientTransportBuilder()
460+
.setPreAuthorizeServer(true)
461+
.setSecurityPolicy(securityPolicy)
462+
.build();
463+
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED.withDescription("xyzzy"));
405464
transport.start(transportListener).run();
406465
Status transportStatus = transportListener.awaitShutdown();
407466
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
467+
assertThat(transportStatus.getDescription()).contains("xyzzy");
408468
transportListener.awaitTermination();
409469
}
410470

@@ -548,26 +608,6 @@ public synchronized void closed(Status status, RpcProgress rpcProgress, Metadata
548608
}
549609
}
550610

551-
/**
552-
* A SecurityPolicy that blocks the transport authorization check until a test sets the outcome.
553-
*/
554-
static class BlockingSecurityPolicy extends SecurityPolicy {
555-
private final BlockingQueue<Status> results = new LinkedBlockingQueue<>();
556-
557-
public void provideNextCheckAuthorizationResult(Status status) {
558-
results.add(status);
559-
}
560-
561-
@Override
562-
public Status checkAuthorization(int uid) {
563-
try {
564-
return results.take();
565-
} catch (InterruptedException e) {
566-
return Status.fromThrowable(e);
567-
}
568-
}
569-
}
570-
571611
/** An AsyncSecurityPolicy that lets a test specify the outcome of checkAuthorizationAsync(). */
572612
static class SettableAsyncSecurityPolicy extends AsyncSecurityPolicy {
573613
private SettableFuture<Status> result = SettableFuture.create();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package io.grpc.binder;
1818

1919
import android.content.Intent;
20+
import android.content.pm.ServiceInfo;
2021
import android.os.UserHandle;
22+
import io.grpc.Attributes;
2123
import io.grpc.ExperimentalApi;
2224
import io.grpc.NameResolver;
2325

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,13 @@ private BinderChannelBuilder(
176176
managedChannelImplBuilder =
177177
new ManagedChannelImplBuilder(
178178
directAddress, directAddress.getAuthority(), transportFactoryBuilder, null);
179+
// TODO(jdcormie): Rollout step 2: Pre-auth *all* addresses by default.
180+
preAuthorizeServers(false);
179181
} else {
180182
managedChannelImplBuilder =
181183
new ManagedChannelImplBuilder(target, transportFactoryBuilder, null);
184+
// TODO(jdcormie): Rollout step 1: Pre-auth NameResolver results by default.
185+
preAuthorizeServers(false);
182186
}
183187
idleTimeout(60, TimeUnit.SECONDS);
184188
}
@@ -279,6 +283,33 @@ public BinderChannelBuilder strictLifecycleManagement() {
279283
return this;
280284
}
281285

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

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
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;
23+
import com.google.common.util.concurrent.ListenableFuture;
2224
import io.grpc.Status;
25+
import io.grpc.StatusException;
2326

2427
/** An interface for managing a {@code Binder} connection. */
2528
interface Bindable {
@@ -45,6 +48,10 @@ interface Observer {
4548
void onUnbound(Status reason);
4649
}
4750

51+
/** Fetches details about the remote service from PackageManager *before* binding to it. */
52+
@AnyThread
53+
ServiceInfo resolve() throws StatusException;
54+
4855
/**
4956
* Attempt to bind with the remote service.
5057
*

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;
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)