MEM51-CPP. 正确地释放动态分配的资源
- 1. 不合规代码示例 (placement new())
- 2. 合规方案 (placement new())
- 3. 不合规代码示例 (Uninitialized delete)
- 4. 合规方案 (Uninitialized delete)
- 5. 不合规代码示例 (Double-Free)
- 6. 合规方案 (Double-Free)
- 7. 不合规代码示例 (array new[])
- 8. 合规方案 (array new[])
- 9. 不合规代码示例 (malloc())
- 10. 实现细节
- 11. 合规方案 (malloc())
- 12. 不合规代码示例 ( new )
- 13. 合规方案 (new)
- 14. 不合规代码示例 (Class new)
- 15. 合规方案 (class new)
- 16. 不合规代码示例 (std::unique_ptr)
- 17. 合规方案 (std::unique_ptr)
- 18. 不合规代码示例 (std::shared_ptr)
- 19. 合规方案 (std::shared_ptr)
- 20. 风险评估
- 21. Automated Detection
- 22. Related Vulnerabilities
- 23. Related Guidelines
- 24. Bibliography
原文链接:
MEM51-CPP. Properly deallocate dynamically allocated resources
C 语言提供了几种分配内存的方式,例如 std::malloc()
, std::calloc()
, 和 std::realloc()
。这些也可以被 C++ 使用。然而,对于释放内存而言,C 语言只定义了一种简单的方式:std::free()
。关于 C 分配和释放内存要求的条款,见 MEM31-C. Free dynamically allocated memory when no longer needed 和 MEM34-C. Only free memory allocated dynamically
C++ 语言提供了额外的集中分配内存的方式,例如运算符 new
, new []
和 placement new
,还有分配器对象(allocator objects) 。不像 C,C++ 提供了多种释放动态内存的方法,例如运算符 delete
,delete []()
和分配器对象上的释放函数(deallocation functions)。
除了 nullptr
,不要对其他对象调用释放函数,或者下述相应分配函数返回的指针。
分配器 | 释放器 |
---|---|
global operator new()/new |
global operator delete ()/delete |
global operator new[]()/new[] |
global operator delete[]()/delete[] |
class-specific operator new()/new |
class-specific operator delete ()/delete |
class-specific operator new[]()/new[] |
class-specific operator delete[]()/delete[] |
placement operator new () |
N/A |
allocator<T>::allocate() |
allocator<T>::deallocate() |
std::malloc() , std::calloc() , std::realloc() |
std::free() |
std::get_temporary_buffer() |
std::return_temporary_buffer() |
向不正确的释放函数传递一个指针将导致 未定义行为.
C++ 标准, [expr.delete], 段落 2 [ISO/IEC 14882-2014], 部分叙述如下:
首选 (delete object),
delete
操作数可能是一个空指针值,一个由前述 new-表达式 创建的指向非数组对象的指针,或者指向一个表示类似 (Clause 10) 的基类对象的子对象 (1.8) 指针。如果不是,该行为是未定义的。其次 (delete array),delete
操作数可能是一个空指针值或者由先前数组 new-表达式 创建的指针。否则,该行为是未定义的。
释放一个非动态分配(包括由 placement new()
产生的非动态指针的指针)是未定义行为,因为该指针并不是从分配函数中获取的。释放一个已经传入过释放函数中的指针是未定义的,因为指针已经不再指向之前已动态分配过的内存。
当像 new
操作符被调用时,这导致同名的可重载的操作符被调用,比如 operator new()
。这些可重载的函数也可能被直接调用,但是和对应操作符有相同的限制。这就是说,调用 operator delete()
,并传入一个指针参数和对该指针调用 delete
操作符限制是一样的。进一步说,重载版本受作用域限制。因此,调用一个类型指定操作符来分配一个对象,但通过全局操作符来释放这个对象,这是可能的(但不允许)。
除了 new
和 delete
操作符,当使用其他内存管理函数,见 MEM53-CPP. Explicitly construct and destruct objects when manually managing object lifetime 获取关于对象生命周期管理信息。
不合规代码示例 (placement new()
)
在这个不合规的代码示例中,局部变量 space
被当作表达式传入 placement new
操作符中。调用返回的指针接着被传入到 ::operator delete()
,由于 ::operator delete()
试图去释放并不是由 ::operator new()
返回的指针,导致了 未定义行为 。
1 |
|
合规方案 (placement new()
)
这个合规方案移除了对 ::operator delete()的
调用,而用显式地调用 s1
的析构函数来替代。这是为数不多的几次需要确保显式调用析构函数。
1 |
|
不合规代码示例 (Uninitialized delete
)
在这个不合规代码例子中,在同一个 try
块内试图进行两次内存分配。如果某一个失败了,那么 catch
块试图释放已经被分配的资源。然而,由于指针值并没有初始化为已知的值,i1
内存分配错误可能造成传入 ::operator delete()
的值 (i2
中的)并不是之前调用 ::operator new()
所返回的,导致 未定义行为。
1 |
|
合规方案 (Uninitialized delete
)
这个兼容方案将两个指针均初始化为 nullptr
,可以被合法传入 ::operator delete().
1 |
|
不合规代码示例 (Double-Free)
一旦指针被传入到正确的释放函数中,该指针值将不可用。当并未在调用后续分配函数返回时重新赋值,该指针第二次传入释放函数,造成试图释放未动态分配的内存。管理着堆的潜在数据结构(注:该指针指向的已经被释放的内存堆)某种程度上会造成崩毁——会在程序中引入安全 漏洞 (vulnerabilities)。 这种类型的问题被称为 二次释放漏洞 (double-free vulnerabilities) 。实践中,二次释放漏洞可能会 利用 执行任意代码。
在这个不合规的代码例子中,类 C
拥有一个 P *
的所有权——后续会在类的析构函数中释放。C++ 标准,[class.copy],段落 7 [ISO/IEC 14882-2014], 陈述如下:
如果在类的定义未显式声明一个拷贝构造函数,将会隐式声明一个拷贝构造函数。如果类的定义声明了一个移动构造函数或者移动赋值运算,被隐式声明的拷贝构造函数将被定义为 deleted;否则,其(注:被隐式声明的拷贝构造函数)将被定义为 default (8.4)。如果该类包含一个用户自定义的拷贝赋值运算或者析构函数,后者要被弃用(注:用户需要显式自定义拷贝构造函数)。
If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.
C
尽管存在用户定义的析构函数,但是含有一个隐式的默认拷贝构造函数,并且这个默认的拷贝构造函数将拷贝存储在 p
中的指针值,这将导致二次释放:第一次释放发生在当 g()
退出时,第二次释放发生在当 h()
退出时。
1 | struct P {}; |
合规方案 (Double-Free)
在这个合规方案中,C
的拷贝构造函数和拷贝赋值运算被显式删除。这个删除将会使先前不合规代码示例程序 不合语法的 (ill-formed) ——由于 g
的定义使用了被删除的拷贝构造函数。因此,g()
被修改为接受其引用参数,消除二次释放。
1 | struct P {}; |
不合规代码示例 (array new[]
)
在接下来这个不合规的代码示例中,一个使用数组 new[]
来分配的数组,但是通过 delete
而不是 delete[]
来释放内存,导致 未定义行为.
1 | void f() { |
合规方案 (array new[]
)
在这个合规方案中,通过调用 delelte[]
替换 delete
来修复,使代码遵循使用正确的配对的内存分配和释放函数
1 |
|
不合规代码示例 (malloc()
)
在这个不合规的代码示例中,混用了 malloc()
和 delete
的调用。
1 |
|
这个例子没有违背 MEM53-CPP. Explicitly construct and destruct objects when manually managing object lifetime 因为它遵循 MEM53-CPP-EX1 异常.
实现细节
有些 ::operator new()
的实现会调用 std::malloc()
。在这类实现中, ::operator delete()
函数被要求调用 std::free()
去释放指针,并且非合规的代码示例将表现为正确定义的行为。然而,不应该依赖于实现,这里有个 实现 的细节,实现没有义务使用潜在的 C 内存管理函数来实现 C++ 内存管理操作。
合规方案 (malloc()
)
在这个合规方案中,指针由 std::malloc()
分配,调用 std::free()
来释放,而不是 delete.
1 |
|
不合规代码示例 ( new
)
这个不合规的代码示例调用 std::free()
来释放通过 new
分配的内存。由此产生了一个 未定义行为 的副作用——由于使用了不正确的释放函数,该对象的析构函数并不会被调用,通过 std::free()
无法释放对象。
1 |
|
此外,该代码违背了 MEM53-CPP. Explicitly construct and destruct objects when manually managing object lifetime.
合规方案 (new
)
在这个合规方案中,由 new
分配的指针通过调用 delete
来释放,而不是 std::free()
。
1 | struct` `S {`` ``~S();``};` `void` `f() {`` ``S *s = ``new` `S();`` ``// ...`` ``delete` `s;``} |
不合规代码示例 (Class new
)
在这个不合规代码示例中,operator new()
的类特定(class-specific)的实现重载了全局 new
操作符。当 new
被调用时,类特定的重载版本被选中,因此 S::operator new()
被调用。然而,由于该对象由作用域内的 ::delete
操作符销毁,全局的 operator delete()
函数被调用,而不是类他特定的实现的 S::operator delete()
,导致 未定义行为.
1 |
|
合规方案 (class new
)
在这个合规方案中,作用域内的 ::delete
调用被非域内的 delete
调用所替代, 引起 S::operator delete()
被调用。
1 |
|
不合规代码示例 (std::unique_ptr
)
在这个不合规代码示例中,声明了一个 std::unique_ptr
来持有一个对象的指针——但是直接由对象数组来初始化的。当该 std::unique_ptr
被销毁时,默认的删除器调用 delete
而不是 delete[]
,导致了未定义行为。
1 |
|
合规方案 (std::unique_ptr
)
在这个合规方案中,声明了一个 std::unique_ptr
来持有一个对象数组,而不是持有指向一个对象的指针。此外,用 std::make_unique()
来初始化这个智能指针。
1 |
|
使用 std::make_unique()
而不是直接初始化,如果 std::unique_ptr
的结果不是正确类型,将发出诊断 (emit a diagnostic) 。如果它被用在不合规的代码例子中,结果将会是不合规范的程序,而不是未定义行为。最好用 std::make_unique()
来代替其他手动初始化的方式。
不合规代码示例 (std::shared_ptr
)
在这个不合规代码示例中,声明了一个 std::shared_ptr
来持有一个对象的指针——但是直接由对象数组来初始化的。和使用 std::unique_ptr
一样,当该 std::shared_ptr
被销毁时,默认的删除器调用 delete
而不是 delete[]
,导致了未定义行为。
1 |
|
合规方案 (std::shared_ptr
)
不同于 std::unique_ptr
的合规方案,那些调用 std::make_unique()
来创建指向数组的独占指针的地方,调用数组类型的 std::make_shared()
是不合语法规范的。反之,这个合规方案为共享指针类型手动指定了一个自定义的删除器,来保证潜在的数组被正确的删除。
1 |
|
风险评估
传递一个从先前并不匹配的分配函数获取的指针值到一个释放函数中会导致 未定义行为,可以引起可利用的 漏洞.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
MEM51-CPP | High | Likely | Medium | P18 | L1 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Astrée | 20.10 | **invalid_dynamic_memory_allocation dangling_pointer_use ** | |
Axivion Bauhaus Suite | 7.2.0 | CertC++-MEM51 | |
Clang | 3.9 | clang-analyzer-cplusplus.NewDeleteLeaks -Wmismatched-new-deleteclang-analyzer-unix.MismatchedDeallocator |
Checked by clang-tidy , but does not catch all violations of this rule |
CodeSonar | 6.1p0 | **ALLOC.FNH ALLOC.DF ALLOC.TM ** | Free non-heap variable Double free Type mismatch |
Helix QAC | 2021.2 | C++2110, C++2111, C++2112, C++2113, C++2118, C++3337, C++3339, C++4262, C++4263, C++4264 | |
Klocwork | 2021.1 | CL.FFM.ASSIGN CL.FFM.COPY CL.FMM****FMM.MIGHT FMM.MUST FNH.MIGHT FNH.MUST FUM.GEN.MIGHT **FUM.GEN.MUSTUNINIT.CTOR.MIGHT UNINIT.CTOR.MUST UNINIT.HEAP.MIGHT UNINIT.HEAP.MUSTUNINIT.STACK.ARRAY.MIGHT UNINIT.STACK.ARRAY.PARTIAL.MUSTUNINIT.STACK.ARRAY.MUST UNINIT.STACK.MIGHT UNINIT.STACK.MUST** | |
LDRA tool suite | 9.7.1 | 232 S, 236 S, 239 S, 407 S, 469 S, 470 S, 483 S, 484 S, 485 S, 64 D, 112 D** ** | Partially implemented |
Parasoft C/C++test | 2021.1 | CERT_CPP-MEM51-a CERT_CPP-MEM51-b CERT_CPP-MEM51-c CERT_CPP-MEM51-d | Use the same form in corresponding calls to new/malloc and delete/free Always provide empty brackets ([]) for delete when deallocating arrays Both copy constructor and copy assignment operator should be declared for classes with a nontrivial destructor Properly deallocate dynamically allocated resources |
Parasoft Insure++ | Runtime detection | ||
Polyspace Bug Finder | R2021b | CERT C++: MEM51-CPP | Checks for:Invalid deletion of pointerInvalid free of pointerDeallocation of previously deallocated pointerRule partially covered. |
PRQA QA-C++ | 4.4 | 2110, 2111, 2112, 2113, 2118*,**3337, 3339*, 4262, 4263, 4264 | |
PVS-Studio | 7.15 | V515, V554, V611, V701, V748, V773, V1066 | |
SonarQube C/C++ Plugin | 4.10 | S1232 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
SEI CERT C++ Coding Standard | MEM53-CPP. Explicitly construct and destruct objects when manually managing object lifetime |
---|---|
SEI CERT C Coding Standard | MEM31-C. Free dynamically allocated memory when no longer needed MEM34-C. Only free memory allocated dynamically |
MITRE CWE | CWE 590, Free of Memory Not on the Heap CWE 415, Double Free CWE 404, Improper Resource Shutdown or Release CWE 762, Mismatched Memory Management Routines |
Bibliography
[Dowd 2007] | “Attacking delete and delete [] in C++” |
---|---|
[Henricson 1997] | Rule 8.1, “delete should only be used with new" Rule 8.2, “delete [] should only be used with new []" |
[ISO/IEC 14882-2014] | Subclause 5.3.5, “Delete” Subclause 12.8, “Copying and Moving Class Objects” Subclause 18.6.1, “Storage Allocation and Deallocation” Subclause 20.7.11, “Temporary Buffers” |
[Meyers 2005] | Item 16, “Use the Same Form in Corresponding Uses of new and delete “ |
[Seacord 2013] | Chapter 4, “Dynamic Memory Management” |
[Viega 2005] | “Doubly Freeing Memory” |
本文标题:MEM51-CPP. 正确地释放动态分配的资源
文章作者:xwnb
发布时间:2021-11-10
最后更新:2023-04-17
原始链接:https://xwnb.github.io/posts/2185119777/
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!并保留本声明。感谢您的阅读和支持!
分享