From 8010cbb3c706c9448c2820ca97e238b67c6b31d6 Mon Sep 17 00:00:00 2001 From: Frederick Mayle Date: Mon, 14 Oct 2024 17:12:30 -0700 Subject: binder: fix FD handling in continueWrite Only close FDs within the truncated part of the parcel. This change also fixes a bug where a parcel truncated into the middle of an object would not properly free that object. That could have resulted in an OOB access in `Parcel::truncateRpcObjects`, so more bounds checking is added. The new tests show how to reproduce the bug by appending to or partially truncating Parcels owned by the kernel. Two cases are disabled because of a bug in the Parcel fdsan code (b/370824489). Cherry-pick notes: Add truncateFileDescriptors method instead of modifying closeFileDescriptors to avoid API change errors. Large diffs in this branch because it didn't have the disruptive RPC FD support, main diff is that the closeFileDescriptors call is move out of the mOwner implementation. Tweaked the test to support older C++ and android-base libs. Flag: EXEMPT bugfix Ignore-AOSP-First: security fix Bug: 239222407, 359179312 Test: atest binderLibTest Merged-In: Iadf7e2e98e3eb97c56ec2fed2b49d1e6492af9a3 Change-Id: Iadf7e2e98e3eb97c56ec2fed2b49d1e6492af9a3 --- libs/binder/IPCThreadState.cpp | 3 +- libs/binder/Parcel.cpp | 20 ++++- libs/binder/include/binder/Parcel.h | 3 + libs/binder/tests/Android.bp | 4 + libs/binder/tests/binderLibTest.cpp | 163 ++++++++++++++++++++++++++++++++++++ 5 files changed, 189 insertions(+), 4 deletions(-) diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp index 445df9eeff..b08568a61c 100644 --- a/libs/binder/IPCThreadState.cpp +++ b/libs/binder/IPCThreadState.cpp @@ -1419,7 +1419,7 @@ status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) { return ret; } -void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, +void IPCThreadState::freeBuffer(Parcel* /*parcel*/, const uint8_t* data, size_t /*dataSize*/, const binder_size_t* /*objects*/, size_t /*objectsSize*/) @@ -1429,7 +1429,6 @@ void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, alog << "Writing BC_FREE_BUFFER for " << data << endl; } ALOG_ASSERT(data != NULL, "Called with NULL data"); - if (parcel != nullptr) parcel->closeFileDescriptors(); IPCThreadState* state = self(); state->mOut.writeInt32(BC_FREE_BUFFER); state->mOut.writePointer((uintptr_t)data); diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index 617708f3d4..0035f68446 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -2076,11 +2076,15 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const void Parcel::closeFileDescriptors() { + truncateFileDescriptors(0); +} + +void Parcel::truncateFileDescriptors(size_t newObjectsSize) { size_t i = mObjectsSize; if (i > 0) { //ALOGI("Closing file descriptors for %zu objects...", i); } - while (i > 0) { + while (i > newObjectsSize) { i--; const flat_binder_object* flat = reinterpret_cast(mData+mObjects[i]); @@ -2228,6 +2232,7 @@ void Parcel::freeDataNoInit() if (mOwner) { LOG_ALLOC("Parcel %p: freeing other owner data", this); //ALOGI("Freeing data ref of %p (pid=%d)", this, getpid()); + closeFileDescriptors(); mOwner(this, mData, mDataSize, mObjects, mObjectsSize); } else { LOG_ALLOC("Parcel %p: freeing allocated data", this); @@ -2343,8 +2348,9 @@ status_t Parcel::continueWrite(size_t desired) if (desired == 0) { objectsSize = 0; } else { + validateReadData(mDataSize); // hack to sort the objects while (objectsSize > 0) { - if (mObjects[objectsSize-1] < desired) + if (mObjects[objectsSize-1] + sizeof(flat_binder_object) <= desired) break; objectsSize--; } @@ -2389,8 +2395,18 @@ status_t Parcel::continueWrite(size_t desired) } if (objects && mObjects) { memcpy(objects, mObjects, objectsSize*sizeof(binder_size_t)); + // All FDs are owned when `mOwner`, even when `cookie == 0`. When + // we switch to `!mOwner`, we need to explicitly mark the FDs as + // owned. + for (size_t i = 0; i < objectsSize; i++) { + flat_binder_object* flat = reinterpret_cast(data + objects[i]); + if (flat->hdr.type == BINDER_TYPE_FD) { + flat->cookie = 1; + } + } } //ALOGI("Freeing data ref of %p (pid=%d)", this, getpid()); + truncateFileDescriptors(objectsSize); mOwner(this, mData, mDataSize, mObjects, mObjectsSize); mOwner = nullptr; diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h index 5aaaa0c3d2..ef8014367e 100644 --- a/libs/binder/include/binder/Parcel.h +++ b/libs/binder/include/binder/Parcel.h @@ -528,6 +528,9 @@ public: void print(TextOutput& to, uint32_t flags = 0) const; private: + // Close all file descriptors in the parcel at object positions >= newObjectsSize. + __attribute__((__visibility__("hidden"))) void truncateFileDescriptors(size_t newObjectsSize); + typedef void (*release_func)(Parcel* parcel, const uint8_t* data, size_t dataSize, const binder_size_t* objects, size_t objectsSize); diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp index fb84f04fde..71e9d3d39b 100644 --- a/libs/binder/tests/Android.bp +++ b/libs/binder/tests/Android.bp @@ -60,7 +60,9 @@ cc_test { defaults: ["binder_test_defaults"], srcs: ["binderLibTest.cpp"], shared_libs: [ + "libbase", "libbinder", + "liblog", "libutils", ], static_libs: [ @@ -101,7 +103,9 @@ cc_test { srcs: ["binderLibTest.cpp"], shared_libs: [ + "libbase", "libbinder", + "liblog", "libutils", ], static_libs: [ diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp index 3db0a8e793..bc6ec2f85f 100644 --- a/libs/binder/tests/binderLibTest.cpp +++ b/libs/binder/tests/binderLibTest.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #include #include #include @@ -33,6 +35,7 @@ #include #include +#include #include #include "../binder_module.h" @@ -41,6 +44,7 @@ #define ARRAY_SIZE(array) (sizeof array / sizeof array[0]) using namespace android; +using android::base::unique_fd; using testing::Not; // e.g. EXPECT_THAT(expr, StatusEq(OK)) << "additional message"; @@ -85,6 +89,8 @@ enum BinderLibTestTranscationCode { BINDER_LIB_TEST_LINK_DEATH_TRANSACTION, BINDER_LIB_TEST_WRITE_FILE_TRANSACTION, BINDER_LIB_TEST_WRITE_PARCEL_FILE_DESCRIPTOR_TRANSACTION, + BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, + BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, BINDER_LIB_TEST_EXIT_TRANSACTION, BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION, BINDER_LIB_TEST_GET_PTR_SIZE_TRANSACTION, @@ -404,6 +410,35 @@ class TestDeathRecipient : public IBinder::DeathRecipient, public BinderLibTestE }; }; +ssize_t countFds() { + DIR* dir = opendir("/proc/self/fd/"); + if (dir == nullptr) return -1; + ssize_t ret = 0; + dirent* ent; + while ((ent = readdir(dir)) != nullptr) ret++; + closedir(dir); + return ret; +} + +struct FdLeakDetector { + int startCount; + + FdLeakDetector() { + // This log statement is load bearing. We have to log something before + // counting FDs to make sure the logging system is initialized, otherwise + // the sockets it opens will look like a leak. + ALOGW("FdLeakDetector counting FDs."); + startCount = countFds(); + } + ~FdLeakDetector() { + int endCount = countFds(); + if (startCount != endCount) { + ADD_FAILURE() << "fd count changed (" << startCount << " -> " << endCount + << ") fd leak?"; + } + } +}; + TEST_F(BinderLibTest, NopTransaction) { Parcel data, reply; EXPECT_THAT(m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION, data, &reply), @@ -805,6 +840,100 @@ TEST_F(BinderLibTest, PassParcelFileDescriptor) { EXPECT_EQ(0, read(read_end.get(), readbuf.data(), datasize)); } +TEST_F(BinderLibTest, RecvOwnedFileDescriptors) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, + &reply)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndWriteInt) { + GTEST_SKIP() << "triggers fdsan false positive: b/370824489"; + + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, + &reply)); + reply.setDataPosition(reply.dataSize()); + reply.writeInt32(0); + reply.setDataPosition(0); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndTruncate) { + GTEST_SKIP() << "triggers fdsan false positive: b/370824489"; + + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, + &reply)); + reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b)); +} + +TEST_F(BinderLibTest, RecvUnownedFileDescriptors) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, + &reply)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndWriteInt) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, + &reply)); + reply.setDataPosition(reply.dataSize()); + reply.writeInt32(0); + reply.setDataPosition(0); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); +} + +// Used to trigger fdsan error (b/239222407). +TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndTruncate) { + FdLeakDetector fd_leak_detector; + + Parcel data; + Parcel reply; + EXPECT_EQ(NO_ERROR, + m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, + &reply)); + reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object)); + unique_fd a, b; + EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); + EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b)); +} + TEST_F(BinderLibTest, PromoteLocal) { sp strong = new BBinder(); wp weak = strong; @@ -1438,6 +1567,40 @@ class BinderLibTestService : public BBinder if (ret != size) return UNKNOWN_ERROR; return NO_ERROR; } + case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION: { + unique_fd fd1(memfd_create("memfd1", MFD_CLOEXEC)); + if (!fd1.ok()) { + PLOG(ERROR) << "memfd_create failed"; + return UNKNOWN_ERROR; + } + unique_fd fd2(memfd_create("memfd2", MFD_CLOEXEC)); + if (!fd2.ok()) { + PLOG(ERROR) << "memfd_create failed"; + return UNKNOWN_ERROR; + } + status_t ret; + ret = reply->writeFileDescriptor(fd1.release(), true); + if (ret != NO_ERROR) { + return ret; + } + ret = reply->writeFileDescriptor(fd2.release(), true); + if (ret != NO_ERROR) { + return ret; + } + return NO_ERROR; + } + case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION: { + status_t ret; + ret = reply->writeFileDescriptor(STDOUT_FILENO, false); + if (ret != NO_ERROR) { + return ret; + } + ret = reply->writeFileDescriptor(STDERR_FILENO, false); + if (ret != NO_ERROR) { + return ret; + } + return NO_ERROR; + } case BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION: alarm(10); return NO_ERROR; -- cgit v1.2.3-59-g8ed1b