You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 42 Next »

Allocating and freeing memory in different modules and levels of abstraction burdens the programmer with tracking the lifetime of that block of memory. This may cause confusion regarding when and if a block of memory has been allocated or freed, leading to programming defects such as double-free vulnerabilities, accessing freed memory, or writing to unallocated memory.

To avoid these situations, it is recommended that memory be allocated and freed at the same level of abstraction, and ideally in the same code module.

The affects of not following this recommendation are best demonstrated by an actual vulnerability. Freeing memory in different modules resulted in a vulnerability in MIT Kerberos 5 MITKRB5-SA-2004-002 . The problem was that the MIT Kerberos 5 code contained error-handling logic, which freed memory allocated by the ASN.1 decoders if pointers to the allocated memory were non-null. However, if a detectable error occured, the ASN.1 decoders freed the memory that they had allocated. When some library functions received errors from the ASN.1 decoders, they also attempted to free, causing a double-free vulnerability.

Non-Compliant Code Example

This example demonstrates an error that can occur when memory is freed in different functions. The array, which is referred to by list and its size, number, are then passed to the verify_list() function. If the number of elements in the array is less than the value MIN_SIZE_ALLOWED, list is processed. Otherwise, it is assumed an error has occurred, list is freed, and the function returns. If the error occurs in verify_list(), the dynamic memory referred to by list will be freed twice: once in verify_list() and again at the end of process_list().

int verify_size(char *list, size_t list_size) {
  if (size < MIN_SIZE_ALLOWED) {
    /* Handle Error Condition */
    free(list);
    return -1;
  }

  /* Process list */

  return 0;
}

void process_list(size_t number) {
  char *list = malloc(number);
  if (list == NULL) {
    /* Handle Allocation Error */
  }

  if (verify_size(list,number) == -1) {
    /* Handle Error */
  }

  /* Continue Processing list */

  free(list);
}

Compliant Solution

To correct this problem, the logic in the error handling code should be changed so that it no longer frees list. This change ensures that list is freed only once, in func1.

#define MIN_SIZE_ALLOWED 10

int  func2(int *list, size_t list_size) {
  if (size < MIN_SIZE_ALLOWED) {
     /* Handle Error Condition */
      return;
  }
  /* Process list */
}

void func1 (size_t number) {
  char *list = malloc(sizeof(int));
  if (list == NULL) {
    /* Handle Allocation Error */
  }
  func2(list,number);

  /* Continue Processing list */

  free(list);
}

Risk Assessment

The mismanagement of memory can lead to freeing memory multiple times or writing to already freed memory. Both of these problems can result in an attacker executing arbitrary code with the permissions of the vulnerable process. Memory management errors can also lead to resource depletion and denial-of-service attacks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MEM00-A

3 (high)

2 (probable)

1 (high)

P6

L2

Examples of vulnerabilities resulting from the violation of this recommendation can be found on the CERTwebsite.

References

[[ISO/IEC 9899-1999]] Section 7.20.3, "Memory Management Functions"
[[Seacord 05]] Chapter 4, "Dynamic Memory Management"
[[Plakosh 05]]
[MIT Kerberos 5 Security Advisory 2004-002 ]

  • No labels