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

Reply via email to