Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The stack is frequently used for convenient temporary storage, because allocated memory is automatically freed when the function returns. AlsoGenerally, the operating system automatically grows will grow the stack if the process accesses memory beyond the current allocation. This as needed. However, this can fail due to a 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(depending on the architecture). When this occurs, the operating system may terminate the program abnormally. 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.

...

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 be used to 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.

The following 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
bgColor#FFcccc
int copy_file(FILE *src, FILE *dst, size_t bufsize) {
  char buf[bufsize];

  while (fgets(buf, bufsize, src))
    fputs(buf, dst);

  return 0;
}

If the size of the buffer is not constrained, a malicious user could specify a buffer of several gigabytes which might cause a crash. If the architecture is set up in a way that the heap exists "below" the stack in memory, a buffer exactly long enough to place the stack pointer into the heap could be used to overwrite memory there with what fputs() and fgets() store on the stack.

Compliant Solution

This compliant solution replaces the dynamic array with a call to malloc(). A malloc() failure should not cause a program to terminate abnormally, and performs appropriate error checking on the the return value of malloc() return value can be checked for success to see if it is safe to continue.

Code Block
bgColor#ccccff
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

Excessive Using recursion can 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.lead to large stack allocations. It needs to be ensured that functions which are recursive do not recurse so deep that the stack grows too large.

The following 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 faultuses recursion.

Code Block
bgColor#FFcccc
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);
  }
}

Compliant Solution

This is a much more efficient solution, using constant space and linear time. Tested The stack space needed grows exponentially with respect to the parameter n. When tested on a Linux system, fib2fib1(100) is calculated almost instantly and with almost no chance of crashing due to a failed stack allocation crashes with a segmentation fault.

Compliant Solution

This implementation of the Fibonacci functions eliminates the use of recursion.

Code Block
bgColor#ccccff
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.

...