CertC++-CTR50¶
Guarantee that container indices and iterators are within the valid range
Required inputs: IR
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 Studio2013), 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 |
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" 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" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
missing_check_bounds |
Index ‘{}’ is missing bounds checking. |
None |
False |
missing_check_iterator |
iterator ‘{}’ is missing bounds checking. |
None |
False |
missing_check_lower_bound |
Ensure that index ‘{}’ is non-negative. |
None |
False |
missing_check_upper_bound |
Index ‘{}’ is missing an upper bound check. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
iterator_operations¶
iterator_operations : set[str] = {'operator++', 'operator--'}
range_access_functions¶
range_access_functions
Set of functions that unchecked accesses the buffer of its container.Type: set[bauhaus.analysis.config.QualifiedName]
Default:
{'std::array::at', 'std::array::operator[]', 'std::basic_string::operator[]', 'std::span::at', 'std::span::operator[]', 'std::string::at', 'std::vector::at', 'std::vector::operator[]'}