diff options
author | 2023-08-03 21:11:37 +0000 | |
---|---|---|
committer | 2023-08-03 21:11:37 +0000 | |
commit | ae6df5eed5e6028926d0d9c23bc8feaaa574e3ba (patch) | |
tree | ca0b3d3b7a1c08e5494e1029a191dfce3ba92714 | |
parent | 8ec865eb2a378337dd769890c91ec3fa9fd5bd4c (diff) | |
parent | ec144333bba7b239e7d756260cbb28c5b119f228 (diff) |
Merge changes from topic "random_parcel_seed_corpus" into main am: ada46f8b26 am: 5f6264faf5 am: d08c883d64 am: ec144333bb
Original change: https://android-review.googlesource.com/c/platform/frameworks/native/+/2679736
Change-Id: I32a39436ee39214c9396940cb182ded6655d060d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
6 files changed, 310 insertions, 42 deletions
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp index 41856f9b79..cd3e7c0fef 100644 --- a/libs/binder/tests/Android.bp +++ b/libs/binder/tests/Android.bp @@ -77,6 +77,8 @@ cc_test { static_libs: [ "binderRecordReplayTestIface-cpp", "binderReadParcelIface-cpp", + "libbinder_random_parcel_seeds", + "libbinder_random_parcel", ], test_suites: ["general-tests"], require_root: true, diff --git a/libs/binder/tests/binderRecordReplayTest.cpp b/libs/binder/tests/binderRecordReplayTest.cpp index 17d5c8a219..6773c95ed6 100644 --- a/libs/binder/tests/binderRecordReplayTest.cpp +++ b/libs/binder/tests/binderRecordReplayTest.cpp @@ -15,6 +15,7 @@ */ #include <BnBinderRecordReplayTest.h> +#include <android-base/file.h> #include <android-base/logging.h> #include <android-base/unique_fd.h> #include <binder/Binder.h> @@ -23,6 +24,11 @@ #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/RecordedTransaction.h> + +#include <fuzzbinder/libbinder_driver.h> +#include <fuzzer/FuzzedDataProvider.h> +#include <fuzzseeds/random_parcel_seeds.h> + #include <gtest/gtest.h> #include <sys/prctl.h> @@ -30,6 +36,7 @@ #include "parcelables/SingleDataParcelable.h" using namespace android; +using android::generateSeedsFromRecording; using android::binder::Status; using android::binder::debug::RecordedTransaction; using parcelables::SingleDataParcelable; @@ -84,6 +91,44 @@ public: GENERATE_GETTER_SETTER(SingleDataParcelableArray, std::vector<SingleDataParcelable>); }; +std::vector<uint8_t> retrieveData(base::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); + EXPECT_TRUE(readResult != 0); + return std::move(buffer); +} + +void replayFuzzService(const sp<BpBinder>& binder, const RecordedTransaction& transaction) { + base::unique_fd seedFd(open("/data/local/tmp/replayFuzzService", + O_RDWR | O_CREAT | O_CLOEXEC | O_TRUNC, 0666)); + ASSERT_TRUE(seedFd.ok()); + + // generate corpus from this transaction. + generateSeedsFromRecording(seedFd, transaction); + + // 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); + + // use fuzzService to replay the corpus + FuzzedDataProvider provider(seedData.data(), seedData.size()); + fuzzService(binder, std::move(provider)); +} + +void replayBinder(const sp<BpBinder>& binder, const RecordedTransaction& transaction) { + // TODO: move logic to replay RecordedTransaction into RecordedTransaction + Parcel data; + data.setData(transaction.getDataParcel().data(), transaction.getDataParcel().dataSize()); + auto result = binder->transact(transaction.getCode(), data, nullptr, transaction.getFlags()); + + // make sure recording does the thing we expect it to do + EXPECT_EQ(OK, result); +} + class BinderRecordReplayTest : public ::testing::Test { public: void SetUp() override { @@ -98,48 +143,46 @@ public: template <typename T, typename U> void recordReplay(Status (IBinderRecordReplayTest::*set)(T), U recordedValue, Status (IBinderRecordReplayTest::*get)(U*), U changedValue) { - base::unique_fd fd(open("/data/local/tmp/binderRecordReplayTest.rec", - O_RDWR | O_CREAT | O_CLOEXEC, 0666)); - ASSERT_TRUE(fd.ok()); - - // record a transaction - mBpBinder->startRecordingBinder(fd); - auto status = (*mInterface.*set)(recordedValue); - EXPECT_TRUE(status.isOk()); - mBpBinder->stopRecordingBinder(); - - // test transaction does the thing we expect it to do - U output; - status = (*mInterface.*get)(&output); - EXPECT_TRUE(status.isOk()); - EXPECT_EQ(output, recordedValue); - - // write over the existing state - status = (*mInterface.*set)(changedValue); - EXPECT_TRUE(status.isOk()); - - status = (*mInterface.*get)(&output); - EXPECT_TRUE(status.isOk()); - - EXPECT_EQ(output, changedValue); - - // replay transaction - ASSERT_EQ(0, lseek(fd.get(), 0, SEEK_SET)); - std::optional<RecordedTransaction> transaction = RecordedTransaction::fromFile(fd); - ASSERT_NE(transaction, std::nullopt); - - // TODO: move logic to replay RecordedTransaction into RecordedTransaction - Parcel data; - data.setData(transaction->getDataParcel().data(), transaction->getDataParcel().dataSize()); - auto result = - mBpBinder->transact(transaction->getCode(), data, nullptr, transaction->getFlags()); - - // make sure recording does the thing we expect it to do - EXPECT_EQ(OK, result); - - status = (*mInterface.*get)(&output); - EXPECT_TRUE(status.isOk()); - EXPECT_EQ(output, recordedValue); + auto replayFunctions = {&replayBinder, &replayFuzzService}; + for (auto replayFunc : replayFunctions) { + base::unique_fd fd(open("/data/local/tmp/binderRecordReplayTest.rec", + O_RDWR | O_CREAT | O_CLOEXEC, 0666)); + ASSERT_TRUE(fd.ok()); + + // record a transaction + mBpBinder->startRecordingBinder(fd); + auto status = (*mInterface.*set)(recordedValue); + EXPECT_TRUE(status.isOk()); + mBpBinder->stopRecordingBinder(); + + // test transaction does the thing we expect it to do + U output; + status = (*mInterface.*get)(&output); + EXPECT_TRUE(status.isOk()); + EXPECT_EQ(output, recordedValue); + + // write over the existing state + status = (*mInterface.*set)(changedValue); + EXPECT_TRUE(status.isOk()); + + status = (*mInterface.*get)(&output); + EXPECT_TRUE(status.isOk()); + + EXPECT_EQ(output, changedValue); + + // replay transaction + ASSERT_EQ(0, lseek(fd.get(), 0, SEEK_SET)); + std::optional<RecordedTransaction> transaction = RecordedTransaction::fromFile(fd); + ASSERT_NE(transaction, std::nullopt); + + const RecordedTransaction& recordedTransaction = *transaction; + // call replay function with recorded transaction + (*replayFunc)(mBpBinder, recordedTransaction); + + status = (*mInterface.*get)(&output); + EXPECT_TRUE(status.isOk()); + EXPECT_EQ(output, recordedValue); + } } private: diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp index 35866adf20..0d1503eda0 100644 --- a/libs/binder/tests/parcel_fuzzer/Android.bp +++ b/libs/binder/tests/parcel_fuzzer/Android.bp @@ -104,3 +104,28 @@ cc_library_static { local_include_dirs: ["include_random_parcel"], export_include_dirs: ["include_random_parcel"], } + +cc_library { + name: "libbinder_random_parcel_seeds", + host_supported: true, + vendor_available: true, + target: { + darwin: { + enabled: false, + }, + }, + srcs: [ + "random_parcel_seeds.cpp", + ], + shared_libs: [ + "libbase", + "libbinder", + "libbinder_ndk", + "libcutils", + "libutils", + ], + local_include_dirs: [ + "include_random_parcel_seeds", + ], + export_include_dirs: ["include_random_parcel_seeds"], +} diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h new file mode 100644 index 0000000000..5755239c8b --- /dev/null +++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 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/hex.h> +#include <android-base/logging.h> + +#include <binder/Binder.h> +#include <binder/Parcel.h> +#include <binder/RecordedTransaction.h> + +#include <private/android_filesystem_config.h> + +#include <vector> + +using android::Parcel; +using android::base::HexString; +using std::vector; + +namespace android { +namespace impl { +// computes the bytes so that if they are passed to FuzzedDataProvider and +// provider.ConsumeIntegralInRange<T>(min, max) is called, it will return val +template <typename T> +void writeReversedBuffer(std::vector<std::byte>& integralBuffer, T min, T max, T val); + +// Calls writeInBuffer method with min and max numeric limits of type T. This method +// is reversal of ConsumeIntegral<T>() in FuzzedDataProvider +template <typename T> +void writeReversedBuffer(std::vector<std::byte>& integralBuffer, T val); +} // namespace impl +void generateSeedsFromRecording(base::borrowed_fd fd, + const binder::debug::RecordedTransaction& transaction); +} // namespace android diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp index b268c5dcd4..47d2a0a701 100644 --- a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp +++ b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp @@ -35,6 +35,11 @@ void fuzzService(const std::vector<sp<IBinder>>& binders, FuzzedDataProvider&& p .extraFds = {}, }; + // Reserved bytes so that we don't have to change fuzzers and seed corpus if + // we introduce anything new in fuzzService. + std::vector<uint8_t> reservedBytes = provider.ConsumeBytes<uint8_t>(8); + (void)reservedBytes; + // always refresh the calling identity, because we sometimes set it below, but also, // the code we're fuzzing might reset it IPCThreadState::self()->clearCallingIdentity(); diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp new file mode 100644 index 0000000000..9e3e2aba77 --- /dev/null +++ b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 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 <binder/RecordedTransaction.h> + +#include <fuzzseeds/random_parcel_seeds.h> + +using android::base::WriteFully; + +namespace android { +namespace impl { +template <typename T> +std::vector<uint8_t> reverseBytes(T min, T max, T val) { + uint64_t range = static_cast<uint64_t>(max) - min; + uint64_t result = val - min; + size_t offset = 0; + + std::vector<uint8_t> reverseData; + uint8_t reversed = 0; + reversed |= result; + + while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0) { + reverseData.push_back(reversed); + reversed = 0; + reversed |= (result >> CHAR_BIT); + result = result >> CHAR_BIT; + offset += CHAR_BIT; + } + + return std::move(reverseData); +} + +template <typename T> +void writeReversedBuffer(std::vector<uint8_t>& integralBuffer, T min, T max, T val) { + std::vector<uint8_t> reversedData = reverseBytes(min, max, val); + // ConsumeIntegral Calls read buffer from the end. Keep inserting at the front of the buffer + // so that we can align fuzzService operations with seed generation for readability. + integralBuffer.insert(integralBuffer.begin(), reversedData.begin(), reversedData.end()); +} + +template <typename T> +void writeReversedBuffer(std::vector<uint8_t>& integralBuffer, T val) { + // For ConsumeIntegral<T>() calls, FuzzedDataProvider uses numeric limits min and max + // as range + writeReversedBuffer(integralBuffer, std::numeric_limits<T>::min(), + std::numeric_limits<T>::max(), val); +} + +} // namespace impl + +void generateSeedsFromRecording(base::borrowed_fd fd, + const binder::debug::RecordedTransaction& transaction) { + // Write Reserved bytes for future use + std::vector<uint8_t> reservedBytes(8); + CHECK(WriteFully(fd, reservedBytes.data(), reservedBytes.size())) << fd.get(); + + std::vector<uint8_t> integralBuffer; + + // Write UID array : Array elements are initialized in the order that they are declared + // UID array index 2 element + // int64_t aidRoot = 0; + impl::writeReversedBuffer(integralBuffer, static_cast<int64_t>(AID_ROOT) << 32, + static_cast<int64_t>(AID_USER) << 32, + static_cast<int64_t>(AID_ROOT) << 32); + + // UID array index 3 element + impl::writeReversedBuffer(integralBuffer, static_cast<int64_t>(AID_ROOT) << 32); + + // always pick AID_ROOT -> index 0 + size_t uidIndex = 0; + impl::writeReversedBuffer(integralBuffer, static_cast<size_t>(0), static_cast<size_t>(3), + uidIndex); + + // Never set uid in seed corpus + uint8_t writeUid = 0; + impl::writeReversedBuffer(integralBuffer, writeUid); + + // Read random code. this will be from recorded transaction + uint8_t selectCode = 1; + impl::writeReversedBuffer(integralBuffer, selectCode); + + // Get from recorded transaction + uint32_t code = transaction.getCode(); + impl::writeReversedBuffer(integralBuffer, code); + + // Get from recorded transaction + uint32_t flags = transaction.getFlags(); + impl::writeReversedBuffer(integralBuffer, flags); + + // always fuzz primary binder + size_t extraBindersIndex = 0; + impl::writeReversedBuffer(integralBuffer, static_cast<size_t>(0), static_cast<size_t>(0), + extraBindersIndex); + + const Parcel& dataParcel = transaction.getDataParcel(); + + // This buffer holds the bytes which will be used for fillRandomParcel API + std::vector<uint8_t> fillParcelBuffer; + + // Don't take rpc path + uint8_t rpcBranch = 0; + impl::writeReversedBuffer(fillParcelBuffer, rpcBranch); + + // Implicit branch on this path -> options->writeHeader(p, provider) + 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(); + + // Write Fill Parcel buffer size in integralBuffer so that fuzzService knows size of data + size_t subDataSize = toWrite + fillParcelBuffer.size(); + impl::writeReversedBuffer(integralBuffer, static_cast<size_t>(0), subDataSize, subDataSize); + + // Write fill parcel buffer + CHECK(WriteFully(fd, fillParcelBuffer.data(), fillParcelBuffer.size())) << fd.get(); + + // Write the integralBuffer to data + CHECK(WriteFully(fd, integralBuffer.data(), integralBuffer.size())) << fd.get(); +} +} // namespace android |