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++ 析构顺序如下:
- 执行派生类析构函数体
- 析构派生类成员
- 执行基类析构函数
- 析构基类成员
因此:
- 子类成员(例如
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
}