Ensuring that array references are within the bounds of the array is almost entirely the responsibility of the programmer. Likewise, when using standard template library vectors, the programmer is responsible for ensuring integer indexes are within the bounds of the vector.

Noncompliant Code Example (Pointers)

This noncompliant code example shows a function, insert_in_table(), that has two int parameters, pos and value, both of which can be influenced by data originating from untrusted sources. The function performs a range check to ensure that pos does not exceed the upper bound of the array, specified by tableSize, but fails to check the lower bound. Because pos is declared as a (signed) int, this parameter can assume a negative value, resulting in a write outside the bounds of the memory referenced by table.

#include <cstddef>
 
void insert_in_table(int *table, std::size_t tableSize, int pos, int value) {
  if (pos >= tableSize) {
    // Handle error
    return;
  }
  table[pos] = value;
}

Compliant Solution (size_t)

In this compliant solution, the parameter pos is declared as size_t, which prevents the passing of negative arguments.

#include <cstddef>
 
void insert_in_table(int *table, std::size_t tableSize, std::size_t pos, int value) {
  if (pos >= tableSize) {
    // Handle error
    return;
  }
  table[pos] = value;
}

Compliant Solution (Non-Type Templates)

Non-type templates can be used to define functions accepting an array type where the array bounds are deduced at compile time. This compliant solution is functionally equivalent to the previous bounds-checking one except that it additionally supports calling insert_in_table() with an array of known bounds.

#include <cstddef>
#include <new>

void insert_in_table(int *table, std::size_t tableSize, std::size_t pos, int value) { // #1
  if (pos >= tableSize) {
    // Handle error
    return;
  }
  table[pos] = value;
}

template <std::size_t N>
void insert_in_table(int (&table)[N], std::size_t pos, int value) { // #2
  insert_in_table(table, N, pos, value);
}
 
void f() {
  // Exposition only
  int table1[100];
  int *table2 = new int[100];
  insert_in_table(table1, 0, 0); // Calls #2
  insert_in_table(table2, 0, 0); // Error, no matching function call
  insert_in_table(table1, 100, 0, 0); // Calls #1
  insert_in_table(table2, 100, 0, 0); // Calls #1
  delete [] table2;
}

Noncompliant Code Example (std::vector)

In this noncompliant code example, a std::vector is used in place of a pointer and size pair. The function performs a range check to ensure that pos does not exceed the upper bound of the container. Because pos is declared as a (signed) long long, this parameter can assume a negative value. On systems where std::vector::size_type is ultimately implemented as an unsigned int (such as with Microsoft Visual Studio 2013), the usual arithmetic conversions applied for the comparison expression will convert the unsigned value to a signed value. If pos has a negative value, this comparison will not fail, resulting in a write outside the bounds of the std::vector object when the negative value is interpreted as a large unsigned value in the indexing operator.

#include <vector>
 
void insert_in_table(std::vector<int> &table, long long pos, int value) {
  if (pos >= table.size()) {
    // Handle error
    return;
  }
  table[pos] = value;
}

Compliant Solution (std::vector, size_t)

In this compliant solution, the parameter pos is declared as size_t, which ensures that the comparison expression will fail when a large, positive value (converted from a negative argument) is given.

#include <vector>
 
void insert_in_table(std::vector<int> &table, std::size_t pos, int value) {
  if (pos >= table.size()) {
    // Handle error
    return;
  }
  table[pos] = value;
}

Compliant Solution (std::vector::at())

In this compliant solution, access to the vector is accomplished with the at() method. This method provides bounds checking, throwing a std::out_of_range exception if pos is not a valid index value. The insert_in_table() function is declared with noexcept(false) in compliance with ERR55-CPP. Honor exception specifications.

#include <vector>
 
void insert_in_table(std::vector<int> &table, std::size_t pos, int value) noexcept(false) {
  table.at(pos) = value;
}

Noncompliant Code Example (Iterators)

In this noncompliant code example, the f_imp() function is given the (correct) ending iterator e for a container, and b is an iterator from the same container. However, it is possible that b is not within the valid range of its container. For instance, if the container were empty, b would equal e and be improperly dereferenced.

#include <iterator>
 
template <typename ForwardIterator>
void f_imp(ForwardIterator b, ForwardIterator e, int val, std::forward_iterator_tag) {
  do {
    *b++ = val;
  } while (b != e);
}

