Add worksource support to binder calls.
Unlike android.os.WorkSource, we only support the leaf UID of the work
chain here for performance reasons.
We use a similar mechanism to how we propagate the StrictMode flags. The
enforceInterface and writeInterfaceToken are used to pass the worksource
through the parcel. enforceInterface/writeInterfaceToken is at least
used by all the services using AIDL.
It might break code which is reading/writing raw parcels. For instance,
a client calling an AIDL service and replicating what enforceInterface
is doing, e.g. service_manager.c. This is the only client which seems to
do that.
Here is an example of how this API is supposed to be used:
Binder.setThreadWorkSource(uid);
try {
// Call an API.
} finally {
Binder.clearThreadWorkSource();
}
An alternative would be to clear the work source automatically on behalf
of the caller. Manually setting the work source has been chosen to give
more control to callers of the API:
- they might choose to call setThreadWorkSource once and do multiple binder calls
- callers can also decide whether they want to restore the work source
to the orginal
value.
We do not throw an exception when #setThreadWorkSource overrides an
existing work source. It would be really hard to figure when it is safe
to call #setThreadWorkSource. For instance, adding a binder transaction
might make some code down the line that calls #setThreadWorkSource throw
an exception (because the binder transaction would set the work source
on the thread).
Ran the binder performance test suite
(test/vts-testcase/performance/binder_benchmark/binder_performance_test/).
There is no difference in terms of cpu/real time:
- without patch
{
"name": "BM_sendVec_binder/4",
"iterations": 43088,
"real_time": 3.0151086752702871e+04, // yes, time is higher without patch
"cpu_time": 1.4715999326958785e+04,
"time_unit": "ns"
{
"name": "BM_sendVec_binder/8",
"iterations": 48335,
"real_time": 2.8371138740033064e+04,
"cpu_time": 1.3710703486086690e+04,
"time_unit": "ns"
},
- with patch
{
"name": "BM_sendVec_binder/4",
"iterations": 48214,
"real_time": 2.8613625191853276e+04,
"cpu_time": 1.4305279856473226e+04,
"time_unit": "ns"
},
{
"name": "BM_sendVec_binder/8",
"iterations": 49998,
"real_time": 2.8749066382655215e+04,
"cpu_time": 1.4422315992639707e+04,
"time_unit": "ns"
},
Test: unit tests and manually tested on my phone
Change-Id: I167abbbfa6e7c4b0933c4cafa04df810f4814e2b
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index d776682..79cd6b5 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -274,6 +274,7 @@
// Note that we ignore the strict_policy and don't propagate it
// further (since we do no outbound RPCs anyway).
strict_policy = bio_get_uint32(msg);
+ bio_get_uint32(msg); // Ignore worksource header.
s = bio_get_string16(msg, &len);
if (s == NULL) {
return -1;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index f052bcb..7a47724 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -109,6 +109,10 @@
"BC_DEAD_BINDER_DONE"
};
+// The work source represents the UID of the process we should attribute the transaction to.
+// We use -1 to specify that the work source was not set using #setWorkSource.
+static const int kUnsetWorkSource = -1;
+
static const char* getReturnString(uint32_t cmd)
{
size_t idx = cmd & 0xff;
@@ -383,6 +387,25 @@
return mStrictModePolicy;
}
+uid_t IPCThreadState::setWorkSource(uid_t uid)
+{
+ uid_t returnValue = mWorkSource;
+ mWorkSource = uid;
+ return returnValue;
+}
+
+uid_t IPCThreadState::getWorkSource() const
+{
+ return mWorkSource;
+}
+
+uid_t IPCThreadState::clearWorkSource()
+{
+ uid_t returnValue = mWorkSource;
+ mWorkSource = kUnsetWorkSource;
+ return returnValue;
+}
+
void IPCThreadState::setLastTransactionBinderFlags(int32_t flags)
{
mLastTransactionBinderFlags = flags;
@@ -736,6 +759,7 @@
IPCThreadState::IPCThreadState()
: mProcess(ProcessState::self()),
+ mWorkSource(kUnsetWorkSource),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 87c9842..bc1a71c 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -601,6 +601,7 @@
{
writeInt32(IPCThreadState::self()->getStrictModePolicy() |
STRICT_MODE_PENALTY_GATHER);
+ writeInt32(IPCThreadState::self()->getWorkSource());
// currently the interface identification token is just its name as a string
return writeString16(interface);
}
@@ -613,6 +614,7 @@
bool Parcel::enforceInterface(const String16& interface,
IPCThreadState* threadState) const
{
+ // StrictModePolicy.
int32_t strictPolicy = readInt32();
if (threadState == nullptr) {
threadState = IPCThreadState::self();
@@ -627,6 +629,10 @@
} else {
threadState->setStrictModePolicy(strictPolicy);
}
+ // WorkSource.
+ int32_t workSource = readInt32();
+ threadState->setWorkSource(workSource);
+ // Interface descriptor.
const String16 str(readString16());
if (str == interface) {
return true;
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 40b51ad..5888e14 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -47,6 +47,13 @@
void setStrictModePolicy(int32_t policy);
int32_t getStrictModePolicy() const;
+ // See Binder#setThreadWorkSource in Binder.java.
+ uid_t setWorkSource(uid_t uid);
+ // See Binder#getThreadWorkSource in Binder.java.
+ uid_t getWorkSource() const;
+ // See Binder#clearThreadWorkSource in Binder.java.
+ uid_t clearWorkSource();
+
void setLastTransactionBinderFlags(int32_t flags);
int32_t getLastTransactionBinderFlags() const;
@@ -155,6 +162,9 @@
status_t mLastError;
pid_t mCallingPid;
uid_t mCallingUid;
+ // The UID of the process who is responsible for this transaction.
+ // This is used for resource attribution.
+ int32_t mWorkSource;
int32_t mStrictModePolicy;
int32_t mLastTransactionBinderFlags;
IPCThreadStateBase *mIPCThreadStateBase;
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 73c2eba..7bcfffd 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -71,6 +71,7 @@
BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION,
BINDER_LIB_TEST_GET_PTR_SIZE_TRANSACTION,
BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION,
+ BINDER_LIB_TEST_GET_WORK_SOURCE_TRANSACTION,
};
pid_t start_server_process(int arg2, bool usePoll = false)
@@ -938,6 +939,43 @@
EXPECT_EQ(NO_ERROR, ret);
}
+TEST_F(BinderLibTest, WorkSourceUnsetByDefault)
+{
+ status_t ret;
+ Parcel data, reply;
+ data.writeInterfaceToken(binderLibTestServiceName);
+ ret = m_server->transact(BINDER_LIB_TEST_GET_WORK_SOURCE_TRANSACTION, data, &reply);
+ EXPECT_EQ(-1, reply.readInt32());
+ EXPECT_EQ(NO_ERROR, ret);
+}
+
+TEST_F(BinderLibTest, WorkSourceSet)
+{
+ status_t ret;
+ Parcel data, reply;
+ uid_t previousWorkSource = IPCThreadState::self()->setWorkSource(100);
+ data.writeInterfaceToken(binderLibTestServiceName);
+ ret = m_server->transact(BINDER_LIB_TEST_GET_WORK_SOURCE_TRANSACTION, data, &reply);
+ EXPECT_EQ(100, reply.readInt32());
+ EXPECT_EQ(-1, previousWorkSource);
+ EXPECT_EQ(NO_ERROR, ret);
+}
+
+TEST_F(BinderLibTest, WorkSourceCleared)
+{
+ status_t ret;
+ Parcel data, reply;
+
+ IPCThreadState::self()->setWorkSource(100);
+ uid_t previousWorkSource = IPCThreadState::self()->clearWorkSource();
+ data.writeInterfaceToken(binderLibTestServiceName);
+ ret = m_server->transact(BINDER_LIB_TEST_GET_WORK_SOURCE_TRANSACTION, data, &reply);
+
+ EXPECT_EQ(-1, reply.readInt32());
+ EXPECT_EQ(100, previousWorkSource);
+ EXPECT_EQ(NO_ERROR, ret);
+}
+
class BinderLibTestService : public BBinder
{
public:
@@ -1236,6 +1274,11 @@
}
return NO_ERROR;
}
+ case BINDER_LIB_TEST_GET_WORK_SOURCE_TRANSACTION: {
+ data.enforceInterface(binderLibTestServiceName);
+ reply->writeInt32(IPCThreadState::self()->getWorkSource());
+ return NO_ERROR;
+ }
default:
return UNKNOWN_TRANSACTION;
};