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;
import static android.os.IBinder.FLAG_ONEWAY;
import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
@ -46,18 +47,28 @@ public final class LeakSafeOneWayBinderTest {
@Test
public void testTransaction() {
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(lastCode).isEqualTo(123);
assertThat(lastParcel).isSameInstanceAs(p);
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
public void testDetach() {
Parcel p = Parcel.obtain();
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.
assertThat(transactionsHandled).isEqualTo(0);
@ -68,8 +79,8 @@ public final class LeakSafeOneWayBinderTest {
@Test
public void testMultipleTransactions() {
Parcel p = Parcel.obtain();
assertThat(binder.onTransact(123, p, null, 0)).isTrue();
assertThat(binder.onTransact(456, p, null, 0)).isTrue();
assertThat(binder.onTransact(123, p, null, FLAG_ONEWAY)).isTrue();
assertThat(binder.onTransact(456, p, null, FLAG_ONEWAY)).isTrue();
assertThat(transactionsHandled).isEqualTo(2);
assertThat(lastCode).isEqualTo(456);
assertThat(lastParcel).isSameInstanceAs(p);

View File

@ -31,6 +31,7 @@ import android.os.TransactionTooLargeException;
import android.os.UserHandle;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.base.Verify;
import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.Attributes;
import io.grpc.CallOptions;
@ -463,14 +464,11 @@ public abstract class BinderTransport
if (inbound == null) {
synchronized (this) {
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);
if (inbound != null) {
Inbound<?> inbound2 = ongoingCalls.putIfAbsent(code, inbound);
if (inbound2 != null) {
inbound = inbound2;
}
Inbound<?> existing = ongoingCalls.put(code, inbound);
// Can't happen as only one invocation of handleTransaction() is running at a time.
Verify.verify(existing == null, "impossible appearance of %s", existing);
}
}
}

View File

@ -17,6 +17,7 @@
package io.grpc.binder.internal;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import io.grpc.Internal;
import java.util.logging.Level;
@ -42,6 +43,21 @@ public final class LeakSafeOneWayBinder extends Binder {
@Internal
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);
}
@ -60,6 +76,10 @@ public final class LeakSafeOneWayBinder extends Binder {
TransactionHandler handler = this.handler;
if (handler != null) {
try {
if ((flags & IBinder.FLAG_ONEWAY) == 0) {
logger.log(Level.WARNING, "ignoring non-oneway transaction. flags=" + flags);
return false;
}
return handler.handleTransaction(code, parcel);
} catch (RuntimeException re) {
logger.log(Level.WARNING, "failure sending transaction " + code, re);