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

Compare with Current View Page History

« Previous Version 59 Next »

The size_t type is the unsigned integer type of the result of the sizeof operator. Variables of type size_t are guaranteed to be of sufficient precision to represent the size of an object. The limit of size_t is specified by the SIZE_MAX macro.

The type size_t generally covers the entire address space. ISO/IEC TR 24731-1-2007 introduces a new type rsize_t, defined to be size_t but explicitly used to hold the size of a single object [[ISO/IEC TR 24731-1-2007]]. In code that documents this purpose by using the type rsize_t, the size of an object can be checked to verify that it is no larger than RSIZE_MAX, the maximum size of a normal single object, which provides additional input validation for library functions. See [STR07-A. Use TR 24731 for remediation of existing string manipulation code] for additional discussion of TR 24731-1.

Any variable that is used to represent the size of an object, including integer values used as sizes, indices, loop counters, and lengths, should be declared as rsize_t if available, or otherwise as size_t.

Non-Compliant Code Example

In this non-compliant code example, the dynamically allocated buffer referenced by p overflows for values of n > INT_MAX.

char *copy(size_t n, char const *str) {
  int i;
  char *p = (char *)malloc(n);
  if (p == NULL) {
    /* Handle malloc failure */
  }
  for ( i = 0; i < n; ++i ) {
    p[i] = *str++;
  }
  return p;
}

char *p = copy(9, "hi there");

Signed integer overflow causes undefined behavior. The following are two possible conditions under which this code constitutes a serious vulnerability:

sizeof(size_t) == sizeof(int)

The unsigned n may contain a value greater than INT_MAX. Assuming quiet wraparound on signed overflow, the loop executes n times because the comparison i < n is an unsigned comparison. Once i > INT_MAX, i takes on negative values starting with (INT_MIN). Consequently, the memory locations referenced by p[i] precede the memory referenced by p and a write-outside-array bounds occurs.

sizeof(size_t) > sizeof(int)

Similar behavior as the case above occurs for values of n <= UINT_MAX. For values of n > UINT_MAX, the expression ++i will wrap around to zero before the condition i < n ever evaluates to false. This causes all memory within [INT_MIN, INT_MAX] from the beginning of the output buffer to be overwritten in an infinite loop.

Compliant Solution (TR 24731-1)

Declaring i to be of type rsize_t eliminates the possible integer overflow condition (in this example).  Also, the argument n is changed to be of type rsize_t to document additional validation in the form of a check against RSIZE_MAX.

char *copy(rsize_t n, char const *str) {
  rsize_t i;
  char *p;
  if (n > RSIZE_MAX) {
    /* Handle unreasonable object size error */
  }
  p = (char *)malloc(n);
  if (p == NULL) {
    /* Handle malloc failure */
  }
  for ( i = 0; i < n; ++i ) {
    p[i] = *str++;
  }
  return p;
}

char *p = copy(9, "hi there");

Non-Compliant Code Example

In this non-compliant code example, an integer overflow is specifically looked for by checking whether length + 1 == 0 (that is, integer wrap around has occurred). If the test passes, a wrapper to malloc() is called to allocate the appropriate data block. In a program compiled using an ILP32 compiler, this code runs as expected, but in an LP64 environment, an integer overflow can occur because length is now a 64-bit value. The result of the expression, however, is truncated to 32 bits when passed as an argument to alloc() because it takes an unsigned int argument.

void *alloc(unsigned int blocksize) {
  return malloc(blocksize);
}

int read_counted_string(int fd) {
  unsigned long length;
  unsigned char *data;

  if (read_integer_from_network(fd, &length) < 0) {
    return -1;
  }

  if (length + 1 == 0) {
    /* handle integer overflow */
  }

  data = (unsigned char*)alloc(length + 1);

  if (read_network_data(fd, data, length) < 0) {
    free(data);
    return -1;
  }
  data[length] = '\0';

  /* ... */
  free( data);
  return 0;
}

Compliant Solution (TR 24731-1)

Declaring both length and the blocksize argument to alloc() as rsize_t eliminates the possibility of truncation.

void *alloc(rsize_t blocksize) {
  if (blocksize > RSIZE_MAX) {
    /* Handle error */
  }
  return malloc(blocksize);
}

int read_counted_string(int fd) {
  rsize_t length;
  unsigned char *data;

  if (read_integer_from_network(fd, &length) < 0) {
    return -1;
  }

  if (length + 1 == 0) {
    /* handle integer overflow */
  }

  data = (unsigned char*)alloc(length + 1);

  if (read_network_data(fd, data, length) < 0) {
    free(data);
    return -1;
  }
  data[length] = '\0';

  /* ... */
  free( data);
  return 0;
}

Risk Assessment

The improper calculation or manipulation of an object's size can result in exploitable vulnerabilities.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

INT01-A

medium

probable

medium

P8

L2

Automated Detection

Fortify SCA Version 5.0 with CERT C Rule Pack will detect integer operations that cause overflow, but not all cases where size_t is not used.

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

References

[[ISO/IEC 9899:1999]] Section 7.17, "Common definitions <stddef.h>", Section 7.20.3, "Memory management functions"
[[ISO/IEC TR 24731-1:2007]]


INT00-A. Understand the data model used by your implementation(s)      04. Integers (INT)       INT02-A. Understand integer conversion rules

  • No labels