danvratil / qcoro

C++ Coroutines for Qt
https://qcoro.dvratil.cz
MIT License
331 stars 53 forks source link

asan reports use-after-free in coroutine using QFuture. #157

Closed jbruechert closed 1 year ago

jbruechert commented 1 year ago

I'm not sure yet whether this is a help request or a bug report.

This code, which looks like it should work to me, causes a heap-use-after-free report from asan.

********* Start testing of SqliteTest *********
Config: Using QtTest library 5.15.8, Qt 5.15.8 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 12.2.0), debian unknown
PASS   : SqliteTest::initTestCase()
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, data TEXT)"
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "INSERT INTO test (data) VALUES (?)"
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "SELECT * FROM test LIMIT 1"
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "SELECT * FROM test"
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "INSERT INTO test (data) VALUES (?)"
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "SELECT * from test ORDER BY id ASC"
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "SELECT * FROM test LIMIT 1"
QDEBUG : SqliteTest::testDeserialization() futuresql: Running "SELECT * from test ORDER BY id ASC"
=================================================================
==55693==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000a10 at pc 0x56438d774210 bp 0x7ffcb074e2e0 sp 0x7ffcb074e2d8
READ of size 8 at 0x603000000a10 thread T0
    #0 0x56438d77420f in operator() /home/jonah/kde/src/futuresql/tests/sqlitetest.cpp:66
    #1 0x56438d77818b in std::__n4861::coroutine_handle<void>::resume() const /usr/include/c++/12/coroutine:135
    #2 0x56438d77e36f in QCoro::detail::QCoroFuture<std::vector<TestDefault, std::allocator<TestDefault> > >::WaitForFinishedOperationBase<std::vector<TestDefault, std::allocator<TestDefault> > >::await_suspend(std::__n4861::coroutine_handle<void>)::{lambda()#1}::operator()() /usr/local/include/qcoro5/qcoro/qcorofuture.h:38
    #3 0x56438d79a4e7 in QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, QCoro::detail::QCoroFuture<std::vector<TestDefault, std::allocator<TestDefault> > >::WaitForFinishedOperationBase<std::vector<TestDefault, std::allocator<TestDefault> > >::await_suspend(std::__n4861::coroutine_handle<void>)::{lambda()#1}>::call(QCoro::detail::QCoroFuture<std::vector<TestDefault, std::allocator<TestDefault> > >::WaitForFinishedOperationBase<std::vector<TestDefault, std::allocator<TestDefault> > >::await_suspend(std::__n4861::coroutine_handle<void>)::{lambda()#1}&, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:146
    #4 0x56438d798d99 in void QtPrivate::Functor<QCoro::detail::QCoroFuture<std::vector<TestDefault, std::allocator<TestDefault> > >::WaitForFinishedOperationBase<std::vector<TestDefault, std::allocator<TestDefault> > >::await_suspend(std::__n4861::coroutine_handle<void>)::{lambda()#1}, 0>::call<QtPrivate::List<>, void>(QCoro::detail::QCoroFuture<std::vector<TestDefault, std::allocator<TestDefault> > >::WaitForFinishedOperationBase<std::vector<TestDefault, std::allocator<TestDefault> > >::await_suspend(std::__n4861::coroutine_handle<void>)::{lambda()#1}&, void*, void**) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:256
    #5 0x56438d79634d in QtPrivate::QFunctorSlotObject<QCoro::detail::QCoroFuture<std::vector<TestDefault, std::allocator<TestDefault> > >::WaitForFinishedOperationBase<std::vector<TestDefault, std::allocator<TestDefault> > >::await_suspend(std::__n4861::coroutine_handle<void>)::{lambda()#1}, 0, QtPrivate::List<>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:443
    #6 0x7fe2f2ee8f4e  (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x2e8f4e)
    #7 0x7fe2f2cd5ba4 in QFutureWatcherBase::event(QEvent*) (/lib/x86_64-linux-gnu/libQt5Core.so.5+0xd5ba4)
    #8 0x7fe2f2eb16cc in QCoreApplication::notifyInternal2(QObject*, QEvent*) (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x2b16cc)
    #9 0x7fe2f2eb4680 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x2b4680)
    #10 0x7fe2f2f0a152  (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x30a152)
    #11 0x7fe2f1f1e7a8 in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x547a8)
    #12 0x7fe2f1f1ea37  (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x54a37)
    #13 0x7fe2f1f1eacb in g_main_context_iteration (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x54acb)
    #14 0x7fe2f2f09835 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x309835)
    #15 0x7fe2f2eb017a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x2b017a)
    #16 0x56438d7767ec in QTestEventLoop::enterLoopMSecs(int) /usr/include/x86_64-linux-gnu/qt5/QtTest/qtesteventloop.h:103
    #17 0x56438d776fe4 in QSignalSpy::wait(int) /usr/include/x86_64-linux-gnu/qt5/QtTest/qsignalspy.h:139
    #18 0x56438d77a651 in SqliteTest::testDeserialization() /home/jonah/kde/src/futuresql/tests/sqlitetest.cpp:90
    #19 0x56438d77498d in SqliteTest::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) /home/jonah/kde/src/futuresql/build/tests/sqlitetest_autogen/include/sqlitetest.moc:87
    #20 0x7fe2f2ebc88d in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x2bc88d)
    #21 0x7fe2f31c4be0  (/lib/x86_64-linux-gnu/libQt5Test.so.5+0x1abe0)
    #22 0x7fe2f31c562f  (/lib/x86_64-linux-gnu/libQt5Test.so.5+0x1b62f)
    #23 0x7fe2f31c5b78  (/lib/x86_64-linux-gnu/libQt5Test.so.5+0x1bb78)
    #24 0x7fe2f31c6041 in QTest::qRun() (/lib/x86_64-linux-gnu/libQt5Test.so.5+0x1c041)
    #25 0x7fe2f31c63ea in QTest::qExec(QObject*, int, char**) (/lib/x86_64-linux-gnu/libQt5Test.so.5+0x1c3ea)
    #26 0x56438d774772 in main /home/jonah/kde/src/futuresql/tests/sqlitetest.cpp:95
    #27 0x7fe2f2646189 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #28 0x7fe2f2646244 in __libc_start_main_impl ../csu/libc-start.c:381
    #29 0x56438d771950 in _start (/home/jonah/kde/src/futuresql/build/tests/sqlitetest+0x7950)

