google / sanitizers

AddressSanitizer, ThreadSanitizer, MemorySanitizer
Other
11.35k stars 1.02k forks source link

ASAN returns a false positive error when extended ams is used with rdrand instruction #1629

Open takuro-sato opened 1 year ago

takuro-sato commented 1 year ago

It happens in x64 machines and looks like address sanitizer creates executables with invalid memory access.

$ cat main.cpp
#include <iostream>

int main () {
  unsigned char ok = 0;
  uint64_t rand = 0;
  asm volatile("rdrand %0; setc %1" : "=r"(rand), "=qm"(ok));

  return rand;
}

$ clang++-17 -g -stdlib=libc++ -fsanitize=address -std=c++20 -fno-omit-frame-pointer main.cpp && ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==21663==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x56095ba667ab bp 0x7ffc96033200 sp 0x7ffc96033180 T0)
==21663==The signal is caused by a READ memory access.
==21663==Hint: this fault was caused by a dereference of a high value address (see register values below).  Disassemble the provided pc to learn which register was used.
    #0 0x56095ba667ab in main /home/takurosato/rdrand/main.cpp:6:3
    #1 0x7f19a469d082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
    #2 0x56095b9912ed in _start (/home/takurosato/rdrand/a.out+0x1e2ed) (BuildId: e8168831d23441cfde390920a5734df30034a8db)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/takurosato/rdrand/main.cpp:6:3 in main
==21663==ABORTING

$ objdump -D a.out
...

   f37a7:       48 0f c7 f0             rdrand %rax
   f37ab:       0f 92 00                setb   (%rax)

...

Somehow the output assembly code uses the random number output of rdrand as a pointer as you can see in result of objdump. The executable without address sanitizer looks like this:

$ clang++-17 -g -stdlib=libc++ -std=c++20 main.cpp && ./a.out # No visible output in stdout
$ echo $?
29
$ objdump -D a.out
...
    1147:       48 0f c7 f0             rdrand %rax
    114b:       0f 92 45 fb             setb   -0x5(%rbp)
    114f:       48 89 45 f0             mov    %rax,-0x10(%rbp)
...

For this case the output is used as a value as expected.

neldredge commented 1 year ago

I believe this is actually a true positive: the code is buggy and ASAN correctly detects it as such. The asm has an implicit input operand (the address of ok), and the rand output operand is written before this input is consumed. Therefore, the rand operand needs an earlyclober & modifier. Without this, it is legitimate for the compiler to use the same register for the address of ok as for rand.

The compiler can and will break such code even without ASAN. If we modify the example slightly, so that the address of operand 1 is already in rax, then the compiler again uses the same register for both:

char *get_okptr();
uint64_t rand;
asm("rdrand %0 ; setc %1" : "=r" (rand), "=m" (*get_okptr()));
return rand;

will emit rdrand %rax ; setb (%rax) under both gcc and clang (using -O3). https://godbolt.org/z/TWdzY45Wr