Fix handling of possible bad gnu_debugdata_size.
Rather than use a std::vector for backing memory, allocate the memory using a new with nothrow, and in MemoryBuffer use realloc. Since the size field is coming from the elf, it could be corrupted or intentionally crafted to cause problems. In addition, add some other protections to make sure that overflows don't occur. Bug: 146215949 Test: Ran unit tests with jemalloc and scudo to verify that they Test: both behave the same way. Change-Id: If14243ce382ba5403a6bacd0ec673452c6b7c3be
This commit is contained in:
parent
a23ce83a3b
commit
8726d3a448
5 changed files with 76 additions and 22 deletions
|
@ -78,10 +78,31 @@ Memory* ElfInterface::CreateGnuDebugdataMemory() {
|
|||
CrcGenerateTable();
|
||||
Crc64GenerateTable();
|
||||
|
||||
std::vector<uint8_t> src(gnu_debugdata_size_);
|
||||
if (!memory_->ReadFully(gnu_debugdata_offset_, src.data(), gnu_debugdata_size_)) {
|
||||
gnu_debugdata_offset_ = 0;
|
||||
gnu_debugdata_size_ = static_cast<uint64_t>(-1);
|
||||
// Verify the request is not larger than the max size_t value.
|
||||
if (gnu_debugdata_size_ > SIZE_MAX) {
|
||||
return nullptr;
|
||||
}
|
||||
size_t initial_buffer_size;
|
||||
if (__builtin_mul_overflow(5, gnu_debugdata_size_, &initial_buffer_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t buffer_increment;
|
||||
if (__builtin_mul_overflow(2, gnu_debugdata_size_, &buffer_increment)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> src(new (std::nothrow) uint8_t[gnu_debugdata_size_]);
|
||||
if (src.get() == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<MemoryBuffer> dst(new MemoryBuffer);
|
||||
if (!dst->Resize(initial_buffer_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!memory_->ReadFully(gnu_debugdata_offset_, src.get(), gnu_debugdata_size_)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -89,21 +110,23 @@ Memory* ElfInterface::CreateGnuDebugdataMemory() {
|
|||
CXzUnpacker state;
|
||||
alloc.Alloc = [](ISzAllocPtr, size_t size) { return malloc(size); };
|
||||
alloc.Free = [](ISzAllocPtr, void* ptr) { return free(ptr); };
|
||||
|
||||
XzUnpacker_Construct(&state, &alloc);
|
||||
|
||||
std::unique_ptr<MemoryBuffer> dst(new MemoryBuffer);
|
||||
int return_val;
|
||||
size_t src_offset = 0;
|
||||
size_t dst_offset = 0;
|
||||
ECoderStatus status;
|
||||
dst->Resize(5 * gnu_debugdata_size_);
|
||||
do {
|
||||
size_t src_remaining = src.size() - src_offset;
|
||||
size_t src_remaining = gnu_debugdata_size_ - src_offset;
|
||||
size_t dst_remaining = dst->Size() - dst_offset;
|
||||
if (dst_remaining < 2 * gnu_debugdata_size_) {
|
||||
dst->Resize(dst->Size() + 2 * gnu_debugdata_size_);
|
||||
dst_remaining += 2 * gnu_debugdata_size_;
|
||||
if (dst_remaining < buffer_increment) {
|
||||
size_t new_size;
|
||||
if (__builtin_add_overflow(dst->Size(), buffer_increment, &new_size) ||
|
||||
!dst->Resize(new_size)) {
|
||||
XzUnpacker_Free(&state);
|
||||
return nullptr;
|
||||
}
|
||||
dst_remaining += buffer_increment;
|
||||
}
|
||||
return_val = XzUnpacker_Code(&state, dst->GetPtr(dst_offset), &dst_remaining, &src[src_offset],
|
||||
&src_remaining, true, CODER_FINISH_ANY, &status);
|
||||
|
@ -112,13 +135,13 @@ Memory* ElfInterface::CreateGnuDebugdataMemory() {
|
|||
} while (return_val == SZ_OK && status == CODER_STATUS_NOT_FINISHED);
|
||||
XzUnpacker_Free(&state);
|
||||
if (return_val != SZ_OK || !XzUnpacker_IsStreamWasFinished(&state)) {
|
||||
gnu_debugdata_offset_ = 0;
|
||||
gnu_debugdata_size_ = static_cast<uint64_t>(-1);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Shrink back down to the exact size.
|
||||
dst->Resize(dst_offset);
|
||||
if (!dst->Resize(dst_offset)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return dst.release();
|
||||
}
|
||||
|
|
|
@ -206,12 +206,12 @@ std::shared_ptr<Memory> Memory::CreateOfflineMemory(const uint8_t* data, uint64_
|
|||
}
|
||||
|
||||
size_t MemoryBuffer::Read(uint64_t addr, void* dst, size_t size) {
|
||||
if (addr >= raw_.size()) {
|
||||
if (addr >= size_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytes_left = raw_.size() - static_cast<size_t>(addr);
|
||||
const unsigned char* actual_base = static_cast<const unsigned char*>(raw_.data()) + addr;
|
||||
size_t bytes_left = size_ - static_cast<size_t>(addr);
|
||||
const unsigned char* actual_base = static_cast<const unsigned char*>(raw_) + addr;
|
||||
size_t actual_len = std::min(bytes_left, size);
|
||||
|
||||
memcpy(dst, actual_base, actual_len);
|
||||
|
@ -219,7 +219,7 @@ size_t MemoryBuffer::Read(uint64_t addr, void* dst, size_t size) {
|
|||
}
|
||||
|
||||
uint8_t* MemoryBuffer::GetPtr(size_t offset) {
|
||||
if (offset < raw_.size()) {
|
||||
if (offset < size_) {
|
||||
return &raw_[offset];
|
||||
}
|
||||
return nullptr;
|
||||
|
|
|
@ -29,18 +29,27 @@ namespace unwindstack {
|
|||
class MemoryBuffer : public Memory {
|
||||
public:
|
||||
MemoryBuffer() = default;
|
||||
virtual ~MemoryBuffer() = default;
|
||||
virtual ~MemoryBuffer() { free(raw_); }
|
||||
|
||||
size_t Read(uint64_t addr, void* dst, size_t size) override;
|
||||
|
||||
uint8_t* GetPtr(size_t offset);
|
||||
|
||||
void Resize(size_t size) { raw_.resize(size); }
|
||||
bool Resize(size_t size) {
|
||||
raw_ = reinterpret_cast<uint8_t*>(realloc(raw_, size));
|
||||
if (raw_ == nullptr) {
|
||||
size_ = 0;
|
||||
return false;
|
||||
}
|
||||
size_ = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t Size() { return raw_.size(); }
|
||||
uint64_t Size() { return size_; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> raw_;
|
||||
uint8_t* raw_ = nullptr;
|
||||
size_t size_ = 0;
|
||||
};
|
||||
|
||||
} // namespace unwindstack
|
||||
|
|
|
@ -105,6 +105,9 @@ class ElfInterfaceFake : public ElfInterface {
|
|||
void FakeSetDynamicVaddrStart(uint64_t vaddr) { dynamic_vaddr_start_ = vaddr; }
|
||||
void FakeSetDynamicVaddrEnd(uint64_t vaddr) { dynamic_vaddr_end_ = vaddr; }
|
||||
|
||||
void FakeSetGnuDebugdataOffset(uint64_t offset) { gnu_debugdata_offset_ = offset; }
|
||||
void FakeSetGnuDebugdataSize(uint64_t size) { gnu_debugdata_size_ = size; }
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, uint64_t> globals_;
|
||||
std::string fake_build_id_;
|
||||
|
|
|
@ -1944,4 +1944,23 @@ TEST_F(ElfInterfaceTest, get_load_bias_exec_negative_64) {
|
|||
CheckLoadBiasInFirstExecPhdr<Elf64_Ehdr, Elf64_Phdr, ElfInterface64>(0x5000, 0x1000, -0x4000);
|
||||
}
|
||||
|
||||
TEST_F(ElfInterfaceTest, huge_gnu_debugdata_size) {
|
||||
ElfInterfaceFake interface(nullptr);
|
||||
|
||||
interface.FakeSetGnuDebugdataOffset(0x1000);
|
||||
interface.FakeSetGnuDebugdataSize(0xffffffffffffffffUL);
|
||||
ASSERT_TRUE(interface.CreateGnuDebugdataMemory() == nullptr);
|
||||
|
||||
interface.FakeSetGnuDebugdataSize(0x4000000000000UL);
|
||||
ASSERT_TRUE(interface.CreateGnuDebugdataMemory() == nullptr);
|
||||
|
||||
// This should exceed the size_t value of the first allocation.
|
||||
#if defined(__LP64__)
|
||||
interface.FakeSetGnuDebugdataSize(0x3333333333333334ULL);
|
||||
#else
|
||||
interface.FakeSetGnuDebugdataSize(0x33333334);
|
||||
#endif
|
||||
ASSERT_TRUE(interface.CreateGnuDebugdataMemory() == nullptr);
|
||||
}
|
||||
|
||||
} // namespace unwindstack
|
||||
|
|
Loading…
Reference in a new issue