memset 和 memcpy 注意事项
memset和memcpy函数存在缓冲区溢出、地址重叠、错误size计算等危险,可能导致程序崩溃、数据损坏或安全漏洞。在C++中应避免对非平凡类型使用这些函数,改用类型安全的替代方案。关键规避措施包括:严格验证缓冲区大小、不确定时用memmove替代、使用安全清除函数处理敏感数据。核心原则是优先选择更安全的替代方案,使用时必须确保参数绝对正确。
·
主要危险及后果
-
缓冲区溢出/下溢 (Buffer Overflow/Underflow)
- 原因: 这是最常见也是最危险的问题。当指定的
size
参数大于目标缓冲区 (memset
的dest
或memcpy
的destination
) 或源缓冲区 (memcpy
的source
) 的实际大小时。 - 后果:
- 崩溃: 覆盖非法内存区域(如只读内存、未映射内存、栈保护区域)导致程序立即崩溃 (Segmentation Fault, Access Violation)。
- 数据损坏: 覆盖相邻变量、数据结构、堆管理元数据、函数返回地址、其他对象虚表指针等,破坏程序状态。这种破坏可能不会立即崩溃,而是导致后续逻辑错误、计算错误、数据丢失等难以追踪的问题。
- 安全漏洞: 精心构造的溢出可以覆盖函数返回地址或函数指针,使攻击者能够执行任意代码 (Exploit)。这是许多严重安全漏洞(如栈溢出攻击)的根源。
- 原因: 这是最常见也是最危险的问题。当指定的
-
源地址和目标地址重叠 (Overlapping Regions -
memcpy
特有)- 原因:
memcpy
不处理源内存区域 (source
) 和目标内存区域 (destination
) 重叠的情况。标准规定其行为在重叠时是未定义 (Undefined Behavior, UB)。 - 后果:
- 数据损坏: 当源和目标重叠时,复制过程中源区域的数据可能在复制完成前就被覆盖,导致最终复制到目标的数据是错误的、损坏的。
- 未定义行为: 程序可能崩溃、产生错误结果或表现出任何不可预测的行为。编译器优化可能会利用 UB 假设做出导致意外结果的优化。
- 原因:
-
错误的
size
计算- 原因:
- 混淆元素个数和字节数(常见于结构体或数组操作,忘记了
sizeof
)。 - 使用了错误的
sizeof
对象(如sizeof(pointer)
而不是sizeof(struct)
或sizeof(array)
)。 - 对复杂结构(如包含指针的结构体)进行浅拷贝时,误以为
memcpy
完成了深拷贝。
- 混淆元素个数和字节数(常见于结构体或数组操作,忘记了
- 后果: 通常会导致缓冲区溢出(如果
size
算大了)或复制不完整(如果size
算小了),引发上述的崩溃、数据损坏或逻辑错误。
- 原因:
-
对非平凡类型使用
memcpy
(C++)- 原因: 在 C++ 中,对于具有非平凡构造、复制、移动或析构函数的类(如管理资源的类
std::string
,std::vector
),直接使用memcpy
进行复制或memset
进行初始化会绕过这些重要的语义函数。 - 后果:
- 资源泄漏: 复制对象时,
memcpy
只复制了指针(如std::vector
的内部数据指针),没有复制指针指向的资源。两个对象指向同一资源,析构时会导致双重释放。 - 未初始化/双重释放:
memset
可能破坏对象的内部状态(如虚表指针、引用计数),导致后续方法调用崩溃或析构函数双重释放资源。 - 绕过逻辑: 绕过了类设计者定义的复制/初始化语义。
- 资源泄漏: 复制对象时,
- 原因: 在 C++ 中,对于具有非平凡构造、复制、移动或析构函数的类(如管理资源的类
-
memset
初始化敏感数据不安全- 原因: 使用
memset
清零密码、密钥等敏感数据后,编译器优化可能会认为后续不再使用该缓冲区而将memset
调用优化掉(Dead Store Elimination)。 - 后果: 敏感数据实际并未从内存中清除,存在被泄露的风险。
- 原因: 使用
如何避免危险
-
严格计算并验证大小 (Size Calculation & Validation)
- 始终使用
sizeof
: 对结构体、数组进行操作时,明确使用sizeof(target_object)
或sizeof(element) * num_elements
。 - 明确缓冲区大小: 在函数设计时,如果传递缓冲区,应同时传递其容量大小。在函数内部使用
memcpy/memset
前,务必检查传入的size
是否小于等于缓冲区的实际容量。 - 使用安全的容器 (C++): 优先使用
std::vector
,std::array
,std::string
等容器,它们自己管理大小和内存,避免手动计算size
。
- 始终使用
-
处理重叠区域 (For
memcpy
)- 使用
memmove
: 如果不确定源和目标内存区域是否重叠,或者确定它们会重叠,必须使用memmove
代替memcpy
。memmove
是专门设计用来正确处理重叠区域的。 - 确保不重叠: 如果逻辑上能 100% 保证源和目标区域绝不重叠,则可以使用
memcpy
(但需非常谨慎并添加注释说明)。
- 使用
-
使用类型安全的替代方案 (C++)
- 赋值操作符 (
=
)、拷贝构造函数: 对于 C++ 对象,优先使用对象自身的拷贝/赋值语义。编译器会自动调用正确的拷贝构造函数或赋值运算符。 std::copy
,std::copy_n
,std::fill
,std::fill_n
: 标准库算法。它们:- 提供迭代器接口,更符合 C++ 风格。
- 能根据迭代器类型选择最优的实现(可能内联赋值或调用
memmove
)。 - 对对象执行正确的拷贝语义(调用拷贝构造函数/赋值运算符),避免浅拷贝问题。
- 编译器类型检查更强。
- 示例:
// 安全复制数组 (C++) int src[100]; int dest[100]; // 使用 std::copy std::copy(std::begin(src), std::end(src), std::begin(dest)); // 或明确元素个数 std::copy_n(src, 100, dest); // 安全初始化数组 (C++) int arr[100]; std::fill(std::begin(arr), std::end(arr), 0); // 清零
- 赋值操作符 (
-
避免对非平凡 C++ 类型使用
memset/memcpy
- 只对 POD (Plain Old Data) 类型(基本类型、由基本类型/POD类型组成的结构体/联合体,没有自定义构造/析构/拷贝/移动等特殊函数)安全地使用
memset
和memcpy
。对于任何包含或可能是非平凡类型的对象,严格禁止使用它们进行复制或初始化。
- 只对 POD (Plain Old Data) 类型(基本类型、由基本类型/POD类型组成的结构体/联合体,没有自定义构造/析构/拷贝/移动等特殊函数)安全地使用
-
安全地清除敏感数据
- 使用特定安全函数: 使用明确设计不会被编译器优化掉的函数,如 Windows 的
SecureZeroMemory
, OpenSSL 的OPENSSL_cleanse
, C11 的memset_s
(如果编译器支持且启用了相关扩展)。 - 禁用优化 (谨慎使用): 使用
volatile
指针强制写入,但这依赖于实现细节且可能不总是有效,不是最佳实践。void secure_zero(void *s, size_t n) { volatile unsigned char *p = (volatile unsigned char *)s; while (n--) *p++ = 0; }
- 使用特定安全函数: 使用明确设计不会被编译器优化掉的函数,如 Windows 的
-
使用现代 C 的安全函数 (如果可用)
memset_s
,memcpy_s
(C11 Annex K): 这些_s
(safe) 版本函数在运行时检查缓冲区大小(需要传入目标缓冲区大小),并在检测到错误(如溢出)时调用约束处理函数(可自定义,默认可能终止程序)。注意: 这个可选附录的实现在不同编译器(MSVC 支持, GCC/Clang 默认通常不支持)和平台间不一致,可移植性受限。使用时需了解目标环境的支持情况。
总结
危险点 | 后果 | 关键规避措施 |
---|---|---|
缓冲区溢出/下溢 | 崩溃、数据损坏、安全漏洞 | 严格计算并验证大小 (sizeof ), 传递并检查缓冲区容量,使用安全容器 (C++) |
地址重叠 (memcpy ) |
数据损坏、未定义行为 | 不确定时用 memmove , 确保不重叠才用 memcpy |
错误的 size 计算 |
溢出或复制不全 | 仔细使用 sizeof , 区分元素数和字节数 |
非平凡类型 (C++) | 资源泄漏、双重释放、崩溃 | 禁止使用! 用赋值、拷贝构造、std::copy /std::fill |
敏感数据清除 | 数据残留泄露 | 用安全清除函数 (SecureZeroMemory , OPENSSL_cleanse , memset_s ) |
核心原则: memset
和 memcpy
是底层、不安全的操作。使用时必须极其谨慎地确保参数(尤其是大小和地址)的绝对正确性。在 C++ 中,应优先使用类型安全、语义正确的替代方案(标准库容器和算法)。在 C 中,应严格验证边界,并考虑使用 memmove
处理潜在重叠。
更多推荐
所有评论(0)