Issue |
127499
|
Summary |
[coro] Use-after-free of callee-destructed parameter in coroutine which doesn't suspend
|
Labels |
coroutines
|
Assignees |
|
Reporter |
zmodem
|
Consider:
```
$ cat /tmp/a.cc
#include <coroutine>
#include <stdio.h>
class BasicCoroutine { // Borrowed from https://theshoemaker.de/posts/yet-another-cpp-coroutine-tutorial
public:
struct Promise {
BasicCoroutine get_return_object() { return BasicCoroutine {}; }
void unhandled_exception() noexcept { }
void return_void() noexcept { }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
};
using promise_type = Promise;
};
struct [[clang::trivial_abi]] Trivial {
Trivial(int x) : x(x) {}
~Trivial() { printf("~Trivial @ %p: %d\n", this, x); }
int x;
};
BasicCoroutine coro(Trivial t) {
co_return;
}
int main() {
Trivial t(42);
coro(t);
}
$ build/bin/clang++ -std=c++20 /tmp/a.cc -fsanitize=address && ASAN_OPTIONS=external_symbolizer_path=$PWD/build/bin/llvm-symbolizer ./a.out
~Trivial @ 0x7c00cfc20058: 42
=================================================================
==3352321==ERROR: AddressSanitizer: heap-use-after-free on address 0x7c00cfc20054 at pc 0x56082ebe8441 bp 0x7ffdcd77b600 sp 0x7ffdcd77b5f8
READ of size 4 at 0x7c00cfc20054 thread T0
#0 0x56082ebe8440 in Trivial::~Trivial() (/work/llvm-project/a.out+0x114440)
#1 0x56082ebe7843 in coro(Trivial) (/work/llvm-project/a.out+0x113843)
#2 0x56082ebe7c8b in main (/work/llvm-project/a.out+0x113c8b)
#3 0x7fd0d0a40c89 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#4 0x7fd0d0a40d44 in __libc_start_main csu/../csu/libc-start.c:360:3
#5 0x56082eb01380 in _start (/work/llvm-project/a.out+0x2d380)
0x7c00cfc20054 is located 20 bytes inside of 32-byte region [0x7c00cfc20040,0x7c00cfc20060)
freed by thread T0 here:
#0 0x56082ebe6c52 in operator delete(void*, unsigned long) /work/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:155:3
#1 0x56082ebe7829 in coro(Trivial) (/work/llvm-project/a.out+0x113829)
#2 0x56082ebe7c8b in main (/work/llvm-project/a.out+0x113c8b)
#3 0x7fd0d0a40c89 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
previously allocated by thread T0 here:
#0 0x56082ebe5fed in operator new(unsigned long) /work/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:86:3
#1 0x56082ebe74fa in coro(Trivial) (/work/llvm-project/a.out+0x1134fa)
#2 0x56082ebe7c8b in main (/work/llvm-project/a.out+0x113c8b)
#3 0x7fd0d0a40c89 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-use-after-free (/work/llvm-project/a.out+0x114440) in Trivial::~Trivial()
Shadow bytes around the buggy address:
0x7c00cfc1fd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c00cfc1fe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c00cfc1fe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c00cfc1ff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c00cfc1ff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7c00cfc20000: fa fa 00 00 00 fa fa fa fd fd[fd]fd fa fa fa fa
0x7c00cfc20080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7c00cfc20100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7c00cfc20180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7c00cfc20200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7c00cfc20280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==3352321==ABORTING
```
Note the `[[clang::trivial_abi]]` attribute, which means the `t` parameter should be destructed at the end of `coro`. The same problem applies without the attribute when targeting Windows with the MSVC ABI.
Clang emits a move-constructed copy of `t` to go in the coro frame, and a call to destruct the original `t` at the end of the ramp-up. However, it also puts the original `t` in the frame, and since `coro` doesn't suspend, the frame is deleted before reaching the destructor call, which ends up dereferencing a pointer into the deleted coro frame.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs