rspec/rules/S3584/cfamily/rule.adoc
Fred Tingaud d3cfe19d7e
Fix broken or dangerous backquotes
Co-authored-by: Marco Borgeaud <89914223+marco-antognini-sonarsource@users.noreply.github.com>
2023-10-30 10:33:56 +01:00

267 lines
9.2 KiB
Plaintext

Memory allocated dynamically with `calloc`, `malloc`, `realloc`, or `new` should be released when it is not needed anymore.
Failure to do so will result in a memory leak that could severely hinder application performance or abort it or the entire host machine.
== Why is this an issue?
Memory is a limited resource shared between all the applications running on the same host machine.
C and {cpp} do not automatically reclaim unused memory.
The developer has to release the memory claimed for their application that is no longer needed.
Unlike the stack that automatically allocates local variables on a function call
and deallocates them on a function return, the heap offers no automatic memory management.
The developer has to make sure to deallocate the memory they allocate dynamically on the heap.
This rule raises an issue when memory is allocated dynamically and not freed within the same function.
=== What is the potential impact?
Neglecting to free the memory leads to a memory leak.
The application that leaks memory will consume more and more of it over time,
eventually claiming all the memory available on the host machine.
When this happens and the system runs out of memory, it typically does one of the following:
- The operating system (if any) terminates the application.
- The operating system (if any) terminates some other application,
and the problem reoccurs when the reclaimed memory gets used up by the leaking application.
- The operating system (if any) starts offloading some of the memory pages to disk and slows down some memory accesses by orders of magnitude.
- The entire system crashes as a whole and reboots automatically or hangs waiting for a manual reboot.
Moreover, memory leaks can help an attacker to take over the system.
An attacker could use a memory leak to fill the memory with malicious code.
This facilitates remote code execution through another chained vulnerability.
Even if the attacker cannot take over the system she can
intentionally trigger the condition leading to a memory leak
to make use of the issue above and cause denial-of-service (DoS) of the system.
A memory leak can have a significant impact on the energy footprint of an application.
- If an application demands more memory than necessary,
the user will have to install more memory banks than necessary.
Each memory bank consumes additional power.
- As the application continues to reserve more and more memory,
it places an increased load on the memory management subsystem.
This increased load can lead to a larger computation demand,
which in turn translates to higher power consumption by the CPU.
Finally, memory leaks degrade the user experience.
The user often experiences a system slowdown
caused by the uncontrolled memory use of an application.
Delayed response time, system freezes, and crashes degrade the user experience
and discourage the further use of the application.
=== Exceptions
If a function ``return``s a pointer to the caller or stores it in an external structure,
this pointer is said to _escape_ (it is now accessible outside of function, and no longer local to it).
This includes storing the pointer in a static or global variable,
passing it to a function that can potentially do that,
or returning the pointer directly or as part of an aggregate object.
The memory pointed to by an escaping pointer might be used somewhere else in the program.
For that reason, the analyzer cannot proclaim a leak for an escaping pointer
by only looking at a function scope.
While in some cases the leak might be detectable in the scope of a caller,
in others, the analyzer would need to simulate the entire program to verify
that the memory is not used anywhere, which is not feasible.
For this technical reason, this rule often ignores escaping pointers.
== How to fix it
Ask yourself whether you need to allocate memory on the heap.
If your object is small enough,
in many cases allocating it as a local variable on the stack is a better choice
as it simplifies memory management.
If you do need to allocate it on the heap,
the direct fix for a memory leak is to make sure you always deallocate memory.
In {cpp} you should use RAII (resource acquisition is initialization) idiom.
See <<Going the extra mile>>.
Alternatively,
you have to manually make sure that every exit of the scope of the pointer to the allocated memory
is prepended by the deallocation of that pointer.
=== Code examples
==== Noncompliant code example
[source,cpp,diff-id=1,diff-type=noncompliant]
----
bool fire(Point pos, Direction dir, State const& s) {
Bullet *bullet = new Bullet{pos, dir};
if (auto affected = bullet->hitAnyone(s)) {
affected->takeHit();
return true; // Noncompliant, the memory pointed to by bullet is not deleted
}
delete bullet;
return false;
}
----
==== Compliant solution
Use a local object
[source,cpp,diff-id=1,diff-type=compliant]
----
bool fire(Point pos, Direction dir, State const& s) {
Bullet bullet{pos, dir};
if (auto affected = bullet.hitAnyone(s)) {
affected->takeHit();
return true; // Compliant: bullet is destroyed and deallocated automatically
}
return false; // Compliant: bullet is destroyed and deallocated automatically
}
----
If you cannot use RAII or a local object,
manually make sure memory is freed on every exit of the scope of the pointer.
==== Noncompliant code example
[source,c,diff-id=2,diff-type=noncompliant]
----
int fun() {
char* name = (char *) malloc (size);
if (!name) {
return 1;
}
if (condition()) {
return 2; // Noncompliant: memory pointed to by "name" has not been released
}
// ...
return 0; // Noncompliant: memory pointed to by "name" has not been released
}
----
==== Compliant solution
[source,c,diff-id=2,diff-type=compliant]
----
int fun() {
char* name = (char *) malloc (size);
if (!name) {
return 1; // Memory wasn't allocated, no need to free it
}
if (condition()) {
free(name);
return 2; // Compliant: memory is freed
}
// ...
free(name);
return 0; // Compliant: memory is freed
}
----
=== Pitfalls
Note that the execution can exit the scope in different ways:
- `return` from the function
- `break` from a `switch` statement or a loop
- `goto` out of a code block (compound statement)
- `+throw+` a {cpp} exception
- `+co_return+` from an {cpp} coroutine
- End of the scope (`}`)
In the following example,
even though the function frees memory before the explicit `return`,
the memory remains allocated when the execution leaves the `while` body
via many other ways.
[source,cpp]
----
void fire(Point pos, Direction dir, State const& s) {
while (condition()) {
Bullet *bullet = new Bullet{pos, dir};
if (bullet->misfired()) break; // Noncompliant: memory is not freed
if (!condition()) {
delete bullet;
return;
}
// Noncompliant: memory is not freed
if (s.tooManyBullets()) throw Exception("Too many bullets");
if (bullet->timeIsUp(s)) goto end; // Noncompliant: memory is not freed
} // Noncompliant: at the end of iteration bullet leaks
end: // Memory allocated in the loop is not freed
std::cout <<"Bullet is lost\n";
}
----
This is why it is very difficult to avoid leaks when managing memory manually.
=== Going the extra mile
include::../../../shared_content/cfamily/memory_raii_extra_mile.adoc[]
The RAII object will take care of the deallocation of the memory when it is no longer used.
To correct the noncompliant example, use `std::unique_ptr`:
[source,cpp]
----
bool fire(Point pos, Direction dir, State const& s) {
auto bullet = std::make_unique<Bullet>(pos, dir);
if (auto affected = bullet->hitAnything(s)) {
affected->takeHit();
return true; // Compliant: unique_ptr<Bullet> automatically frees memory
}
return false; // Compliant: unique_ptr<Bullet> automatically frees memory
}
----
== Resources
=== Documentation
* Wikipedia - https://en.wikipedia.org/wiki/Memory_leak[Memory leak]
* {cpp} reference - https://en.cppreference.com/w/cpp/language/raii[RAII]
=== Standards
* CWE - https://cwe.mitre.org/data/definitions/401[401 Improper Release of Memory Before Removing Last Reference ('Memory Leak')]
* CERT - https://wiki.sei.cmu.edu/confluence/x/FtYxBQ[MEM00-C. Allocate and free memory in the same module, at the same level of abstraction]
* CERT - https://wiki.sei.cmu.edu/confluence/x/GNYxBQ[MEM31-C. Free dynamically allocated memory when no longer needed]
=== Related rules
* S5025 discourages manual memory management, which helps to avoid memory leaks.
ifdef::env-github,rspecator-view[]
'''
== Implementation Specification
(visible only on this page)
=== Message
Review the data-flow; this memory allocation might not have been released when reaching exit point at line ``++line++``.
=== Highlighting
* Primary: the allocation call - [m|c|re]alloc|new
* Additional: statement exiting the function
** Message: Exit point
'''
== Comments And Links
(visible only on this page)
=== on 30 Mar 2016, 17:03:39 Ann Campbell wrote:
\[~massimo.paladin] I've expanded the description, and added an "issue raised when" section. It describes what seems like a reasonable scope for the rule, but may not match the scope you had in mind.
Also, I've greatly expanded the references section based on the standards' titles.
endif::env-github,rspecator-view[]