netcan/asyncio

wait_for控制超时导致coredump

Closed this issue · 7 comments

在测试echo_client时,使用wait_for为read增加超时机制。在很快收到echo_server响应后,打印data.data()数据时,程序coredump了。请帮忙看看是什么问题,谢谢!

运行环境:
Ubuntu 18.04.4 LTS
gcc version 11.1.0

测试代码如下:

#include <iostream>
#include <asyncio/open_connection.h>
#include <asyncio/runner.h>
#include <asyncio/sleep.h>
#include <asyncio/schedule_task.h>
#include <asyncio/wait_for.h>
using asyncio::Stream;
using asyncio::Task;

using namespace std::chrono;

Task<> tcp_echo_client(std::string_view message) {
        auto stream = co_await asyncio::open_connection("127.0.0.1", 8888);

        fmt::print("Send: '{}'\n", message);
        co_await stream.write(Stream::Buffer(message.begin(), message.end()));

        fmt::print("Send: ok\n");

        auto data = co_await asyncio::wait_for(stream.read(100), 300ms);

        fmt::print("wait_for: ok data len {}\n", data.size());
        fmt::print("Received: '{}'\n", data.data());

        fmt::print("Close the connection\n");
        stream.close();
}

int main(int argc, char** argv) {
    asyncio::run(tcp_echo_client("hello world!"));
    return 0;
}

输出和报错信息如下:

