binder: oneway txns cannot arrive out of order (#10754)

See https://developer.android.com/reference/android/os/IBinder#FLAG_ONEWAY
This commit is contained in:
John Cormie 2024-01-03 22:31:51 -08:00 committed by GitHub
parent 91d15ce4e6
commit 5e07310d06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 39 additions and 10 deletions

View File

@ -16,6 +16,7 @@
package io.grpc.binder.internal; package io.grpc.binder.internal;
import static android.os.IBinder.FLAG_ONEWAY;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel; import android.os.Parcel;
@ -46,18 +47,28 @@ public final class LeakSafeOneWayBinderTest {
@Test @Test
public void testTransaction() { public void testTransaction() {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
assertThat(binder.onTransact(123, p, null, 0)).isTrue(); assertThat(binder.onTransact(123, p, null, FLAG_ONEWAY)).isTrue();
assertThat(transactionsHandled).isEqualTo(1); assertThat(transactionsHandled).isEqualTo(1);
assertThat(lastCode).isEqualTo(123); assertThat(lastCode).isEqualTo(123);
assertThat(lastParcel).isSameInstanceAs(p); assertThat(lastParcel).isSameInstanceAs(p);
p.recycle(); p.recycle();
} }
@Test
public void testDropsTwoWayTransactions() {
Parcel p = Parcel.obtain();
Parcel reply = Parcel.obtain();
assertThat(binder.onTransact(123, p, reply, 0)).isFalse();
assertThat(transactionsHandled).isEqualTo(0);
p.recycle();
reply.recycle();
}
@Test @Test
public void testDetach() { public void testDetach() {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
binder.detach(); binder.detach();
assertThat(binder.onTransact(456, p, null, 0)).isFalse(); assertThat(binder.onTransact(456, p, null, FLAG_ONEWAY)).isFalse();
// The transaction shouldn't have been processed. // The transaction shouldn't have been processed.
assertThat(transactionsHandled).isEqualTo(0); assertThat(transactionsHandled).isEqualTo(0);
@ -68,8 +79,8 @@ public final class LeakSafeOneWayBinderTest {
@Test @Test
public void testMultipleTransactions() { public void testMultipleTransactions() {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
assertThat(binder.onTransact(123, p, null, 0)).isTrue(); assertThat(binder.onTransact(123, p, null, FLAG_ONEWAY)).isTrue();
assertThat(binder.onTransact(456, p, null, 0)).isTrue(); assertThat(binder.onTransact(456, p, null, FLAG_ONEWAY)).isTrue();
assertThat(transactionsHandled).isEqualTo(2); assertThat(transactionsHandled).isEqualTo(2);
assertThat(lastCode).isEqualTo(456); assertThat(lastCode).isEqualTo(456);
assertThat(lastParcel).isSameInstanceAs(p); assertThat(lastParcel).isSameInstanceAs(p);

View File

@ -31,6 +31,7 @@ import android.os.TransactionTooLargeException;
import android.os.UserHandle; import android.os.UserHandle;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker; import com.google.common.base.Ticker;
import com.google.common.base.Verify;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.Attributes; import io.grpc.Attributes;
import io.grpc.CallOptions; import io.grpc.CallOptions;
@ -463,14 +464,11 @@ public abstract class BinderTransport
if (inbound == null) { if (inbound == null) {
synchronized (this) { synchronized (this) {
if (!isShutdown()) { if (!isShutdown()) {
// Create a new inbound. Strictly speaking we could end up doing this twice on
// two threads, hence the need to use putIfAbsent, and check its result.
inbound = createInbound(code); inbound = createInbound(code);
if (inbound != null) { if (inbound != null) {
Inbound<?> inbound2 = ongoingCalls.putIfAbsent(code, inbound); Inbound<?> existing = ongoingCalls.put(code, inbound);
if (inbound2 != null) { // Can't happen as only one invocation of handleTransaction() is running at a time.
inbound = inbound2; Verify.verify(existing == null, "impossible appearance of %s", existing);
}
} }
} }
} }

View File

@ -17,6 +17,7 @@
package io.grpc.binder.internal; package io.grpc.binder.internal;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel; import android.os.Parcel;
import io.grpc.Internal; import io.grpc.Internal;
import java.util.logging.Level; import java.util.logging.Level;
@ -42,6 +43,21 @@ public final class LeakSafeOneWayBinder extends Binder {
@Internal @Internal
public interface TransactionHandler { public interface TransactionHandler {
/**
* Delivers a binder transaction to this handler.
*
* <p>Implementations need not be thread-safe. Each invocation "happens-before" the next in the
* same order that transactions were sent ("oneway" semantics). However implementations must not
* be thread-hostile as different calls can come in on different threads.
*
* <p>{@code parcel} is only valid for the duration of this call. Ownership is retained by the
* caller.
*
* @param code the transaction code originally passed to {@link IBinder#transact}
* @param code a copy of the parcel originally passed to {@link IBinder#transact}.
* @return the value to return from {@link Binder#onTransact}. NB: "oneway" semantics mean this
* result will not delivered to the caller of {@link IBinder#transact}
*/
boolean handleTransaction(int code, Parcel data); boolean handleTransaction(int code, Parcel data);
} }
@ -60,6 +76,10 @@ public final class LeakSafeOneWayBinder extends Binder {
TransactionHandler handler = this.handler; TransactionHandler handler = this.handler;
if (handler != null) { if (handler != null) {
try { try {
if ((flags & IBinder.FLAG_ONEWAY) == 0) {
logger.log(Level.WARNING, "ignoring non-oneway transaction. flags=" + flags);
return false;
}
return handler.handleTransaction(code, parcel); return handler.handleTransaction(code, parcel);
} catch (RuntimeException re) { } catch (RuntimeException re) {
logger.log(Level.WARNING, "failure sending transaction " + code, re); logger.log(Level.WARNING, "failure sending transaction " + code, re);