libbinder: fuzzer for RPC server
Fuzz sending arbitrary commands to a single connection on a single RPC
server.
Future considerations:
- support fuzzing multiple clients w/ multiple connections
- better way to wait for a service to shutdown
Bug: 182938024
Test: fuzzer can run for a few minutes w/o crashing
Change-Id: Ifc1da577208ba1effb02fa33b0b30fd5cfd98d9e
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 06c3a42..99c907a 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -118,30 +118,33 @@
}
void RpcServer::join() {
+ while (true) {
+ (void)acceptOne();
+ }
+}
+
+bool RpcServer::acceptOne() {
LOG_ALWAYS_FATAL_IF(!mAgreedExperimental, "no!");
+ LOG_ALWAYS_FATAL_IF(mServer.get() == -1, "RpcServer must be setup to join.");
+
+ unique_fd clientFd(
+ TEMP_FAILURE_RETRY(accept4(mServer.get(), nullptr, nullptr /*length*/, SOCK_CLOEXEC)));
+
+ if (clientFd < 0) {
+ ALOGE("Could not accept4 socket: %s", strerror(errno));
+ return false;
+ }
+ LOG_RPC_DETAIL("accept4 on fd %d yields fd %d", mServer.get(), clientFd.get());
+
{
std::lock_guard<std::mutex> _l(mLock);
- LOG_ALWAYS_FATAL_IF(mServer.get() == -1, "RpcServer must be setup to join.");
+ std::thread thread =
+ std::thread(&RpcServer::establishConnection, this,
+ std::move(sp<RpcServer>::fromExisting(this)), std::move(clientFd));
+ mConnectingThreads[thread.get_id()] = std::move(thread);
}
- while (true) {
- unique_fd clientFd(TEMP_FAILURE_RETRY(
- accept4(mServer.get(), nullptr, nullptr /*length*/, SOCK_CLOEXEC)));
-
- if (clientFd < 0) {
- ALOGE("Could not accept4 socket: %s", strerror(errno));
- continue;
- }
- LOG_RPC_DETAIL("accept4 on fd %d yields fd %d", mServer.get(), clientFd.get());
-
- {
- std::lock_guard<std::mutex> _l(mLock);
- std::thread thread =
- std::thread(&RpcServer::establishConnection, this,
- std::move(sp<RpcServer>::fromExisting(this)), std::move(clientFd));
- mConnectingThreads[thread.get_id()] = std::move(thread);
- }
- }
+ return true;
}
std::vector<sp<RpcSession>> RpcServer::listSessions() {
@@ -154,6 +157,11 @@
return sessions;
}
+size_t RpcServer::numUninitializedSessions() {
+ std::lock_guard<std::mutex> _l(mLock);
+ return mConnectingThreads.size();
+}
+
void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clientFd) {
LOG_ALWAYS_FATAL_IF(this != server.get(), "Must pass same ownership object");
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index b1e5519..ec741a8 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -102,9 +102,16 @@
void join();
/**
+ * Accept one connection on this server. You must have at least one client
+ * session before calling this.
+ */
+ [[nodiscard]] bool acceptOne();
+
+ /**
* For debugging!
*/
std::vector<sp<RpcSession>> listSessions();
+ size_t numUninitializedSessions();
~RpcServer();
diff --git a/libs/binder/tests/rpc_fuzzer/Android.bp b/libs/binder/tests/rpc_fuzzer/Android.bp
new file mode 100644
index 0000000..1c75306
--- /dev/null
+++ b/libs/binder/tests/rpc_fuzzer/Android.bp
@@ -0,0 +1,40 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_fuzz {
+ name: "binder_rpc_fuzzer",
+ host_supported: true,
+
+ fuzz_config: {
+ cc: ["smoreland@google.com"],
+ },
+
+ srcs: [
+ "main.cpp",
+ ],
+ static_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+
+ target: {
+ android: {
+ shared_libs: [
+ "libbinder",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libbinder",
+ ],
+ },
+ },
+}
diff --git a/libs/binder/tests/rpc_fuzzer/main.cpp b/libs/binder/tests/rpc_fuzzer/main.cpp
new file mode 100644
index 0000000..3603ebe
--- /dev/null
+++ b/libs/binder/tests/rpc_fuzzer/main.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <binder/Binder.h>
+#include <binder/Parcel.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcSession.h>
+
+#include <sys/resource.h>
+#include <sys/un.h>
+
+namespace android {
+
+static const std::string kSock = std::string(getenv("TMPDIR") ?: "/tmp") +
+ "/binderRpcFuzzerSocket_" + std::to_string(getpid());
+
+size_t getHardMemoryLimit() {
+ struct rlimit limit;
+ CHECK(0 == getrlimit(RLIMIT_AS, &limit)) << errno;
+ return limit.rlim_max;
+}
+
+void setMemoryLimit(size_t cur, size_t max) {
+ const struct rlimit kLimit = {
+ .rlim_cur = cur,
+ .rlim_max = max,
+ };
+ CHECK(0 == setrlimit(RLIMIT_AS, &kLimit)) << errno;
+}
+
+class SomeBinder : public BBinder {
+ status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) {
+ (void)flags;
+
+ if ((code & 1) == 0) {
+ sp<IBinder> binder;
+ (void)data.readStrongBinder(&binder);
+ if (binder != nullptr) {
+ (void)binder->pingBinder();
+ }
+ }
+ if ((code & 2) == 0) {
+ (void)data.readInt32();
+ }
+ if ((code & 4) == 0) {
+ (void)reply->writeStrongBinder(sp<BBinder>::make());
+ }
+
+ return OK;
+ }
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size > 50000) return 0;
+
+ unlink(kSock.c_str());
+
+ sp<RpcServer> server = RpcServer::make();
+ server->setRootObject(sp<SomeBinder>::make());
+ server->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
+ CHECK(server->setupUnixDomainServer(kSock.c_str()));
+
+ static constexpr size_t kMemLimit = 1llu * 1024 * 1024 * 1024;
+ size_t hardLimit = getHardMemoryLimit();
+ setMemoryLimit(std::min(kMemLimit, hardLimit), hardLimit);
+
+ std::thread serverThread([=] { (void)server->acceptOne(); });
+
+ sockaddr_un addr{
+ .sun_family = AF_UNIX,
+ };
+ CHECK_LT(kSock.size(), sizeof(addr.sun_path));
+ memcpy(&addr.sun_path, kSock.c_str(), kSock.size());
+
+ base::unique_fd clientFd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
+ CHECK_NE(clientFd.get(), -1);
+ CHECK_EQ(0,
+ TEMP_FAILURE_RETRY(
+ connect(clientFd.get(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr))))
+ << strerror(errno);
+
+ serverThread.join();
+
+ // TODO(b/182938024): fuzz multiple sessions, instead of just one
+
+#if 0
+ // make fuzzer more productive locally by forcing it to create a new session
+ int32_t id = -1;
+ CHECK(base::WriteFully(clientFd, &id, sizeof(id)));
+#endif
+
+ CHECK(base::WriteFully(clientFd, data, size));
+
+ clientFd.reset();
+
+ // TODO(b/185167543): better way to force a server to shutdown
+ while (!server->listSessions().empty() && server->numUninitializedSessions()) {
+ usleep(1);
+ }
+
+ setMemoryLimit(hardLimit, hardLimit);
+
+ return 0;
+}
+
+} // namespace android