0x603000000a10 is located 16 bytes inside of 24-byte region [0x603000000a00,0x603000000a18)
freed by thread T0 here:
    #0 0x7fe2f32ba3c8 in operator delete(void*, unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:164
    #1 0x56438d78dc9a in QtPrivate::QFunctorSlotObject<SqliteTest::testDeserialization()::{lambda()#1}, 0, QtPrivate::List<>, QCoro::Task<void> >::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs_impl.h:440
    #2 0x7fe2f2ebb8ad in QMetaObject::invokeMethodImpl(QObject*, QtPrivate::QSlotObjectBase*, Qt::ConnectionType, void*) (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x2bb8ad)

previously allocated by thread T0 here:
    #0 0x7fe2f32b94c8 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
    #1 0x56438d77eacd in _ZN11QMetaObject12invokeMethodIZN10SqliteTest19testDeserializationEvEUlvE_EENSt9enable_ifIXaaaantsrN9QtPrivate15FunctionPointerIT_EE25IsPointerToMemberFunctioneqsrS7_13ArgumentCountLin1EntsrSt14is_convertibleIS6_PKcE5valueEbE4typeEP7QObjectS6_N2Qt14ConnectionTypeEPDTclfL0p0_EE /usr/include/x86_64-linux-gnu/qt5/QtCore/qobjectdefs.h:526
    #2 0x56438d77a61a in SqliteTest::testDeserialization() /home/jonah/kde/src/futuresql/tests/sqlitetest.cpp:66
    #3 0x56438d77498d in SqliteTest::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) /home/jonah/kde/src/futuresql/build/tests/sqlitetest_autogen/include/sqlitetest.moc:87
    #4 0x7fe2f2ebc88d in QMetaMethod::invoke(QObject*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument) const (/lib/x86_64-linux-gnu/libQt5Core.so.5+0x2bc88d)
    #5 0x7fe2f31c4be0  (/lib/x86_64-linux-gnu/libQt5Test.so.5+0x1abe0)
    #6 0x7fe2f31c562f  (/lib/x86_64-linux-gnu/libQt5Test.so.5+0x1b62f)

SUMMARY: AddressSanitizer: heap-use-after-free /home/jonah/kde/src/futuresql/tests/sqlitetest.cpp:66 in operator()
Shadow bytes around the buggy address:
  0x0c067fff80f0: fa fa fd fd fd fd fa fa 00 00 00 fa fa fa 00 00
  0x0c067fff8100: 00 00 fa fa fd fd fd fd fa fa fd fd fd fd fa fa
  0x0c067fff8110: fd fd fd fa fa fa fd fd fd fa fa fa fd fd fd fa
  0x0c067fff8120: fa fa fd fd fd fa fa fa fd fd fd fa fa fa fd fd
  0x0c067fff8130: fd fa fa fa fd fd fd fa fa fa fd fd fd fa fa fa
=>0x0c067fff8140: fd fd[fd]fa fa fa 00 00 00 fa fa fa 00 00 00 fa
  0x0c067fff8150: fa fa 00 00 00 00 fa fa fd fd fd fd fa fa fd fd
  0x0c067fff8160: fd fd fa fa fd fd fd fd fa fa fd fd fd fd fa fa
  0x0c067fff8170: 00 00 00 00 fa fa 00 00 00 00 fa fa fd fd fd fa
  0x0c067fff8180: fa fa fd fd fd fd fa fa fd fd fd fd fa fa fd fd
  0x0c067fff8190: fd fd fa fa fd fd fd fa fa fa fd fd fd fd 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
==55693==ABORTING

Do you know whether there is something I am doing wrong here? Thanks :)

danvratil commented 1 year ago

I think what's happening here is simple: when the lambda is suspended for the first time, it returns back to the event loop, which immediatelly deletes the lambda because it thinks it has finished.

As a simple test, try refactoring the lambda to a regular member function - it shouldn't crash anymore.

As to how to solve this inside QCoro - that might be tricky :-)

jbruechert commented 1 year ago

Thank you for the hint! That did indeed work :)