C++程序动态分配中的安全陷阱与防范

C++程序时,很多人为了灵活管理内存,习惯用new和delete做动态分配。但用得不好,轻则程序崩溃,重则被攻击者钻空子,变成安全漏洞的突破口。

动态分配是怎么出问题的?

比如你在开发一个处理用户上传图片的小工具,每来一张图就new一块内存去存数据。如果用户恶意传一堆超大文件,而你没检查大小,内存很快就被耗光。这种就是典型的资源耗尽攻击,程序一崩,服务就断。

更危险的是指针操作不当。下面这段代码看着没啥问题:

int* data = new int[10];
data[10] = 42;  // 越界写入
delete[] data;

但data[10]已经越界了,写到了不属于它的内存区域。这种bug在调试时可能不显现,上线后却可能被利用来执行非法指令。

释放后使用:隐蔽的安全地雷

另一个常见问题是“释放后使用”(Use After Free)。看这个例子:

char* buffer = new char[256];
fill_data(buffer);  // 填充数据
delete[] buffer;
// ... 中间做了些别的事
if (buffer) {
    process(buffer);  // 错误:buffer已释放
}

虽然指针还指着那块内存,但系统早已回收,这时候再读写,结果完全不可控。黑客可以提前布置好伪造数据,让程序跳转到恶意代码段。

别迷信手动管理

有些老派程序员觉得手动控制内存才够“硬核”,但在实际项目里,一个疏忽就可能酿成大错。现代C++提供了unique_ptr、shared_ptr这些智能指针,能自动管理生命周期。

比如把上面的例子改成:

std::unique_ptr<int[]> data = std::make_unique<int[]>(10);
data[9] = 42;  // 安全访问
// 不用手动delete,离开作用域自动释放

不仅代码更干净,安全性也高了不少。

输入验证不能偷懒

动态分配往往依赖用户输入决定内存大小。比如根据网络包长度申请缓冲区,如果直接拿过来就用,很容易中招。

正确的做法是加一层检查:

size_t size = get_user_input_size();
if (size == 0 || size > MAX_ALLOWED_SIZE) {
    log_error("Invalid size detected");
    return -1;
}
char* buf = new char[size];

哪怕多写两行判断,也能挡住大部分低级攻击。

说到底,动态分配不是洪水猛兽,但它像一把双刃剑。用得好提升性能,用不好就成了系统的阿喀琉斯之踵。尤其是在涉及网络、文件解析等外部输入的场景,每一声new,都得心里有数。