The stack is frequently used for convenient temporary storage, because allocated memory is automatically freed when the function returns. Generally, the operating system will grow the stack as needed. However, this can fail due to a lack of memory or collision with other allocated areas of the address space (depending on the architecture). When this occurs, the operating system may terminate the program abnormally. This condition can be exploited to cause a denial-of-service attack in cases where an attacker can control or influence the amount of stack memory allocated.
Non-Compliant Code Example
C99 includes support for variable length arrays. If the array length is derived from an untrusted data source, an attacker could cause the process to perform an excessive allocation on the stack.
This non-compliant code example temporarily stores data read from a source file into a buffer. The buffer is allocated on the stack as a variable length array of size bufsize
.
int copy_file(FILE *src, FILE *dst, size_t bufsize) { char buf[bufsize]; while (fgets(buf, bufsize, src)) { fputs(buf, dst); } return 0; }
If bufsize
can be controlled by a malicious user, this code could be exploited to cause a denial-of-service attack.
Compliant Solution
This compliant solution replaces the variable length array with a call to malloc()
. If malloc()
fails, the return value can be checked to prevent the program from terminating abnormally.
int copy_file(FILE *src, FILE *dst, size_t bufsize) { char *buf = malloc(bufsize); if (!buf) { return -1; } while (fgets(buf, bufsize, src)) { fputs(buf, dst); } return 0; }
Non-Compliant Code Example
Recursion can also lead to large stack allocations. Recursive functions must ensures they they do not exhaust the stack due to excessive recursions.
This non-compliant implementation of the Fibonacci function uses recursion.
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 stack space needed grows exponentially with respect to the parameter n
. When tested on a Linux system, fib1(100)
crashes with a segmentation fault.
Compliant Solution
This implementation of the Fibonacci functions eliminates the use of recursion.
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; }
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
Stack overflow caused by excessive stack allocations or recursion could lead to abnormal termination and denial-of-service attacks.
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 be used to write to an arbitrary memory location.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
MEM05-A |
1 (low) |
1 (unlikely) |
2 (medium) |
P2 |
L3 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Automated Detection
The Coverity Prevent STACK_USE checker can help detect single stack allocations that are dangerously large, although it will not detect excessive stack use resulting from recursion. Coverity Prevent cannot discover all violations of this rule so further verification is necessary.
References
[van Sprundel 06] "Stack Overflow"
MEM04-A. Do not make assumptions about the result of allocating 0 bytes 08. Memory Management (MEM) MEM07-A. Ensure that size arguments to calloc() do not result in an integer overflow