diff options
author | 2024-02-08 07:08:45 +0000 | |
---|---|---|
committer | 2024-02-08 07:08:45 +0000 | |
commit | a8021fc84a162e90607aa7746065d17addbf7807 (patch) | |
tree | b6e0f5d8aadcf02e2edd45b0e1eb3f1a40af6698 | |
parent | eee1cf5f26a34d7444990c88a9db33705a514b08 (diff) | |
parent | 76b1815fc7ca5ed5cf4b413ea8c05f203a92c00f (diff) |
Merge "Replay recorded fds and binders in fuzzers" into main
-rw-r--r-- | libs/binder/RecordedTransaction.cpp | 59 | ||||
-rw-r--r-- | libs/binder/include/binder/Parcel.h | 6 | ||||
-rw-r--r-- | libs/binder/include/binder/RecordedTransaction.h | 6 | ||||
-rw-r--r-- | libs/binder/tests/Android.bp | 7 | ||||
-rw-r--r-- | libs/binder/tests/IBinderRecordReplayTest.aidl | 6 | ||||
-rw-r--r-- | libs/binder/tests/binderRecordReplayTest.cpp | 86 | ||||
-rw-r--r-- | libs/binder/tests/parcel_fuzzer/Android.bp | 12 | ||||
-rw-r--r-- | libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h | 14 | ||||
-rw-r--r-- | libs/binder/tests/parcel_fuzzer/random_binder.cpp | 66 | ||||
-rw-r--r-- | libs/binder/tests/parcel_fuzzer/random_parcel.cpp | 4 | ||||
-rw-r--r-- | libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp | 180 |
11 files changed, 377 insertions, 69 deletions
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp index 525ba2e9b9..de2a69f7cf 100644 --- a/libs/binder/RecordedTransaction.cpp +++ b/libs/binder/RecordedTransaction.cpp @@ -114,8 +114,8 @@ static_assert(PADDING8(8) == 0); RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept { mData = t.mData; - mSent.setData(t.getDataParcel().data(), t.getDataParcel().dataSize()); - mReply.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize()); + mSentDataOnly.setData(t.getDataParcel().data(), t.getDataParcel().dataSize()); + mReplyDataOnly.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize()); } std::optional<RecordedTransaction> RecordedTransaction::fromDetails( @@ -136,12 +136,21 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails( return std::nullopt; } - if (t.mSent.setData(dataParcel.data(), dataParcel.dataBufferSize()) != android::NO_ERROR) { + if (const auto* kernelFields = dataParcel.maybeKernelFields()) { + for (size_t i = 0; i < kernelFields->mObjectsSize; i++) { + uint64_t offset = kernelFields->mObjects[i]; + t.mData.mSentObjectData.push_back(offset); + } + } + + if (t.mSentDataOnly.setData(dataParcel.data(), dataParcel.dataBufferSize()) != + android::NO_ERROR) { ALOGE("Failed to set sent parcel data."); return std::nullopt; } - if (t.mReply.setData(replyParcel.data(), replyParcel.dataBufferSize()) != android::NO_ERROR) { + if (t.mReplyDataOnly.setData(replyParcel.data(), replyParcel.dataBufferSize()) != + android::NO_ERROR) { ALOGE("Failed to set reply parcel data."); return std::nullopt; } @@ -154,6 +163,7 @@ enum { DATA_PARCEL_CHUNK = 2, REPLY_PARCEL_CHUNK = 3, INTERFACE_NAME_CHUNK = 4, + DATA_PARCEL_OBJECT_CHUNK = 5, END_CHUNK = 0x00ffffff, }; @@ -265,21 +275,30 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd break; } case DATA_PARCEL_CHUNK: { - if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap), - chunk.dataSize) != android::NO_ERROR) { + if (t.mSentDataOnly.setData(reinterpret_cast<const unsigned char*>(payloadMap), + chunk.dataSize) != android::NO_ERROR) { ALOGE("Failed to set sent parcel data."); return std::nullopt; } break; } case REPLY_PARCEL_CHUNK: { - if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap), - chunk.dataSize) != android::NO_ERROR) { + if (t.mReplyDataOnly.setData(reinterpret_cast<const unsigned char*>(payloadMap), + chunk.dataSize) != android::NO_ERROR) { ALOGE("Failed to set reply parcel data."); return std::nullopt; } break; } + case DATA_PARCEL_OBJECT_CHUNK: { + const uint64_t* objects = reinterpret_cast<const uint64_t*>(payloadMap); + size_t metaDataSize = (chunk.dataSize / sizeof(uint64_t)); + ALOGI("Total objects found in saved parcel %zu", metaDataSize); + for (size_t index = 0; index < metaDataSize; ++index) { + t.mData.mSentObjectData.push_back(objects[index]); + } + break; + } case END_CHUNK: break; default: @@ -343,14 +362,26 @@ android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const { return UNKNOWN_ERROR; } - if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataBufferSize(), mSent.data())) { + if (NO_ERROR != + writeChunk(fd, DATA_PARCEL_CHUNK, mSentDataOnly.dataBufferSize(), mSentDataOnly.data())) { ALOGE("Failed to write sent Parcel to fd %d", fd.get()); return UNKNOWN_ERROR; } - if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataBufferSize(), mReply.data())) { + + if (NO_ERROR != + writeChunk(fd, REPLY_PARCEL_CHUNK, mReplyDataOnly.dataBufferSize(), + mReplyDataOnly.data())) { ALOGE("Failed to write reply Parcel to fd %d", fd.get()); return UNKNOWN_ERROR; } + + if (NO_ERROR != + writeChunk(fd, DATA_PARCEL_OBJECT_CHUNK, mData.mSentObjectData.size() * sizeof(uint64_t), + reinterpret_cast<const uint8_t*>(mData.mSentObjectData.data()))) { + ALOGE("Failed to write sent parcel object metadata to fd %d", fd.get()); + return UNKNOWN_ERROR; + } + if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) { ALOGE("Failed to write end chunk to fd %d", fd.get()); return UNKNOWN_ERROR; @@ -384,10 +415,14 @@ uint32_t RecordedTransaction::getVersion() const { return mData.mHeader.version; } +const std::vector<uint64_t>& RecordedTransaction::getObjectOffsets() const { + return mData.mSentObjectData; +} + const Parcel& RecordedTransaction::getDataParcel() const { - return mSent; + return mSentDataOnly; } const Parcel& RecordedTransaction::getReplyParcel() const { - return mReply; + return mReplyDataOnly; } diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h index 09da6e3c4a..d7096d8a75 100644 --- a/libs/binder/include/binder/Parcel.h +++ b/libs/binder/include/binder/Parcel.h @@ -55,6 +55,9 @@ class String8; class TextOutput; namespace binder { class Status; +namespace debug { +class RecordedTransaction; +} } class Parcel { @@ -1443,6 +1446,9 @@ private: // TODO(b/202029388): Remove 'getBlobAshmemSize' once no prebuilts reference // this size_t getBlobAshmemSize() const; + + // Needed so that we can save object metadata to the disk + friend class android::binder::debug::RecordedTransaction; }; // --------------------------------------------------------------------------- diff --git a/libs/binder/include/binder/RecordedTransaction.h b/libs/binder/include/binder/RecordedTransaction.h index 505c1992b9..f0bee7f39e 100644 --- a/libs/binder/include/binder/RecordedTransaction.h +++ b/libs/binder/include/binder/RecordedTransaction.h @@ -50,6 +50,7 @@ public: uint32_t getVersion() const; const Parcel& getDataParcel() const; const Parcel& getReplyParcel() const; + const std::vector<uint64_t>& getObjectOffsets() const; private: RecordedTransaction() = default; @@ -75,10 +76,11 @@ private: struct MovableData { // movable TransactionHeader mHeader; std::string mInterfaceName; + std::vector<uint64_t> mSentObjectData; /* Object Offsets */ }; MovableData mData; - Parcel mSent; - Parcel mReply; + Parcel mSentDataOnly; + Parcel mReplyDataOnly; }; } // namespace binder::debug diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp index dd2be94a76..aba2319485 100644 --- a/libs/binder/tests/Android.bp +++ b/libs/binder/tests/Android.bp @@ -69,10 +69,14 @@ cc_test { cc_test { name: "binderRecordReplayTest", srcs: ["binderRecordReplayTest.cpp"], + cflags: [ + "-DBINDER_WITH_KERNEL_IPC", + ], shared_libs: [ "libbinder", "libcutils", "libutils", + "liblog", ], static_libs: [ "binderRecordReplayTestIface-cpp", @@ -96,6 +100,9 @@ aidl_interface { enabled: true, platform_apis: true, }, + ndk: { + enabled: false, + }, }, } diff --git a/libs/binder/tests/IBinderRecordReplayTest.aidl b/libs/binder/tests/IBinderRecordReplayTest.aidl index bd6b03c6e0..29267e91c6 100644 --- a/libs/binder/tests/IBinderRecordReplayTest.aidl +++ b/libs/binder/tests/IBinderRecordReplayTest.aidl @@ -69,4 +69,10 @@ interface IBinderRecordReplayTest { void setSingleDataParcelableArray(in SingleDataParcelable[] input); SingleDataParcelable[] getSingleDataParcelableArray(); + + void setBinder(in IBinder binder); + IBinder getBinder(); + + void setFileDescriptor(in FileDescriptor fd); + FileDescriptor getFileDescriptor(); } diff --git a/libs/binder/tests/binderRecordReplayTest.cpp b/libs/binder/tests/binderRecordReplayTest.cpp index 73c0a94ce2..b975fad2c9 100644 --- a/libs/binder/tests/binderRecordReplayTest.cpp +++ b/libs/binder/tests/binderRecordReplayTest.cpp @@ -24,7 +24,10 @@ #include <binder/RecordedTransaction.h> #include <binder/unique_fd.h> +#include <cutils/ashmem.h> + #include <fuzzbinder/libbinder_driver.h> +#include <fuzzbinder/random_binder.h> #include <fuzzer/FuzzedDataProvider.h> #include <fuzzseeds/random_parcel_seeds.h> @@ -37,6 +40,7 @@ using namespace android; using android::generateSeedsFromRecording; +using android::RandomBinder; using android::binder::borrowed_fd; using android::binder::Status; using android::binder::unique_fd; @@ -44,6 +48,7 @@ using android::binder::debug::RecordedTransaction; using parcelables::SingleDataParcelable; const String16 kServerName = String16("binderRecordReplay"); +extern std::string kRandomInterfaceName; #define GENERATE_GETTER_SETTER_PRIMITIVE(name, T) \ Status set##name(T input) { \ @@ -81,6 +86,7 @@ public: GENERATE_GETTER_SETTER(String, String16); GENERATE_GETTER_SETTER(SingleDataParcelable, SingleDataParcelable); + GENERATE_GETTER_SETTER(Binder, sp<IBinder>); GENERATE_GETTER_SETTER(BooleanArray, std::vector<bool>); GENERATE_GETTER_SETTER(ByteArray, std::vector<uint8_t>); @@ -91,12 +97,22 @@ public: GENERATE_GETTER_SETTER(DoubleArray, std::vector<double>); GENERATE_GETTER_SETTER(StringArray, std::vector<::android::String16>); GENERATE_GETTER_SETTER(SingleDataParcelableArray, std::vector<SingleDataParcelable>); + + Status setFileDescriptor(unique_fd input) { + mFd = std::move(unique_fd(dup(input))); + return Status::ok(); + } + + Status getFileDescriptor(unique_fd* output) { + *output = std::move(unique_fd(dup(mFd))); + return Status::ok(); + } + unique_fd mFd; }; std::vector<uint8_t> retrieveData(borrowed_fd fd) { struct stat fdStat; EXPECT_TRUE(fstat(fd.get(), &fdStat) != -1); - EXPECT_TRUE(fdStat.st_size != 0); std::vector<uint8_t> buffer(fdStat.st_size); auto readResult = android::base::ReadFully(fd, buffer.data(), fdStat.st_size); @@ -115,6 +131,7 @@ void replayFuzzService(const sp<BpBinder>& binder, const RecordedTransaction& tr // Read the data which has been written to seed corpus ASSERT_EQ(0, lseek(seedFd.get(), 0, SEEK_SET)); std::vector<uint8_t> seedData = retrieveData(seedFd); + EXPECT_TRUE(seedData.size() != 0); // use fuzzService to replay the corpus FuzzedDataProvider provider(seedData.data(), seedData.size()); @@ -148,7 +165,14 @@ public: template <typename T, typename U> void recordReplay(Status (IBinderRecordReplayTest::*set)(T), U recordedValue, Status (IBinderRecordReplayTest::*get)(U*), U changedValue) { - auto replayFunctions = {&replayBinder, &replayFuzzService}; + using ReplayFunc = decltype(&replayFuzzService); + vector<ReplayFunc> replayFunctions = {&replayFuzzService}; + if (!std::is_same_v<U, unique_fd> && !std::is_same_v<U, sp<IBinder>>) { + // Parcel retrieved from record replay doesn't have object information. use it for + // replaying primitive types only. + replayFunctions.push_back(&replayBinder); + } + for (auto replayFunc : replayFunctions) { unique_fd fd(open("/data/local/tmp/binderRecordReplayTest.rec", O_RDWR | O_CREAT | O_CLOEXEC, 0666)); @@ -156,7 +180,7 @@ public: // record a transaction mBpBinder->startRecordingBinder(fd); - auto status = (*mInterface.*set)(recordedValue); + auto status = (*mInterface.*set)(std::move(recordedValue)); EXPECT_TRUE(status.isOk()); mBpBinder->stopRecordingBinder(); @@ -164,16 +188,22 @@ public: U output; status = (*mInterface.*get)(&output); EXPECT_TRUE(status.isOk()); - EXPECT_EQ(output, recordedValue); + + // Expect this equal only if types are primitives + if (!std::is_same_v<U, unique_fd> && !std::is_same_v<U, sp<IBinder>>) { + EXPECT_EQ(output, recordedValue); + } // write over the existing state - status = (*mInterface.*set)(changedValue); + status = (*mInterface.*set)(std::move(changedValue)); EXPECT_TRUE(status.isOk()); status = (*mInterface.*get)(&output); EXPECT_TRUE(status.isOk()); - EXPECT_EQ(output, changedValue); + if (!std::is_same_v<U, unique_fd> && !std::is_same_v<U, sp<IBinder>>) { + EXPECT_EQ(output, changedValue); + } // replay transaction ASSERT_EQ(0, lseek(fd.get(), 0, SEEK_SET)); @@ -186,7 +216,23 @@ public: status = (*mInterface.*get)(&output); EXPECT_TRUE(status.isOk()); - EXPECT_EQ(output, recordedValue); + + // FDs and binders will be replaced with random fd and random binders + if constexpr (std::is_same_v<U, unique_fd>) { + // verify that replayed fd is /dev/null. This is being replayed from random_fd.cpp + // and choosing /dav/null while generating seed in binder2corpus + std::string fdPath = "/proc/self/fd/" + std::to_string(output.get()); + char path[PATH_MAX]; + ASSERT_GT(readlink(fdPath.c_str(), path, sizeof(path)), 0); + EXPECT_EQ(strcmp("/dev/null", path), 0); + } else if constexpr (std::is_same_v<U, sp<IBinder>>) { + // This is binder is replayed from random_binder.cpp using seed data which writes + // this interface. + EXPECT_EQ(String16(kRandomInterfaceName.c_str(), kRandomInterfaceName.size()), + output->getInterfaceDescriptor()); + } else { + ASSERT_EQ(recordedValue, output); + } } } @@ -319,6 +365,32 @@ TEST_F(BinderRecordReplayTest, ReplaySingleDataParcelableArray) { &IBinderRecordReplayTest::getSingleDataParcelableArray, changed); } +TEST_F(BinderRecordReplayTest, ReplayBinder) { + vector<uint8_t> data = {0x8A, 0x19, 0x0D, 0x44, 0x37, 0x0D, 0x38, 0x5E, 0x9B, 0xAA, 0xF3, 0xDA}; + sp<IBinder> saved = new RandomBinder(String16("random_interface"), std::move(data)); + sp<IBinder> changed = IInterface::asBinder(defaultServiceManager()); + recordReplay(&IBinderRecordReplayTest::setBinder, saved, &IBinderRecordReplayTest::getBinder, + changed); +} + +TEST_F(BinderRecordReplayTest, ReplayFd) { + // Write something to both fds we are setting + unique_fd saved(open("/data/local/tmp/test_fd", O_RDWR | O_CREAT | O_CLOEXEC, 0666)); + std::string contentSaved = "This will be never read again for recorded fd!"; + CHECK(android::base::WriteFully(saved, contentSaved.data(), contentSaved.size())) + << saved.get(); + + unique_fd changed(open("/data/local/tmp/test_des", O_RDWR | O_CREAT | O_CLOEXEC, 0666)); + std::string contentChanged = "This will be never read again from changed fd!"; + CHECK(android::base::WriteFully(changed, contentChanged.data(), contentChanged.size())) + << changed.get(); + + // When fds are replayed, it will be replaced by /dev/null..reading from it should yield + // null data + recordReplay(&IBinderRecordReplayTest::setFileDescriptor, std::move(unique_fd(dup(saved))), + &IBinderRecordReplayTest::getFileDescriptor, std::move(unique_fd(dup(changed)))); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp index 83db6c9b6d..fbab8f08a9 100644 --- a/libs/binder/tests/parcel_fuzzer/Android.bp +++ b/libs/binder/tests/parcel_fuzzer/Android.bp @@ -131,6 +131,13 @@ cc_library { "libcutils", "libutils", ], + static_libs: [ + "libbinder_random_parcel", + ], + include_dirs: [ + "bionic/libc/kernel/android/uapi/", + "bionic/libc/kernel/uapi/", + ], local_include_dirs: [ "include_random_parcel_seeds", ], @@ -140,8 +147,12 @@ cc_library { cc_binary_host { name: "binder2corpus", static_libs: [ + "libbinder_random_parcel", "libbinder_random_parcel_seeds", ], + cflags: [ + "-DBINDER_WITH_KERNEL_IPC", + ], srcs: [ "binder2corpus/binder2corpus.cpp", ], @@ -149,5 +160,6 @@ cc_binary_host { "libbase", "libbinder", "libutils", + "libcutils", ], } diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h index 8fc9263b98..7a1688b7b7 100644 --- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h +++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h @@ -16,11 +16,25 @@ #pragma once +#include <binder/Binder.h> #include <binder/IBinder.h> #include <fuzzer/FuzzedDataProvider.h> namespace android { +class RandomBinder : public BBinder { +public: + RandomBinder(const String16& descriptor, std::vector<uint8_t>&& bytes); + const String16& getInterfaceDescriptor() const override; + status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; + +private: + String16 mDescriptor; + // note may not all be used + std::vector<uint8_t> mBytes; + FuzzedDataProvider mProvider; +}; + // Get a random binder object for use in fuzzing. // // May return nullptr. diff --git a/libs/binder/tests/parcel_fuzzer/random_binder.cpp b/libs/binder/tests/parcel_fuzzer/random_binder.cpp index 8a1fecb212..f41c35bfbe 100644 --- a/libs/binder/tests/parcel_fuzzer/random_binder.cpp +++ b/libs/binder/tests/parcel_fuzzer/random_binder.cpp @@ -21,56 +21,52 @@ #include <binder/IInterface.h> #include <binder/IServiceManager.h> +size_t kRandomInterfaceLength = 50; namespace android { -class RandomBinder : public BBinder { -public: - RandomBinder(const String16& descriptor, std::vector<uint8_t>&& bytes) - : mDescriptor(descriptor), - mBytes(std::move(bytes)), - mProvider(mBytes.data(), mBytes.size()) {} - const String16& getInterfaceDescriptor() const override { return mDescriptor; } +RandomBinder::RandomBinder(const String16& descriptor, std::vector<uint8_t>&& bytes) + : mDescriptor(descriptor), + mBytes(std::move(bytes)), + mProvider(mBytes.data(), mBytes.size()) {} - status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override { - (void)code; - (void)data; - (void)reply; - (void)flags; // note - for maximum coverage even ignore if oneway +const String16& RandomBinder::getInterfaceDescriptor() const { + return mDescriptor; +} - if (mProvider.ConsumeBool()) { - return mProvider.ConsumeIntegral<status_t>(); - } +status_t RandomBinder::onTransact(uint32_t code, const Parcel& data, Parcel* reply, + uint32_t flags) { + (void)code; + (void)data; + (void)reply; + (void)flags; // note - for maximum coverage even ignore if oneway - if (reply == nullptr) return OK; + if (mProvider.ConsumeBool()) { + return mProvider.ConsumeIntegral<status_t>(); + } - // TODO: things we could do to increase state space - // - also pull FDs and binders from 'data' - // (optionally combine these into random parcel 'options') - // - also pull FDs and binders from random parcel 'options' - RandomParcelOptions options; + if (reply == nullptr) return OK; - // random output - std::vector<uint8_t> subData = mProvider.ConsumeBytes<uint8_t>( - mProvider.ConsumeIntegralInRange<size_t>(0, mProvider.remaining_bytes())); - fillRandomParcel(reply, FuzzedDataProvider(subData.data(), subData.size()), &options); + // TODO: things we could do to increase state space + // - also pull FDs and binders from 'data' + // (optionally combine these into random parcel 'options') + // - also pull FDs and binders from random parcel 'options' + RandomParcelOptions options; - return OK; - } + // random output + std::vector<uint8_t> subData = mProvider.ConsumeBytes<uint8_t>( + mProvider.ConsumeIntegralInRange<size_t>(0, mProvider.remaining_bytes())); + fillRandomParcel(reply, FuzzedDataProvider(subData.data(), subData.size()), &options); -private: - String16 mDescriptor; - - // note may not all be used - std::vector<uint8_t> mBytes; - FuzzedDataProvider mProvider; -}; + return OK; +} sp<IBinder> getRandomBinder(FuzzedDataProvider* provider) { auto makeFunc = provider->PickValueInArray<const std::function<sp<IBinder>()>>({ [&]() { // descriptor is the length of a class name, e.g. // "some.package.Foo" - std::string str = provider->ConsumeRandomLengthString(100 /*max length*/); + std::string str = + provider->ConsumeRandomLengthString(kRandomInterfaceLength /*max length*/); // arbitrarily consume remaining data to create a binder that can return // random results - coverage guided fuzzer should ensure all of the remaining diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp index 4e58dc4899..62b84330cd 100644 --- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp +++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp @@ -73,7 +73,7 @@ void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOpti return; } - if (options->extraFds.size() > 0 && provider.ConsumeBool()) { + if (provider.ConsumeBool() && options->extraFds.size() > 0) { const unique_fd& fd = options->extraFds.at( provider.ConsumeIntegralInRange<size_t>(0, options->extraFds.size() - @@ -102,7 +102,7 @@ void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOpti } sp<IBinder> binder; - if (options->extraBinders.size() > 0 && provider.ConsumeBool()) { + if (provider.ConsumeBool() && options->extraBinders.size() > 0) { binder = options->extraBinders.at( provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp index 7b3c80642b..fd9777a916 100644 --- a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp +++ b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp @@ -14,16 +14,26 @@ * limitations under the License. */ +#include <linux/android/binder.h> + #include <android-base/logging.h> +#include <binder/Parcel.h> #include <binder/RecordedTransaction.h> #include <fuzzseeds/random_parcel_seeds.h> +#include <stack> +#include <string> #include "../../file.h" using android::binder::borrowed_fd; using android::binder::WriteFully; +using std::stack; + +extern size_t kRandomInterfaceLength; +// Keep this in sync with max_length in random_binder.cpp while creating a RandomBinder +std::string kRandomInterfaceName(kRandomInterfaceLength, 'i'); namespace android { namespace impl { @@ -66,6 +76,162 @@ void writeReversedBuffer(std::vector<uint8_t>& integralBuffer, T val) { } // namespace impl +struct ProviderMetadata { + size_t position; + size_t value; + + ProviderMetadata() { + value = 0; + position = 0; + } +}; + +// Assuming current seed path is inside the fillRandomParcel function, start of the loop. +void writeRandomBinder(borrowed_fd fd, vector<uint8_t>& fillParcelBuffer, + stack<ProviderMetadata>& remainingPositions) { + // Choose 2 index in array + size_t fillFuncIndex = 2; + impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2), + fillFuncIndex); + + // navigate to getRandomBinder. provide consume bool false + bool flag = false; + impl::writeReversedBuffer(fillParcelBuffer, flag); + + // selecting RandomBinder, other binders in the list are not recorded as KernelObjects + size_t randomBinderIndex = 0; + impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2), + randomBinderIndex); + + // write random string of length 100 in actual buffer array. + CHECK(WriteFully(fd, kRandomInterfaceName.c_str(), kRandomInterfaceName.size())) << fd.get(); + + // These will be bytes which are used inside of RandomBinder + // simplest path for these bytes is going to be consume bool -> return random status + vector<uint8_t> randomBinderBuffer; + + bool returnRandomInt = true; + impl::writeReversedBuffer(randomBinderBuffer, returnRandomInt); + + status_t randomStatus = 0; + impl::writeReversedBuffer(randomBinderBuffer, randomStatus); + + // write integral in range to consume bytes for random binder + ProviderMetadata providerData; + providerData.position = fillParcelBuffer.size(); + providerData.value = randomBinderBuffer.size(); + remainingPositions.push(providerData); + + // Write to fd + CHECK(WriteFully(fd, randomBinderBuffer.data(), randomBinderBuffer.size())) << fd.get(); +} + +// Assuming current seed path is inside the fillRandomParcelFunction, start of the loop. +void writeRandomFd(vector<uint8_t>& fillParcelBuffer) { + // path to random fd + size_t fillFuncIndex = 1; + impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2), + fillFuncIndex); + + bool flag = false; + impl::writeReversedBuffer(fillParcelBuffer, flag); + + // go for /dev/null index 1 + size_t fdIndex = 1; + impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(3), + fdIndex); +} + +void writeParcelData(borrowed_fd fd, vector<uint8_t>& fillParcelBuffer, + stack<ProviderMetadata>& remainingPositions, const uint8_t* data, size_t start, + size_t length) { + // need to write parcel data till next offset with instructions to pick random bytes till offset + size_t fillFuncIndex = 0; + impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2), + fillFuncIndex); + + // provide how much bytes to read in control buffer + ProviderMetadata providerData; + providerData.position = fillParcelBuffer.size(); + providerData.value = length; + remainingPositions.push(providerData); + + // provide actual bytes + CHECK(WriteFully(fd, data + start, length)) << fd.get(); +} + +/** + * Generate sequence of copy data, write fd and write binder instructions and required data. + * Data which will be read using consumeBytes is written to fd directly. Data which is read in + * form integer is consumed from rear end FuzzedDataProvider. So insert it in fillParcelBuffer and + * then write to fd + */ +size_t regenerateParcel(borrowed_fd fd, vector<uint8_t>& fillParcelBuffer, const Parcel& p, + size_t dataSize, const vector<uint64_t>& objectOffsets) { + stack<ProviderMetadata> remainingPositions; + size_t copiedDataPosition = 0; + const uint8_t* parcelData = p.data(); + size_t numBinders = 0; + size_t numFds = 0; + + for (auto offset : objectOffsets) { + // Check what type of object is present here + const flat_binder_object* flatObject = + reinterpret_cast<const flat_binder_object*>(parcelData + offset); + // Copy till the object offset + writeParcelData(fd, fillParcelBuffer, remainingPositions, parcelData, copiedDataPosition, + offset - copiedDataPosition); + copiedDataPosition = offset; + if (flatObject->hdr.type == BINDER_TYPE_BINDER || + flatObject->hdr.type == BINDER_TYPE_HANDLE) { + writeRandomBinder(fd, fillParcelBuffer, remainingPositions); + numBinders++; + // In case of binder, stability is written after the binder object. + // We want to move the copiedDataPosition further to account for this stability field + copiedDataPosition += sizeof(int32_t) + sizeof(flat_binder_object); + } else if (flatObject->hdr.type == BINDER_TYPE_FD) { + writeRandomFd(fillParcelBuffer); + numFds++; + copiedDataPosition += sizeof(flat_binder_object); + } + } + + if (copiedDataPosition < dataSize) { + // copy remaining data from recorded parcel -> last Object to end of the data + writeParcelData(fd, fillParcelBuffer, remainingPositions, parcelData, copiedDataPosition, + dataSize - copiedDataPosition); + } + + // We need to write bytes for selecting integer within range of 0 to provide.remaining_bytes() + // is called. + size_t totalWrittenBytes = dataSize - (sizeof(flat_binder_object) * objectOffsets.size()) - + (sizeof(int32_t) * numBinders) + + (kRandomInterfaceName.size() /*Interface String*/ + sizeof(bool) + sizeof(status_t)) * + numBinders; + + // Code in fuzzService relies on provider.remaining_bytes() to select random bytes using + // consume integer. use the calculated remaining_bytes to generate byte buffer which can + // generate required fds and binders in fillRandomParcel function. + while (!remainingPositions.empty()) { + auto meta = remainingPositions.top(); + remainingPositions.pop(); + size_t remainingBytes = totalWrittenBytes + fillParcelBuffer.size() - meta.position; + + vector<uint8_t> remReversedBytes; + impl::writeReversedBuffer(remReversedBytes, static_cast<size_t>(0), remainingBytes, + meta.value); + // Check the order of buffer which is being written + fillParcelBuffer.insert(fillParcelBuffer.end() - meta.position, remReversedBytes.begin(), + remReversedBytes.end()); + } + + return totalWrittenBytes; +} + +/** + * Current corpus format + * |Reserved bytes(8)|parcel data|fillParcelBuffer|integralBuffer| + */ void generateSeedsFromRecording(borrowed_fd fd, const binder::debug::RecordedTransaction& transaction) { // Write Reserved bytes for future use @@ -123,17 +289,9 @@ void generateSeedsFromRecording(borrowed_fd fd, uint8_t writeHeaderInternal = 0; impl::writeReversedBuffer(fillParcelBuffer, writeHeaderInternal); - // Choose to write data in parcel - size_t fillFuncIndex = 0; - impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2), - fillFuncIndex); - - // Write parcel data size from recorded transaction - size_t toWrite = transaction.getDataParcel().dataBufferSize(); - impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), toWrite, toWrite); - - // Write parcel data with size towrite from recorded transaction - CHECK(WriteFully(fd, dataParcel.data(), toWrite)) << fd.get(); + auto objectMetadata = transaction.getObjectOffsets(); + size_t toWrite = regenerateParcel(fd, fillParcelBuffer, dataParcel, dataParcel.dataBufferSize(), + objectMetadata); // Write Fill Parcel buffer size in integralBuffer so that fuzzService knows size of data size_t subDataSize = toWrite + fillParcelBuffer.size(); |