template <typename ForwardIterator>
void f(ForwardIterator b, ForwardIterator e, int val) {
  typename std::iterator_traits<ForwardIterator>::iterator_category cat;
  f_imp(b, e, val, cat);
}

Compliant Solution

This compliant solution tests for iterator validity before attempting to dereference b.

#include <iterator>
 
template <typename ForwardIterator>
void f_imp(ForwardIterator b, ForwardIterator e, int val, std::forward_iterator_tag) {
  while (b != e) {
    *b++ = val;
  }
}

template <typename ForwardIterator>
void f(ForwardIterator b, ForwardIterator e, int val) {
  typename std::iterator_traits<ForwardIterator>::iterator_category cat;
  f_imp(b, e, val, cat);
}

Risk Assessment

Using an invalid array or container index can result in an arbitrary memory overwrite or abnormal program termination.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

CTR50-CPP

High

Likely

High

P9

L2

Automated Detection

Tool

Version

Checker

Description

Astrée

22.10

overflow_upon_dereference
CodeSonar
9.0p0

LANG.MEM.BO
LANG.MEM.BU
LANG.MEM.TO
LANG.MEM.TU
LANG.MEM.TBA
LANG.STRUCT.PBB
LANG.STRUCT.PPE
LANG.STRUCT.PARITH

Buffer overrun
Buffer underrun
Type overrun
Type underrun
Tainted buffer access
Pointer before beginning of object
Pointer past end of object
Pointer Arithmetic

Helix QAC

2025.1

C++3139, C++3140

DF2891


Klocwork
2025.1

ABV.ANY_SIZE_ARRAY
ABV.GENERAL
ABV.GENERAL.MULTIDIMENSION
ABV.NON_ARRAY
ABV.STACK 
ABV.TAINTED
SV.TAINTED.ALLOC_SIZE
SV.TAINTED.CALL.INDEX_ACCESS
SV.TAINTED.CALL.LOOP_BOUND
SV.TAINTED.INDEX_ACCESS


LDRA tool suite
9.7.1

 

45 D, 47 S, 476 S, 489 S, 64 X, 66 X, 68 X, 69 X, 70 X, 71 X, 79 X

Partially implemented

Parasoft C/C++test
2024.2
CERT_CPP-CTR50-a
Guarantee that container indices are within the valid range
Polyspace Bug Finder

R2024b

CERT C++: CTR50-CPP

Checks for:

  • Array access out of bounds
  • Array access with tainted index
  • Pointer dereference with tainted offset

Rule partially covered.

PVS-Studio

7.37

V781

Related Vulnerabilities

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

Related Guidelines

SEI CERT C Coding Standard ARR30-C. Do not form or use out-of-bounds pointers or array subscripts
MITRE CWECWE 119, Failure to Constrain Operations within the Bounds of a Memory Buffer
CWE 129, Improper Validation of Array Index

Bibliography

[ISO/IEC 14882-2014]

Clause 23, "Containers Library"
Subclause 24.2.1, "In General" 

[ISO/IEC TR 24772-2013]Boundary Beginning Violation [XYX]
Wrap-Around Error [XYY]
Unchecked Array Indexing [XYZ]
[Viega 2005]Section 5.2.13, "Unchecked Array Indexing"



2 Comments

  1. I wonder about: Noncompliant Code Example (Pointers)

    The text says: "Because pos is declared as a (signed) int, this parameter can assume a negative value, resulting in a write outside the bounds of the memory referenced by table."

    Well.. if `tableSize` is 10 and `pos` is -1 it does not result in a write outside the bounds because `pos` is converted to unsigned in the condition and the condition will be true then right?

    And the highest bit in `tableSize` can't be set because sizeof(int) is not 1?

    1. Daniel:
      The C standard, section 6.3.1.8 says, wrt converting integers in a binary operation:

      If the operand that has unsigned integer type has rank >= the rank of the operand with signed integer type, the operand with signed integer type is converted to the type of the operand with unsigned integer type

      On an ILP64 platform, where both int and size_t are 64 bits, you are correct, this means the inequality 'pos >= tableSize' aka '-1 >= 10' will be evaluated as a size_t, and ((size_t) -1) == SIZE_MAX. Which means that -1 will be treated as an invalid index.  So this code will not write outside the array on an ILP64 platform (or ILP32 platform FTM).

      But ISO C permits platforms where sizeof(size_t) < sizeof(int), and on these platforms, the inequality will be evaluated as signed ints, in which case, pos=-1 will slip past the if statement and cause an out-of-bounds write to the array.