<html>
<head>
<base href="https://bugs.llvm.org/">
</head>
<body><table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Bug ID</th>
<td><a class="bz_bug_link
bz_status_NEW "
title="NEW - [coroutines] [aarch64] ASAN stack-use-after-return when using initializer_list in the loop"
href="https://bugs.llvm.org/show_bug.cgi?id=51515">51515</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>[coroutines] [aarch64] ASAN stack-use-after-return when using initializer_list in the loop
</td>
</tr>
<tr>
<th>Product</th>
<td>clang
</td>
</tr>
<tr>
<th>Version</th>
<td>12.0
</td>
</tr>
<tr>
<th>Hardware</th>
<td>PC
</td>
</tr>
<tr>
<th>OS</th>
<td>Linux
</td>
</tr>
<tr>
<th>Status</th>
<td>NEW
</td>
</tr>
<tr>
<th>Severity</th>
<td>normal
</td>
</tr>
<tr>
<th>Priority</th>
<td>P
</td>
</tr>
<tr>
<th>Component</th>
<td>C++2a
</td>
</tr>
<tr>
<th>Assignee</th>
<td>unassignedclangbugs@nondot.org
</td>
</tr>
<tr>
<th>Reporter</th>
<td>pa.solodovnikov@scylladb.com
</td>
</tr>
<tr>
<th>CC</th>
<td>blitzrakete@gmail.com, erik.pilkington@gmail.com, llvm-bugs@lists.llvm.org, richard-llvm@metafoo.co.uk
</td>
</tr></table>
<p>
<div>
<pre>Created <span class=""><a href="attachment.cgi?id=25152" name="attach_25152" title="Preprocessed source">attachment 25152</a> <a href="attachment.cgi?id=25152&action=edit" title="Preprocessed source">[details]</a></span>
Preprocessed source
The following program will report stack-use-after-return, when compiled with
Clang 12.0.0 (12.0.1, 13.0.0 and trunk, as well) and ASAN enabled:
#include <initializer_list>
...
class resumable {
public:
struct promise_type;
using coro_handle =
std::experimental::coroutine_handle<promise_type>;
resumable(coro_handle& handle) : handle(handle) {}
resumable(resumable&&) = delete;
~resumable() {
handle.destroy();
}
bool resume() {
if (!handle.done()) {
handle.resume();
}
return !handle.done();
}
private:
coro_handle handle;
};
struct resumable::promise_type {
using coro_handle =
std::experimental::coroutine_handle<promise_type>;
auto get_return_object() {
return coro_handle::from_promise(*this);
}
auto initial_suspend() { return
std::experimental::suspend_always(); }
auto final_suspend() noexcept { return
std::experimental::suspend_always(); }
void return_void() {}
void unhandled_exception() {
throw;
}
};
struct test_init_list {
int item;
test_init_list(std::initializer_list<int> items) {
item = *items.begin();
}
};
resumable foo() {
for (int _ : {1, 2}) {
(void)_;
test_init_list x {0}; // <-- Crashes after the first
suspension
co_await std::experimental::suspend_always();
}
}
int main() {
auto p = foo();
while (p.resume());
return 0;
}
Steps to reproduce:
# build
/usr/bin/clang++ -std=c++20 -fsanitize=address -Wall -Werror -Og -g -o
clang_coro_init_list_bug.cc.o -c clang_coro_init_list_bug.cc
# link
/usr/bin/clang++ clang_coro_init_list_bug.cc.o -o
clang_coro_init_list_bug -fsanitize=address
# execute with ASAN
ASAN_OPTIONS='disable_coredump=0:abort_on_error=0:detect_stack_use_after_return=1'
./clang_coro_init_list_bug
Execution result:
=================================================================
==24==ERROR: AddressSanitizer: stack-use-after-return on address 0xffff8630e0e0
at pc 0x0000004eeed0 bp 0xffffdc7a11c0 sp 0xffffdc7a11d8
READ of size 4 at 0xffff8630e0e0 thread T0
#0 0x4eeecc (/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x4eeecc)
#1 0x4ee964 (/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x4ee964)
#2 0x4ef170 (/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x4ef170)
#3 0x4eef58 (/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x4eef58)
#4 0x4ee698 (/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x4ee698)
#5 0xffff899a2b18 (/lib64/libc.so.6+0x24b18)
#6 0x421d8c (/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x421d8c)
Address 0xffff8630e0e0 is located in stack of thread T0 at offset 32 in frame
#0 0x4ee71c (/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x4ee71c)
This frame has 1 object(s):
[32, 36) 'ref.tmp14' <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack
unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-return
(/home/pa.solodovnikov/clang_coro_init_list_bug_pp+0x4eeecc)
Shadow bytes around the buggy address:
0x200ff0c61bc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x200ff0c61bd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x200ff0c61be0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x200ff0c61bf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x200ff0c61c00: f1 f1 f1 f1 00 f3 f3 f3 f5 f5 f5 f5 f5 f5 f5 f5
=>0x200ff0c61c10: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5[f5]f5 f5 f5
0x200ff0c61c20: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
0x200ff0c61c30: f1 f1 f1 f1 04 f3 f3 f3 f1 f1 f1 f1 00 00 f3 f3
0x200ff0c61c40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x200ff0c61c50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x200ff0c61c60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
Shadow gap: cc
==24==ABORTING
Decoded stack trace:
[Backtrace #0]
test_init_list at /home/pa.solodovnikov/clang_coro_init_list_bug.cc:129
foo() at /home/pa.solodovnikov/clang_coro_init_list_bug.cc:136
std::experimental::coroutine_handle<resumable::promise_type>::resume() const at
/home/pa.solodovnikov/clang_coro_init_list_bug.cc:38
resumable::resume() at /home/pa.solodovnikov/clang_coro_init_list_bug.cc:102
main at /home/pa.solodovnikov/clang_coro_init_list_bug.cc:143
__libc_start_main at ??:?
_start at ??:?
This points to the following place in the code, where initializer list data is
accessed:
test_init_list(std::initializer_list<int> items) {
item = *items.begin();
}
List initialization is broken after the first coroutine suspension. I have
added debug printouts around the crash site:
test_init_list(std::initializer_list<int> items) {
std::cout << "initializer list address: " << &items << std::endl;
std::cout << "initializer list data pointer: " << std::data(items) <<
std::endl;
item = *items.begin();
}
This shows, that the address to the underlying storage is the same (obviously
points to garbage after a coroutine is resumed):
initializer list address: 0xffff85fbd120
initializer list data pointer: 0xffff85fbd0e0
initializer list address: 0xffff85fbd220
initializer list data pointer: 0xffff85fbd0e0
The problem only happens with the following combination: aarch64 target, ASAN
enabled (-fsanitize=address), -Og optimization level (-Oz or -Os are also
affected). Tested with libstdc++ 11.2.1.
When built with GCC 11.2.1, the code compiles and executes without errors.
I have also attached a preprocessed version of source code.</pre>
</div>
</p>
<hr>
<span>You are receiving this mail because:</span>
<ul>
<li>You are on the CC list for the bug.</li>
</ul>
</body>
</html>