<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/97627>97627</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
TSAN macOS - iterating load commands in TSAN can result in a segfault
</td>
</tr>
<tr>
<th>Labels</th>
<td>
new issue
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
kubkon
</td>
</tr>
</table>
<pre>
While working on enabling TSAN on macOS in Zig, I have hit a rare issue with current TSAN implementation. Namely, if a sanitized image dynamically links a dylib which has `headerpad` forced to 0, TSAN will crash when initializing itself.
Short repro:
```c
// a.c
int foo = 42;
void dummy() {
foo--;
}
```
```c
// main.c
#include <stdio.h>
#include <pthread.h>
void dummy();
int Global;
void *Thread1(void *x) {
Global++;
return NULL;
}
void *Thread2(void *x) {
Global--;
return NULL;
}
int main() {
dummy();
pthread_t t[2];
pthread_create(&t[0], NULL, Thread1, NULL);
pthread_create(&t[1], NULL, Thread2, NULL);
pthread_join(t[0], NULL);
pthread_join(t[1], NULL);
}
```
Now build with system clang or LLVM clang, but make sure to use LLVM's linker as Apple's linker ignores `headerpad=0` for images.
```
$ cc -shared a.c -Wl,-headerpad,0 -Wl,-install_name,@rpath/liba.dylib -o liba.dylib -fuse-ld=/opt/llvm18-release/bin/ld64.lld
ld64.lld: warning: directory not found for option -L/usr/local/lib
$ cc main.c -fsanitize=thread -la -L. -Wl,-rpath,. -fuse-ld=/opt/llvm18-release/bin/ld64.lld
ld64.lld: warning: directory not found for option -L/usr/local/lib
$ ./a.out
fish: Job 1, './a.out' terminated by signal SIGSEGV (Address boundary error)
```
Running in the debugger reveals where the bug is hidden:
```
$ lldb a.out
(lldb) target create "a.out"
Current executable set to 'a.out' (arm64).
(lldb) settings set target.disable-aslr false
(lldb) r
Process 19672 launched: 'a.out' (arm64)
Process 19672 stopped
* thread #1, stop reason = EXC_BAD_ACCESS (code=1, address=0x34bcf7975)
frame #0: 0x00000001050e7eec libclang_rt.tsan_osx_dynamic.dylib`__sanitizer::MemoryMappingLayout::Next(__sanitizer::MemoryMappedSegment*) + 348
libclang_rt.tsan_osx_dynamic.dylib`__sanitizer::MemoryMappingLayout::Next:
-> 0x1050e7eec <+348>: ldr w8, [x23]
0x1050e7ef0 <+352>: cmp w8, #0xc
0x1050e7ef4 <+356>: b.ne 0x1050e7ee0 ; <+336>
0x1050e7ef8 <+360>: ldr w8, [x23, #0x8]
Target 0: (a.out) stopped.
(lldb) bt
* thread #1, stop reason = EXC_BAD_ACCESS (code=1, address=0x34bcf7975)
* frame #0: 0x00000001050e7eec libclang_rt.tsan_osx_dynamic.dylib`__sanitizer::MemoryMappingLayout::Next(__sanitizer::MemoryMappedSegment*) + 348
frame #1: 0x0000000105153c10 libclang_rt.tsan_osx_dynamic.dylib`__tsan::CheckAndProtect() + 104
frame #2: 0x000000010515358c libclang_rt.tsan_osx_dynamic.dylib`__tsan::InitializePlatform() + 28
frame #3: 0x00000001051330ec libclang_rt.tsan_osx_dynamic.dylib`__tsan::Initialize(__tsan::ThreadState*) + 280
frame #4: 0x0000000105102fbc libclang_rt.tsan_osx_dynamic.dylib`__tsan::ScopedInterceptor::ScopedInterceptor(__tsan::ThreadState*, char const*, unsigned long) + 176
frame #5: 0x0000000105114ff0 libclang_rt.tsan_osx_dynamic.dylib`wrap_strlcpy + 72
frame #6: 0x00000001887476e8 libsystem_c.dylib`__guard_setup + 132
frame #7: 0x00000001887474a8 libsystem_c.dylib`_libc_initializer + 72
frame #8: 0x00000001955115f4 libSystem.B.dylib`libSystem_initializer + 168
frame #9: 0x000000018852505c dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const::$_0::operator()() const + 168
frame #10: 0x0000000188563308 dyld`invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 340
frame #11: 0x000000018855699c dyld`invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 496
frame #12: 0x00000001885062fc dyld`dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const + 300
frame #13: 0x0000000188555930 dyld`dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 192
frame #14: 0x0000000188562e1c dyld`dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 516
frame #15: 0x0000000188521070 dyld`dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 524
frame #16: 0x000000018852ad28 dyld`dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const + 44
frame #17: 0x000000018854435c dyld`dyld4::APIs::runAllInitializersForMain() + 84
frame #18: 0x000000018850af7c dyld`dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3156
frame #19: 0x0000000188509edc dyld`start + 1844
(lldb)
```
Namely, `NextCommand` function in `sanitizer_procmaps_mac.cpp` makes an incorrect assumption that there always is zeroed out padding between the end of load command table and beginning of the first loadable section. Incidentally, zeroed out padding of size at least 8 bytes can be interpreted as `LC_NULL` load command with size `0` which would terminate the iterator. Forcing the padding to `0` will make the iterator iterate past the end of the load command table.
Faulty code in question: https://github.com/llvm/llvm-project/blob/c02e8f762a410e55581866c43636efcd6504c1bd/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_mac.cpp#L333-L335
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzUWdtv4zaX_2uYlwMbFHWx_JAHxxkXs8hMB023XeyLQVFHFhuK1JJUEs9fv6AkXxIrmbT4vuJrECQSeW6_c-NF3Dm504jXJL0h6e0V73xt7PVDVzwYfVWYcn_9ey0VwpOxD1LvwGhAzQsVnn-9X30NAw0XP9-D1PC_ckfYGj5DzR8RaumBg-UWQTrXITxJX4PorEXtB2bZtAob1J57afQcvvIG1T7IkBVwcFxLL79jCbLhO4Ryr3kjBVdqD0rqBwccyr2SBTzVUtRQcwckozXyEm3LS5JRqIwVWII3QIPcXu2TVAqE5a6Gpxo1yKCGK_k9oJLeoarmhN4Suhr-3tfGerDYWkPi1fkMyejwK8Z3tiFsA3w-vkvtoTIGSHwLCSPxzTnzo5EllF3T7AnLCVsCWYzzgWc2O5Evbl-p-4ANDZd6fhyKpRaqKxFIvHa-lGZek_jT1Gzra4u8PJ-ftPYVmID0J2UKrqZQErb6tRcbEZYfRp5fYj6ws5vwGx-HLfrOavj633d3Ex6Z0MF-qOPMtx-SH8AFf17EadIhMLpw68GT9IaR9PZM3WFSWOQee9YskNFAxtaDHSFTD_46DE0oeC0jmpTBJmUchfxhemCXNvyAOHqD-P1s_WqeoOikKod24PbOYwNC8dBcLNzd_fZleAuiiy74_QHBdRZDDXcOexLCFq5vAWiBO1i1rcLzMbnTxuKrbhDf0rEjDP3EzSfL6FAUCQgBM1dzi2WoaJj9rghbz04C2ZoeBqV2niu11bxBwtYkobblviZso2TB50OXmhk4f6s6hzMV7CJsY1ofiNVjE-Uziwq5Q8I2RXD3RpVZMleqHEw7vsUreOJWS70Lj6W0KLyxe9AmdJ1Olz1W04bmCrM7wjads0GcEaHOgmknsAHt0DNgVh06L4lvh8DDTHGY3c0PeEd06_l_Gow5YRs-N50fhirp6iD1v0wBfS0RtjiRsAV4tI3U3GMJxR7CasgV3H_-6f7TT78BYfmqLC06B0UwhNs9oLXGhnx_O8d_6bTulxINvkYoseh2O7Rg8RG5cmHRCelcIxTdDqSDWpYl6rfWlhM4pcoCztARloeh0JM8tzv0MHQEIIyNCNlAuR6XXXxG0XleKASHPpQUYYujMwjLuW2yhLDl_EKDQ--l3rmBsVc3L6ULsmbcKQsVVw4v2Oww8s0aEfwYLbMFA8U7LWrsY_-GAVNszpu2xfKgYwVjchIW98EN82CRO6P7JffT_6y3N6vb7Wq9_nR_H6QLU4ak7qn5ENrQF57jpBDVYrlIj5oBACrLm-DMmAZD6TMdfiKaUlwgilDOfbPaWj_3juutcc_bcZsylDnJ6HZ7KCcbQhyvvmBj7P4Lb1upd3d8H8D3E1_x2ROWv8OA5T3uwo6JsFW_FLEbiJN8rKh_gzWHnJyR-BPQ5xN0Eq8Juwm640_BO6q0wWVPeV9l6c0zi8MCcfTlkbeiB96Ujbyiac94WUyfxRRjcmTMRsZirvGcBCm8_CHxzYEpzo47mjOZ-WE6o-8AOZiVHyH9OtQbHTI4HzN4eUjRy_Ip_N-UtkHBPyxxX9Ra9NrkKI1FRD9qcpgalK9rFA8rXX6zxqPwh80bu4GIJoeN9kErm9Ca5h921Enr58NhAr8p7itjmzPFLH-tN77QG8f04wGa0tsH4jQxbALvfb9TXJ0soRO-Ty6soawq_oI198K0WH7WHq3A1hv71vD7tq5B1NyCMNr5caDT_ZG1BGXCJnEM6CKbQJNeoImSqvpgJj1Z3m6dt0q0-17Hgk2oyF6qyPNFssgwDyqGre323D-7jtty69B37WB2PCVzMSEz4W_IDFi2xwMs2rdNzV-KXaZpFKVVEsTe92LnN0exx7EL0VE2VbPL1yanLKWpCKfzcAqX-tGI_owPVafF8GAsFMqIh7BNCnTJkAJ3Jmyvh-dK6nKly186vVLqlODWEZafsfzSaS8bHNMmC0kxZEw_S1iypcOjadHyIetCwxzqsid9B1tEL8BlcUzzPwUuHvshF_XPK83V_tgiK2M_cVGfoSMsv5V8p43zUrge0PoHQn77Evapa6Mf0Xo8FkzPOR6F82PhSN2vVL1929bIUI1HwlOpnXsmTqa6RRRdeCbNlss_F_ZzUBup8IVX7rHnOh3o87dZRtrPujIv8BfGqNP_bAL6OdJkOdVJInaBlGasOiL9IZCQ1WvTNFyXk-E9wFMmHOsHwhdt72PWx3QyTvFlnNJlTD9s_d8fhmg51cKi5LIUGUZvhuEfWGlpNJl_6WWDjejiRQT_9f1zMIglUwZdLHwp4yXLJwz6ZrHopPLnhtlO_0VzkklrLpbMNEniVExYs_r22R1teOWVjbFfzm752A3kk9ryy2bAq8WUttZiyy2-xNeb8H6uvUiOvrCjdDIvLhdeusTyaIrz3I4FlR88d3Ymee-e7ngVTzIatveH7pXRUzOXOswe9_zb1hrR8NZtGy7mom0DccMf0AEPxMJYi8IDd65rhuscX3MPvr8P4eqJ7x1IB9_RGizBdB5aXpZS76BA_4Q43KegLsFUEFolHFrlcKURngrcyeH6xVQ9eSWt8z31eO0hho8Nn7WQJWrP1YBzQqupwMnvCNyDQu485FDsPToQXEOB0Bd0a9FjCcO3h7v1tr8PzehL-4bLziCLZLS_hhw-WTyZTpWnS6jeYOmHXcocNsaKYEcYPdjkzUmEVGq4Hz1nGx8Ch_PnDguPl057cQm64Z3yewjHzRDc_-vQ9Y0_XkHtfdtXTv95YSd93RVzYZrxum_8N2ut-aM_bG0KZQrCNoIyzKtFxngSUUzTNI_yLBNJnMUZVqLMUpqIqCgDqWlaqdDOrB-v9djmlFzB6rAGbd7JNxbfxXE8u4vj9Kq8jstlvORXeB0tGKVxmqbZVX3NC8rSRUbTaClSFi1onC-KRV7ElOcJFcsrec0oS-iCxtEyXkbJnGdVlkcxliKt8ozGJKHYcKnmAfHc2N1V_4HrernI2OJK8QKV67-oMabxafj6RRgj6e2Vve69VHQ7RxKqpPPuJMVLr_C6_0Q1fFObjbEMcT-PnAvB6elCIlp0nfJhiIPDXRVieNVZdf2nQ9Zb6gjbDEger9n_BwAA__-VzniW">