/* * Copyright (C) 2022, 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 "file.h" #include #include #include #include #include #include #include using namespace android::binder::impl; using android::Parcel; using android::binder::borrowed_fd; using android::binder::ReadFully; using android::binder::unique_fd; using android::binder::WriteFully; using android::binder::debug::RecordedTransaction; #define PADDING8(s) ((8 - (s) % 8) % 8) static_assert(PADDING8(0) == 0); static_assert(PADDING8(1) == 7); static_assert(PADDING8(7) == 1); static_assert(PADDING8(8) == 0); // Transactions are sequentially recorded to a file descriptor. // // An individual RecordedTransaction is written with the following format: // // WARNING: Though the following format is designed to be stable and // extensible, it is under active development and should be considered // unstable until this warning is removed. // // A RecordedTransaction is written to a file as a sequence of Chunks. // // A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum. // // The ChunkDescriptor identifies the type of Data in the chunk, and the size // of the Data. // // 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 ││ // │└───┴─────────────────────┘│ // │┌─────────────────────────┐│ // ││checksum ││ // ││uint64_t ││ // │└─────────────────────────┘│ // └───────────────────────────┘ // // A RecordedTransaction is written as a Header Chunk with fields about the // transaction, a Data Parcel chunk, a Reply Parcel Chunk, and an End Chunk. // ┌──────────────────────┐ // │ Header Chunk │ // ├──────────────────────┤ // │ Sent Parcel Chunk │ // ├──────────────────────┤ // │ Reply Parcel Chunk │ // ├──────────────────────┤ // ║ End Chunk ║ // ╚══════════════════════╝ // // 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. // 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 an empty, meaningless RecordedTransaction. RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept { mData = t.mData; mSentDataOnly.setData(t.getDataParcel().data(), t.getDataParcel().dataSize()); mReplyDataOnly.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize()); } std::optional RecordedTransaction::fromDetails( const String16& interfaceName, uint32_t code, uint32_t flags, timespec timestamp, const Parcel& dataParcel, const Parcel& replyParcel, status_t err) { RecordedTransaction t; t.mData.mHeader = {code, flags, static_cast(err), dataParcel.isForRpc() ? static_cast(1) : static_cast(0), static_cast(timestamp.tv_sec), static_cast(timestamp.tv_nsec), 0}; t.mData.mInterfaceName = std::string(String8(interfaceName).c_str()); if (interfaceName.size() != t.mData.mInterfaceName.size()) { ALOGE("Interface Name is not valid. Contains characters that aren't single byte utf-8."); return std::nullopt; } 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.mReplyDataOnly.setData(replyParcel.data(), replyParcel.dataBufferSize()) != android::NO_ERROR) { ALOGE("Failed to set reply parcel data."); return std::nullopt; } return std::optional(std::move(t)); } enum { HEADER_CHUNK = 1, DATA_PARCEL_CHUNK = 2, REPLY_PARCEL_CHUNK = 3, INTERFACE_NAME_CHUNK = 4, DATA_PARCEL_OBJECT_CHUNK = 5, END_CHUNK = 0x00ffffff, }; struct ChunkDescriptor { uint32_t chunkType = 0; uint32_t dataSize = 0; }; static_assert(sizeof(ChunkDescriptor) % 8 == 0); constexpr uint32_t kMaxChunkDataSize = 0xfffffff0; typedef uint64_t transaction_checksum_t; std::optional RecordedTransaction::fromFile(const unique_fd& fd) { RecordedTransaction t; ChunkDescriptor chunk; const long pageSize = sysconf(_SC_PAGE_SIZE); struct stat fileStat; if (fstat(fd.get(), &fileStat) != 0) { ALOGE("Unable to get file information"); return std::nullopt; } off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); if (fdCurrentPosition == -1) { ALOGE("Invalid offset in file descriptor."); return std::nullopt; } do { if (fileStat.st_size < (fdCurrentPosition + (off_t)sizeof(ChunkDescriptor))) { ALOGE("Not enough file remains to contain expected chunk descriptor"); return std::nullopt; } if (!ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) { ALOGE("Failed to read ChunkDescriptor from fd %d. %s", fd.get(), strerror(errno)); return std::nullopt; } transaction_checksum_t checksum = *reinterpret_cast(&chunk); fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR); if (fdCurrentPosition == -1) { ALOGE("Invalid offset in file descriptor."); return std::nullopt; } off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize; off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart; if (chunk.dataSize > kMaxChunkDataSize) { ALOGE("Chunk data exceeds maximum size."); return std::nullopt; } size_t chunkPayloadSize = chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t); if (chunkPayloadSize > (size_t)(fileStat.st_size - fdCurrentPosition)) { ALOGE("Chunk payload exceeds remaining file size."); return std::nullopt; } if (PADDING8(chunkPayloadSize) != 0) { ALOGE("Invalid chunk size, not aligned %zu", chunkPayloadSize); return std::nullopt; } size_t memoryMappedSize = chunkPayloadSize + mmapPayloadStartOffset; void* mappedMemory = mmap(nullptr, memoryMappedSize, PROT_READ, MAP_SHARED, fd.get(), mmapPageAlignedStart); auto mmap_guard = make_scope_guard( [mappedMemory, memoryMappedSize] { munmap(mappedMemory, memoryMappedSize); }); transaction_checksum_t* payloadMap = reinterpret_cast(mappedMemory); payloadMap += mmapPayloadStartOffset / sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap // page-alignment if (payloadMap == MAP_FAILED) { ALOGE("Memory mapping failed for fd %d: %d %s", 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) { ALOGE("Checksum failed."); return std::nullopt; } fdCurrentPosition = lseek(fd.get(), chunkPayloadSize, SEEK_CUR); if (fdCurrentPosition == -1) { ALOGE("Invalid offset in file descriptor."); return std::nullopt; } switch (chunk.chunkType) { case HEADER_CHUNK: { if (chunk.dataSize != static_cast(sizeof(TransactionHeader))) { ALOGE("Header Chunk indicated size %" PRIu32 "; Expected %zu.", chunk.dataSize, sizeof(TransactionHeader)); return std::nullopt; } t.mData.mHeader = *reinterpret_cast(payloadMap); break; } case INTERFACE_NAME_CHUNK: { t.mData.mInterfaceName = std::string(reinterpret_cast(payloadMap), chunk.dataSize); break; } case DATA_PARCEL_CHUNK: { if (t.mSentDataOnly.setData(reinterpret_cast(payloadMap), chunk.dataSize) != android::NO_ERROR) { ALOGE("Failed to set sent parcel data."); return std::nullopt; } break; } case REPLY_PARCEL_CHUNK: { if (t.mReplyDataOnly.setData(reinterpret_cast(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(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: ALOGI("Unrecognized chunk."); break; } } while (chunk.chunkType != END_CHUNK); return std::optional(std::move(t)); } android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType, size_t byteCount, const uint8_t* data) const { if (byteCount > kMaxChunkDataSize) { ALOGE("Chunk data exceeds maximum size"); return BAD_VALUE; } ChunkDescriptor descriptor = {.chunkType = chunkType, .dataSize = static_cast(byteCount)}; // Prepare Chunk content as byte * const std::byte* descriptorBytes = reinterpret_cast(&descriptor); const std::byte* dataBytes = reinterpret_cast(data); // Add Chunk to intermediate buffer, except checksum std::vector 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(buffer.data()); transaction_checksum_t checksumValue = 0; for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) { checksumValue ^= checksumData[idx]; } // Write checksum to buffer std::byte* checksumBytes = reinterpret_cast(&checksumValue); buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t)); // Write buffer to file if (!WriteFully(fd, buffer.data(), buffer.size())) { ALOGE("Failed to write chunk fd %d", fd.get()); return UNKNOWN_ERROR; } return NO_ERROR; } android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const { if (NO_ERROR != writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader), reinterpret_cast(&(mData.mHeader)))) { ALOGE("Failed to write transactionHeader to fd %d", fd.get()); return UNKNOWN_ERROR; } if (NO_ERROR != writeChunk(fd, INTERFACE_NAME_CHUNK, mData.mInterfaceName.size() * sizeof(uint8_t), reinterpret_cast(mData.mInterfaceName.c_str()))) { ALOGI("Failed to write Interface Name Chunk to fd %d", fd.get()); return UNKNOWN_ERROR; } 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, 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(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, nullptr)) { ALOGE("Failed to write end chunk to fd %d", fd.get()); return UNKNOWN_ERROR; } return NO_ERROR; } const std::string& RecordedTransaction::getInterfaceName() const { return mData.mInterfaceName; } uint32_t RecordedTransaction::getCode() const { return mData.mHeader.code; } uint32_t RecordedTransaction::getFlags() const { return mData.mHeader.flags; } int32_t RecordedTransaction::getReturnedStatus() const { return mData.mHeader.statusReturned; } timespec RecordedTransaction::getTimestamp() const { time_t sec = mData.mHeader.timestampSeconds; int32_t nsec = mData.mHeader.timestampNanoseconds; return (timespec){.tv_sec = sec, .tv_nsec = nsec}; } uint32_t RecordedTransaction::getVersion() const { return mData.mHeader.version; } const std::vector& RecordedTransaction::getObjectOffsets() const { return mData.mSentObjectData; } const Parcel& RecordedTransaction::getDataParcel() const { return mSentDataOnly; } const Parcel& RecordedTransaction::getReplyParcel() const { return mReplyDataOnly; }