[sanitizer-common] [Darwin] Fix overlapping dyld segment addresses (attempt 2)#167800
Merged
[sanitizer-common] [Darwin] Fix overlapping dyld segment addresses (attempt 2)#167800
Conversation
…lvm#166005) This fixes two problems: - dyld itself resides within the shared cache. MemoryMappingLayout incorrectly computes the slide for dyld's segments, causing them to (appear to) overlap with other modules. This can cause symbolication issues. - The MemoryMappingLayout ranges on Darwin are not disjoint due to the fact that the LINKEDIT segments overlap for each module. We now ignore these segments to ensure the mapping is disjoint. This adds a check for disjointness, and a runtime warning if this is ever violated (as that suggests issues in the sanitizer memory mapping). There is now a test to ensure that these problems do not recur. rdar://163149325 (cherry picked from commit e330985)
Member
|
@llvm/pr-subscribers-compiler-rt-sanitizer Author: Andrew Haberlandt (ndrewh) ChangesThis re-lands #166005, which was reverted due to the issue described in #167797. There are 4 small changes:
This should not be merged until after #167797. rdar://163149325 Full diff: https://github.com/llvm/llvm-project/pull/167800.diff 2 Files Affected:
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp
index a5ec85ae16460..122f11ba523ca 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp
@@ -45,7 +45,6 @@ struct MemoryMappedSegmentData {
const char *current_load_cmd_addr;
u32 lc_type;
uptr base_virt_addr;
- uptr addr_mask;
};
template <typename Section>
@@ -54,12 +53,60 @@ 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);
}
+static bool VerifyMemoryMapping(MemoryMappingLayout* mapping) {
+ InternalMmapVector<LoadedModule> modules;
+ modules.reserve(128); // matches DumpProcessMap
+ mapping->DumpListOfModules(&modules);
+
+ InternalMmapVector<LoadedModule::AddressRange> segments;
+ for (uptr i = 0; i < modules.size(); ++i) {
+ for (auto& range : modules[i].ranges()) {
+ segments.push_back(range);
+ }
+ }
+
+ // 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(),
+ [](LoadedModule::AddressRange& a, LoadedModule::AddressRange& b) {
+ return a.beg < b.beg;
+ });
+
+ // 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].beg;
+ uptr prev_end = segments[i - 1].end;
+ if (cur_start < prev_end) {
+ well_formed = false;
+ VReport(2, "Overlapping mappings: %s start = %p, %s end = %p\n",
+ segments[i].name, (void*)cur_start, segments[i - 1].name,
+ (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;
+ }
+ }
+ }
+
+ for (auto& m : modules) m.clear();
+
+ mapping->Reset();
+ return well_formed;
+}
+
void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) {
// Don't iterate over sections when the caller hasn't set up the
// data pointer, when there are no sections, or when the segment
@@ -85,6 +132,7 @@ void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) {
MemoryMappingLayout::MemoryMappingLayout(bool cache_enabled) {
Reset();
+ VerifyMemoryMapping(this);
}
MemoryMappingLayout::~MemoryMappingLayout() {
@@ -190,6 +238,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));
@@ -258,23 +307,22 @@ 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 (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;
- } else {
+ if (internal_strncmp(sc->segname, "__LINKEDIT", sizeof("__LINKEDIT")) ==
+ 0) {
+ // The LINKEDIT sections are for internal linker use, and may alias
+ // with the LINKEDIT section for other modules. (If we included them,
+ // our memory map would contain overlappping sections.)
+ return false;
+ }
+
+ uptr base_virt_addr;
+ if (layout_data->current_image == kDyldImageIdx)
+ 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.
@@ -284,9 +332,9 @@ 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));
+ seg_data->name[ARRAY_SIZE(seg_data->name) - 1] = 0;
}
// Return the initial protection.
@@ -300,6 +348,7 @@ static bool NextSegmentLoad(MemoryMappedSegment *segment,
? kDyldPath
: _dyld_get_image_name(layout_data->current_image);
internal_strncpy(segment->filename, src, segment->filename_size);
+ segment->filename[segment->filename_size - 1] = 0;
}
segment->arch = layout_data->current_arch;
internal_memcpy(segment->uuid, layout_data->current_uuid, kModuleUUIDSize);
diff --git a/compiler-rt/test/asan/TestCases/Darwin/asan-verify-module-map.cpp b/compiler-rt/test/asan/TestCases/Darwin/asan-verify-module-map.cpp
new file mode 100644
index 0000000000000..15be1cd6754c3
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Darwin/asan-verify-module-map.cpp
@@ -0,0 +1,25 @@
+// This test simply checks that the "Invalid dyld module map" warning is not printed
+// in the output of a backtrace.
+
+// RUN: %clangxx_asan -DSHARED_LIB -g %s -dynamiclib -o %t.dylib
+// RUN: %clangxx_asan -O0 -g %s %t.dylib -o %t.executable
+// RUN: %env_asan_opts="print_module_map=2" not %run %t.executable 2>&1 | FileCheck %s -DDYLIB=%{t:stem}.tmp.dylib
+
+// CHECK-NOT: WARN: Invalid dyld module map
+// CHECK-DAG: 0x{{.*}}-0x{{.*}} {{.*}}[[DYLIB]]
+// CHECK-DAG: 0x{{.*}}-0x{{.*}} {{.*}}libsystem
+
+#ifdef SHARED_LIB
+extern "C" void foo(int *a) { *a = 5; }
+#else
+# include <cstdlib>
+
+extern "C" void foo(int *a);
+
+int main() {
+ int *a = (int *)malloc(sizeof(int));
+ free(a);
+ foo(a);
+ return 0;
+}
+#endif
\ No newline at end of file
|
DanBlackwell
approved these changes
Nov 13, 2025
Contributor
DanBlackwell
left a comment
There was a problem hiding this comment.
Thanks for this. Good catch with the null-termination.
ndrewh
added a commit
to swiftlang/llvm-project
that referenced
this pull request
Dec 2, 2025
…ttempt 2) (llvm#167800) This re-lands llvm#166005, which was reverted due to the issue described in llvm#167797. There are 4 small changes: - Fix LoadedModule leak by calling Clear() on the modules list - Fix internal_strncpy calls that are not null-terminated - Improve test to accept the dylib being loaded from a different path than compiled `{{.*}}[[DYLIB]]` - strcmp => internal_strncmp This should not be merged until after llvm#167797. rdar://163149325 (cherry picked from commit 4fe79a7)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This re-lands #166005, which was reverted due to the issue described in #167797.
There are 4 small changes:
{{.*}}[[DYLIB]]This should not be merged until after #167797.
rdar://163149325