diff options
| -rw-r--r-- | libs/binder/RecordedTransaction.cpp | 19 | ||||
| -rw-r--r-- | libs/binder/tests/Android.bp | 22 | ||||
| -rw-r--r-- | libs/binder/tests/IBinderRecordReplayTest.aidl | 20 | ||||
| -rw-r--r-- | libs/binder/tests/binderRecordReplayTest.cpp | 115 |
4 files changed, 162 insertions, 14 deletions
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp index 1c7613584b..44a9e3befa 100644 --- a/libs/binder/RecordedTransaction.cpp +++ b/libs/binder/RecordedTransaction.cpp @@ -161,17 +161,6 @@ static_assert(sizeof(ChunkDescriptor) % 8 == 0); constexpr uint32_t kMaxChunkDataSize = 0xfffffff0; typedef uint64_t transaction_checksum_t; -static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut, - transaction_checksum_t* sum) { - if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) { - LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get(); - return android::UNKNOWN_ERROR; - } - - *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut); - return android::NO_ERROR; -} - std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) { RecordedTransaction t; ChunkDescriptor chunk; @@ -192,11 +181,13 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd LOG(ERROR) << "Not enough file remains to contain expected chunk descriptor"; return std::nullopt; } - transaction_checksum_t checksum = 0; - if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) { - LOG(ERROR) << "Failed to read chunk descriptor."; + + if (!android::base::ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) { + LOG(ERROR) << "Failed to read ChunkDescriptor from fd " << fd.get() << ". " + << strerror(errno); return std::nullopt; } + transaction_checksum_t checksum = *reinterpret_cast<transaction_checksum_t*>(&chunk); fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); if (fdCurrentPosition == -1) { diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp index 873e9550f9..7a462db797 100644 --- a/libs/binder/tests/Android.bp +++ b/libs/binder/tests/Android.bp @@ -111,6 +111,28 @@ cc_test { } cc_test { + name: "binderRecordReplayTest", + srcs: ["binderRecordReplayTest.cpp"], + shared_libs: [ + "libbinder", + "libcutils", + "libutils", + ], + static_libs: [ + "binderRecordReplayTestIface-cpp", + ], + test_suites: ["general-tests"], +} + +aidl_interface { + name: "binderRecordReplayTestIface", + unstable: true, + srcs: [ + "IBinderRecordReplayTest.aidl", + ], +} + +cc_test { name: "binderLibTest", defaults: ["binder_test_defaults"], product_variables: { diff --git a/libs/binder/tests/IBinderRecordReplayTest.aidl b/libs/binder/tests/IBinderRecordReplayTest.aidl new file mode 100644 index 0000000000..3c8c722da4 --- /dev/null +++ b/libs/binder/tests/IBinderRecordReplayTest.aidl @@ -0,0 +1,20 @@ +/* + * 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. + */ + +interface IBinderRecordReplayTest { + void setInt(int input); + int getInt(); +} diff --git a/libs/binder/tests/binderRecordReplayTest.cpp b/libs/binder/tests/binderRecordReplayTest.cpp new file mode 100644 index 0000000000..55148acf14 --- /dev/null +++ b/libs/binder/tests/binderRecordReplayTest.cpp @@ -0,0 +1,115 @@ +/* + * 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 <BnBinderRecordReplayTest.h> +#include <android-base/logging.h> +#include <android-base/unique_fd.h> +#include <binder/Binder.h> +#include <binder/BpBinder.h> +#include <binder/IBinder.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/RecordedTransaction.h> +#include <gtest/gtest.h> + +#include <sys/prctl.h> + +using namespace android; +using android::binder::Status; +using android::binder::debug::RecordedTransaction; + +const String16 kServerName = String16("binderRecordReplay"); + +class MyRecordReplay : public BnBinderRecordReplayTest { +public: + Status setInt(int input) { + mInt = input; + return Status::ok(); + } + Status getInt(int* output) { + *output = mInt; + return Status::ok(); + } + +private: + int mInt = 0; +}; + +TEST(BinderClearBuf, RecordReplayRepeatInt) { + // get the remote service + sp<IBinder> binder = defaultServiceManager()->getService(kServerName); + ASSERT_NE(nullptr, binder); + sp<IBinderRecordReplayTest> iface = interface_cast<IBinderRecordReplayTest>(binder); + sp<BpBinder> bpBinder = binder->remoteBinder(); + ASSERT_NE(nullptr, bpBinder); + + base::unique_fd fd( + open("/data/local/tmp/binderRecordReplayTest.rec", O_RDWR | O_CREAT | O_CLOEXEC, 0666)); + ASSERT_TRUE(fd.ok()); + + // record a transaction + bpBinder->startRecordingBinder(fd); + EXPECT_TRUE(iface->setInt(3).isOk()); + bpBinder->stopRecordingBinder(); + + // test transaction does the thing we expect it to do + int output; + EXPECT_TRUE(iface->getInt(&output).isOk()); + EXPECT_EQ(output, 3); + + // write over the existing state + EXPECT_TRUE(iface->setInt(5).isOk()); + EXPECT_TRUE(iface->getInt(&output).isOk()); + EXPECT_EQ(output, 5); + + // 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()); + status_t status = binder->remoteBinder()->transact(transaction->getCode(), data, nullptr, + transaction->getFlags()); + + // make sure recording does the thing we expect it to do + EXPECT_EQ(OK, status); + EXPECT_TRUE(iface->getInt(&output).isOk()); + EXPECT_EQ(output, 3); + + // TODO: we should also make sure we can convert the recording to a fuzzer + // corpus entry, and we will be able to replay it in the same way +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + if (fork() == 0) { + prctl(PR_SET_PDEATHSIG, SIGHUP); + + auto server = sp<MyRecordReplay>::make(); + android::defaultServiceManager()->addService(kServerName, server.get()); + + IPCThreadState::self()->joinThreadPool(true); + exit(1); // should not reach + } + + // not racey, but getService sleeps for 1s + usleep(100000); + + return RUN_ALL_TESTS(); +} |