Issue 114793
Summary memset interception in compiler-rt asan is incompatible with ntdll.dll 10.0.26100.2161 from Windows 11 24H2
Labels new issue
Assignees
Reporter yjugl
    ntdll
===

[ntdll.dll 10.0.26100.2161](https://msdl.microsoft.com/download/symbols/ntdll.dll/6C29F8C2263000/ntdll.dll) from Windows 11 24H2 26100.2161 differs from previous versions of ntdll.dll in a subtle way.

Previously RtlDispatchException would almost directly reach into the vectored exception handlers:

```
// ntdll 10.0.19041.5007
push    rbp
push    rsi
push    rdi
push r12
push    r13
push    r14
push    r15
sub     rsp, 1D0h
lea rbp, [rsp+40h]
mov     qword ptr [rbp+1E0h], rbx
mov     rax, qword ptr [ntdll!__security_cookie]
xor     rax, rbp
mov     qword ptr [rbp+180h], rax
mov     rax, qword ptr gs:[60h]
xor     ebx, ebx
mov r15, rdx
mov     qword ptr [rbp+40h], rdx
mov     rsi, rcx
mov byte ptr [rbp], bl
test    dword ptr [rax+0BCh], 800000h
jne ntdll!RtlDispatchException+0x687c4
xor     r8d, r8d
mov     rdx, r15
mov     rcx, rsi
call ntdll!RtlpCallVectoredHandlers
```

But now, two buffers of respective sizes 0x58 and 0xD8 are memset to zero before reaching into the vectored exception handlers:

```
// 10.0.26100.2161
 ntdll!RtlDispatchException:
push    rbp
push    rsi
push rdi
push    r12
push    r13
push    r14
push    r15
sub rsp, 210h
lea     rbp, [rsp+60h]
mov     qword ptr [rbp+200h], rbx
mov     rax, qword ptr [ntdll!__security_cookie]
xor     rax, rbp
mov     qword ptr [rbp+1A0h], rax
xor     esi, esi
mov     r15, rdx
mov     rdi, rcx
mov     dword ptr [rbp+20h], esi
xor     edx, edx
lea     rcx, [rbp+50h]
lea     r8d, [rsi+50h]
  // memset(something, 0, 0x50)
call ntdll!memset$thunk$772440563353939046
xor     edx, edx
mov     byte ptr [rbp], sil
mov     r8d, 0D8h
mov     qword ptr [rbp+8], rsi
lea rcx, [rbp+0C0h]
mov     qword ptr [rbp+10h], rsi
mov     qword ptr [rbp+18h], rsi
mov     qword ptr [rbp+48h], rsi
mov     qword ptr [rbp+28h], rsi
mov     qword ptr [rbp+40h], rsi
  // memset(something, 0, 0xD8)
call    ntdll!memset$thunk$772440563353939046
mov     rax, qword ptr gs:[60h]
test    dword ptr [rax+0BCh], 800000h
je ntdll!RtlDispatchException+0xa0
cmp     qword ptr [ntdll!RtlpExceptionLog2], rsi
mov     byte ptr [rbp], 1
jne ntdll!RtlDispatchException+0x609
xor     r8d, r8d
mov     rdx, r15
mov     rcx, rdi
call ntdll!RtlpCallVectoredHandlers
```

Now let me detail why we care here.

memset interception with ASAN
=====================

When compiler-rt ASAN instrumentation is in place, memset is replaced for instrumentation purposes. So any memset will go through:

```c++
#define COMMON_INTERCEPTOR_MEMSET_IMPL(ctx, dst, v, size) \
  { \
    if (COMMON_INTERCEPTOR_NOTHING_IS_INITIALIZED)        \
 return internal_memset(dst, v, size);               \
 COMMON_INTERCEPTOR_ENTER(ctx, memset, dst, v, size);  \
    if (common_flags()->intercept_intrin)                 \
 COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dst, size);     \
    return REAL(memset)(dst, v, size);                    \
  }
```

Which, after following macros, uses:

```c++
#define ACCESS_MEMORY_RANGE(ctx, offset, size, isWrite)                   \
  do {                                                                    \
 uptr __offset = (uptr)(offset);                                       \
 uptr __size = (uptr)(size); \
    uptr __bad = 0; \
    if (UNLIKELY(__offset > __offset + __size)) { \
      GET_STACK_TRACE_FATAL_HERE; \
      ReportStringFunctionSizeOverflow(__offset, __size, &stack); \
    } \
    if (UNLIKELY(!QuickCheckForUnpoisonedRegion(__offset, __size)) &&     \
        (__bad = __asan_region_is_poisoned(__offset, __size))) {          \
      AsanInterceptorContext *_ctx = (AsanInterceptorContext *)ctx;       \
      bool suppressed = false; \
      if (_ctx) { \
        suppressed = IsInterceptorSuppressed(_ctx->interceptor_name);     \
        if (!suppressed && HaveStackTraceBasedSuppressions()) {           \
 GET_STACK_TRACE_FATAL_HERE;                                     \
 suppressed = IsStackTraceSuppressed(&stack);                    \
 }                                                                 \
 }                                                                   \
 if (!suppressed) { \
        GET_CURRENT_PC_BP_SP; \
        ReportGenericError(pc, bp, sp, __bad, isWrite, __size, 0, false); \
      } \
    } \
  } while (0)
```

In particular, `__asan_region_is_poisoned` will access the shadow memory corresponding to the region that we memset, in order to check if the region is poisoned.

Shadow memory lazy commit on Windows
============================

On Windows, shadow memory pages are first allocated as `MEM_RESERVE`. They are dynamically turned to `MEM_COMMIT` on demand -- meaning that we rely on an exception handler `ShadowExceptionHandler` to change the status of the page when we fail to access a reserved shadow memory page because it is not yet commited.

Putting it together
============

The memset interception in ASAN is incompatible with ntdll 10.0.26100.2161. As soon as a first access violation gets raised because a shadow memory page is reserved but not committed, we immediately reach a new call to memset before we get a chance to reach the `ShadowExceptionHandler`. The new call to memset itself triggers a new access violation and a new call to memset, etc. This is a neverending cycle, until eventually we overflow the stack.

```c++
 # Child-SP          RetAddr               Call Site
00 0000003e`f3000fa0 00007ffb`03adf0da     clang_rt_asan_dynamic_x86_64!__asan_wrap_memset+0x18e [/builds/worker/fetches/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc @ 87] 
01 0000003e`f3001830 00007ffb`03c236de ntdll!RtlDispatchException+0x4a
02 0000003e`f3001a80 00007ffa`8c4c8632 ntdll!KiUserExceptionDispatch+0x2e
03 (Inline Function) --------`-------- clang_rt_asan_dynamic_x86_64!__asan::AddressIsPoisoned+0xe [/builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_mapping.h @ 395] 
04 0000003e`f3002180 00007ffa`8c4c56a3 clang_rt_asan_dynamic_x86_64!__asan_region_is_poisoned+0xf2 [/builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_poisoning.cpp @ 189] 
05 0000003e`f30021e0 00007ffb`03adf0da clang_rt_asan_dynamic_x86_64!__asan_wrap_memset+0x193 [/builds/worker/fetches/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc @ 87] 
06 0000003e`f3002a70 00007ffb`03c236de ntdll!RtlDispatchException+0x4a
07 0000003e`f3002cc0 00007ffa`8c4c8632 ntdll!KiUserExceptionDispatch+0x2e
08 (Inline Function) --------`-------- clang_rt_asan_dynamic_x86_64!__asan::AddressIsPoisoned+0xe [/builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_mapping.h @ 395] 
09 0000003e`f30033c0 00007ffa`8c4c56a3 clang_rt_asan_dynamic_x86_64!__asan_region_is_poisoned+0xf2 [/builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_poisoning.cpp @ 189] 
// ...
```

Related Firefox bug [here](https://bugzilla.mozilla.org/show_bug.cgi?id=1926680).
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to