mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-27 06:36:46 +00:00
[Sanitizers] Fix read buffer overrun in scanning loader commands
The fix only affects Darwin, but to write the test I had to modify the MemoryMappingLayout class which is used by all OSes, to allow for mocking of image header (this change should be NFC). Hence no [Darwin] in the subject so I can get more eyes on it. While looking for a memory gap to put the shadow area into, the sanitizer code scans through the loaded images, and for each image it scans through its loader command to determine the occupied memory ranges. While doing so, if the 'segment load' (kLCSegment) loader comand is encountered, the command scanning function returns success (true), but does not decrement the command list iterator counter. The result is that the function is called again and again, with the iterator counter now being too high. The command scanner keeps updating the loader command pointer, by using the command size field. If the loop counter is too high, the command pointer lands into unintended area ( beyond +sizeof(mac_header64)+header->sizeofcmds ), and result depends on the random content found there. The random content interpreted as loader command might contain a large integer value in the cmdsize field - this value is added to the current loader command pointer, which might now point to an inaccessible memory address. It can occasionally result in a crash if it happens to run beyond the mapped memory segment. Note that when the area after the loader command list contains zeros or small integers only, the loop will end normally and the problem will go unnoticed. So it happened until now since having a some big value after the header area, falling into command size field is a pretty rare situation. The fix makes sure that the iterator counter gets updated when the segment load (kLCSegment) loader command is found too, and in the same code location so the updates will always go together. Undo the changes in the sanitizer_procmaps_mac.cpp to see the test failing. rdar://101161047 rdar://102819707 Differential Revision: https://reviews.llvm.org/D142164
This commit is contained in:
parent
42129deaa6
commit
82d852c69f
@ -65,6 +65,8 @@ class MemoryMappedSegment {
|
||||
MemoryMappedSegmentData *data_;
|
||||
};
|
||||
|
||||
struct ImageHeader;
|
||||
|
||||
class MemoryMappingLayoutBase {
|
||||
public:
|
||||
virtual bool Next(MemoryMappedSegment *segment) { UNIMPLEMENTED(); }
|
||||
@ -75,10 +77,18 @@ class MemoryMappingLayoutBase {
|
||||
~MemoryMappingLayoutBase() {}
|
||||
};
|
||||
|
||||
class MemoryMappingLayout final : public MemoryMappingLayoutBase {
|
||||
class MemoryMappingLayout : public MemoryMappingLayoutBase {
|
||||
public:
|
||||
explicit MemoryMappingLayout(bool cache_enabled);
|
||||
|
||||
// This destructor cannot be virtual, as it would cause an operator new() linking
|
||||
// failures in hwasan test cases. However non-virtual destructors emit warnings
|
||||
// in macOS build, hence disabling those
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
|
||||
~MemoryMappingLayout();
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
virtual bool Next(MemoryMappedSegment *segment) override;
|
||||
virtual bool Error() const override;
|
||||
virtual void Reset() override;
|
||||
@ -90,10 +100,14 @@ class MemoryMappingLayout final : public MemoryMappingLayoutBase {
|
||||
// Adds all mapped objects into a vector.
|
||||
void DumpListOfModules(InternalMmapVectorNoCtor<LoadedModule> *modules);
|
||||
|
||||
protected:
|
||||
#if SANITIZER_APPLE
|
||||
virtual const ImageHeader *CurrentImageHeader();
|
||||
#endif
|
||||
MemoryMappingLayoutData data_;
|
||||
|
||||
private:
|
||||
void LoadFromCache();
|
||||
|
||||
MemoryMappingLayoutData data_;
|
||||
};
|
||||
|
||||
// Returns code range for the specified module.
|
||||
|
@ -250,7 +250,9 @@ static bool NextSegmentLoad(MemoryMappedSegment *segment,
|
||||
MemoryMappedSegmentData *seg_data,
|
||||
MemoryMappingLayoutData *layout_data) {
|
||||
const char *lc = layout_data->current_load_cmd_addr;
|
||||
|
||||
layout_data->current_load_cmd_addr += ((const load_command *)lc)->cmdsize;
|
||||
layout_data->current_load_cmd_count--;
|
||||
if (((const load_command *)lc)->cmd == kLCSegment) {
|
||||
const SegmentCommand* sc = (const SegmentCommand *)lc;
|
||||
uptr base_virt_addr, addr_mask;
|
||||
@ -358,11 +360,16 @@ static bool IsModuleInstrumented(const load_command *first_lc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ImageHeader *MemoryMappingLayout::CurrentImageHeader() {
|
||||
const mach_header *hdr = (data_.current_image == kDyldImageIdx)
|
||||
? get_dyld_hdr()
|
||||
: _dyld_get_image_header(data_.current_image);
|
||||
return (const ImageHeader *)hdr;
|
||||
}
|
||||
|
||||
bool MemoryMappingLayout::Next(MemoryMappedSegment *segment) {
|
||||
for (; data_.current_image >= kDyldImageIdx; data_.current_image--) {
|
||||
const mach_header *hdr = (data_.current_image == kDyldImageIdx)
|
||||
? get_dyld_hdr()
|
||||
: _dyld_get_image_header(data_.current_image);
|
||||
const mach_header *hdr = (const mach_header *)CurrentImageHeader();
|
||||
if (!hdr) continue;
|
||||
if (data_.current_load_cmd_count < 0) {
|
||||
// Set up for this image;
|
||||
@ -392,7 +399,7 @@ bool MemoryMappingLayout::Next(MemoryMappedSegment *segment) {
|
||||
(const load_command *)data_.current_load_cmd_addr);
|
||||
}
|
||||
|
||||
for (; data_.current_load_cmd_count >= 0; data_.current_load_cmd_count--) {
|
||||
while (data_.current_load_cmd_count > 0) {
|
||||
switch (data_.current_magic) {
|
||||
// data_.current_magic may be only one of MH_MAGIC, MH_MAGIC_64.
|
||||
#ifdef MH_MAGIC_64
|
||||
@ -413,6 +420,7 @@ bool MemoryMappingLayout::Next(MemoryMappedSegment *segment) {
|
||||
}
|
||||
// If we get here, no more load_cmd's in this image talk about
|
||||
// segments. Go on to the next image.
|
||||
data_.current_load_cmd_count = -1; // This will trigger loading next image
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ set(SANITIZER_UNITTESTS
|
||||
sanitizer_posix_test.cpp
|
||||
sanitizer_printf_test.cpp
|
||||
sanitizer_procmaps_test.cpp
|
||||
sanitizer_procmaps_mac_test.cpp
|
||||
sanitizer_ring_buffer_test.cpp
|
||||
sanitizer_quarantine_test.cpp
|
||||
sanitizer_stack_store_test.cpp
|
||||
|
@ -0,0 +1,137 @@
|
||||
//===-- sanitizer_procmaps_mac_test.cpp ---------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file is a part of ThreadSanitizer/AddressSanitizer runtime.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
# include "sanitizer_common/sanitizer_platform.h"
|
||||
|
||||
# if SANITIZER_APPLE
|
||||
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# include <stdint.h>
|
||||
# include <stdio.h>
|
||||
|
||||
# include <vector>
|
||||
# include <mach-o/dyld.h>
|
||||
# include <mach-o/loader.h>
|
||||
|
||||
# include "gtest/gtest.h"
|
||||
|
||||
# include "sanitizer_common/sanitizer_procmaps.h"
|
||||
|
||||
namespace __sanitizer {
|
||||
|
||||
class MemoryMappingLayoutMock final : public MemoryMappingLayout {
|
||||
private:
|
||||
static constexpr uuid_command mock_uuid_command = {
|
||||
.cmd = LC_UUID,
|
||||
.cmdsize = sizeof(uuid_command),
|
||||
.uuid = {}
|
||||
};
|
||||
|
||||
static constexpr char dylib_name[] = "libclang_rt.\0\0\0"; // 8 bytes aligned, padded with zeros per loader.h
|
||||
static constexpr dylib_command mock_dylib_command = {
|
||||
.cmd = LC_LOAD_DYLIB,
|
||||
.cmdsize = sizeof(dylib_command) + sizeof(dylib_name),
|
||||
.dylib = {
|
||||
.name = {
|
||||
.offset = sizeof(dylib_command)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr uuid_command mock_trap_command = {
|
||||
.cmd = LC_UUID,
|
||||
.cmdsize = 0x10000,
|
||||
.uuid = {}
|
||||
};
|
||||
|
||||
const char *start_load_cmd_addr;
|
||||
size_t sizeofcmds;
|
||||
std::vector<unsigned char> mock_header;
|
||||
|
||||
public:
|
||||
MemoryMappingLayoutMock(): MemoryMappingLayout(false) {
|
||||
EXPECT_EQ(mock_uuid_command.cmdsize % 8, 0u);
|
||||
EXPECT_EQ(mock_dylib_command.cmdsize % 8, 0u);
|
||||
|
||||
Reset();
|
||||
|
||||
#ifdef MH_MAGIC_64
|
||||
const struct mach_header_64 *header = (mach_header_64 *)_dyld_get_image_header(0); // Any header will do
|
||||
const size_t header_size = sizeof(mach_header_64);
|
||||
#else
|
||||
const struct mach_header *header = _dyld_get_image_header(0);
|
||||
const size_t header_size = sizeof(mach_header);
|
||||
#endif
|
||||
const size_t mock_header_size_with_extras = header_size + header->sizeofcmds +
|
||||
mock_uuid_command.cmdsize + mock_dylib_command.cmdsize + sizeof(uuid_command);
|
||||
|
||||
mock_header.reserve(mock_header_size_with_extras);
|
||||
// Copy the original header
|
||||
copy((unsigned char *)header,
|
||||
(unsigned char *)header + header_size + header->sizeofcmds,
|
||||
back_inserter(mock_header));
|
||||
// The following commands are not supposed to be processed
|
||||
// by the (correct) ::Next method at all, since they're not
|
||||
// accounted for in header->ncmds .
|
||||
copy((unsigned char *)&mock_uuid_command,
|
||||
((unsigned char *)&mock_uuid_command) + mock_uuid_command.cmdsize,
|
||||
back_inserter(mock_header));
|
||||
copy((unsigned char *)&mock_dylib_command,
|
||||
((unsigned char *)&mock_dylib_command) + sizeof(dylib_command), // as mock_dylib_command.cmdsize contains the following string
|
||||
back_inserter(mock_header));
|
||||
copy((unsigned char *)dylib_name,
|
||||
((unsigned char *)dylib_name) + sizeof(dylib_name),
|
||||
back_inserter(mock_header));
|
||||
|
||||
// Append a command w. huge size to have the test detect the read overrun
|
||||
copy((unsigned char *)&mock_trap_command,
|
||||
((unsigned char *)&mock_trap_command) + sizeof(uuid_command),
|
||||
back_inserter(mock_header));
|
||||
|
||||
start_load_cmd_addr = (const char *)(mock_header.data() + header_size);
|
||||
sizeofcmds = header->sizeofcmds;
|
||||
|
||||
const char *last_byte_load_cmd_addr = (start_load_cmd_addr+sizeofcmds-1);
|
||||
data_.current_image = -1; // So the loop in ::Next runs just once
|
||||
}
|
||||
|
||||
size_t SizeOfLoadCommands() {
|
||||
return sizeofcmds;
|
||||
}
|
||||
|
||||
size_t CurrentLoadCommandOffset() {
|
||||
size_t offset = data_.current_load_cmd_addr - start_load_cmd_addr;
|
||||
return offset;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ImageHeader *CurrentImageHeader() override {
|
||||
return (ImageHeader *)mock_header.data();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(MemoryMappingLayout, Next) {
|
||||
__sanitizer::MemoryMappingLayoutMock memory_mapping;
|
||||
__sanitizer::MemoryMappedSegment segment;
|
||||
size_t size = memory_mapping.SizeOfLoadCommands();
|
||||
while (memory_mapping.Next(&segment)) {
|
||||
size_t offset = memory_mapping.CurrentLoadCommandOffset();
|
||||
EXPECT_LE(offset, size);
|
||||
}
|
||||
size_t final_offset = memory_mapping.CurrentLoadCommandOffset();
|
||||
EXPECT_EQ(final_offset, size); // All commands processed, no more, no less
|
||||
}
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
# endif // SANITIZER_APPLE
|
Loading…
x
Reference in New Issue
Block a user