CertC-ARR30

Do not form or use out-of-bounds pointers or array subscripts

Required inputs: IR, StaticSemanticAnalysis

The C Standard identifies the following distinct situations in which undefined behavior (UB) can arise as a result of invalid pointer operations:

UB Description Example Code
46 Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object. Forming Out-of-Bounds Pointer, Null Pointer Arithmetic
47 Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that points just beyond the array object and is used as the operand of a unary * operator that is evaluated. Dereferencing Past the End Pointer, Using Past the End Index
49 An array subscript is out of range, even if an object is apparently accessible with the given subscript, for example, in the lvalue expression a[1][7] given the declaration int a[4][5]). Apparently Accessible Out-of-Range Index
62 An attempt is made to access, or generate a pointer to just past, a flexible array member of a structure when the referenced object provides no elements for that array. Pointer Past Flexible Array Member
Noncompliant Code Example (Forming Out-of-Bounds Pointer)

In this noncompliant code example, the function f() attempts to validate the index before using it as an offset to the statically allocated table of integers. However, the function fails to reject negative index values. When index is less than zero, the behavior of the addition expression in the return statement of the function is undefined behavior 46. On some implementations, the addition alone can trigger a hardware trap. On other implementations, the addition may produce a result that when dereferenced triggers a hardware trap. Other implementations still may produce a dereferenceable pointer that points to an object distinct from table. Using such a pointer to access the object may lead to information exposure or cause the wrong object to be modified.

enum { TABLESIZE = 100 };

static int table[TABLESIZE];

int *f(int index) {
  if (index < TABLESIZE) {
    return table + index;
  }
  return NULL;
}
Compliant Solution

One compliant solution is to detect and reject invalid values of index if using them in pointer arithmetic would result in an invalid pointer:

enum { TABLESIZE = 100 };

static int table[TABLESIZE];

int *f(int index) {
  if (index >= 0 && index < TABLESIZE) {
    return table + index;
  }
  return NULL;
}
Compliant Solution

Another slightly simpler and potentially more efficient compliant solution is to use an unsigned type to avoid having to check for negative values while still rejecting out-of-bounds positive values of index:

#include <stddef.h>
 
enum { TABLESIZE = 100 };

static int table[TABLESIZE];

int *f(size_t index) {
  if (index < TABLESIZE) {
    return table + index;
  }
  return NULL;
}
Noncompliant Code Example (Dereferencing Past-the-End Pointer)

This noncompliant code example shows the flawed logic in the Windows Distributed Component Object Model (DCOM) Remote Procedure Call (RPC) interface that was exploited by the W32.Blaster.Worm. The error is that the while loop in the GetMachineName() function (used to extract the host name from a longer string) is not sufficiently bounded. When the character array pointed to by pwszTemp does not contain the backslash character among the first MAX_COMPUTERNAME_LENGTH_FQDN + 1 elements, the final valid iteration of the loop will dereference past the end pointer, resulting in exploitable   undefined behavior 47. In this case, the actual exploit allowed the attacker to inject executable code into a running program. Economic damage from the Blaster worm has been estimated to be at least $525 million [ Pethia 2003].

For a discussion of this programming error in the Common Weakness Enumeration database, see CWE-119, "Improper Restriction of Operations within the Bounds of a Memory Buffer," and CWE-121, "Stack-based Buffer Overflow" [ MITRE 2013].

error_status_t _RemoteActivation(
      /* ... */, WCHAR *pwszObjectName, ... ) {
   *phr = GetServerPath(
              pwszObjectName, &pwszObjectName);
    /* ... */
}

HRESULT GetServerPath(
  WCHAR *pwszPath, WCHAR **pwszServerPath ){
  WCHAR *pwszFinalPath = pwszPath;
  WCHAR wszMachineName[MAX_COMPUTERNAME_LENGTH_FQDN+1];
  hr = GetMachineName(pwszPath, wszMachineName);
  *pwszServerPath = pwszFinalPath;
}

HRESULT GetMachineName(
  WCHAR *pwszPath,
  WCHAR wszMachineName[MAX_COMPUTERNAME_LENGTH_FQDN+1])
{
  pwszServerName = wszMachineName;
  LPWSTR pwszTemp = pwszPath + 2;
  while (*pwszTemp != L'\\')
    *pwszServerName++ = *pwszTemp++;
  /* ... */
}
Compliant Solution

In this compliant solution, the while loop in the GetMachineName() function is bounded so that the loop terminates when a backslash character is found, the null-termination character ( L'\0') is discovered, or the end of the buffer is reached. Or, as coded, the while loop continues as long as each character is neither a backslash nor a null character and is not at the end of the buffer. This code does not result in a buffer overflow even if no backslash character is found in wszMachineName.

HRESULT GetMachineName(
  wchar_t *pwszPath,
  wchar_t wszMachineName[MAX_COMPUTERNAME_LENGTH_FQDN+1])
{
  wchar_t *pwszServerName = wszMachineName;
  wchar_t *pwszTemp = pwszPath + 2;
  wchar_t *end_addr
    = pwszServerName + MAX_COMPUTERNAME_LENGTH_FQDN;
  while ((*pwszTemp != L'\\') &&
         (*pwszTemp != L'\0') &&
         (pwszServerName < end_addr))
  {
    *pwszServerName++ = *pwszTemp++;
  }

  /* ... */
}

This compliant solution is for illustrative purposes and is not necessarily the solution implemented by Microsoft. This particular solution may not be correct because there is no guarantee that a backslash is found.

Noncompliant Code Example (Using Past-the-End Index)

Similar to the dereferencing-past-the-end-pointer error, the function insert_in_table() in this noncompliant code example uses an otherwise valid index to attempt to store a value in an element just past the end of an array.

First, the function incorrectly validates the index pos against the size of the buffer. When pos is initially equal to size, the function attempts to store value in a memory location just past the end of the buffer.

Second, when the index is greater than size, the function modifies size before growing the size of the buffer. If the call to realloc() fails to increase the size of the buffer, the next call to the function with a value of pos equal to or greater than the original value of size will again attempt to store value in a memory location just past the end of the buffer or beyond.

Third, the function violates INT30-C. Ensure that unsigned integer operations do not wrap, which could lead to wrapping when 1 is added to  pos or when  size is multiplied by the size of  int.

For a discussion of this programming error in the Common Weakness Enumeration database, see CWE-122, "Heap-based Buffer Overflow," and CWE-129, "Improper Validation of Array Index" [ MITRE 2013].

#include <stdlib.h>
 
static int *table = NULL;
static size_t size = 0;

int insert_in_table(size_t pos, int value) {
  if (size < pos) {
    int *tmp;
    size = pos + 1;
    tmp = (int *)realloc(table, sizeof(*table) * size);
    if (tmp == NULL) {
      return -1;   /* Failure */
    }
    table = tmp;
  }

  table[pos] = value;
  return 0;
}
Compliant Solution

This compliant solution correctly validates the index pos by using the <= relational operator, ensures the multiplication will not overflow, and avoids modifying size until it has verified that the call to realloc() was successful:

#include <stdint.h>
#include <stdlib.h>
 
static int *table = NULL;
static size_t size = 0;

int insert_in_table(size_t pos, int value) {
  if (size <= pos) {
    if ((SIZE_MAX - 1 < pos) ||
        ((pos + 1) > SIZE_MAX / sizeof(*table))) {
      return -1;
    }

    int *tmp = (int *)realloc(table, sizeof(*table) * (pos + 1));
    if (tmp == NULL) {
      return -1;
    }
    /* Modify size only after realloc() succeeds */
    size  = pos + 1;
    table = tmp;
  }

  table[pos] = value;
  return 0;
}
Noncompliant Code Example (Apparently Accessible Out-of-Range Index)

This noncompliant code example declares matrix to consist of 7 rows and 5 columns in row-major order. The function init_matrix iterates over all 35 elements in an attempt to initialize each to the value given by the function argument x. However, because multidimensional arrays are declared in C in row-major order, the function iterates over the elements in column-major order, and when the value of j reaches the value COLS during the first iteration of the outer loop, the function attempts to access element matrix[0][5]. Because the type of matrix is int[7][5], the j subscript is out of range, and the access has undefined behavior 49.

