原文链接:

CTR50-CPP. 保证容器索引和迭代器在有效范围内

https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR50-CPP.+Guarantee+that+container+indices+and+iterators+are+within+the+valid+range

保证数据引用在数据的边界范围内是程序员一定要负全责的。同理,当程序员使用标准模板容器库,也需要确保索引在容器范围之内。

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)

这个不合规代码示例展示了一个函数,insert_in_table(),包含两个 int 参数,posvalue,会受来自不可信源的数据影响。函数执行了范围检查以确保 pos 不会超出数组的上界,由 tableSize 指定,但是未检查下界。因为 pos 被声明为一个 (signed) int,这个参数可以设想为一个负数,导致一次超出 table 引用的内存范围的写入。

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.

1
2
3
4
5
6
7
8
9
#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;
}

合规的方案 (size_t) Compliant Solution (size_t)

这个合规的方案中,参数 pos 被声明为 size_t,可以避免传入负值变量。

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

1
2
3
4
5
6
7
8
9
#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;
}

合规的方案 (Non-Type Templates) Compliant Solution (Non-Type Templates)

无类型模板可以被用来定义接收一个数组类型的函数,数组边界可以编译器推导。这个合规的方案与先前需要在调用 insert_in_table 时额外提供一个已知边界的边界检查方案,在功能上等价的。

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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;
}

不合规代码示例 (std::vector) Noncompliant Code Example (std::vector)

在这个不合规的代码示例中,std::vector 被用来替换指针和尺寸。这个函数执行一次范围检查确保 pos 不会超出容器的上边界。因为 pos 被声明为一个 (signed) long long,这个参数可以是负数。在把 std::vector::size_typeunsigned int 来实现的系统中 (例如 Microsoft Visual Studio2013),对于比较表达式,一般的算数转换会被用来将无符号值转换为有符号值。如果 pos 是一个负值, 这次比较将不会失败,当负值在索引操作中被解释为一个大的无符号值,将导致一次 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.

1
2
3
4
5
6
7
8
9
#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;
}

合规方案 (std::vector, size_t) Compliant Solution (std::vector, size_t)

在这个合规的方案中,参数 pos 被声明为 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.

1
2
3
4
5
6
7
8
9
#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;
}

合规的方案 (std::vector::at()) Compliant Solution (std::vector::at())

这个合规的方案中,通过 at() 方法来访问容器。这个方案提供了边界检查,如果 pos 不是一个有效的索引值时,抛出一个 std::out_of_range 异常。insert_in_table() 被定义为一个 noexcept(false) 函数,符合 ERR55-CPP. Honor exception specifications

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.

1
2
3
4
5
#include <vector>

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

不合规代码示例 (Iterrators) Noncompliant Code Example (Iterators)

在这个不合规的代码示例中,函数 f_imp() 给定了 (正确的) 容器尾迭代器 eb 是同个容器的迭代器。然而,b 不在该容器的有效范围内也是有可能的。举个例子,如果容器是空的,b 将和 e 相等,导致不正确的引用。

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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

这个合规方案在试图解引用 b 前对迭代器有效性做了测试。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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
CodeSonar 5.3p0 **LANG.MEM.BO LANG.MEM.BU LANG.MEM.TO LANG.MEM.TU **LANG.MEM.TBA **LANG.STRUCT.PBB ****LANG.STRUCT.PPE** Buffer overrun Buffer underrun Type overrun Type underrun Tainted buffer access Pointer before beginning of object Pointer past end of object
Klocwork 2018 ABV.ANY_SIZE_ARRAY ABV.GENERAL 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 10.4.2 CERT_CPP-CTR50-a Guarantee that container indices are within the valid range
Polyspace Bug Finder R2020a CERT C++: CTR50-CPP Checks for:Array access out of boundsArray access with tainted indexPointer dereference with tainted offsetRule partially covered.
PRQA QA-C++ 4.4 2891, 3139, 3140
PVS-Studio 7.07 V781

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

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”

img img img