Quuxplusone / LLVMBugzillaTest

0 stars 0 forks source link

[coroutines] [aarch64] ASAN stack-use-after-return when using initializer_list in the loop #50482

Open Quuxplusone opened 3 years ago

Quuxplusone commented 3 years ago
Bugzilla Link PR51515
Status NEW
Importance P normal
Reported by Pavel Solodovnikov (pa.solodovnikov@scylladb.com)
Reported on 2021-08-17 14:48:45 -0700
Last modified on 2021-09-07 03:22:55 -0700
Version 12.0
Hardware PC Linux
CC blitzrakete@gmail.com, erik.pilkington@gmail.com, llvm-bugs@lists.llvm.org, richard-llvm@metafoo.co.uk, yedeng.yd@linux.alibaba.com
Fixed by commit(s)
Attachments clang_coro_init_list_bug_pp.cc (7879 bytes, text/x-c++src)
Blocks
Blocked by
See also
Created attachment 25152
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.
Quuxplusone commented 3 years ago

Attached clang_coro_init_list_bug_pp.cc (7879 bytes, text/x-c++src): Preprocessed source

Quuxplusone commented 3 years ago

This program is reduced from the code that uses seastar framework (https://github.com/scylladb/seastar).

Currently, we use "clang + libstdc++" combination, but gcc's coroutine impl header doesn't work with clang cleanly out of the box. So, for ease of testing, I've just copied the needed boilerplate code for coroutines impl directly from gcc's header and changed namespace from std to std::experimental.

Quuxplusone commented 3 years ago
Hi Pavel,

  Thanks for reporting this. But I failed to reproduce this. My clang version is 13.  And the version of compiler-rt is 13 too. For the preprocessed reproducer, I tried both libstdc++ and libc++, but it compiles and executes normally.

My version libstdc++ is 9.2.1. And testing target is AArch64.