...
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 table_size
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
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <cstddef> void insert_in_table(int *table, std::size_t table_sizetableSize, int pos, int value) { if (pos >= table_sizetableSize) { // Handle error return; } table[pos] = value; } |
...
In this compliant solution, the parameter pos
is declared as size_t
, which prevents passing the passing of negative arguments (see INT01-CPP. Use rsize_t or size_t for all integer values representing the size of an object).
Code Block | ||||
---|---|---|---|---|
| ||||
#include <cstddef> void insert_in_table(int *table, std::size_t table_sizetableSize, std::size_t pos, int value) { if (pos >= table_sizetableSize) { // Handle error return; } table[pos] = value; } |
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <cstddef> #include <new> void insert_in_table(int *table, std::size_t table_sizetableSize, std::size_t pos, int value) { // #1 if (pos >= table_sizetableSize) { // 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; } |
...
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 array but fails to check the lower bound for table
container. Because pos
is declared as a (signed) int
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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <vector> void insert_in_table(std::vector<int> &table, long intlong pos, int value) { if (pos >= table_.size()) { // Handle error return; } table[pos] = value; } |
...
In this compliant solution, the parameter pos
is declared as size_t
, which prevents passing of negative arguments (see INT01-CPP. Use rsize_t or size_t for all integer values representing the size of an object)ensures that the comparison expression will fail when a large, positive value (converted from a negative argument) is given.
Code Block | ||||
---|---|---|---|---|
| ||||
#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; } |
...
In this compliant solution, access to the vector is accomplished with the at()
method. This method provides bounds checking, throwing an 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 ERR50with ERR55-CPP. Do not call std::terminate(), std::abort(), or std::_Exit()Honor exception specifications.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <vector> void insert_in_table(std::vector<int> &table, std::size_t pos, int value) noexcept(false) { table.at(pos) = value; } |
...
In this noncompliant code example, it is possible that the the f_imp()
function is given a valid iterator but that the iterator is not within a valid range. For instance, if f()
were called with iterators obtained from an empty container, the end()
iterator could be 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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iterator> template <typename ForwardIterator> void f_imp(ForwardIterator Bb, ForwardIterator Ee, int Valval, std::forward_iterator_tag) { do { *Bb++ = Valval; } while (Bb != Ee); } template <typename ForwardIterator> void f(ForwardIterator Bb, ForwardIterator Ee, int Valval) { typename std::iterator_traits<ForwardIterator>::iterator_category Catcat; f_imp(Bb, Ee, Valval, Catcat); } |
Compliant Solution
This compliant solution tests for iterator validity before attempting to dereference the forward iterator: b.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iterator> template <typename ForwardIterator> void f_imp(ForwardIterator Bb, ForwardIterator Ee, int Valval, std::forward_iterator_tag) { while (Bb != Ee) { *Bb++ = Valval; } } template <typename ForwardIterator> void f(ForwardIterator Bb, ForwardIterator Ee, int Valval) { typename std::iterator_traits<ForwardIterator>::iterator_category Catcat; f_imp(Bb, Ee, Valval, Catcat); } |
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 |
| overflow_upon_dereference | |||||||
CodeSonar |
| LANG.MEM.BO | Buffer overrun | ||||||
Helix QAC |
| C++3139, C++3140 DF2891 | |||||||
Klocwork |
| ABV.ANY_SIZE_ARRAY ABV.GENERAL ABV.GENERAL.MULTIDIMENSION 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 |
| 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 |
| CERT_CPP-CTR50-a | Guarantee that container indices are within the valid range | ||||||
Polyspace Bug Finder |
| CERT C++: CTR50-CPP | Checks for:
Rule partially covered. | ||||||
PVS-Studio |
| 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 CWE | CWE 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" |
[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" |
...
...