std::function 在析构阶段触发非法内存访问

Bug Report: std::function 在析构阶段触发非法内存访问(UAF)

问题描述

在当前事件回调设计中,父类中定义了一个 std::function<> 类型的回调成员(例如 onHover),子类在构造阶段为其赋值,并在 lambda 中捕获了子类的成员(例如 innerC4B 或 this 指针)。

在对象析构阶段,程序偶现崩溃。调用栈表面上显示为:

  • 崩溃发生在 std::function 相关代码
  • 或发生在 lambda 调用过程中
  • 或发生在 std::function 析构期间

但实际根因并非 std::function 本身,而是 析构顺序导致的悬空访问(Use-After-Free)问题。


现象

  • 在销毁派生类对象时偶现崩溃
  • 崩溃位置可能显示为:
    • std::function::operator()
    • lambda 内部
    • std::_Func_impl
    • 或 STL 内部函数
  • AddressSanitizer 可能报错:
    • heap-use-after-free
    • stack-use-after-scope
    • invalid read

根因分析

1️⃣ 对象析构顺序

C++ 析构顺序如下:

  1. 执行派生类析构函数体
  2. 析构派生类成员
  3. 执行基类析构函数
  4. 析构基类成员

因此:

  • 子类成员(例如 innerC4B)会先被销毁
  • 父类中的 std::function onHover 会更晚才销毁

2️⃣ 危险模式

当出现如下结构时会产生隐患:

cpp 复制代码
struct Base {
    std::function<void()> onHover;
};

struct Derived : Base {
    Inner innerC4B;

    Derived() {
        onHover = [this]() {
            innerC4B.use();
        };
    }
};

最小复现伪代码

cpp 复制代码
#include <functional>
#include <vector>
#include <iostream>

struct EventBus {
    std::vector<std::function<void()>> listeners;

    void add(std::function<void()> cb) {
        listeners.push_back(std::move(cb));
    }

    void removeAll_and_fire() {
        // 模拟析构期间错误触发
        for (auto& cb : listeners) {
            cb();  // <-- 此时可能访问已析构成员
        }
        listeners.clear();
    }
};

struct Base {
    std::function<void()> onHover;
    EventBus* bus = nullptr;

    virtual ~Base() {
        if (bus) {
            bus->removeAll_and_fire();
        }
    }
};

struct Derived : Base {
    struct Inner {
        int value = 42;
        void use() { std::cout << value << std::endl; }
        ~Inner() { std::cout << "~Inner\n"; }
    };

    Inner innerC4B;

    Derived(EventBus& b) {
        bus = &b;

        onHover = [this]() {
            innerC4B.use();  // <-- innerC4B 可能已析构
        };

        bus->add(onHover);
    }

    ~Derived() override {
        std::cout << "~Derived\n";
    }
};

int main() {
    EventBus bus;
    {
        Derived d(bus);
    } // 析构顺序:~Derived -> ~Inner -> ~Base(触发回调) -> UAF
}
相关推荐
dtq042412 小时前
C++小案例 - 日志实战
开发语言·c++
码匠许师傅13 小时前
【C++三方组件】Qt 概览:桌面 C++ 的事实标准与许可证三轨
开发语言·c++·qt
码匠许师傅14 小时前
【C++三方组件】POCO:跨平台系统编程全家桶
开发语言·c++
WBluuue16 小时前
数据结构与算法:分块(一):块状数组、块状链表
数据结构·c++·算法·链表
必须得开心呀18 小时前
字节序大小端科普
c++·程序人生
会编程的小孩18 小时前
C++ 子集
c++
SunkingYang19 小时前
C++20 vs C++17:新特性对比与用法指南
c++·c++20·特性·版本·新特性·对比·c++17
前进吧-程序员19 小时前
C++20 协程入门实战:从 co_await 到一个 100 行的异步任务调度器
网络·c++·c++20
前进吧-程序员19 小时前
实战:C++20 协程 + io_uring 写一个 400 行的 echo server
网络·c++·c++20
青少儿编程课堂1 天前
蓝桥杯青少组 + 全国青少年信息素养大赛 + 电子学会图形化考级|校园推箱子大挑战解析
c++·python·算法·bfs·信息学竞赛