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

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

问题描述

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

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

  • 崩溃发生在 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
}
相关推荐
王老师青少年编程1 天前
csp信奥赛C++高频考点专项训练之贪心算法 --【排序贪心】:拼数
c++·算法·贪心·csp·信奥赛·排序贪心·拼数
程序猿编码1 天前
给Linux程序穿“隐身衣”——ELF运行时加密器全解析(C/C++代码实现)
linux·c语言·c++·网络安全·elf·内存安全
John_ToDebug1 天前
从 Win10 到 Win11 22H2+:任务栏美化中的“蒙版”和“Hover 色块”渲染原理解析
c++·chrome·windows
谭欣辰1 天前
AC自动机:多模式匹配的高效利器
数据结构·c++·算法
三月微暖寻春笋1 天前
【和春笋一起学C++】(六十三)虚函数特性(二)
c++·基类·派生类·虚函数特性
历程里程碑1 天前
MySQL事务深度解析:ACID到MVCC实战+万字长文解析
开发语言·数据结构·数据库·c++·sql·mysql·排序算法
鲸渔1 天前
【C++ 跳转语句】break、continue、goto 与 return
开发语言·c++·算法
syker1 天前
AIFerric v2.0 项目总结报告
c语言·开发语言·c++
ShineWinsu1 天前
对于Linux:进程间通信IPC(命名管道)的解析
linux·c++·面试·笔试·进程·ipc·命名管道
️是781 天前
信息奥赛一本通—编程启蒙(3371:【例64.2】 生日相同)
开发语言·c++·算法