Description
Clang 17+ (and the current master) crashes with UBSAN when a JIT compiled function is being called. For some reason it crashes on address, which is funcPtr - 8
. Note that both ASAN and valgrind pass without any issues. Clang's 16 UBSAN and older work, GCC's UBSAN works as well.
It seems to me that something is wrong with clang's 17+ UBSAN, but I have no idea why funcPtr - 8
is the problem - such arithmetic is not used in the code; it seems injected by UBSAN.
I have been able to make this reproducible with asmjit library, without the need to modify anything:
git clone https://github.com/asmjit/asmjit.git
cd asmjit/tools
CC=clang CXX=clang++ ./configure-sanitizers.sh
cd ..
cd build/Release_UBSAN
make
./asmjit_test_emitters
When ./asmjit_test_emitters
is executed, the output is the following:
AsmJit Emitters Test-Suite v1.10.0
Using x86::Assembler:
mov rax, rdi
mov rcx, rsi
movdqu xmm0, [rcx]
movdqu xmm1, [rdx]
paddd xmm0, xmm1
movdqu [rax], xmm0
ret
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==309866==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x7f03e8aacff8 (pc 0x555d011597e7 bp 0x7fff4f1e86e0 sp 0x7fff4f1e82e0 T309866)
==309866==The signal is caused by a READ memory access.
#0 0x555d011597e7 (/home/petr/workspace/asmjit/build/Release_UBSAN/asmjit_test_emitters+0x327e7)
#1 0x555d01158ba3 (/home/petr/workspace/asmjit/build/Release_UBSAN/asmjit_test_emitters+0x31ba3)
#2 0x7f03e86456c9 (/lib/x86_64-linux-gnu/libc.so.6+0x276c9) (BuildId: 072feb34c63e054d60d94cbc68d92e4caad25d72)
#3 0x7f03e8645784 (/lib/x86_64-linux-gnu/libc.so.6+0x27784) (BuildId: 072feb34c63e054d60d94cbc68d92e4caad25d72)
#4 0x555d0112e5a0 (/home/petr/workspace/asmjit/build/Release_UBSAN/asmjit_test_emitters+0x75a0)
UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV (/home/petr/workspace/asmjit/build/Release_UBSAN/asmjit_test_emitters+0x327e7)
==309866==ABORTING
The logger is okay - it successfully assembled the function, however, it then fails when trying to execute it. When I have debugged this, I have found out that the pointer to the function is actually 0x7f03e8aad000
, but UBSAN fails with 0x7f03e8aacff8
address, which is 8 bytes less and doesn't exist.
To add a bit more to the context, here is a snippet of the code from the test:
// Add the code generated to the runtime.
SumIntsFunc fn;
err = rt.add(&fn, &code);
if (err) {
printf("** FAILURE: JitRuntime::add() failed (%s) **\n", DebugUtils::errorAsString(err));
return 1;
}
// Execute the generated function.
int inA[4] = { 4, 3, 2, 1 };
int inB[4] = { 1, 5, 2, 8 };
int out[4];
// <---- IT FAILS HERE, WHEN CALLING `fn`
fn(out, inA, inB);
// Should print {5 8 4 9}.
printf("Result = { %d %d %d %d }\n\n", out[0], out[1], out[2], out[3]);
rt.release(fn);
return !(out[0] == 5 && out[1] == 8 && out[2] == 4 && out[3] == 9);
It fails exactly when calling fn
via a pointer to function. UBSAN for some reason accesses fn - 8
(probably to get some metadata, not sure) and then crashes. AsmJit's virtual memory allocator uses mmap()
or VirtualAlloc()
to allocate executable memory, which is used only for executable code (there is nothing else stored to maintain the pages), and the first generated function is exactly at the beginning of the allocated page. I would say with 99.9999% confidence that UBSAN is trying to read from a memory that is not mapped in this case. And I personally consider this a bug, because I'm not aware of any material which would state that a function cannot start at the beginning of a page.
OS: Debian Linux
Architecture: x86_64
Clang Version: 17+, master