Send: 'hello world!'
Send: ok
wait_for: ok data len 12
=================================================================
==16717==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000019c at pc 0x55555557d747 bp 0x7fffffffcfa0 sp 0x7fffffffc748
READ of size 13 at 0x60200000019c thread T0
    #0 0x55555557d746 in __interceptor_strlen.part.0 (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x29746)
    #1 0x5555556906bd in fmt::v8::basic_string_view<char>::basic_string_view(char const*) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/core.h:440
    #2 0x5555556906bd in fmt::v8::appender fmt::v8::detail::write<char, fmt::v8::appender>(fmt::v8::appender, char const*) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/format.h:2122
    #3 0x55555568a2f5 in fmt::v8::appender fmt::v8::detail::default_arg_formatter<char>::operator()<char const*>(char const*) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/format.h:2162
    #4 0x55555569254b in decltype ({parm#1}(0)) fmt::v8::visit_format_arg<fmt::v8::detail::default_arg_formatter<char>, fmt::v8::basic_format_context<fmt::v8::appender, char> >(fmt::v8::detail::default_arg_formatter<char>&&, fmt::v8::basic_format_arg<fmt::v8::basic_format_context<fmt::v8::appender, char> > const&) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/core.h:1560
    #5 0x55555569254b in fmt::v8::detail::vformat_to<char>(fmt::v8::detail::buffer<char>&, fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<std::conditional<std::is_same<fmt::v8::type_identity<char>::type, char>::value, fmt::v8::appender, std::back_insert_iterator<fmt::v8::detail::buffer<fmt::v8::type_identity<char>::type> > >::type, fmt::v8::type_identity<char>::type> >, fmt::v8::detail::locale_ref)::format_handler::on_replacement_field(int, char const*) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/format.h:2919
    #6 0x55555569254b in char const* fmt::v8::detail::parse_replacement_field<char, fmt::v8::detail::vformat_to<char>(fmt::v8::detail::buffer<char>&, fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<std::conditional<std::is_same<fmt::v8::type_identity<char>::type, char>::value, fmt::v8::appender, std::back_insert_iterator<fmt::v8::detail::buffer<fmt::v8::type_identity<char>::type> > >::type, fmt::v8::type_identity<char>::type> >, fmt::v8::detail::locale_ref)::format_handler&>(char const*, char const*, fmt::v8::detail::vformat_to<char>(fmt::v8::detail::buffer<char>&, fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<std::conditional<std::is_same<fmt::v8::type_identity<char>::type, char>::value, fmt::v8::appender, std::back_insert_iterator<fmt::v8::detail::buffer<fmt::v8::type_identity<char>::type> > >::type, fmt::v8::type_identity<char>::type> >, fmt::v8::detail::locale_ref)::format_handler&) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/core.h:2536
    #7 0x55555567b74c in void fmt::v8::detail::parse_format_string<false, char, fmt::v8::detail::vformat_to<char>(fmt::v8::detail::buffer<char>&, fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<std::conditional<std::is_same<fmt::v8::type_identity<char>::type, char>::value, fmt::v8::appender, std::back_insert_iterator<fmt::v8::detail::buffer<fmt::v8::type_identity<char>::type> > >::type, fmt::v8::type_identity<char>::type> >, fmt::v8::detail::locale_ref)::format_handler>(fmt::v8::basic_string_view<char>, fmt::v8::detail::vformat_to<char>(fmt::v8::detail::buffer<char>&, fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<std::conditional<std::is_same<fmt::v8::type_identity<char>::type, char>::value, fmt::v8::appender, std::back_insert_iterator<fmt::v8::detail::buffer<fmt::v8::type_identity<char>::type> > >::type, fmt::v8::type_identity<char>::type> >, fmt::v8::detail::locale_ref)::format_handler&&) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/core.h:2571
    #8 0x55555567b74c in void fmt::v8::detail::vformat_to<char>(fmt::v8::detail::buffer<char>&, fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<std::conditional<std::is_same<fmt::v8::type_identity<char>::type, char>::value, fmt::v8::appender, std::back_insert_iterator<fmt::v8::detail::buffer<fmt::v8::type_identity<char>::type> > >::type, fmt::v8::type_identity<char>::type> >, fmt::v8::detail::locale_ref) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/format.h:2945
    #9 0x55555566dc10 in fmt::v8::vprint(_IO_FILE*, fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<fmt::v8::appender, char> >) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/format-inl.h:2611
    #10 0x55555566de4a in fmt::v8::vprint(fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<fmt::v8::appender, char> >) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/format-inl.h:2627
    #11 0x555555640102 in void fmt::v8::print<char*>(fmt::v8::basic_format_string<char, fmt::v8::type_identity<char*>::type>, char*&&) /home/ubuntu/c++/asyncio/third_party/fmt/include/fmt/core.h:3150
    #12 0x555555640102 in tcp_echo_client(std::basic_string_view<char, std::char_traits<char> >) [clone .actor] /home/ubuntu/c++/asyncio/test/st/echo_client.cpp:26
    #13 0x555555662401 in std::__n4861::coroutine_handle<asyncio::Task<void>::promise_type>::resume() const (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x10e401)
    #14 0x555555661671 in asyncio::Task<void>::promise_type::run() (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x10d671)
    #15 0x5555556634c3 in asyncio::EventLoop::run_once() /home/ubuntu/c++/asyncio/src/event_loop.cpp:63
    #16 0x555555662805 in asyncio::EventLoop::run_until_complete() /home/ubuntu/c++/asyncio/src/event_loop.cpp:17
    #17 0x555555647d14 in decltype(auto) asyncio::run<asyncio::Task<void> >(asyncio::Task<void>&&) (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0xf3d14)
    #18 0x55555564099b in main /home/ubuntu/c++/asyncio/test/st/echo_client.cpp:33
    #19 0x7ffff6a15bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
    #20 0x555555564bb9 in _start (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x10bb9)