#include <stddef.h>
#define COLS 5
#define ROWS 7
static int matrix[ROWS][COLS];

void init_matrix(int x) {
  for (size_t i = 0; i < COLS; i++) {
    for (size_t j = 0; j < ROWS; j++) {
      matrix[i][j] = x;
    }
  }
}
Compliant Solution

This compliant solution avoids using out-of-range indices by initializing matrix elements in the same row-major order as multidimensional objects are declared in C:

#include <stddef.h>
#define COLS 5
#define ROWS 7
static int matrix[ROWS][COLS];

void init_matrix(int x) {
  for (size_t i = 0; i < ROWS; i++) {
    for (size_t j = 0; j < COLS; j++) {
      matrix[i][j] = x;
    }
  }
}
Noncompliant Code Example (Pointer Past Flexible Array Member)

In this noncompliant code example, the function find() attempts to iterate over the elements of the flexible array member buf, starting with the second element. However, because function g() does not allocate any storage for the member, the expression first++ in find() attempts to form a pointer just past the end of buf when there are no elements. This attempt is undefined behavior 62. (See MSC21-C. Use robust loop termination conditions for more information.)

#include <stdlib.h>
 
struct S {
  size_t len;
  char buf[];  /* Flexible array member */
};

const char *find(const struct S *s, int c) {
  const char *first = s->buf;
  const char *last  = s->buf + s->len;

  while (first++ != last) { /* Undefined behavior */
    if (*first == (unsigned char)c) {
      return first;
    }
  }
  return NULL;
}
 
void g(void) {
  struct S *s = (struct S *)malloc(sizeof(struct S));
  if (s == NULL) {
    /* Handle error */
  }
  s->len = 0;
  find(s, 'a');
}
Compliant Solution

This compliant solution avoids incrementing the pointer unless a value past the pointer's current value is known to exist:

#include <stdlib.h>
 
struct S {
  size_t len;
  char buf[];  /* Flexible array member */
};

const char *find(const struct S *s, int c) {
  const char *first = s->buf;
  const char *last  = s->buf + s->len;

  while (first != last) { /* Avoid incrementing here */
    if (*++first == (unsigned char)c) {
      return first;
    }
  }
  return NULL;
}
 
void g(void) {
  struct S *s = (struct S *)malloc(sizeof(struct S));
  if (s == NULL) {
    /* Handle error */
  }
  s->len = 0;
  find(s, 'a');
}
Noncompliant Code Example (Null Pointer Arithmetic)

This noncompliant code example is similar to an Adobe Flash Player vulnerability that was first exploited in 2008. This code allocates a block of memory and initializes it with some data. The data does not belong at the beginning of the block, which is left uninitialized. Instead, it is placed offset bytes within the block. The function ensures that the data fits within the allocated block. 

#include <string.h>
#include <stdlib.h>

char *init_block(size_t block_size, size_t offset,
                 char *data, size_t data_size) {
  char *buffer = malloc(block_size);
  if (data_size > block_size || block_size - data_size < offset) {
    /* Data won't fit in buffer, handle error */
  }
  memcpy(buffer + offset, data, data_size);
  return buffer;
}

This function fails to check if the allocation succeeds, which is a violation of  ERR33-C. Detect and handle standard library errors. If the allocation fails, then malloc() returns a null pointer. The null pointer is added to offset and passed as the destination argument to memcpy(). Because a null pointer does not point to a valid object, the result of the pointer arithmetic is undefined behavior 46.

An attacker who can supply the arguments to this function can exploit it to execute arbitrary code. This can be accomplished by providing an overly large value for  block_size, which causes malloc() to fail and return a null pointer. The offset argument will then serve as the destination address to the call to memcpy(). The attacker can specify the data and data_size arguments to provide the address and length of the address, respectively, that the attacker wishes to write into the memory referenced by offset. The overall result is that the call to memcpy() can be exploited by an attacker to overwrite an arbitrary memory location with an attacker-supplied address, typically resulting in arbitrary code execution.

Compliant Solution  (Null Pointer Arithmetic)

