Merge "binderRpcBenchmark: device tests kernel as ctrl"
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 37fc9a9..e3c4ede 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -266,7 +266,10 @@
chmod 0666 /sys/kernel/debug/tracing/per_cpu/cpu15/trace
chmod 0666 /sys/kernel/tracing/per_cpu/cpu15/trace
-on post-fs-data
+# Only create the tracing instance if persist.mm_events.enabled
+# Attempting to remove the tracing instance after it has been created
+# will likely fail with EBUSY as it would be in use by traced_probes.
+on post-fs-data && property:persist.mm_events.enabled=true
# Create MM Events Tracing Instance for Kmem Activity Trigger
mkdir /sys/kernel/debug/tracing/instances/mm_events 0755 system system
mkdir /sys/kernel/tracing/instances/mm_events 0755 system system
@@ -275,10 +278,18 @@
chmod 0666 /sys/kernel/debug/tracing/instances/mm_events/buffer_size_kb
chmod 0666 /sys/kernel/tracing/instances/mm_events/buffer_size_kb
+# Set the default buffer size to the minimum
+ write /sys/kernel/debug/tracing/instances/mm_events/buffer_size_kb 1
+ write /sys/kernel/tracing/instances/mm_events/buffer_size_kb 1
+
# Read and enable tracing
chmod 0666 /sys/kernel/debug/tracing/instances/mm_events/tracing_on
chmod 0666 /sys/kernel/tracing/instances/mm_events/tracing_on
+# Tracing disabled by default
+ write /sys/kernel/debug/tracing/instances/mm_events/tracing_on 0
+ write /sys/kernel/tracing/instances/mm_events/tracing_on 0
+
# Read and truncate kernel trace
chmod 0666 /sys/kernel/debug/tracing/instances/mm_events/trace
chmod 0666 /sys/kernel/tracing/instances/mm_events/trace
diff --git a/cmds/cmd/fuzzer/Android.bp b/cmds/cmd/fuzzer/Android.bp
new file mode 100644
index 0000000..0c78c5a
--- /dev/null
+++ b/cmds/cmd/fuzzer/Android.bp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_fuzz {
+ name: "cmd_fuzzer",
+ srcs: [
+ "cmd_fuzzer.cpp",
+ ],
+ static_libs: [
+ "libcmd",
+ "libutils",
+ "liblog",
+ "libselinux",
+ ],
+ shared_libs: [
+ "libbinder",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
diff --git a/cmds/cmd/fuzzer/README.md b/cmds/cmd/fuzzer/README.md
new file mode 100644
index 0000000..db37ece
--- /dev/null
+++ b/cmds/cmd/fuzzer/README.md
@@ -0,0 +1,51 @@
+# Fuzzer for libcmd_fuzzer
+
+## Plugin Design Considerations
+The fuzzer plugin for libcmd is designed based on the understanding of the library and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+libcmd supports the following parameters:
+1. In (parameter name: `in`)
+2. Out (parameter name: `out`)
+3. Err (parameter name: `err`)
+4. Run Mode (parameter name: `runMode`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `in` | `INT32_MIN` to `INT32_MAX` | Value obtained from FuzzedDataProvider|
+| `out` | `INT32_MIN` to `INT32_MAX` | Value obtained from FuzzedDataProvider|
+| `err` | `INT32_MIN` to `INT32_MAX` | Value obtained from FuzzedDataProvider|
+| `runMode` | 1.`RunMode::kStandalone` 2. `RunMode::kLibrary` | Value chosen from valid values using FuzzedDataProvider|
+
+This also ensures that the plugin is always deterministic for any given input.
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the cmd module.
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build cmd_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) cmd_fuzzer
+```
+#### Steps to run
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/${TARGET_ARCH}/cmd_fuzzer/cmd_fuzzer
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/cmds/cmd/fuzzer/cmd_fuzzer.cpp b/cmds/cmd/fuzzer/cmd_fuzzer.cpp
new file mode 100644
index 0000000..ab514a1
--- /dev/null
+++ b/cmds/cmd/fuzzer/cmd_fuzzer.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/TextOutput.h>
+#include <cmd.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+using namespace std;
+using namespace android;
+
+class TestTextOutput : public TextOutput {
+public:
+ TestTextOutput() {}
+ virtual ~TestTextOutput() {}
+
+ virtual status_t print(const char* /*txt*/, size_t /*len*/) { return NO_ERROR; }
+ virtual void moveIndent(int /*delta*/) { return; }
+ virtual void pushBundle() { return; }
+ virtual void popBundle() { return; }
+};
+
+class CmdFuzzer {
+public:
+ void process(const uint8_t* data, size_t size);
+
+private:
+ FuzzedDataProvider* mFDP = nullptr;
+};
+
+void CmdFuzzer::process(const uint8_t* data, size_t size) {
+ mFDP = new FuzzedDataProvider(data, size);
+ vector<string> arguments;
+ if (mFDP->ConsumeBool()) {
+ if (mFDP->ConsumeBool()) {
+ arguments = {"-w", "media.aaudio"};
+ } else {
+ arguments = {"-l"};
+ }
+ } else {
+ while (mFDP->remaining_bytes() > 0) {
+ size_t sizestr = mFDP->ConsumeIntegralInRange<size_t>(1, mFDP->remaining_bytes());
+ string argument = mFDP->ConsumeBytesAsString(sizestr);
+ arguments.emplace_back(argument);
+ }
+ }
+ vector<string_view> argSV;
+ for (auto& argument : arguments) {
+ argSV.emplace_back(argument.c_str());
+ }
+ int32_t in = open("/dev/null", O_RDWR | O_CREAT);
+ int32_t out = open("/dev/null", O_RDWR | O_CREAT);
+ int32_t err = open("/dev/null", O_RDWR | O_CREAT);
+ TestTextOutput output;
+ TestTextOutput error;
+ RunMode runMode = mFDP->ConsumeBool() ? RunMode::kStandalone : RunMode::kLibrary;
+ cmdMain(argSV, output, error, in, out, err, runMode);
+ delete mFDP;
+ close(in);
+ close(out);
+ close(err);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ CmdFuzzer cmdFuzzer;
+ cmdFuzzer.process(data, size);
+ return 0;
+}
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 200d923..62ea187 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -110,6 +110,10 @@
return mMaxThreads;
}
+void RpcServer::setProtocolVersion(uint32_t version) {
+ mProtocolVersion = version;
+}
+
void RpcServer::setRootObject(const sp<IBinder>& binder) {
std::lock_guard<std::mutex> _l(mLock);
mRootObjectWeak = mRootObject = binder;
@@ -245,13 +249,37 @@
RpcConnectionHeader header;
status_t status = server->mShutdownTrigger->interruptableReadFully(clientFd.get(), &header,
sizeof(header));
- bool idValid = status == OK;
- if (!idValid) {
+ if (status != OK) {
ALOGE("Failed to read ID for client connecting to RPC server: %s",
statusToString(status).c_str());
// still need to cleanup before we can return
}
- bool incoming = header.options & RPC_CONNECTION_OPTION_INCOMING;
+
+ bool incoming = false;
+ uint32_t protocolVersion = 0;
+ RpcAddress sessionId = RpcAddress::zero();
+ bool requestingNewSession = false;
+
+ if (status == OK) {
+ incoming = header.options & RPC_CONNECTION_OPTION_INCOMING;
+ protocolVersion = std::min(header.version,
+ server->mProtocolVersion.value_or(RPC_WIRE_PROTOCOL_VERSION));
+ sessionId = RpcAddress::fromRawEmbedded(&header.sessionId);
+ requestingNewSession = sessionId.isZero();
+
+ if (requestingNewSession) {
+ RpcNewSessionResponse response{
+ .version = protocolVersion,
+ };
+
+ status = server->mShutdownTrigger->interruptableWriteFully(clientFd.get(), &response,
+ sizeof(response));
+ if (status != OK) {
+ ALOGE("Failed to send new session response: %s", statusToString(status).c_str());
+ // still need to cleanup before we can return
+ }
+ }
+ }
std::thread thisThread;
sp<RpcSession> session;
@@ -269,19 +297,16 @@
};
server->mConnectingThreads.erase(threadId);
- if (!idValid || server->mShutdownTrigger->isTriggered()) {
+ if (status != OK || server->mShutdownTrigger->isTriggered()) {
return;
}
- RpcAddress sessionId = RpcAddress::fromRawEmbedded(&header.sessionId);
-
- if (sessionId.isZero()) {
+ if (requestingNewSession) {
if (incoming) {
ALOGE("Cannot create a new session with an incoming connection, would leak");
return;
}
- sessionId = RpcAddress::zero();
size_t tries = 0;
do {
// don't block if there is some entropy issue
@@ -295,6 +320,7 @@
session = RpcSession::make();
session->setMaxThreads(server->mMaxThreads);
+ if (!session->setProtocolVersion(protocolVersion)) return;
if (!session->setForServer(server,
sp<RpcServer::EventListener>::fromExisting(
static_cast<RpcServer::EventListener*>(
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index 1c37651..90ce4d6 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -77,6 +77,25 @@
return mMaxThreads;
}
+bool RpcSession::setProtocolVersion(uint32_t version) {
+ if (version >= RPC_WIRE_PROTOCOL_VERSION_NEXT &&
+ version != RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
+ ALOGE("Cannot start RPC session with version %u which is unknown (current protocol version "
+ "is %u).",
+ version, RPC_WIRE_PROTOCOL_VERSION);
+ return false;
+ }
+
+ std::lock_guard<std::mutex> _l(mMutex);
+ mProtocolVersion = version;
+ return true;
+}
+
+std::optional<uint32_t> RpcSession::getProtocolVersion() {
+ std::lock_guard<std::mutex> _l(mMutex);
+ return mProtocolVersion;
+}
+
bool RpcSession::setupUnixDomainClient(const char* path) {
return setupSocketClient(UnixSocketAddress(path));
}
@@ -424,6 +443,18 @@
if (!setupOneSocketConnection(addr, RpcAddress::zero(), false /*incoming*/)) return false;
+ {
+ ExclusiveConnection connection;
+ status_t status = ExclusiveConnection::find(sp<RpcSession>::fromExisting(this),
+ ConnectionUse::CLIENT, &connection);
+ if (status != OK) return false;
+
+ uint32_t version;
+ status = state()->readNewSessionResponse(connection.get(),
+ sp<RpcSession>::fromExisting(this), &version);
+ if (!setProtocolVersion(version)) return false;
+ }
+
// TODO(b/189955605): we should add additional sessions dynamically
// instead of all at once.
// TODO(b/186470974): first risk of blocking
@@ -484,7 +515,10 @@
return false;
}
- RpcConnectionHeader header{.options = 0};
+ RpcConnectionHeader header{
+ .version = mProtocolVersion.value_or(RPC_WIRE_PROTOCOL_VERSION),
+ .options = 0,
+ };
memcpy(&header.sessionId, &id.viewRawEmbedded(), sizeof(RpcWireAddress));
if (incoming) header.options |= RPC_CONNECTION_OPTION_INCOMING;
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 332c75f..f3406bb 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -315,6 +315,18 @@
return OK;
}
+status_t RpcState::readNewSessionResponse(const sp<RpcSession::RpcConnection>& connection,
+ const sp<RpcSession>& session, uint32_t* version) {
+ RpcNewSessionResponse response;
+ if (status_t status =
+ rpcRec(connection, session, "new session response", &response, sizeof(response));
+ status != OK) {
+ return status;
+ }
+ *version = response.version;
+ return OK;
+}
+
status_t RpcState::sendConnectionInit(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session) {
RpcOutgoingConnectionInit init{
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index 5ac0b97..1446eec 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -60,6 +60,8 @@
RpcState();
~RpcState();
+ status_t readNewSessionResponse(const sp<RpcSession::RpcConnection>& connection,
+ const sp<RpcSession>& session, uint32_t* version);
status_t sendConnectionInit(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session);
status_t readConnectionInit(const sp<RpcSession::RpcConnection>& connection,
diff --git a/libs/binder/RpcWireFormat.h b/libs/binder/RpcWireFormat.h
index 2a44c7a..0f8efd2 100644
--- a/libs/binder/RpcWireFormat.h
+++ b/libs/binder/RpcWireFormat.h
@@ -37,9 +37,20 @@
* either as part of a new session or an existing session
*/
struct RpcConnectionHeader {
+ uint32_t version; // maximum supported by caller
+ uint8_t reserver0[4];
RpcWireAddress sessionId;
uint8_t options;
- uint8_t reserved[7];
+ uint8_t reserved1[7];
+};
+
+/**
+ * In response to an RpcConnectionHeader which corresponds to a new session,
+ * this returns information to the server.
+ */
+struct RpcNewSessionResponse {
+ uint32_t version; // maximum supported by callee <= maximum supported by caller
+ uint8_t reserved[4];
};
#define RPC_CONNECTION_INIT_OKAY "cci"
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index a8094dd..40ff78c 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -105,6 +105,13 @@
size_t getMaxThreads();
/**
+ * By default, the latest protocol version which is supported by a client is
+ * used. However, this can be used in order to prevent newer protocol
+ * versions from ever being used. This is expected to be useful for testing.
+ */
+ void setProtocolVersion(uint32_t version);
+
+ /**
* The root object can be retrieved by any client, without any
* authentication. TODO(b/183988761)
*
@@ -164,6 +171,7 @@
bool mAgreedExperimental = false;
size_t mMaxThreads = 1;
+ std::optional<uint32_t> mProtocolVersion;
base::unique_fd mServer; // socket we are accepting sessions on
std::mutex mLock; // for below
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index 2101df8..1f7c029 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -37,6 +37,10 @@
class RpcSocketAddress;
class RpcState;
+constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 0;
+constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL = 0xF0000000;
+constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL;
+
/**
* This represents a session (group of connections) between a client
* and a server. Multiple connections are needed for multiple parallel "binder"
@@ -60,6 +64,13 @@
size_t getMaxThreads();
/**
+ * By default, the minimum of the supported versions of the client and the
+ * server will be used. Usually, this API should only be used for debugging.
+ */
+ [[nodiscard]] bool setProtocolVersion(uint32_t version);
+ std::optional<uint32_t> getProtocolVersion();
+
+ /**
* This should be called once per thread, matching 'join' in the remote
* process.
*/
@@ -291,6 +302,7 @@
std::mutex mMutex; // for all below
size_t mMaxThreads = 0;
+ std::optional<uint32_t> mProtocolVersion;
std::condition_variable mAvailableConnectionCv; // for mWaitingThreads
size_t mWaitingThreads = 0;
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 40ebd9c..d5786bc 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -47,6 +47,9 @@
namespace android {
+static_assert(RPC_WIRE_PROTOCOL_VERSION + 1 == RPC_WIRE_PROTOCOL_VERSION_NEXT ||
+ RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+
TEST(BinderRpcParcel, EntireParcelFormatted) {
Parcel p;
p.writeInt32(3);
@@ -67,6 +70,19 @@
ASSERT_EQ(sinkFd, retrieved.get());
}
+TEST(BinderRpc, CannotUseNextWireVersion) {
+ auto session = RpcSession::make();
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT));
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 1));
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 2));
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15));
+}
+
+TEST(BinderRpc, CanUseExperimentalWireVersion) {
+ auto session = RpcSession::make();
+ EXPECT_TRUE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
+}
+
using android::binder::Status;
#define EXPECT_OK(status) \