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
138 lines
4.4 KiB
C++
138 lines
4.4 KiB
C++
//===-- 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
|