Skip to content

Commit 58e38d0

Browse files
committed
binder: Cancel checkAuthorization() request if still pending upon termination (grpc#12167)
# Conflicts: # binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java # binder/src/main/java/io/grpc/binder/internal/BinderTransport.java # binder/src/testFixtures/java/io/grpc/binder/internal/SettableAsyncSecurityPolicy.java
1 parent 948a4ec commit 58e38d0

File tree

3 files changed

+80
-34
lines changed

3 files changed

+80
-34
lines changed

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

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717
package io.grpc.binder.internal;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static java.util.concurrent.TimeUnit.SECONDS;
2021

2122
import android.content.Context;
2223
import android.os.DeadObjectException;
2324
import android.os.Parcel;
2425
import android.os.RemoteException;
2526
import androidx.test.core.app.ApplicationProvider;
2627
import androidx.test.ext.junit.runners.AndroidJUnit4;
27-
import com.google.common.util.concurrent.Futures;
28-
import com.google.common.util.concurrent.ListenableFuture;
2928
import com.google.common.util.concurrent.SettableFuture;
3029
import com.google.errorprone.annotations.CanIgnoreReturnValue;
3130
import com.google.errorprone.annotations.concurrent.GuardedBy;
@@ -39,13 +38,13 @@
3938
import io.grpc.Status;
4039
import io.grpc.Status.Code;
4140
import io.grpc.binder.AndroidComponentAddress;
42-
import io.grpc.binder.AsyncSecurityPolicy;
4341
import io.grpc.binder.BinderServerBuilder;
4442
import io.grpc.binder.HostServices;
4543
import io.grpc.binder.SecurityPolicy;
4644
import io.grpc.binder.internal.OneWayBinderProxies.BlackHoleOneWayBinderProxy;
4745
import io.grpc.binder.internal.OneWayBinderProxies.BlockingBinderDecorator;
4846
import io.grpc.binder.internal.OneWayBinderProxies.ThrowingOneWayBinderProxy;
47+
import io.grpc.binder.internal.SettableAsyncSecurityPolicy.AuthRequest;
4948
import io.grpc.internal.ClientStream;
5049
import io.grpc.internal.ClientStreamListener;
5150
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
@@ -62,7 +61,6 @@
6261
import java.util.concurrent.ExecutorService;
6362
import java.util.concurrent.Executors;
6463
import java.util.concurrent.ScheduledExecutorService;
65-
import java.util.concurrent.TimeUnit;
6664
import javax.annotation.Nullable;
6765
import org.junit.After;
6866
import org.junit.Before;
@@ -101,7 +99,7 @@ public final class BinderClientTransportTest {
10199

102100
AndroidComponentAddress serverAddress;
103101
BinderTransport.BinderClientTransport transport;
104-
SettableAsyncSecurityPolicy blockingSecurityPolicy = new SettableAsyncSecurityPolicy();
102+
BlockingSecurityPolicy blockingSecurityPolicy = new BlockingSecurityPolicy();
105103

106104
private final ObjectPool<ScheduledExecutorService> executorServicePool =
107105
new FixedObjectPool<>(Executors.newScheduledThreadPool(1));
@@ -172,6 +170,7 @@ public BinderClientTransportBuilder setReadyTimeoutMillis(int timeoutMillis) {
172170
return this;
173171
}
174172

173+
@CanIgnoreReturnValue
175174
public BinderClientTransportBuilder setPreAuthorizeServer(boolean preAuthorizeServer) {
176175
factoryBuilder.setPreAuthorizeServers(preAuthorizeServer);
177176
return this;
@@ -196,7 +195,7 @@ public void tearDown() throws Exception {
196195
private static void shutdownAndTerminate(ExecutorService executorService)
197196
throws InterruptedException {
198197
executorService.shutdownNow();
199-
if (!executorService.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
198+
if (!executorService.awaitTermination(TIMEOUT_SECONDS, SECONDS)) {
200199
throw new AssertionError("executor failed to terminate promptly");
201200
}
202201
}
@@ -292,16 +291,16 @@ public void testMessageProducerClosedAfterStream_b169313545() throws Exception {
292291
@Test
293292
public void testNewStreamBeforeTransportReadyFails() throws Exception {
294293
// Use a special SecurityPolicy that lets us act before the transport is setup/ready.
295-
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
296-
transport = new BinderClientTransportBuilder().setSecurityPolicy(securityPolicy).build();
294+
transport =
295+
new BinderClientTransportBuilder().setSecurityPolicy(blockingSecurityPolicy).build();
297296
transport.start(transportListener).run();
298297
ClientStream stream =
299298
transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT, tracers);
300299
stream.start(streamListener);
301300
assertThat(streamListener.awaitClose().getCode()).isEqualTo(Code.INTERNAL);
302301

303302
// Unblock the SETUP_TRANSPORT handshake and make sure it becomes ready in the usual way.
304-
securityPolicy.setAuthorizationResult(Status.OK);
303+
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
305304
transportListener.awaitReady();
306305
}
307306

@@ -380,15 +379,21 @@ public void testBlackHoleEndpointConnectTimeout() throws Exception {
380379
public void testBlackHoleSecurityPolicyAuthTimeout() throws Exception {
381380
transport =
382381
new BinderClientTransportBuilder()
383-
.setSecurityPolicy(blockingSecurityPolicy)
384382
.setPreAuthorizeServer(false)
383+
.setSecurityPolicy(blockingSecurityPolicy)
385384
.setReadyTimeoutMillis(1_234)
386385
.build();
387386
transport.start(transportListener).run();
387+
// Take the next authRequest but don't respond to it, in order to trigger the ready timeout.
388+
AuthRequest authRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
389+
388390
Status transportStatus = transportListener.awaitShutdown();
389391
assertThat(transportStatus.getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
390392
assertThat(transportStatus.getDescription()).contains("1234");
391393
transportListener.awaitTermination();
394+
395+
// If the transport gave up waiting on auth, it should cancel its request.
396+
assertThat(authRequest.isCancelled()).isTrue();
392397
}
393398

394399
@Test
@@ -432,8 +437,8 @@ public void testAsyncSecurityPolicyPreAuthFailure() throws Exception {
432437
.setSecurityPolicy(securityPolicy)
433438
.build();
434439
RuntimeException exception = new NullPointerException();
435-
securityPolicy.setAuthorizationException(exception);
436440
transport.start(transportListener).run();
441+
securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS).setResult(exception);
437442
Status transportStatus = transportListener.awaitShutdown();
438443
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
439444
assertThat(transportStatus.getCause()).isEqualTo(exception);
@@ -466,12 +471,45 @@ public void testAsyncSecurityPolicyPreAuthSuccess() throws Exception {
466471
.build();
467472
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED.withDescription("xyzzy"));
468473
transport.start(transportListener).run();
474+
securityPolicy
475+
.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS)
476+
.setResult(Status.PERMISSION_DENIED);
469477
Status transportStatus = transportListener.awaitShutdown();
470478
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
471479
assertThat(transportStatus.getDescription()).contains("xyzzy");
472480
transportListener.awaitTermination();
473481
}
474482

483+
@Test
484+
public void testAsyncSecurityPolicyAuthCancelledUponExternalTermination() throws Exception {
485+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
486+
transport = new BinderClientTransportBuilder()
487+
.setSecurityPolicy(securityPolicy)
488+
.setPreAuthorizeServer(false)
489+
.build();
490+
transport.start(transportListener).run();
491+
AuthRequest authRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
492+
transport.shutdownNow(Status.UNAVAILABLE); // 'authRequest' remains unanswered!
493+
transportListener.awaitShutdown();
494+
transportListener.awaitTermination();
495+
assertThat(authRequest.isCancelled()).isTrue();
496+
}
497+
498+
@Test
499+
public void testAsyncSecurityPolicyPreAuthCancelledUponExternalTermination() throws Exception {
500+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
501+
transport = new BinderClientTransportBuilder()
502+
.setSecurityPolicy(securityPolicy)
503+
.setPreAuthorizeServer(true)
504+
.build();
505+
transport.start(transportListener).run();
506+
AuthRequest preAuthRequest = securityPolicy.takeNextAuthRequest(TIMEOUT_SECONDS, SECONDS);
507+
transport.shutdownNow(Status.UNAVAILABLE); // 'authRequest' remains unanswered!
508+
transportListener.awaitShutdown();
509+
transportListener.awaitTermination();
510+
assertThat(preAuthRequest.isCancelled()).isTrue();
511+
}
512+
475513
private static void startAndAwaitReady(
476514
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
477515
throws Exception {
@@ -493,7 +531,7 @@ public void transportShutdown(Status shutdownStatus) {
493531
}
494532

495533
public Status awaitShutdown() throws Exception {
496-
return shutdownStatus.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
534+
return shutdownStatus.get(TIMEOUT_SECONDS, SECONDS);
497535
}
498536

499537
@Override
@@ -504,7 +542,7 @@ public void transportTerminated() {
504542
}
505543

506544
public void awaitTermination() throws Exception {
507-
isTerminated.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
545+
isTerminated.get(TIMEOUT_SECONDS, SECONDS);
508546
}
509547

510548
@Override
@@ -515,7 +553,7 @@ public void transportReady() {
515553
}
516554

517555
public void awaitReady() throws Exception {
518-
isReady.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
556+
isReady.get(TIMEOUT_SECONDS, SECONDS);
519557
}
520558

521559
@Override
@@ -612,24 +650,23 @@ public synchronized void closed(Status status, RpcProgress rpcProgress, Metadata
612650
}
613651
}
614652

615-
/** An AsyncSecurityPolicy that lets a test specify the outcome of checkAuthorizationAsync(). */
616-
static class SettableAsyncSecurityPolicy extends AsyncSecurityPolicy {
617-
private SettableFuture<Status> result = SettableFuture.create();
653+
/**
654+
* A SecurityPolicy that blocks the transport authorization check until a test sets the outcome.
655+
*/
656+
static class BlockingSecurityPolicy extends SecurityPolicy {
657+
private final BlockingQueue<Status> results = new LinkedBlockingQueue<>();
618658

619-
public void clearAuthorizationResult() {
620-
result = SettableFuture.create();
659+
public void provideNextCheckAuthorizationResult(Status status) {
660+
results.add(status);
621661
}
622662

623-
public boolean setAuthorizationResult(Status status) {
624-
return result.set(status);
625-
}
626-
627-
public boolean setAuthorizationException(Throwable t) {
628-
return result.setException(t);
629-
}
630-
631-
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
632-
return Futures.nonCancellationPropagating(result);
663+
@Override
664+
public Status checkAuthorization(int uid) {
665+
try {
666+
return results.take();
667+
} catch (InterruptedException e) {
668+
return Status.fromThrowable(e);
669+
}
633670
}
634671
}
635672
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,8 @@ public static final class BinderClientTransport extends BinderTransport
585585

586586
@GuardedBy("this")
587587
private ScheduledFuture<?> readyTimeoutFuture; // != null iff timeout scheduled.
588-
588+
@GuardedBy("this")
589+
@Nullable private ListenableFuture<Status> authResultFuture; // null before we check auth.
589590
@GuardedBy("this")
590591
private ListenableFuture<Status> preAuthResultFuture;
591592

@@ -813,7 +814,9 @@ void notifyTerminated() {
813814
}
814815
if (preAuthResultFuture != null) {
815816
preAuthResultFuture.cancel(false);
816-
preAuthResultFuture = null;
817+
}
818+
if (authResultFuture != null) {
819+
authResultFuture.cancel(false); // No effect if already complete.
817820
}
818821
serviceBinding.unbind();
819822
clientTransportListener.transportTerminated();
@@ -834,9 +837,10 @@ protected void handleSetupTransport(Parcel parcel) {
834837
shutdownInternal(
835838
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
836839
} else {
840+
authResultFuture = checkServerAuthorizationAsync(remoteUid);
837841
Futures.addCallback(
838-
checkServerAuthorizationAsync(remoteUid),
839-
new FutureCallback<Status>() {
842+
authResultFuture,
843+
new FutureCallback<Status>() {
840844
@Override
841845
public void onSuccess(Status result) {
842846
handleAuthResult(binder, result);

binder/src/testFixtures/java/io/grpc/binder/internal/SettableAsyncSecurityPolicy.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,10 @@ public void setResult(Status result) {
7474
public void setResult(Throwable t) {
7575
checkState(resultFuture.setException(t));
7676
}
77+
78+
/** Tests if the future returned for this authorization request was cancelled by the caller. */
79+
public boolean isCancelled() {
80+
return resultFuture.isCancelled();
81+
}
7782
}
78-
}
83+
}

0 commit comments

Comments
 (0)