Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8726d3a4 authored by Christopher Ferris's avatar Christopher Ferris
Browse files

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
parent a23ce83a
Loading
Loading
Loading
Loading
+37 −14
Original line number Diff line number Diff line
@@ -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();
}
+4 −4
Original line number Diff line number Diff line
@@ -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;
+13 −4
Original line number Diff line number Diff line
@@ -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
+3 −0
Original line number Diff line number Diff line
@@ -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_;
+19 −0
Original line number Diff line number Diff line
@@ -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