0x60200000019c is located 0 bytes to the right of 12-byte region [0x602000000190,0x60200000019c)
allocated by thread T0 here:
    #0 0x5555555f5297 in operator new(unsigned long) (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0xa1297)
    #1 0x555555659af1 in __gnu_cxx::new_allocator<char>::allocate(unsigned long, void const*) /usr/include/c++/11/ext/new_allocator.h:121
    #2 0x5555556552dd in std::allocator<char>::allocate(unsigned long) /usr/include/c++/11/bits/allocator.h:173
    #3 0x5555556552dd in std::allocator_traits<std::allocator<char> >::allocate(std::allocator<char>&, unsigned long) /usr/include/c++/11/bits/alloc_traits.h:460
    #4 0x555555653ae9 in std::_Vector_base<char, std::allocator<char> >::_M_allocate(unsigned long) (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0xffae9)
    #5 0x55555565374c in std::_Vector_base<char, std::allocator<char> >::_M_create_storage(unsigned long) (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0xff74c)
    #6 0x55555564ee7c in std::_Vector_base<char, std::allocator<char> >::_Vector_base(unsigned long, std::allocator<char> const&) (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0xfae7c)
    #7 0x555555657ccd in std::vector<char, std::allocator<char> >::vector(std::vector<char, std::allocator<char> > const&) (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x103ccd)
    #8 0x555555654b6e in asyncio::Result<std::vector<char, std::allocator<char> > >::result() & (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x100b6e)
    #9 0x555555651d35 in asyncio::detail::WaitForAwaiter<std::vector<char, std::allocator<char> >, std::chrono::duration<long, std::ratio<1l, 1000l> > >::await_resume() (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0xfdd35)
    #10 0x555555641124 in asyncio::Task<decltype ((((declval<asyncio::detail::GetAwaiter<asyncio::Task<std::vector<char, std::allocator<char> > > >::type>)()).await_resume)())> asyncio::detail::wait_for<asyncio::Task<std::vector<char, std::allocator<char> > >, long, std::ratio<1l, 1000l> >(asyncio::NoWaitAtInitialSuspend, asyncio::Task<std::vector<char, std::allocator<char> > >&&, std::chrono::duration<long, std::ratio<1l, 1000l> >) [clone .actor] /home/ubuntu/c++/asyncio/include/asyncio/wait_for.h:100
    #11 0x5555556623a1 in std::__n4861::coroutine_handle<asyncio::Task<std::vector<char, std::allocator<char> > >::promise_type>::resume() const (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x10e3a1)
    #12 0x555555660f19 in asyncio::Task<std::vector<char, std::allocator<char> > >::promise_type::run() (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x10cf19)
    #13 0x5555556634c3 in asyncio::EventLoop::run_once() /home/ubuntu/c++/asyncio/src/event_loop.cpp:63
    #14 0x555555662805 in asyncio::EventLoop::run_until_complete() /home/ubuntu/c++/asyncio/src/event_loop.cpp:17
    #15 0x555555647d14 in decltype(auto) asyncio::run<asyncio::Task<void> >(asyncio::Task<void>&&) (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0xf3d14)
    #16 0x55555564099b in main /home/ubuntu/c++/asyncio/test/st/echo_client.cpp:33
    #17 0x7ffff6a15bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/ubuntu/c++/asyncio/build/test/st/echo_client+0x29746) in __interceptor_strlen.part.0
Shadow bytes around the buggy address:
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff8000: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8010: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8020: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
=>0x0c047fff8030: fa fa 00[04]fa fa fd fd fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8080: 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
  Shadow gap:              cc
==16717==ABORTING
[1] + Done                       "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-qj4itxmc.b1y" 1>"/tmp/Microsoft-MIEngine-Out-gs1cjxb4.rcj"

看样子是发生了越界读,原因是客户端发送的字符串少发了'\0'。已经修复,可以参考这个提交。
1481361#diff-4a4b47f02ae41b2a8b7e9512e447950c24862dfe67049798ff0653c047e210caR15

谢谢答复!
有个疑问:没有修改echo_client代码前,测试没有问题。那里也是没有添加结束符的。现在就改调用wait_for就有问题了,这里面有没有可能还有其它问题?
麻烦帮忙分析下,或者有什么方法进行分析,我可以尝试去分析下,谢谢!

另外发现个问题:
auto data = co_await stream.read(100); 中,data.data()的地址,与stream.read中分配的result.data()地址一致。说明是使用了移动构造。
auto data = co_await asyncio::wait_for(stream.read(100), 300ms);中,data.data()的地址,与stream.read中分配的result.data()地址不是一致。说明是使用了拷贝或者赋值构造,这里是否可以优化下?

