Skip to content

va_arg miscompiles on x86_64 #141361

Open
Open
@folkertdev

Description

@folkertdev

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions