【问题标题】:Boost deadline_timer causes stack-buffer-overflow提升deadline_timer导致堆栈缓冲区溢出
【发布时间】:2021-02-19 13:49:08
【问题描述】:

在过去的几天里,我一直被 Boost Deadline_timer 的一个非常奇怪的错误所困扰。 桌面:Ubuntu 18.04 提升:v1.65.01

当我在类 AddressSanitizer 的构造函数中创建新的deadline_timer 时,会捕获来自Boost 库内部的堆栈缓冲区溢出。

我有几点意见:

  • 我还注意到,如果没有 AddressSanitizer,计时器会一直超时,因为 expiry_time 为负数,或者永不过期。所以好像有人在某个地方改变了那个内存区域。
  • 我正在使用的类非常大,并且使用相同的 Boost io_service 通过 UDP 发送数据。
  • 我无法仅在独立的源文件中重现该错误。
  • 当我删除代码以隔离问题时,无论我删除多少代码,问题仍然存在。我只创建了一个主文件,创建了一个 io_service 和一个 deadline_timer,但它仍然会抛出该错误。如果我在另一个文件中复制它并复制 CMakeLists 条目,我仍然无法复制它。

类的结构不是很复杂,这里有一个基本相同的示例类 udp_timer.hpp

#include "boost/asio.hpp"


class UdpTimer {
    public:
        UdpTimer();
        ~UdpTimer();
        void run();

        void timer_callback(const boost::system::error_code &e);
        void udp_callback(const boost::system::error_code &e, size_t bytes_recvd);
        boost::asio::io_service io;
    
    private:
        boost::asio::ip::udp::socket *socket;
        boost::asio::ip::udp::endpoint *ep;
        boost::asio::deadline_timer *timer;
        char recv_buf[2048];
        unsigned int tot_bytes_recved;
};

udp_timer.cpp

#include "udp_timer.hpp"
#include "boost/bind.hpp"
#include <iostream>

UdpTimer::UdpTimer() {
    // Set up UDP part
  ep = new boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 30042);
  socket = new boost::asio::ip::udp::socket(io, *ep);
  socket->async_receive_from(
    boost::asio::buffer(recv_buf, 2048), *ep,
      boost::bind(&UdpTimer::udp_callback, this,
      boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred)
  );

  tot_bytes_recved = 0;

  timer = new boost::asio::deadline_timer(io, boost::posix_time::seconds(1));
  timer->async_wait(boost::bind(&UdpTimer::timer_callback, this, boost::asio::placeholders::error));
}

UdpTimer::~UdpTimer() {
    delete ep;
    delete socket;
    delete timer;
}

void UdpTimer::run() {
    io.run(); // Never returns
}


// Timer callback. Print info and reset timer
void UdpTimer::timer_callback(const boost::system::error_code &e) {       
    if (e) return;
    static int count = 0;
    std::cout <<"Timer Callback #" <<count++ <<"Bytes received = " <<tot_bytes_recved <<std::endl;
    std::cout <<recv_buf <<std::endl;

    timer->expires_from_now(boost::posix_time::seconds(1));
    timer->async_wait(boost::bind(&UdpTimer::timer_callback, this, boost::asio::placeholders::error));
}

// Udp callback. Update bytes received count
void UdpTimer::udp_callback(const boost::system::error_code &e, size_t bytes_recvd) {
    if (e) return;

    tot_bytes_recved += bytes_recvd;

    socket->async_receive_from(
    boost::asio::buffer(recv_buf, 2048), *ep,
      boost::bind(&UdpTimer::udp_callback, this,
      boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred)
  );
}


int main(void)  {
    UdpTimer udp_timer;
    udp_timer.run();
}

这个放在程序里面就足以产生那个错误了。

=================================================================
==20441==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe4a7621d0 at pc 0x55d73239950c bp 0x7ffe4a761f50 sp 0x7ffe4a761f40
WRITE of size 16 at 0x7ffe4a7621d0 thread T0
    #0 0x55d73239950b in boost::date_time::base_time<boost::posix_time::ptime, boost::date_time::split_timedate_system<boost::posix_time::posix_time_system_config> >::base_time(boost::gregorian::date const&, boost::posix_time::time_duration const&, boost::date_time::dst_flags) (/home/erl/dev/test/build/prog_ins+0x61950b)
    #1 0x55d732396495 in boost::posix_time::ptime::ptime(boost::gregorian::date, boost::posix_time::time_duration) /usr/include/boost/date_time/posix_time/ptime.hpp:40
    #2 0x55d7323d4855 in boost::date_time::microsec_clock<boost::posix_time::ptime>::create_time(tm* (*)(long const*, tm*)) /usr/include/boost/date_time/microsec_time_clock.hpp:116
    #3 0x55d7323d12f6 in boost::date_time::microsec_clock<boost::posix_time::ptime>::universal_time() /usr/include/boost/date_time/microsec_time_clock.hpp:76
    #4 0x55d7323cb501 in boost::asio::time_traits<boost::posix_time::ptime>::now() /usr/include/boost/asio/time_traits.hpp:48
    #5 0x55d7323db197 in boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::expires_from_now(boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::implementation_type&, boost::posix_time::time_duration const&, boost::system::error_code&) (/home/erl/dev/test/build/prog_ins+0x65b197)
    #6 0x55d7323d6a25 in boost::asio::deadline_timer_service<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> >::expires_from_now(boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::implementation_type&, boost::posix_time::time_duration const&, boost::system::error_code&) /usr/include/boost/asio/deadline_timer_service.hpp:129
    #7 0x55d7323d2ca8 in boost::asio::basic_deadline_timer<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime>, boost::asio::deadline_timer_service<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> > >::basic_deadline_timer(boost::asio::io_service&, boost::posix_time::time_duration const&) /usr/include/boost/asio/basic_deadline_timer.hpp:187
    #8 0x55d7323b7f22 in InsHandler::InsHandler(InsConfig*, spdlog::logger*) /home/erl/dev/test/src/InsHandler.cpp:57
    #9 0x55d7323a3fb0 in main /home/erl/dev/test/src/prog_ins.cpp:74
    #10 0x7f369ed89bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
    #11 0x55d7322894d9 in _start (/home/erl/dev/test/build/prog_ins+0x5094d9)

Address 0x7ffe4a7621d0 is located in stack of thread T0 at offset 480 in frame
    #0 0x55d7323d426f in boost::date_time::microsec_clock<boost::posix_time::ptime>::create_time(tm* (*)(long const*, tm*)) /usr/include/boost/date_time/microsec_time_clock.hpp:80

  This frame has 10 object(s):
    [32, 34) '<unknown>'
    [96, 98) '<unknown>'
    [160, 162) '<unknown>'
    [224, 228) 'd'
    [288, 296) 't'
    [352, 360) 'td'
    [416, 424) '<unknown>'
    [480, 488) '<unknown>' <== Memory access at offset 480 partially overflows this variable
    [544, 560) 'tv'
    [608, 664) 'curr'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/erl/dev/test/build/prog_ins+0x61950b) in boost::date_time::base_time<boost::posix_time::ptime, boost::date_time::split_timedate_system<boost::posix_time::posix_time_system_config> >::base_time(boost::gregorian::date const&, boost::posix_time::time_duration const&, boost::date_time::dst_flags)
Shadow bytes around the buggy address:
  0x1000494e43e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000494e43f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
  0x1000494e4400: f1 f1 f8 f2 f2 f2 f2 f2 f2 f2 f8 f2 f2 f2 f2 f2
  0x1000494e4410: f2 f2 f8 f2 f2 f2 f2 f2 f2 f2 04 f2 f2 f2 f2 f2
  0x1000494e4420: f2 f2 00 f2 f2 f2 f2 f2 f2 f2 00 f2 f2 f2 f2 f2
=>0x1000494e4430: f2 f2 00 f2 f2 f2 f2 f2 f2 f2[00]f2 f2 f2 f2 f2
  0x1000494e4440: f2 f2 00 00 f2 f2 f2 f2 f2 f2 00 00 00 00 00 00
  0x1000494e4450: 00 f2 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000494e4460: 00 00 00 00 f1 f1 f1 f1 00 f2 f2 f2 f2 f2 f2 f2
  0x1000494e4470: 00 f2 f2 f2 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000494e4480: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 f2 f2
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
==20441==ABORTING

从这个错误打印输出看来,Boost 库中似乎存在一个错误,它将 16 个字节写入仅分配了 8 个字节的东西。但为什么会如此断断续续地浮出水面呢?我还注意到我们有几个词被标记为 stack-use-after-scope ,即 f8。这是否意味着程序的另一部分在超出范围后使用指向堆栈分配对象的指针?