另外发现个问题: auto data = co_await stream.read(100); 中,data.data()的地址,与stream.read中分配的result.data()地址一致。说明是使用了移动构造。 auto data = co_await asyncio::wait_for(stream.read(100), 300ms);中,data.data()的地址,与stream.read中分配的result.data()地址不是一致。说明是使用了拷贝或者赋值构造,这里是否可以优化下?

你分析的对,中间应该是发生了拷贝构造。可以优化,得找出发生拷贝构造的时机点,然后通过完美转发之类的移动。暂时没时间改,你可以尝试提下PR,或者等我后续修复也行。

两种情况对比测试发现,在result.h头文件中,调用如下的左值和右值函数,导致一个使用移动构造,一个使用赋值构造。
具体要如何修复,我也不是很清楚。

wait_for 调用了第一个函数进行结果赋值,将return *res,修改为return std::move(*res)测试可以,但是不清楚会不会带来其它问题。
非wait_for 调用了第二个函数进行结果赋值

constexpr T result() & {
        if (auto exception = std::get_if<std::exception_ptr>(&result_)) {
            std::rethrow_exception(*exception);
        }
        if (auto res = std::get_if<T>(&result_)) {
            return *res;
        }
        throw NoResultError{};
    }
    constexpr T result() && {
        if (auto exception = std::get_if<std::exception_ptr>(&result_)) {
            std::rethrow_exception(*exception);
        }
        if (auto res = std::get_if<T>(&result_)) {
            return std::move(*res);
        }
        throw NoResultError{};
    }

两种情况对比测试发现,在result.h头文件中,调用如下的左值和右值函数,导致一个使用移动构造,一个使用赋值构造。 具体要如何修复,我也不是很清楚。

wait_for 调用了第一个函数进行结果赋值,将return *res,修改为return std::move(*res)测试可以,但是不清楚会不会带来其它问题。 非wait_for 调用了第二个函数进行结果赋值

constexpr T result() & {
        if (auto exception = std::get_if<std::exception_ptr>(&result_)) {
            std::rethrow_exception(*exception);
        }
        if (auto res = std::get_if<T>(&result_)) {
            return *res;
        }
        throw NoResultError{};
    }
    constexpr T result() && {
        if (auto exception = std::get_if<std::exception_ptr>(&result_)) {
            std::rethrow_exception(*exception);
        }
        if (auto res = std::get_if<T>(&result_)) {
            return std::move(*res);
        }
        throw NoResultError{};
    }

那应该就是这一行的问题了:
https://github.com/netcan/asyncio/blob/master/include/asyncio/wait_for.h#L21

改成:

return std::move(result_).result();

然后所有测试通过的话,应该就没问题了。

两种情况对比测试发现,在result.h头文件中,调用如下的左值和右值函数,导致一个使用移动构造,一个使用赋值构造。 具体要如何修复,我也不是很清楚。
wait_for 调用了第一个函数进行结果赋值,将return *res,修改为return std::move(*res)测试可以,但是不清楚会不会带来其它问题。 非wait_for 调用了第二个函数进行结果赋值

constexpr T result() & {
        if (auto exception = std::get_if<std::exception_ptr>(&result_)) {
            std::rethrow_exception(*exception);
        }
        if (auto res = std::get_if<T>(&result_)) {
            return *res;
        }
        throw NoResultError{};
    }
    constexpr T result() && {
        if (auto exception = std::get_if<std::exception_ptr>(&result_)) {
            std::rethrow_exception(*exception);
        }
        if (auto res = std::get_if<T>(&result_)) {
            return std::move(*res);
        }
        throw NoResultError{};
    }

那应该就是这一行的问题了: https://github.com/netcan/asyncio/blob/master/include/asyncio/wait_for.h#L21

改成:

return std::move(result_).result();

然后所有测试通过的话,应该就没问题了。

按照你给的修改方案,58个测试用例测试都可以了。wait_for那里也没有再使用赋值构造了,直接使用了移动构造。