diff options
-rw-r--r-- | cmds/atrace/atrace.rc | 13 | ||||
-rw-r--r-- | cmds/cmd/fuzzer/Android.bp | 37 | ||||
-rw-r--r-- | cmds/cmd/fuzzer/README.md | 51 | ||||
-rw-r--r-- | cmds/cmd/fuzzer/cmd_fuzzer.cpp | 85 | ||||
-rw-r--r-- | libs/binder/RpcServer.cpp | 42 | ||||
-rw-r--r-- | libs/binder/RpcSession.cpp | 36 | ||||
-rw-r--r-- | libs/binder/RpcState.cpp | 12 | ||||
-rw-r--r-- | libs/binder/RpcState.h | 2 | ||||
-rw-r--r-- | libs/binder/RpcWireFormat.h | 13 | ||||
-rw-r--r-- | libs/binder/include/binder/RpcServer.h | 8 | ||||
-rw-r--r-- | libs/binder/include/binder/RpcSession.h | 12 | ||||
-rw-r--r-- | libs/binder/tests/binderRpcTest.cpp | 16 |
12 files changed, 316 insertions, 11 deletions
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc index 37fc9a9356..e3c4edebbd 100644 --- a/cmds/atrace/atrace.rc +++ b/cmds/atrace/atrace.rc @@ -266,7 +266,10 @@ on late-init 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 @@ on post-fs-data 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 0000000000..0c78c5a18b --- /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 0000000000..db37ecea32 --- /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 0000000000..ab514a1d04 --- /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 200d923b6d..62ea187719 100644 --- a/libs/binder/RpcServer.cpp +++ b/libs/binder/RpcServer.cpp @@ -110,6 +110,10 @@ size_t RpcServer::getMaxThreads() { 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 @@ void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clie 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 @@ void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clie }; 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 @@ void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clie 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 1c376518f0..90ce4d6d3f 100644 --- a/libs/binder/RpcSession.cpp +++ b/libs/binder/RpcSession.cpp @@ -77,6 +77,25 @@ size_t RpcSession::getMaxThreads() { 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 @@ bool RpcSession::setupSocketClient(const RpcSocketAddress& addr) { 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 @@ bool RpcSession::setupOneSocketConnection(const RpcSocketAddress& addr, const Rp 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 332c75f9e7..f3406bb10b 100644 --- a/libs/binder/RpcState.cpp +++ b/libs/binder/RpcState.cpp @@ -315,6 +315,18 @@ status_t RpcState::rpcRec(const sp<RpcSession::RpcConnection>& connection, 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 5ac0b973f1..1446eecc64 100644 --- a/libs/binder/RpcState.h +++ b/libs/binder/RpcState.h @@ -60,6 +60,8 @@ public: 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 2a44c7af04..0f8efd2391 100644 --- a/libs/binder/RpcWireFormat.h +++ b/libs/binder/RpcWireFormat.h @@ -37,9 +37,20 @@ struct RpcWireAddress { * 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 a8094dd081..40ff78cd37 100644 --- a/libs/binder/include/binder/RpcServer.h +++ b/libs/binder/include/binder/RpcServer.h @@ -105,6 +105,13 @@ public: 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 @@ private: 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 2101df85c4..1f7c0291a9 100644 --- a/libs/binder/include/binder/RpcSession.h +++ b/libs/binder/include/binder/RpcSession.h @@ -37,6 +37,10 @@ class RpcServer; 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 @@ public: 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 @@ private: 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 40ebd9c8bc..d5786bcbe1 100644 --- a/libs/binder/tests/binderRpcTest.cpp +++ b/libs/binder/tests/binderRpcTest.cpp @@ -47,6 +47,9 @@ using namespace std::chrono_literals; 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 @@ TEST(BinderRpc, SetExternalServer) { 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) \ |