使用 valgrind 运行给了我这个

==27251== Conditional jump or move depends on uninitialised value(s)
==27251==    at 0x578FA1: boost::date_time::int_adapter<long>::is_infinity() const (int_adapter.hpp:114)
==27251==    by 0x5772A9: boost::date_time::int_adapter<long>::is_special() const (int_adapter.hpp:131)
==27251==    by 0x5A1069: boost::date_time::counted_time_rep<boost::posix_time::millisec_posix_time_system_config>::is_special() const (time_system_counted.hpp:108)
==27251==    by 0x59FCD3: boost::date_time::counted_time_system<boost::date_time::counted_time_rep<boost::posix_time::millisec_posix_time_system_config> >::add_time_duration(boost::date_time::counted_time_rep<boost::posix_time::millisec_posix_time_system_config> const&, boost::posix_time::time_duration) (time_system_counted.hpp:226)
==27251==    by 0x59EA90: boost::date_time::base_time<boost::posix_time::ptime, boost::date_time::counted_time_system<boost::date_time::counted_time_rep<boost::posix_time::millisec_posix_time_system_config> > >::operator+(boost::posix_time::time_duration const&) const (time.hpp:163)
==27251==    by 0x59E46B: boost::asio::time_traits<boost::posix_time::ptime>::add(boost::posix_time::ptime const&, boost::posix_time::time_duration const&) (time_traits.hpp:57)
==27251==    by 0x5A1BEC: boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::expires_from_now(boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::implementation_type&, boost::posix_time::time_duration const&, boost::system::error_code&) (deadline_timer_service.hpp:161)
==27251==    by 0x5A0811: boost::asio::deadline_timer_service<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> >::expires_from_now(boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::implementation_type&, boost::posix_time::time_duration const&, boost::system::error_code&) (deadline_timer_service.hpp:129)
==27251==    by 0x59F20B: boost::asio::basic_deadline_timer<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime>, boost::asio::deadline_timer_service<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> > >::basic_deadline_timer(boost::asio::io_service&, boost::posix_time::time_duration const&) (basic_deadline_timer.hpp:187)
==27251==    by 0x59DA57: OutputTimer::OutputTimer(boost::asio::io_service*, unsigned int, boost::function<OutputStates ()>) (output_timer.cpp:5)
==27251==    by 0x5877D5: InsHandler::InsHandler(InsConfig*, spdlog::logger*) (InsHandler.cpp:57)
==27251==    by 0x57B149: main (senti_ins.cpp:74)
==27251==  Uninitialised value was created by a stack allocation
==27251==    at 0x59FB3C: boost::date_time::microsec_clock<boost::posix_time::ptime>::create_time(tm* (*)(long const*, tm*)) (microsec_time_clock.hpp:80)

我真的迷路了。我对源代码所做的更改与结果行为之间实际上没有任何联系。我可以通过删除包含一个完全不相关的头文件来消除错误。但是,当包含带有一些函数定义和枚举的 mock_header 时,错误再次出现。所以当这个错误出现时,它似乎真的是随机的。

对于如何解决此类问题的任何建议,我将非常高兴。

非常感谢您对此提出任何建议

