0008-libsanitizer-uclibc-off_t-abi-mismatch.patch 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. libsanitizer: fix OFF_T ABI mismatch on uClibc-ng
  2. On 32-bit ARM with uClibc-ng, AddressSanitizer's mmap interceptor was
  3. silently corrupting the offset argument, making every internal mmap()
  4. call from libuClibc.so fail with EINVAL. The most visible casualty was
  5. pthread_create(): allocate_stack() in libpthread/nptl/allocatestack.c
  6. mmap()s the new thread stack, the syscall returned EINVAL, and the
  7. caller saw "Invalid argument" before the thread ever started.
  8. Two GCC testsuite tests were observed to fail because of this:
  9. - g++.dg/asan/deep-thread-stack-1.C (any subsequent thread create)
  10. - c-c++-common/asan/pr98920.c
  11. but in practice _every_ ASan-instrumented program that calls
  12. pthread_create (including indirectly via std::thread, OpenMP, libc
  13. routines, ...) was broken on uClibc-ng/ARM.
  14. Root cause
  15. ==========
  16. libsanitizer/sanitizer_common/sanitizer_internal_defs.h selects the
  17. type used for off_t in interceptor signatures:
  18. #if SANITIZER_FREEBSD || SANITIZER_NETBSD || SANITIZER_APPLE ||
  19. (SANITIZER_SOLARIS && (defined(_LP64) || _FILE_OFFSET_BITS == 64)) ||
  20. (SANITIZER_LINUX && !SANITIZER_GLIBC && !SANITIZER_ANDROID) || <-- HERE
  21. (SANITIZER_LINUX && (defined(__x86_64__) || defined(__hexagon__)))
  22. typedef u64 OFF_T;
  23. #else
  24. typedef uptr OFF_T;
  25. #endif
  26. The "Linux && !glibc && !Android" branch is meant for musl, which
  27. indeed uses 64-bit off_t on all architectures. But after our patch
  28. 0001-libsanitizer-do-not-treat-uClibc-ng-as-glibc.patch SANITIZER_GLIBC
  29. is also 0 on uClibc-ng builds, so uClibc-ng falls through this branch
  30. too, and OFF_T is set to u64.
  31. uClibc-ng on 32-bit ARM, however, keeps __off_t = long = 32 bit.
  32. libuClibc.so calls its own mmap() wrapper with a 32-bit offset.
  33. Once ASan inserts itself between the caller and the wrapper, its
  34. interceptor reads "OFF_T offset" off the stack/registers as 64 bit,
  35. picks up garbage in the upper word, and forwards that to the mmap2
  36. syscall. The kernel's mmap2 expects (offset >> PAGE_SHIFT) to fit in
  37. unsigned long, so any non-zero upper word -> EINVAL.
  38. This was also confirmed empirically: a syscall(192, ...) issued from
  39. the same call site succeeded under ASan, while the libc wrapper
  40. failed. Bypassing the interceptor entirely fixed the symptom.
  41. Verification
  42. ============
  43. Direct trigger:
  44. asan: pthread_create(stack=16M) -> r=22 errno=22 (Invalid argument)
  45. ^^^ before patch (silent thread-create failure)
  46. asan: pthread_create(stack=16M) -> r=0 errno=0 (Success)
  47. ^^^ after patch
  48. deep-thread-stack-1.C now produces the expected
  49. "AddressSanitizer: heap-use-after-free" report; pr98920.c exits 0.
  50. The fix
  51. =======
  52. Exclude SANITIZER_UCLIBC from the "Linux && !glibc && !Android"
  53. fallthrough so OFF_T defaults to uptr on 32-bit uClibc-ng targets,
  54. matching the host ABI. No effect on glibc/Android/musl/BSD/macOS.
  55. A static "sizeof(::OFF_T) == sizeof(off_t)" check exists in
  56. interception/interception_type_test.cpp and would have caught this if
  57. SANITIZER_UCLIBC had been distinguished from SANITIZER_GLIBC earlier;
  58. patch 0001 introduced that discriminator and this patch puts it to
  59. work where it actually matters at runtime.
  60. Other OFF_T users were audited
  61. (sanitizer_common_interceptors.inc: pread/pwrite/preadv/pwritev/
  62. ftello/fseeko/funopen/SHA1FileChunk/RMD160FileChunk/mmap and
  63. sanitizer_linux.cpp / sanitizer_posix.cpp: internal_lseek,
  64. MapWritableFileToMemory, internal_syscall(SYS_mmap, SYS_ftruncate)).
  65. All of them benefit from the same one-line change because they all
  66. share the same OFF_T typedef, and uClibc-ng uses a 32-bit __off_t for
  67. all of them on 32-bit ARM (the *64 variants take OFF64_T which is
  68. unaffected).
  69. Signed-off-by: Ramin Moussavi <ramin.moussavi@yacoub.de>
  70. ---
  71. --- a/libsanitizer/sanitizer_common/sanitizer_internal_defs.h 2025-08-08 06:51:41.000000000 +0000
  72. +++ b/libsanitizer/sanitizer_common/sanitizer_internal_defs.h 2026-05-16 00:30:00.000000000 +0000
  73. @@ -189,7 +189,8 @@
  74. #if SANITIZER_FREEBSD || SANITIZER_NETBSD || SANITIZER_APPLE || \
  75. (SANITIZER_SOLARIS && (defined(_LP64) || _FILE_OFFSET_BITS == 64)) || \
  76. - (SANITIZER_LINUX && !SANITIZER_GLIBC && !SANITIZER_ANDROID) || \
  77. + (SANITIZER_LINUX && !SANITIZER_GLIBC && !SANITIZER_ANDROID && \
  78. + !SANITIZER_UCLIBC) || \
  79. (SANITIZER_LINUX && (defined(__x86_64__) || defined(__hexagon__)))
  80. typedef u64 OFF_T;
  81. #else