Skip to content

Commit 7fee6a3

Browse files
authoredJun 10, 2024··
Don't block an offload thread when the client's SecurityPolicy is async (#11272)
#10566
1 parent 47249c5 commit 7fee6a3

File tree

2 files changed

+97
-32
lines changed

2 files changed

+97
-32
lines changed
 

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

+54
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import android.os.RemoteException;
2525
import androidx.test.core.app.ApplicationProvider;
2626
import androidx.test.ext.junit.runners.AndroidJUnit4;
27+
import com.google.common.util.concurrent.Futures;
28+
import com.google.common.util.concurrent.ListenableFuture;
2729
import com.google.common.util.concurrent.SettableFuture;
2830
import com.google.protobuf.Empty;
2931
import io.grpc.CallOptions;
@@ -35,6 +37,7 @@
3537
import io.grpc.Status;
3638
import io.grpc.Status.Code;
3739
import io.grpc.binder.AndroidComponentAddress;
40+
import io.grpc.binder.AsyncSecurityPolicy;
3841
import io.grpc.binder.BinderServerBuilder;
3942
import io.grpc.binder.HostServices;
4043
import io.grpc.binder.SecurityPolicy;
@@ -381,6 +384,34 @@ public void testBlackHoleSecurityPolicyConnectTimeout() throws Exception {
381384
blockingSecurityPolicy.provideNextCheckAuthorizationResult(Status.OK);
382385
}
383386

387+
@Test
388+
public void testAsyncSecurityPolicyFailure() throws Exception {
389+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
390+
transport = new BinderClientTransportBuilder()
391+
.setSecurityPolicy(securityPolicy)
392+
.build();
393+
RuntimeException exception = new NullPointerException();
394+
securityPolicy.setAuthorizationException(exception);
395+
transport.start(transportListener).run();
396+
Status transportStatus = transportListener.awaitShutdown();
397+
assertThat(transportStatus.getCode()).isEqualTo(Code.INTERNAL);
398+
assertThat(transportStatus.getCause()).isEqualTo(exception);
399+
transportListener.awaitTermination();
400+
}
401+
402+
@Test
403+
public void testAsyncSecurityPolicySuccess() throws Exception {
404+
SettableAsyncSecurityPolicy securityPolicy = new SettableAsyncSecurityPolicy();
405+
transport = new BinderClientTransportBuilder()
406+
.setSecurityPolicy(securityPolicy)
407+
.build();
408+
securityPolicy.setAuthorizationResult(Status.PERMISSION_DENIED);
409+
transport.start(transportListener).run();
410+
Status transportStatus = transportListener.awaitShutdown();
411+
assertThat(transportStatus.getCode()).isEqualTo(Code.PERMISSION_DENIED);
412+
transportListener.awaitTermination();
413+
}
414+
384415
private static void startAndAwaitReady(
385416
BinderTransport.BinderClientTransport transport, TestTransportListener transportListener)
386417
throws Exception {
@@ -540,4 +571,27 @@ public Status checkAuthorization(int uid) {
540571
}
541572
}
542573
}
574+
575+
/**
576+
* An AsyncSecurityPolicy that lets a test specify the outcome of checkAuthorizationAsync().
577+
*/
578+
static class SettableAsyncSecurityPolicy extends AsyncSecurityPolicy {
579+
private SettableFuture<Status> result = SettableFuture.create();
580+
581+
public void clearAuthorizationResult() {
582+
result = SettableFuture.create();
583+
}
584+
585+
public boolean setAuthorizationResult(Status status) {
586+
return result.set(status);
587+
}
588+
589+
public boolean setAuthorizationException(Throwable t) {
590+
return result.setException(t);
591+
}
592+
593+
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
594+
return Futures.nonCancellationPropagating(result);
595+
}
596+
}
543597
}

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

