Skip to main content
GitHub

INT01-C. Use size_t for all integer values representing the size of an object

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. 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 size_t .

Noncompliant Code Example

In this noncompliant code example, the dynamically allocated buffer referenced by p overflows for values of n > INT_MAX :

Non-compliant code
char *copy(size_t n, const char *c_str) {
  int i;
  char *p;

  if (n == 0) {
    /* Handle unreasonable object size error */
  }
  p = (char *)malloc(n);
  if (p == NULL) {
    return NULL; /* Indicate malloc failure */
  }
  for ( i = 0; i < n; ++i ) {
    p[i] = *c_str++;
  }
  return p;
}

/* ... */

char c_str[] = "hi there";
char *p = copy(sizeof(c_str), c_str);

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 is incremented beyond 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)

For values of n where 0 < n <= INT_MAX , the loop executes n times, as expected.

For values of n where INT_MAX < n <= (size_t)INT_MIN , the loop executes INT_MAX times. Once i becomes negative, the loop stops, and i remains in the range 0 through INT_MAX .

For values of n where (size_t)INT_MIN < n <= SIZE_MAX , i wraps and takes the values INT_MIN to INT_MIN + (n - (size_t)INT_MIN - 1) . Execution of the loop overwrites memory from p[INT_MIN] through p[INT_MIN + (n - (size_t)INT_MIN - 1)] .

Compliant Solution

Declaring i to be of type size_t eliminates the possible integer overflow condition (in this example):

Compliant code
char *copy(size_t n, const char *c_str) {
  size_t i;
  char *p;

  if (n == 0) {
    /* Handle unreasonable object size error */
  }
  p = (char *)malloc(n);
  if (p == NULL) {
    return NULL;  /* Indicate malloc failure */
  }
  for (i = 0; i < n; ++i) {
    p[i] = *c_str++;
  }
  return p;
}

/* ... */

char c_str[] = "hi there";
char *p = copy(sizeof(c_str), c_str);

Noncompliant Code Example

In this noncompliant code example, the value of length is read from a network connection and passed as an argument to a wrapper to malloc() to allocate the appropriate data block. Provided that the size of an unsigned long is equal to the size of an unsigned int , and both sizes are equal to or smaller than the size of size_t , this code runs as expected. However, if the size of an unsigned long is greater than the size of an unsigned int , the value stored in length may be truncated when passed as an argument to alloc() .  Both read functions return zero on success and nonzero on failure.

Non-compliant code
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)) {
    return -1;
  }

  data = (unsigned char*)alloc(length+1);
  if (data == NULL) {
    return -1;  /* Indicate failure */
  }

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

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

Compliant Solution

Declaring both length and the blocksize argument to alloc() as size_t eliminates the possibility of truncation. This compliant solution assumes that read_integer_from_network() and read_network_data() can also be modified to accept a length argument of type pointer to size_t and size_t , respectively. If these functions are part of an external library that cannot be updated, care must be taken when casting length into an unsigned long to ensure that integer truncation does not occur.

Compliant code
void *alloc(size_t blocksize) {
  if (blocksize == 0) {
    return NULL;  /* Indicate failure */
  }
  return malloc(blocksize);
}

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

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

  data = (unsigned char*)alloc(length+1);
  if (data == NULL) {
    return -1; /* Indicate failure */
  }

  if (read_network_data(fd, data, length)) {
    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 Detectable Repairable Priority Level
INT01-C Medium Probable No Yes P8 L2

Automated Detection

Tool

Version

Checker

Description

Axivion Suite

7.12.0

CertC-INT01
CodeSonar
9.1p0

LANG.TYPE.BASIC

Basic numerical type used
Compass/ROSE



Can detect violations of this recommendation. In particular, it catches comparisons and operations where one operand is of type size_t or rsize_t and the other is not

Security Reviewer - Static Reviewer

6.02

C999

Fully implemented

Splint
3.1.1



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

SEI CERT C++ Coding StandardVOID INT01-CPP. Use rsize_t or size_t for all integer values representing the size of an object

Bibliography

[ Meyers 2004 ]