Avoid excessive stack allocations, particularly in situations where the growth of the stack can be controlled or influenced by an attacker. See INT04-C. Enforce limits on integer values originating from tainted sources for more information on preventing attacker-controlled integers from exhausting memory.
Noncompliant Code Example
The C Standard includes support for variable length arrays (VLAs). If the array length is derived from an untrusted data source, an attacker can cause the process to perform an excessive allocation on the stack.
This noncompliant code example temporarily stores data read from a source file into a buffer. The buffer is allocated on the stack as a VLA of size bufsize
. If bufsize
can be controlled by a malicious user, this code can be exploited to cause a denial-of-service attack:
The stack is frequently used for convenient temporary storage, because allocated memory is automatically freed when the function returns. Also, the operating system automatically grows the stack if the process accesses memory beyond the current allocation. This can fail due to lack of memory or collision with other allocated areas of the address space. However, most methods of stack allocation have no way to report failure. Instead of returning an error code, a failure to grow the stack results in the process being killed. If user input is able to influence the amount of stack memory allocated then an attacker could use this in a denial-of-service attack.
Non-Compliant Code Example
C99 includes support for variable length arrays. If the value used for the length of the array is influenced by user input, an attacker could cause the program to use a large number of stack pages, possibly resulting in the process being killed due to lack of memory, or simply cause the stack pointer to point to a different region of memory. The latter could result in a page fault and the process being killed or a write to an arbitrary memory location. An easy solution is to use the malloc family of functions to allocate and free memory, and handle any errors that malloc returns.
This non-compliant code example copies a file. It allocates a buffer of user-defined size on the stack to temporarily store data read from the source file. If the size of the buffer is not constrained, a malicious user could specify a buffer of several gigabytes and cause a crash. A more malicious user could specify a buffer long enough to place the stack pointer into the heap and overwrite memory there with what fputs()
and fgets()
store on the stack.
Code Block | ||||
---|---|---|---|---|
| ||||
int copy_file(FILE *src, FILE *dst, size_t bufsize) { char buf[bufsize]; while (fgets(buf, bufsize, src)) { if (fputs(buf, dst);) == EOF) { /* Handle error */ } } return 0; } |
The BSD extension function alloca()
behaves in a similar fashion to VLAs; its use is not recommended [Loosemore 2007].
Compliant Solution
This compliant solution replaces the dynamic array the VLA with a call to malloc()
, and performs appropriate error checking on the . If malloc()
fails, the return value can be checked to prevent the program from terminating abnormally.
Code Block | ||||
---|---|---|---|---|
| ||||
int copy_file(FILE *src, FILE *dst, size_t bufsize) { if (bufsize == 0) { /* Handle error */ } char *buf = (char *)malloc(bufsize); if (!buf) { /* return -1;Handle error */ } while (fgets(buf, bufsize, src)) { if (fputs(buf, dst);) == EOF) { /* Handle error */ } } /* ... */ free(buf); return 0; } |
...
Noncompliant Code Example
Excessive recursion also requires the operating system to grow the stack, and can consequently lead to the process being killed due to lack of memory. Depending on the algorithm, this can be much more difficult to fix than the use of dynamic arrays. However, the use of recursion in most C programs is limited in part because non-recursive solutions are often faster.
Recursion can also lead to large stack allocations. Recursive functions must ensure that they do not exhaust the stack as a result of excessive recursions.
This noncompliant This is a naive implementation of the Fibonacci function using exponential recursion (as well as exponential time). When tested on a Linux system, fib1(100)
crashes with a segmentation fault.uses recursion:
Code Block | ||||
---|---|---|---|---|
| ||||
unsigned long fib1(unsigned int n) {
if (n == 0) {
return 0;
}
else if (n == 1 || n == 2) {
return 1;
}
else {
return fib1(n-1) + fib1(n-2);
}
}
|
The amount of stack space needed grows linearly with respect to the parameter n
. Large values of n
have been shown to cause abnormal program termination.
Compliant Solution
This is a much more efficient solution, using constant space and linear time. Tested on a Linux system, fib2(100)
is calculated almost instantly and with almost no chance of crashing due to a failed stack allocation.implementation of the Fibonacci functions eliminates the use of recursion:
Code Block | ||||
---|---|---|---|---|
| ||||
unsigned long fib2(unsigned int n) { if (n == 0) { return 0; } else if (n == 1 || n == 2) { return 1; } unsigned long prev = 1; unsigned long cur = 1; unsigned int i; for (i = 3; i <= n; i++) { unsigned long tmp = cur; cur = cur + prev; prev = tmp; } return cur; } |
Risk Assessment
Stack overflow caused by excessive stack allocations or recursion could lead to abnormal termination and denial-of-service attacks.
Because there is no recursion, the amount of stack space needed does not depend on the parameter n
, greatly reducing the risk of stack overflow.
Risk Assessment
Program stacks are frequently used for convenient temporary storage because allocated memory is automatically freed when the function returns. Generally, the operating system grows the stack as needed. However, growing the stack can fail because of a lack of memory or a collision with other allocated areas of the address space (depending on the architecture). When the stack is exhausted, the operating system can terminate the program abnormally. This behavior can be exploited, and an attacker can cause a denial-of-service attack if he or she can control or influence the amount of stack memory allocated.
Recommendation |
---|
Severity | Likelihood | Remediation Cost | Priority | Level | |
---|---|---|---|---|---|
MEM05- |
1 (low)
1 (unlikely)
2 (medium)
P2
L3
References
C | Medium | Likely | Medium | P12 | L1 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
CodeSonar |
| IO.TAINT.SIZE MISC.MEM.SIZE.BAD | Tainted Allocation Size Unreasonable Size Argument | ||||||
| STACK_USE | Can help detect single stack allocations that are dangerously large, although it will not detect excessive stack use resulting from recursion | |||||||
Helix QAC |
| C1051, C1520, C3670 | |||||||
Klocwork |
| MISRA.FUNC.RECUR | |||||||
LDRA tool suite |
| 44 S | Enhanced Enforcement | ||||||
Parasoft C/C++test |
| CERT_C-MEM05-a | Do not use recursion | ||||||
PC-lint Plus |
| 9035, 9070 | Partially supported: reports use of variable length arrays and recursion | ||||||
Polyspace Bug Finder |
| Checks for:
Rec. partially covered. | |||||||
PVS-Studio |
| V505 |
Related Vulnerabilities
Stack overflow has been implicated in Toyota unintended acceleration cases, where Camry and other Toyota vehicles accelerated unexpectedly. Michael Barr testified at the trial that a stack overflow could corrupt the critical variables of the operating system, because they were located in memory adjacent to the top of the stack [Samek 2014].
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
SEI CERT C++ Coding Standard | VOID MEM05-CPP. Avoid large stack allocations |
ISO/IEC TR 24772:2013 | Recursion [GDL] |
MISRA C:2012 | Rule 17.2 (required) |
Bibliography
[Loosemore 2007] | Section 3.2.5, "Automatic Storage with Variable Size" |
[Samek 2014] | Are We Shooting Ourselves in the Foot with Stack Overflow? Monday, February 17th, 2014 by Miro Samek |
[Seacord 2013] | Chapter 4, "Dynamic Memory Management" |
...
\[[Sprundel 06|http://ilja.netric.org/files/Unusual%20bugs.pdf]\] "Stack Overflow" Wiki Markup