+43-32
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import com.google.common.annotations.VisibleForTesting;
3333
import com.google.common.base.Ticker;
3434
import com.google.common.base.Verify;
35+
import com.google.common.util.concurrent.FutureCallback;
36+
import com.google.common.util.concurrent.Futures;
3537
import com.google.common.util.concurrent.ListenableFuture;
3638
import io.grpc.Attributes;
3739
import io.grpc.CallOptions;
@@ -47,6 +49,7 @@
4749
import io.grpc.Status;
4850
import io.grpc.StatusException;
4951
import io.grpc.binder.AndroidComponentAddress;
52+
import io.grpc.binder.AsyncSecurityPolicy;
5053
import io.grpc.binder.InboundParcelablePolicy;
5154
import io.grpc.binder.SecurityPolicy;
5255
import io.grpc.internal.ClientStream;
@@ -743,8 +746,8 @@ void notifyTerminated() {
743746
@Override
744747
@GuardedBy("this")
745748
protected void handleSetupTransport(Parcel parcel) {
746-
// Add the remote uid to our attributes.
747-
attributes = setSecurityAttrs(attributes, Binder.getCallingUid());
749+
int remoteUid = Binder.getCallingUid();
750+
attributes = setSecurityAttrs(attributes, remoteUid);
748751
if (inState(TransportState.SETUP)) {
749752
int version = parcel.readInt();
750753
IBinder binder = parcel.readStrongBinder();
@@ -755,46 +758,54 @@ protected void handleSetupTransport(Parcel parcel) {
755758
shutdownInternal(
756759
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
757760
} else {
758-
offloadExecutor.execute(() -> checkSecurityPolicy(binder));
761+
ListenableFuture<Status> authFuture = (securityPolicy instanceof AsyncSecurityPolicy) ?
762+
((AsyncSecurityPolicy) securityPolicy).checkAuthorizationAsync(remoteUid) :
763+
Futures.submit(() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor);
764+
Futures.addCallback(
765+
authFuture,
766+
new FutureCallback<Status>() {
767+
@Override
768+
public void onSuccess(Status result) { handleAuthResult(binder, result); }
769+
770+
@Override
771+
public void onFailure(Throwable t) { handleAuthResult(t); }
772+
},
773+
offloadExecutor);
759774
}
760775
}
761776
}
762777

763-
private void checkSecurityPolicy(IBinder binder) {
764-
Status authorization;
765-
Integer remoteUid;
766-
synchronized (this) {
767-
remoteUid = attributes.get(REMOTE_UID);
768-
}
769-
if (remoteUid == null) {
770-
authorization = Status.UNAUTHENTICATED.withDescription("No remote UID available");
771-
} else {
772-
authorization = securityPolicy.checkAuthorization(remoteUid);
773-
}
774-
synchronized (this) {
775-
if (inState(TransportState.SETUP)) {
776-
if (!authorization.isOk()) {
777-
shutdownInternal(authorization, true);
778-
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
779-
shutdownInternal(
780-
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
781-
} else {
782-
// Check state again, since a failure inside setOutgoingBinder (or a callback it
783-
// triggers), could have shut us down.
784-
if (!isShutdown()) {
785-
setState(TransportState.READY);
786-
attributes = clientTransportListener.filterTransport(attributes);
787-
clientTransportListener.transportReady();
788-
if (readyTimeoutFuture != null) {
789-
readyTimeoutFuture.cancel(false);
790-
readyTimeoutFuture = null;
791-
}
778+
private synchronized void handleAuthResult(IBinder binder, Status authorization) {
779+
if (inState(TransportState.SETUP)) {
780+
if (!authorization.isOk()) {
781+
shutdownInternal(authorization, true);
782+
} else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) {
783+
shutdownInternal(
784+
Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true);
785+
} else {
786+
// Check state again, since a failure inside setOutgoingBinder (or a callback it
787+
// triggers), could have shut us down.
788+
if (!isShutdown()) {
789+
setState(TransportState.READY);
790+
attributes = clientTransportListener.filterTransport(attributes);
791+
clientTransportListener.transportReady();
792+
if (readyTimeoutFuture != null) {
793+
readyTimeoutFuture.cancel(false);
794+
readyTimeoutFuture = null;
792795
}
793796
}
794797
}
795798
}
796799
}
797800

801+
private synchronized void handleAuthResult(Throwable t) {
802+
shutdownInternal(
803+
Status.INTERNAL
804+
.withDescription("Could not evaluate SecurityPolicy")
805+
.withCause(t),
806+
true);
807+
}
808+
798809
@GuardedBy("this")
799810
@Override
800811
protected void handlePingResponse(Parcel parcel) {

0 commit comments

Comments
 (0)
Please sign in to comment.