Skip to content

Commit 4725ced

Browse files
authored
binder: Avoid potential deadlock when canceling AsyncSecurityPolicy futures (#12283)
Move future cancellation outside of synchronized block in `BinderClientTransport.notifyTerminated()` to prevent deadlock if `AsyncSecurityPolicy` uses `directExecutor()` for callbacks. Fixes #12190 --------- Signed-off-by: Hyunsang Han <gustkd3@gmail.com>
1 parent 7f0a19d commit 4725ced

File tree

2 files changed

+24
-15
lines changed

2 files changed

+24
-15
lines changed

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,6 @@ public final class BinderClientTransport extends BinderTransport
8787
@GuardedBy("this")
8888
private ScheduledFuture<?> readyTimeoutFuture; // != null iff timeout scheduled.
8989

90-
@GuardedBy("this")
91-
@Nullable
92-
private ListenableFuture<Status> authResultFuture; // null before we check auth.
93-
94-
@GuardedBy("this")
95-
@Nullable
96-
private ListenableFuture<Status> preAuthResultFuture; // null before we pre-auth.
9790

9891
/**
9992
* Constructs a new transport instance.
@@ -193,7 +186,8 @@ private void preAuthorize(ServiceInfo serviceInfo) {
193186
// unauthorized server a chance to run, but the connection will still fail by SecurityPolicy
194187
// check later in handshake. Pre-auth remains effective at mitigating abuse because malware
195188
// can't typically control the exact timing of its installation.
196-
preAuthResultFuture = checkServerAuthorizationAsync(serviceInfo.applicationInfo.uid);
189+
ListenableFuture<Status> preAuthResultFuture =
190+
register(checkServerAuthorizationAsync(serviceInfo.applicationInfo.uid));
197191
Futures.addCallback(
198192
preAuthResultFuture,
199193
new FutureCallback<Status>() {
@@ -314,12 +308,6 @@ void notifyTerminated() {
314308
readyTimeoutFuture.cancel(false);
315309
readyTimeoutFuture = null;
316310
}
317-
if (preAuthResultFuture != null) {
318-
preAuthResultFuture.cancel(false); // No effect if already complete.
319-
}
320-
if (authResultFuture != null) {
321-
authResultFuture.cancel(false); // No effect if already complete.
322-
}
323311
serviceBinding.unbind();
324312
clientTransportListener.transportTerminated();
325313
}
@@ -339,7 +327,8 @@ protected void handleSetupTransport(Parcel parcel) {
339327
} else {
340328
restrictIncomingBinderToCallsFrom(remoteUid);
341329
attributes = setSecurityAttrs(attributes, remoteUid);
342-
authResultFuture = checkServerAuthorizationAsync(remoteUid);
330+
ListenableFuture<Status> authResultFuture =
331+
register(checkServerAuthorizationAsync(remoteUid));
343332
Futures.addCallback(
344333
authResultFuture,
345334
new FutureCallback<Status>() {
@@ -398,6 +387,7 @@ protected void handlePingResponse(Parcel parcel) {
398387
pingTracker.onPingResponse(parcel.readInt());
399388
}
400389

390+
401391
private static ClientStream newFailingClientStream(
402392
Status failure, Attributes attributes, Metadata headers, ClientStreamTracer[] tracers) {
403393
StatsTraceContext statsTraceContext =

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@
4545
import java.util.ArrayList;
4646
import java.util.Iterator;
4747
import java.util.LinkedHashSet;
48+
import java.util.List;
4849
import java.util.Map;
4950
import java.util.NoSuchElementException;
5051
import java.util.concurrent.ConcurrentHashMap;
52+
import java.util.concurrent.Future;
5153
import java.util.concurrent.ScheduledExecutorService;
5254
import java.util.logging.Level;
5355
import java.util.logging.Logger;
@@ -166,6 +168,9 @@ protected enum TransportState {
166168
@GuardedBy("this")
167169
private final LinkedHashSet<Integer> callIdsToNotifyWhenReady = new LinkedHashSet<>();
168170

171+
@GuardedBy("this")
172+
private final List<Future<?>> ownedFutures = new ArrayList<>(); // To cancel upon terminate.
173+
169174
@GuardedBy("this")
170175
protected Attributes attributes;
171176

@@ -249,6 +254,13 @@ void releaseExecutors() {
249254
executorServicePool.returnObject(scheduledExecutorService);
250255
}
251256

257+
// Registers the specified future for eventual safe cancellation upon shutdown/terminate.
258+
@GuardedBy("this")
259+
protected final <T extends Future<?>> T register(T future) {
260+
ownedFutures.add(future);
261+
return future;
262+
}
263+
252264
@GuardedBy("this")
253265
boolean inState(TransportState transportState) {
254266
return this.transportState == transportState;
@@ -299,6 +311,8 @@ final void shutdownInternal(Status shutdownStatus, boolean forceTerminate) {
299311
sendShutdownTransaction();
300312
ArrayList<Inbound<?>> calls = new ArrayList<>(ongoingCalls.values());
301313
ongoingCalls.clear();
314+
ArrayList<Future<?>> futuresToCancel = new ArrayList<>(ownedFutures);
315+
ownedFutures.clear();
302316
scheduledExecutorService.execute(
303317
() -> {
304318
for (Inbound<?> inbound : calls) {
@@ -310,6 +324,11 @@ final void shutdownInternal(Status shutdownStatus, boolean forceTerminate) {
310324
notifyTerminated();
311325
}
312326
releaseExecutors();
327+
328+
for (Future<?> future : futuresToCancel) {
329+
// Not holding any locks here just in case some listener runs on a direct Executor.
330+
future.cancel(false); // No effect if already isDone().
331+
}
313332
});
314333
}
315334
}

0 commit comments

Comments
 (0)