Description
The good
function below works as expected, but the bad
function miscompiles, and will read uninitialized memory.
https://godbolt.org/z/PWsMaYerv
; Function Attrs: nounwind nonlazybind uwtable
define i64 @"bad"(ptr align 8 %0) unnamed_addr #3 {
start:
%1 = alloca ptr, align 8
store ptr %0, ptr %1, align 8
%2 = load ptr, ptr %1, align 8
%3 = va_arg ptr %2, i64
ret i64 %3
}
; Function Attrs: nounwind nonlazybind uwtable
define i64 @"good"(ptr align 8 %0) unnamed_addr #3 {
start:
%3 = va_arg ptr %0, i64
ret i64 %3
}
I don't see why storing and then loading a pointer, then using it, would cause the va_arg
to be invalid.
build/run instructions
#include <stdarg.h>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
extern size_t good(va_list ap);
extern size_t bad(va_list ap);
int tester(size_t (*fn)(va_list), ...) {
size_t ret = 0;
va_list ap;
va_start(ap, fn);
ret = fn(ap);
va_end(ap);
return ret;
}
int main(int argc, char* argv[]) {
// assert(tester(good, 0x01LL, 0x02, 0x03LL) == 1);
assert(tester(bad, 0x01LL, 0x02, 0x03LL) == 1);
return 0;
}
Run this as
> clang --version
ClangBuiltLinux clang version 20.1.5 (https://github.com/llvm/llvm-project.git 7b09d7b446383b71b63d429b21ee45ba389c5134)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: ...
> clang bug.c bug.ll -o bug
> ./bug
> valgrind --track-origins=yes ./bug
the run will segfault, valgrind shows
==378134== Use of uninitialised value of size 8
==378134== at 0x1092BE: bad (in /tmp/bug)
==378134== by 0x1091D1: tester (in /tmp/bug)
==378134== by 0x109265: main (in /tmp/bug)
==378134== Uninitialised value was created by a stack allocation
==378134== at 0x1092A0: bad (in /tmp/bug)
==378134==
==378134== Invalid read of size 8
==378134== at 0x1092BE: bad (in /tmp/bug)
==378134== by 0x1091D1: tester (in /tmp/bug)
==378134== by 0x109265: main (in /tmp/bug)
==378134== Address 0x100000000000011 is not stack'd, malloc'd or (recently) free'd
==378134==
==378134==
==378134== Process terminating with default action of signal 11 (SIGSEGV)
==378134== General Protection Fault
==378134== at 0x1092BE: bad (in /tmp/bug)
==378134== by 0x1091D1: tester (in /tmp/bug)
==378134== by 0x109265: main (in /tmp/bug)
From what I can tell, the offending code is
mov rcx, qword ptr [rsp - 32]
mov edx, dword ptr [rsp - 12]
mov rsi, qword ptr [rcx + 16]
Nothing useful is stored at rsp - 32
, it is uninitialized memory!
Probably this mostly went unnoticed because clang uses its own implementation of va_arg
, and seemingly never uses this LLVM intrinsic.
This issue came up in the context of rust-lang/rust#44930 (comment), where we're trying to improve the API for C variadic functions in rust. Apparently so far the optimizer/inliner have made sure invalid patterns did not occur, but that is unreliable.
this is an issue that seems similar, but has less information #61021.