【问题讨论】:

    标签: c++ boost deadline-timer


    【解决方案1】:

    UDPATE 到已编辑的问题

    我看到大量动态分配 (Why should C++ programmers minimize use of 'new'?)。

    我看到重复的魔法常数 (1s, 2048),无法 NUL 终止 recv_buf,然后将其视为 C 字符串,吞下错误。

    删除所有这些:

    Live On Coliru

    Live On Wandbox

    • udp_timer.h

       //#define BOOST_BIND_NO_PLACEHOLDERS
       #include <boost/asio.hpp>
      
       using boost::asio::ip::udp;
       using namespace std::chrono_literals;
      
       class UdpTimer {
         public:
           UdpTimer();
           void run();
      
         private:
           using error_code = boost::system::error_code;
           void timer_callback(error_code e);
           void udp_callback(error_code e, size_t bytes_recvd);
      
           void do_recv();
           void do_timer();
      
           boost::asio::io_service io;
           udp::endpoint ep { {}, 30042 };
           udp::socket socket { io, ep };
           boost::asio::steady_timer timer { io };
      
           std::array<char, 2048> recv_buf{};
           unsigned int tot_bytes_recved = 0;
       };
      
    • udp_timer.cpp

       #include "udp_timer.hpp"
       using namespace boost::asio::placeholders;
       #include <boost/bind/bind.hpp>
       #include <iostream>
       #include <iomanip>
      
       UdpTimer::UdpTimer() {
           do_recv();
           do_timer();
       }
      
       void UdpTimer::do_recv() {
           socket.async_receive_from(boost::asio::buffer(recv_buf), ep,
               boost::bind(&UdpTimer::udp_callback, this, error, bytes_transferred));
       }
      
       void UdpTimer::do_timer() {
           timer.expires_from_now(1s);
           timer.async_wait(boost::bind(&UdpTimer::timer_callback, this, error));
       }
      
       void UdpTimer::run() {
           io.run(); // Never returns
       }
      
       // Timer callback. Print info and reset timer
       void UdpTimer::timer_callback(error_code e)
       {
           if (e) {
               std::cout << "timer_callback: " << e.message() << std::endl;
               return;
           }
      
           static int count = 0;
           std::cout << "Timer Callback #" << count++
                     << " Bytes received = " << tot_bytes_recved << std::endl
                     << " Last received: " << std::quoted(recv_buf.data()) << std::endl;
      
           do_timer();
       }
      
       // Udp callback. Update bytes received count
       void UdpTimer::udp_callback(error_code e, size_t bytes_recvd) {
           if (e) {
               std::cout << "timer_callback: " << e.message() << std::endl;
               recv_buf[0] = '\0';
               return;
           }
      
           // because you want to print the buffer, you will also want to make sure it
           // is actually NUL terminated
           assert(bytes_recvd < recv_buf.size());
           recv_buf[bytes_recvd] = '\0';
           tot_bytes_recved += bytes_recvd;
      
           do_recv();
       }
      
    • main.cpp

       int main()
       {
           UdpTimer udp_timer;
           udp_timer.run();
       }
      

    运行演示,启用 ASAN+UBSAN

    旧答案:

    boost::asio::io_service io2;
    boost::asio::deadline_timer* t = new boost::asio::deadline_timer(io2, boost::posix_time::seconds(1)); 
    

    这只是内存泄漏,但在没有其他代码的情况下,它不可能导致任何症状,仅仅是因为没有生成更多代码: Live On Compiler Explorer

    现在所有其他观察结果都让你怀疑。理所当然的!

    我无法仅在独立的源文件中重现该错误。

    这是关键。您的代码中有未定义的行为。它可能与计时器有关,也可能无关,但它肯定不是由这种实例化引起的

    代码的一个明显问题是内存泄漏,以及您首先进行手动分配的事实。这为终身问题打开了大门。

    例如可以想象

    • 你在函数中有这些行,io2 超出范围,时间持有对它的陈旧引用。

      其实这直接对应“stack-use-after-scope”检测

    • 许多其他场景假设你也 t-&gt;async_wait() somehwere

    侧面观察是io2 暗示您使用两个 io 服务(为什么?)。除此之外,我希望您在实际代码中使用更好的名称,因为很容易迷失在 io2、i、m3、t 等的海洋中:)

    【讨论】:

    • 感谢您的帮助!是的,那将是内存泄漏。我真的只使用一个 io_service 但我尝试添加另一个 io_service 以查看它是否可能存在问题。 io 服务不会超出范围,因为它是实例化计时器的类的成员变量。我想你是对的。在程序的其他部分中存在一些未定义的行为,这可能与 boost 有关,因为我们正在运行侦听 UDP 的 boost 套接字。
    • 你好@sehe。我添加了示例代码,展示了如何实现 udp 客户端 + 截止时间计时器。您认为这样的实现会出现内存损坏问题的任何原因吗?
    • 我无法重现该问题。但是,我可以看到很多需要改进/简化的东西,所以我做到了。还有一个现场演示显示它在 ASAN+UBSAN 下运行
    • 你好,谢赫。非常感谢您为改进代码提供的所有帮助和提示。我终于找到了这个错误。虽然我不太明白这里发生了什么。该项目包含一个头文件,其中定义了以下宏:#define BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG 这以某种方式改变了用于在 boost::posix 中表示时间的底层结构,并且不知何故这与deadline_timer不兼容。
    • ODR 再次来袭!找到它的好工作。教训:总是让这些编译器定义全局。 (请记住,在构建库部件时您甚至可能必须提供它)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 1970-01-01
    • 2010-11-11
    • 2015-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多