This compliant solution ensures that the call to  malloc() succeeds:

#include <string.h>
#include <stdlib.h>

char *init_block(size_t block_size, size_t offset,
                 char *data, size_t data_size) {
  char *buffer = malloc(block_size);
  if (NULL == buffer) {
    /* Handle error */
  }
  if (data_size > block_size || block_size - data_size < offset) {
    /* Data won't fit in buffer, handle error */
  }
  memcpy(buffer + offset, data, data_size);
  return buffer;
}
Risk Assessment

Writing to out-of-range pointers or array subscripts can result in a buffer overflow and the execution of arbitrary code with the permissions of the vulnerable process. Reading from out-of-range pointers or array subscripts can result in unintended information disclosure.

Rule Severity Likelihood Remediation Cost Priority Level
ARR30-C High Likely High P9 L2
Related Guidelines
Taxonomy Taxonomy item Relationship
ISO/IEC TR 24772:2013 Arithmetic Wrap-Around Error [FIF] Prior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TR 24772:2013 Unchecked Array Indexing [XYZ] Prior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TS 17961 Forming or using out-of-bounds pointers or array subscripts [invptr] Prior to 2018-01-12: CERT: Unspecified Relationship
CWE 2.11 CWE-119, Improper Restriction of Operations within the Bounds of a Memory Buffer 2017-05-18: CERT: Rule subset of CWE
CWE 2.11 CWE-123, Write-what-where Condition 2017-05-18: CERT: Partial overlap
CWE 2.11 CWE-125, Out-of-bounds Read 2017-05-18: CERT: Partial overlap
MISRA C:2012 Rule 18.1 (required) Prior to 2018-01-12: CERT: Unspecified Relationship
Bibliography
[ Finlay 2003]
[ Microsoft 2003]
[ Pethia 2003]
[ Seacord 2013b] Chapter 1, "Running with Scissors"
[ Viega 2005] Section 5.2.13, "Unchecked Array Indexing"
[ xorl 2009 ] "CVE-2008-1517: Apple Mac OS X (XNU) Missing Array Index Validation"
Excerpt from SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition) and SEI CERT C Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/arrays-arr/arr30-c], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

Possible Messages

Key

Text

Severity

Disabled

arithmetic_out_of_bounds

Pointer arithmetic on {node0} might create pointer outside array bounds of {name0}

None

False

out_of_bounds

Access into array is out of bounds

None

False

possible_indirect_out_of_bounds

Pointer-indirect access through {node0} might be out of bounds accessing {name0}

None

False

possible_out_of_bounds

Access into array might be out of bounds

None

False

undereferenced_arithmetic_out_of_bounds

Pointer arithmetic on {node0} might create pointer one past the end of {name0} (but not dereferenced)

None

False

undereferenced_out_of_bounds

Access is one past the end of the array (but not dereferenced)

None

False

undereferenced_possible_indirect_out_of_bounds

Pointer-indirect access through {node0} might be one past the end accessing {name0} (but not dereferenced)

None

False

undereferenced_possible_out_of_bounds

Access might be one past the end of the array (but not dereferenced)

None

False

Options

abstract_interpretation_out_of_bounds

abstract_interpretation_out_of_bounds : bool = False

Use additional "symbolic expression analysis" as postprocessing step. This can remove false positives, but might require more time. Option is automatically active if option StaticSemanticAnalysis/performance.general.enhanced_analysis is active.
 

exclude_very_high_indices

exclude_very_high_indices : bool = True

Enables heuristic to detect false positives: When index used for array access is very high in comparison to the array's size, assume false positive.
 

report_unbounded_arrays

report_unbounded_arrays : bool = False

If true, accesses into arrays with unknown bound are reported as being potentially outside the allowed range. This affects arrays like extern char buf[];.
 

report_undereferenced_one_past_the_end

report_undereferenced_one_past_the_end : bool = False

If true, report accesses one past the end of an array even if there is no dereference of the resulting pointer.
 

report_unknown_index

report_unknown_index : bool = False

If false, do not report possible out-of-bound findings for which the analysis was not able to infer any restricting information about the array index (this can lead to excluding both false positives and true findings).