Loading libs/binder/BinderRecordReplay.cpp +133 −110 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 │paddingSize│││ // │││uint32_t │uint32_t ├┼┼───┐ // ││├───────────┴───────────┤││ │ // │││dataSize │││ │ // │││uint64_t ├┼┼─┐ │ // ││└───────────────────────┘││ │ │ // │└─────────────────────────┘│ │ │ // │┌─────────────────────────┐│ │ │ // ││Data ││ │ │ // ││bytes * dataSize │◀─┘ │ // │└─────────────────────────┘│ │ // ││┌───────────┬──────────┐│ │ // │││chunkType │dataSize ├┼─┼─┐ // │││uint32_t │uint32_t ││ │ │ // ││└───────────┴──────────┘│ │ │ // │└────────────────────────┘ │ │ // │┌─────────────────────────┐│ │ // ││Padding ││ │ // ││bytes * paddingSize │◀───┘ // ││Data ││ │ // ││bytes * dataSize │◀─┘ // ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│ // ││ Padding ││ // │└───┴─────────────────────┘│ // │┌─────────────────────────┐│ // ││checksum ││ // ││uint64_t ││ // │└─────────────────────────┘│ // └───────────────────────────┘ // Loading @@ -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; Loading @@ -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; } Loading @@ -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 " LOG(ERROR) << "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(); 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(); 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; } if (t.mSent.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { LOG(INFO) << "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); Loading @@ -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; Loading @@ -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; Loading libs/binder/tests/binderRecordedTransactionTest.cpp +78 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #include <binder/BinderRecordReplay.h> #include <gtest/gtest.h> #include <utils/Errors.h> using android::Parcel; using android::status_t; Loading Loading @@ -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); } } Loading
libs/binder/BinderRecordReplay.cpp +133 −110 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 │paddingSize│││ // │││uint32_t │uint32_t ├┼┼───┐ // ││├───────────┴───────────┤││ │ // │││dataSize │││ │ // │││uint64_t ├┼┼─┐ │ // ││└───────────────────────┘││ │ │ // │└─────────────────────────┘│ │ │ // │┌─────────────────────────┐│ │ │ // ││Data ││ │ │ // ││bytes * dataSize │◀─┘ │ // │└─────────────────────────┘│ │ // ││┌───────────┬──────────┐│ │ // │││chunkType │dataSize ├┼─┼─┐ // │││uint32_t │uint32_t ││ │ │ // ││└───────────┴──────────┘│ │ │ // │└────────────────────────┘ │ │ // │┌─────────────────────────┐│ │ // ││Padding ││ │ // ││bytes * paddingSize │◀───┘ // ││Data ││ │ // ││bytes * dataSize │◀─┘ // ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│ // ││ Padding ││ // │└───┴─────────────────────┘│ // │┌─────────────────────────┐│ // ││checksum ││ // ││uint64_t ││ // │└─────────────────────────┘│ // └───────────────────────────┘ // Loading @@ -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; Loading @@ -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; } Loading @@ -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 " LOG(ERROR) << "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(); 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(); 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; } if (t.mSent.setData(bytes.data(), chunk.dataSize) != android::NO_ERROR) { LOG(INFO) << "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); Loading @@ -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; Loading @@ -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; Loading
libs/binder/tests/binderRecordedTransactionTest.cpp +78 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #include <binder/BinderRecordReplay.h> #include <gtest/gtest.h> #include <utils/Errors.h> using android::Parcel; using android::status_t; Loading Loading @@ -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); } }