Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_procmaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ class MemoryMappingLayout : public MemoryMappingLayoutBase {
// Adds all mapped objects into a vector.
void DumpListOfModules(InternalMmapVectorNoCtor<LoadedModule> *modules);

# if SANITIZER_APPLE
// Verify that the memory mapping is well-formed. (e.g. mappings do not
// overlap)
bool Verify();
# endif

protected:
#if SANITIZER_APPLE
virtual const ImageHeader *CurrentImageHeader();
Expand Down
76 changes: 62 additions & 14 deletions compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ struct MemoryMappedSegmentData {
const char *current_load_cmd_addr;
u32 lc_type;
uptr base_virt_addr;
uptr addr_mask;
};

template <typename Section>
Expand All @@ -51,7 +50,7 @@ static void NextSectionLoad(LoadedModule *module, MemoryMappedSegmentData *data,
const Section *sc = (const Section *)data->current_load_cmd_addr;
data->current_load_cmd_addr += sizeof(Section);

uptr sec_start = (sc->addr & data->addr_mask) + data->base_virt_addr;
uptr sec_start = sc->addr + data->base_virt_addr;
uptr sec_end = sec_start + sc->size;
module->addAddressRange(sec_start, sec_end, /*executable=*/false, isWritable,
sc->sectname);
Expand Down Expand Up @@ -82,6 +81,7 @@ void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) {

MemoryMappingLayout::MemoryMappingLayout(bool cache_enabled) {
Reset();
Verify();
}

MemoryMappingLayout::~MemoryMappingLayout() {
Expand Down Expand Up @@ -187,6 +187,7 @@ typedef struct dyld_shared_cache_dylib_text_info

extern bool _dyld_get_shared_cache_uuid(uuid_t uuid);
extern const void *_dyld_get_shared_cache_range(size_t *length);
extern intptr_t _dyld_get_image_slide(const struct mach_header* mh);
extern int dyld_shared_cache_iterate_text(
const uuid_t cacheUuid,
void (^callback)(const dyld_shared_cache_dylib_text_info *info));
Expand Down Expand Up @@ -255,23 +256,21 @@ static bool NextSegmentLoad(MemoryMappedSegment *segment,
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;
if (strncmp(sc->segname, "__LINKEDIT", sizeof("__LINKEDIT")) == 0) {
// The LINKEDIT sections alias, so we ignore these sections to
// ensure our mappings are disjoint.
return false;
}

uptr base_virt_addr;
if (layout_data->current_image == kDyldImageIdx) {
base_virt_addr = (uptr)get_dyld_hdr();
// vmaddr is masked with 0xfffff because on macOS versions < 10.12,
// it contains an absolute address rather than an offset for dyld.
// To make matters even more complicated, this absolute address
// isn't actually the absolute segment address, but the offset portion
// of the address is accurate when combined with the dyld base address,
// and the mask will give just this offset.
addr_mask = 0xfffff;
base_virt_addr = (uptr)_dyld_get_image_slide(get_dyld_hdr());
} else {
base_virt_addr =
(uptr)_dyld_get_image_vmaddr_slide(layout_data->current_image);
addr_mask = ~0;
}

segment->start = (sc->vmaddr & addr_mask) + base_virt_addr;
segment->start = sc->vmaddr + base_virt_addr;
segment->end = segment->start + sc->vmsize;
// Most callers don't need section information, so only fill this struct
// when required.
Expand All @@ -281,7 +280,6 @@ static bool NextSegmentLoad(MemoryMappedSegment *segment,
(const char *)lc + sizeof(SegmentCommand);
seg_data->lc_type = kLCSegment;
seg_data->base_virt_addr = base_virt_addr;
seg_data->addr_mask = addr_mask;
internal_strncpy(seg_data->name, sc->segname,
ARRAY_SIZE(seg_data->name));
}
Expand Down Expand Up @@ -445,6 +443,56 @@ bool MemoryMappingLayout::Next(MemoryMappedSegment *segment) {
return false;
}

// NOTE: Verify expects to be called immediately after Reset(), since otherwise
// it may miss some mappings. Verify will Reset() the layout after verification.
bool MemoryMappingLayout::Verify() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make it a static function, with parameters of what is needed
and avoid Verify in the header?

Unless there is a plan to extend it to other platforms?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had imagined it would eventually be extended to other platforms, but I didn't personally do that here since I wasn't sure other platforms guaranteed non-overlapping regions (and didn't have the ability to easily test this change on other platforms).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make it a static function in this file. If other platforms need to introduce something similar then it can be promoted to MemoryMappingLayout

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not straightforward as a static function because data_ is a private member of MemoryMappedSegment which is only accessible inside MemoryMappingLayout because it is a friend class.

InternalMmapVector<char> module_name(kMaxPathLength);
MemoryMappedSegment segment(module_name.data(), module_name.size());
MemoryMappedSegmentData data;
segment.data_ = &data;

InternalMmapVector<MemoryMappedSegment> segments;
while (Next(&segment)) {
// skip the __PAGEZERO segment, its vmsize is 0
if (segment.filename[0] == '\0' || (segment.start == segment.end))
continue;

segments.push_back(segment);
}

// Verify that none of the segments overlap:
// 1. Sort the segments by the start address
// 2. Check that every segment starts after the previous one ends.
Sort(segments.data(), segments.size(),
[](MemoryMappedSegment& a, MemoryMappedSegment& b) {
return a.start < b.start;
});

// To avoid spam, we only print the report message once-per-process.
static bool invalid_module_map_reported = false;
bool well_formed = true;

for (size_t i = 1; i < segments.size(); i++) {
uptr cur_start = segments[i].start;
uptr prev_end = segments[i - 1].end;
if (cur_start < prev_end) {
well_formed = false;
VReport(2, "Overlapping mappings: cur_start = %p prev_end = %p\n",
(void*)cur_start, (void*)prev_end);
if (!invalid_module_map_reported) {
Report(
"WARN: Invalid dyld module map detected. This is most likely a bug "
"in the sanitizer.\n");
Report("WARN: Backtraces may be unreliable.\n");
invalid_module_map_reported = true;
}
}
}

Reset();
return well_formed;
}

void MemoryMappingLayout::DumpListOfModules(
InternalMmapVectorNoCtor<LoadedModule> *modules) {
Reset();
Expand Down
20 changes: 20 additions & 0 deletions compiler-rt/test/asan/TestCases/Darwin/asan-verify-module-map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This test simply checks that the "Invalid dyld module map" warning is not printed
// in the output of a backtrace.

// RUN: %clangxx_asan -O0 -g %s -o %t.executable
// RUN: %env_asan_opts="print_module_map=2" not %run %t.executable 2>&1 | FileCheck %s

// CHECK-NOT: WARN: Invalid dyld module map

#include <cstdlib>

extern "C" void foo(int *a) { *a = 5; }

int main() {
int *a = (int *)malloc(sizeof(int));
if (!a)
return 0;
free(a);
foo(a);
return 0;
}