diff options
| -rw-r--r-- | libs/binder/BinderRecordReplay.cpp | 243 | ||||
| -rw-r--r-- | libs/binder/tests/binderRecordedTransactionTest.cpp | 78 |
2 files changed, 211 insertions, 110 deletions
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/BinderRecordReplay.cpp index 8ba18a87f8..58bb106b37 100644 --- a/libs/binder/BinderRecordReplay.cpp +++ b/libs/binder/BinderRecordReplay.cpp @@ -18,6 +18,7 @@ #include <android-base/logging.h> #include <android-base/unique_fd.h> #include <binder/BinderRecordReplay.h> +#include <sys/mman.h> #include <algorithm> using android::Parcel; @@ -42,34 +43,38 @@ static_assert(PADDING8(8) == 0); // // A RecordedTransaction is written to a file as a sequence of Chunks. // -// A Chunk consists of a ChunkDescriptor, Data, and Padding. +// A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum. // -// Data and Padding may each be zero-length as specified by the -// ChunkDescriptor. +// The ChunkDescriptor identifies the type of Data in the chunk, and the size +// of the Data. // -// The ChunkDescriptor identifies the type of data in the chunk, the size of -// the data in bytes, and the number of zero-bytes padding to land on an -// 8-byte boundary by the end of the Chunk. +// The Data may be any uint32 number of bytes in length in [0-0xfffffff0]. +// +// Padding is between [0-7] zero-bytes after the Data such that the Chunk ends +// on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the +// size of Padding. +// +// The checksum is a 64-bit wide XOR of all previous data from the start of the +// ChunkDescriptor to the end of Padding. // // ┌───────────────────────────┐ // │Chunk │ +// │┌────────────────────────┐ │ +// ││ChunkDescriptor │ │ +// ││┌───────────┬──────────┐│ │ +// │││chunkType │dataSize ├┼─┼─┐ +// │││uint32_t │uint32_t ││ │ │ +// ││└───────────┴──────────┘│ │ │ +// │└────────────────────────┘ │ │ +// │┌─────────────────────────┐│ │ +// ││Data ││ │ +// ││bytes * dataSize │◀─┘ +// ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│ +// ││ Padding ││ +// │└───┴─────────────────────┘│ // │┌─────────────────────────┐│ -// ││ChunkDescriptor ││ -// ││┌───────────┬───────────┐││ -// │││chunkType │paddingSize│││ -// │││uint32_t │uint32_t ├┼┼───┐ -// ││├───────────┴───────────┤││ │ -// │││dataSize │││ │ -// │││uint64_t ├┼┼─┐ │ -// ││└───────────────────────┘││ │ │ -// │└─────────────────────────┘│ │ │ -// │┌─────────────────────────┐│ │ │ -// ││Data ││ │ │ -// ││bytes * dataSize │◀─┘ │ -// │└─────────────────────────┘│ │ -// │┌─────────────────────────┐│ │ -// ││Padding ││ │ -// ││bytes * paddingSize │◀───┘ +// ││checksum ││ +// ││uint64_t ││ // │└─────────────────────────┘│ // └───────────────────────────┘ // @@ -85,20 +90,20 @@ static_assert(PADDING8(8) == 0); // ║ End Chunk ║ // ╚══════════════════════╝ // -// On reading a RecordedTransaction, an unrecognized chunk is skipped using -// the size information in the ChunkDescriptor. Chunks are read and either -// assimilated or skipped until an End Chunk is encountered. This has three -// notable implications: +// On reading a RecordedTransaction, an unrecognized chunk is checksummed +// then skipped according to size information in the ChunkDescriptor. Chunks +// are read and either assimilated or skipped until an End Chunk is +// encountered. This has three notable implications: // // 1. Older and newer implementations should be able to read one another's // Transactions, though there will be loss of information. -// 2. With the exception of the End Chunk, Chunks can appear in any -// order and even repeat, though this is not recommended. +// 2. With the exception of the End Chunk, Chunks can appear in any order +// and even repeat, though this is not recommended. // 3. If any Chunk is repeated, old values will be overwritten by versions // encountered later in the file. // // No effort is made to ensure the expected chunks are present. A single -// End Chunk may therefore produce a empty, meaningless RecordedTransaction. +// End Chunk may therefore produce an empty, meaningless RecordedTransaction. RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept { mHeader = t.mHeader; @@ -121,12 +126,12 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t cod 0}; if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) { - LOG(INFO) << "Failed to set sent parcel data."; + LOG(ERROR) << "Failed to set sent parcel data."; return std::nullopt; } if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) { - LOG(INFO) << "Failed to set reply parcel data."; + LOG(ERROR) << "Failed to set reply parcel data."; return std::nullopt; } @@ -134,94 +139,111 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t cod } enum { - HEADER_CHUNK = 0x00000001, - DATA_PARCEL_CHUNK = 0x00000002, - REPLY_PARCEL_CHUNK = 0x00000003, - INVALID_CHUNK = 0x00fffffe, + HEADER_CHUNK = 1, + DATA_PARCEL_CHUNK = 2, + REPLY_PARCEL_CHUNK = 3, END_CHUNK = 0x00ffffff, }; struct ChunkDescriptor { uint32_t chunkType = 0; - uint32_t padding = 0; uint32_t dataSize = 0; - uint32_t reserved = 0; // Future checksum }; +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) { +static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut, + transaction_checksum_t* sum) { if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) { - LOG(INFO) << "Failed to read Chunk Descriptor from fd " << fd.get(); + LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get(); return android::UNKNOWN_ERROR; } - if (PADDING8(chunkOut->dataSize) != chunkOut->padding) { - chunkOut->chunkType = INVALID_CHUNK; - LOG(INFO) << "Chunk data and padding sizes do not align." << fd.get(); - return android::BAD_VALUE; - } + + *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut); return android::NO_ERROR; } std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) { RecordedTransaction t; ChunkDescriptor chunk; - + const long pageSize = sysconf(_SC_PAGE_SIZE); do { - if (NO_ERROR != readChunkDescriptor(fd, &chunk)) { - LOG(INFO) << "Failed to read chunk descriptor."; + transaction_checksum_t checksum = 0; + if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) { + LOG(ERROR) << "Failed to read chunk descriptor."; + return std::nullopt; + } + off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); + off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize; + off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart; + + if (chunk.dataSize > kMaxChunkDataSize) { + LOG(ERROR) << "Chunk data exceeds maximum size."; + return std::nullopt; + } + + size_t chunkPayloadSize = + chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t); + + if (PADDING8(chunkPayloadSize) != 0) { + LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize; + return std::nullopt; + } + + transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>( + mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED, + fd.get(), mmapPageAlignedStart)); + payloadMap += mmapPayloadStartOffset / + sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap + // page-alignment + if (payloadMap == MAP_FAILED) { + LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " " + << strerror(errno); + return std::nullopt; + } + for (size_t checksumIndex = 0; + checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) { + checksum ^= payloadMap[checksumIndex]; + } + if (checksum != 0) { + LOG(ERROR) << "Checksum failed."; return std::nullopt; } + lseek(fd.get(), chunkPayloadSize, SEEK_CUR); + switch (chunk.chunkType) { case HEADER_CHUNK: { if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) { - LOG(INFO) << "Header Chunk indicated size " << chunk.dataSize << "; Expected " - << sizeof(TransactionHeader) << "."; - return std::nullopt; - } - if (!android::base::ReadFully(fd, &t.mHeader, chunk.dataSize)) { - LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get(); + LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected " + << sizeof(TransactionHeader) << "."; return std::nullopt; } - lseek(fd.get(), chunk.padding, SEEK_CUR); + t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap); break; } case DATA_PARCEL_CHUNK: { - std::vector<uint8_t> bytes; - bytes.resize(chunk.dataSize); - if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) { - LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get(); - return std::nullopt; - } - if (t.mSent.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { - LOG(INFO) << "Failed to set sent parcel data."; + if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap), + chunk.dataSize) != android::NO_ERROR) { + LOG(ERROR) << "Failed to set sent parcel data."; return std::nullopt; } - lseek(fd.get(), chunk.padding, SEEK_CUR); break; } case REPLY_PARCEL_CHUNK: { - std::vector<uint8_t> bytes; - bytes.resize(chunk.dataSize); - if (!android::base::ReadFully(fd, bytes.data(), chunk.dataSize)) { - LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get(); + if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap), + chunk.dataSize) != android::NO_ERROR) { + LOG(ERROR) << "Failed to set reply parcel data."; return std::nullopt; } - if (t.mReply.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { - LOG(INFO) << "Failed to set reply parcel data."; - return std::nullopt; - } - lseek(fd.get(), chunk.padding, SEEK_CUR); break; } - case INVALID_CHUNK: - LOG(INFO) << "Invalid chunk."; - return std::nullopt; case END_CHUNK: - LOG(INFO) << "Read end chunk"; - FALLTHROUGH_INTENDED; - default: - // Unrecognized or skippable chunk - lseek(fd.get(), chunk.dataSize + chunk.padding, SEEK_CUR); break; + default: + LOG(INFO) << "Unrecognized chunk."; + continue; } } while (chunk.chunkType != END_CHUNK); @@ -230,36 +252,37 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType, size_t byteCount, const uint8_t* data) const { - // Write Chunk Descriptor - // - Chunk Type - if (!android::base::WriteFully(fd, &chunkType, sizeof(uint32_t))) { - LOG(INFO) << "Failed to write chunk header to fd " << fd.get(); - return UNKNOWN_ERROR; - } - // - Chunk Data Padding Size - uint32_t additionalPaddingCount = static_cast<uint32_t>(PADDING8(byteCount)); - if (!android::base::WriteFully(fd, &additionalPaddingCount, sizeof(uint32_t))) { - LOG(INFO) << "Failed to write chunk padding size to fd " << fd.get(); - return UNKNOWN_ERROR; - } - // - Chunk Data Size - uint64_t byteCountToWrite = (uint64_t)byteCount; - if (!android::base::WriteFully(fd, &byteCountToWrite, sizeof(uint64_t))) { - LOG(INFO) << "Failed to write chunk size to fd " << fd.get(); - return UNKNOWN_ERROR; - } - if (byteCount == 0) { - return NO_ERROR; + if (byteCount > kMaxChunkDataSize) { + LOG(ERROR) << "Chunk data exceeds maximum size"; + return BAD_VALUE; } + ChunkDescriptor descriptor = {.chunkType = chunkType, + .dataSize = static_cast<uint32_t>(byteCount)}; + // Prepare Chunk content as byte * + const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor); + const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data); - if (!android::base::WriteFully(fd, data, byteCount)) { - LOG(INFO) << "Failed to write chunk data to fd " << fd.get(); - return UNKNOWN_ERROR; + // Add Chunk to intermediate buffer, except checksum + std::vector<std::byte> buffer; + buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor)); + buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount); + std::byte zero{0}; + buffer.insert(buffer.end(), PADDING8(byteCount), zero); + + // Calculate checksum from buffer + transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data()); + transaction_checksum_t checksumValue = 0; + for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) { + checksumValue ^= checksumData[idx]; } - const uint8_t zeros[7] = {0}; - if (!android::base::WriteFully(fd, zeros, additionalPaddingCount)) { - LOG(INFO) << "Failed to write chunk padding to fd " << fd.get(); + // Write checksum to buffer + std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue); + buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t)); + + // Write buffer to file + if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) { + LOG(ERROR) << "Failed to write chunk fd " << fd.get(); return UNKNOWN_ERROR; } return NO_ERROR; @@ -269,19 +292,19 @@ android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const { if (NO_ERROR != writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader), reinterpret_cast<const uint8_t*>(&mHeader))) { - LOG(INFO) << "Failed to write transactionHeader to fd " << fd.get(); + LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) { - LOG(INFO) << "Failed to write sent Parcel to fd " << fd.get(); + LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) { - LOG(INFO) << "Failed to write reply Parcel to fd " << fd.get(); + LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get(); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) { - LOG(INFO) << "Failed to write end chunk to fd " << fd.get(); + LOG(ERROR) << "Failed to write end chunk to fd " << fd.get(); return UNKNOWN_ERROR; } return NO_ERROR; diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp index 2ece3156ee..67553fc811 100644 --- a/libs/binder/tests/binderRecordedTransactionTest.cpp +++ b/libs/binder/tests/binderRecordedTransactionTest.cpp @@ -16,6 +16,7 @@ #include <binder/BinderRecordReplay.h> #include <gtest/gtest.h> +#include <utils/Errors.h> using android::Parcel; using android::status_t; @@ -54,3 +55,80 @@ TEST(BinderRecordedTransaction, RoundTripEncoding) { EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2); EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99); } + +TEST(BinderRecordedTransaction, Checksum) { + Parcel d; + d.writeInt32(12); + d.writeInt64(2); + Parcel r; + r.writeInt32(99); + timespec ts = {1232456, 567890}; + auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0); + + auto file = std::tmpfile(); + auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1)); + + status_t status = transaction->dumpToFile(fd); + ASSERT_EQ(android::NO_ERROR, status); + + lseek(fd.get(), 9, SEEK_SET); + uint32_t badData = 0xffffffff; + write(fd.get(), &badData, sizeof(uint32_t)); + std::rewind(file); + + auto retrievedTransaction = RecordedTransaction::fromFile(fd); + + EXPECT_FALSE(retrievedTransaction.has_value()); +} + +TEST(BinderRecordedTransaction, PayloadsExceedPageBoundaries) { + // File contents are read with mmap. + // This test verifies that transactions are read from portions + // of files that cross page boundaries and don't start at a + // page boundary offset of the fd. + const size_t pageSize = sysconf(_SC_PAGE_SIZE); + const size_t largeDataSize = pageSize + 100; + std::vector<uint8_t> largePayload; + uint8_t filler = 0xaa; + largePayload.insert(largePayload.end(), largeDataSize, filler); + Parcel d; + d.writeInt32(12); + d.writeInt64(2); + d.writeByteVector(largePayload); + Parcel r; + r.writeInt32(99); + timespec ts = {1232456, 567890}; + auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0); + + auto file = std::tmpfile(); + auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1)); + + // Write to file twice + status_t status = transaction->dumpToFile(fd); + ASSERT_EQ(android::NO_ERROR, status); + status = transaction->dumpToFile(fd); + ASSERT_EQ(android::NO_ERROR, status); + + std::rewind(file); + + for (int i = 0; i < 2; i++) { + auto retrievedTransaction = RecordedTransaction::fromFile(fd); + + EXPECT_EQ(retrievedTransaction->getCode(), 1); + EXPECT_EQ(retrievedTransaction->getFlags(), 42); + EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec); + EXPECT_EQ(retrievedTransaction->getTimestamp().tv_nsec, ts.tv_nsec); + EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), d.dataSize()); + EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4); + EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0); + EXPECT_EQ(retrievedTransaction->getVersion(), 0); + + EXPECT_EQ(retrievedTransaction->getDataParcel().readInt32(), 12); + EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2); + std::optional<std::vector<uint8_t>> payloadOut; + EXPECT_EQ(retrievedTransaction->getDataParcel().readByteVector(&payloadOut), android::OK); + EXPECT_EQ(payloadOut.value(), largePayload); + + EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99); + } +} |