原文链接:

CTR56-CPP. Do not use pointer arithmetic on polymorphic objects

https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR56-CPP.+Do+not+use+pointer+arithmetic+on+polymorphic+objects


来自 C++ 标准的 指针运算 的定义,[expr.add], paragraph 7 [ISO/IEC 14882-2014],陈述如下:

The definition of pointer arithmetic from the C++ Standard, [expr.add], paragraph 7 [ISO/IEC 14882-2014], states the following:

For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T is different from the cv-unqualified array element type, the behavior is undefined. [Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. —end note]

指针运算不考虑多态对象的大小,试图在多态对象上运用指针运算将导致undefined behavior

C++ 标准, [expr.sub], paragraph 1 [ISO/IEC 14882-2014], 定义了数组下标操作被视为指针运算。特别地,它陈述如下:

Pointer arithmetic does not account for polymorphic object sizes, and attempting to perform pointer arithmetic on a polymorphic object value results in undefined behavior.

The C++ Standard, [expr.sub], paragraph 1 [ISO/IEC 14882-2014], defines array subscripting as being identical to pointer arithmetic. Specifically, it states the following:

The expression E1[E2] is identical (by definition) to *((E1)+(E2)).

不要使用指针运算,包括数组下标操作或在多态对象上。

以下代码假定是后续静态对象和类的定义。

Do not use pointer arithmetic, including array subscripting, on polymorphic objects.

The following code examples assume the following static variables and class definitions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int globI;
double globD;

struct S {
int i;

S() : i(globI++) {}
};

struct T : S {
double d;

T() : S(), d(globD++) {}
};

不合规的代码示例 (指针运算) Noncompliant Code Example (Pointer Arithmetic)

在这个不合规的代码示例中,f() 接受一个 S 对象的数据作为第一个形参。然而,main() 传递了 T 对象的数组作为 f() 的实参,这将造成 undefined behavior ,因为 for 循环中使用了指针运算。

In this noncompliant code example, f() accepts an array of S objects as its first parameter. However, main() passes an array of T objects as the first argument to f(), which results in undefined behavior due to the pointer arithmetic used within the for loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

// ... definitions for S, T, globI, globD ...

void f(const S *someSes, std::size_t count) {
for (const S *end = someSes + count; someSes != end; ++someSes) {
std::cout << someSes->i << std::endl;
}
}

int main() {
T test[5];
f(test, 5);
}

不合规的代码示例 (数组下标操作) Noncompliant Code Example (Array Subscripting)

在这个不合规的代码示例中,for 循环使用了数组下标操作。因为数组下标操作使用了指针运算,这份代码也导致未定义行为。

In this noncompliant code example, the for loop uses array subscripting. Since array subscripts are computed using pointer arithmetic, this code also results in undefined behavior.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

// ... definitions for S, T, globI, globD ...

void f(const S *someSes, std::size_t count) {
for (std::size_t i = 0; i < count; ++i) {
std::cout << someSes[i].i << std::endl;
}
}

int main() {
T test[5];
f(test, 5);
}

合规方案 (数组) Compliant Solution (Array)

替换具有对象数组,指针数组解决了不同对象大小不同的问题,如这个合规方案。

Instead of having an array of objects, an array of pointers solves the problem of the objects being of different sizes, as in this compliant solution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

// ... definitions for S, T, globI, globD ...

void f(const S * const *someSes, std::size_t count) {
for (const S * const *end = someSes + count; someSes != end; ++someSes) {
std::cout << (*someSes)->i << std::endl;
}
}

int main() {
S *test[] = {new T, new T, new T, new T, new T};
f(test, 5);
for (auto v : test) {
delete v;
}
}

数组里面的元素不在是多态对象 (而是指向多态对象的指针)。所以这里的指针运算不会有 undefined behavior

The elements in the arrays are no longer polymorphic objects (instead, they are pointers to polymorphic objects), and so there is no undefined behavior with the pointer arithmetic.

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

另外一种方法是使用标准模板库 (STL) 容器来替换数组。并且该方法的 f() 接收迭代器作为形参,如该合规方案。然而,因为 STL 容器需要同构元素,因此容器内依然需要指针。

Another approach is to use a standard template library (STL) container instead of an array and have f() accept iterators as parameters, as in this compliant solution. However, because STL containers require homogeneous elements, pointers are still required within the container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>

// ... definitions for S, T, globI, globD ...
template <typename Iter>
void f(Iter i, Iter e) {
for (; i != e; ++i) {
std::cout << (*i)->i << std::endl;
}
}

int main() {
std::vector<S *> test{new T, new T, new T, new T, new T};
f(test.cbegin(), test.cend());
for (auto v : test) {
delete v;
}
}

风险指针 Risk Assessment

使用多态类型地数组会造成内存崩溃,这会导致攻击者能够执行任意代码。

Using arrays polymorphically can result in memory corruption, which could lead to an attacker being able to execute arbitrary code.

Rule Severity Likelihood Remediation Cost Priority Level
CTR56-CPP High Likely High P9 L2

Automated Detection

Tool Version Checker Description
Axivion Bauhaus Suite img CertC++-CTR56
Parasoft C/C++test img CERT_CPP-CTR56-a CERT_CPP-CTR56-b CERT_CPP-CTR56-c Don’t treat arrays polymorphically A pointer to an array of derived class objects should not be converted to a base class pointer Do not treat arrays polymorphically
LDRA tool suite img 567 S** ** Enhanced Enforcement
PRQA QA-C++ img 3073
PVS-Studio img V777

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

SEI CERT C Coding Standard ARR39-C. Do not add or subtract a scaled integer to a pointer

Bibliography

[ISO/IEC 14882-2014] Subclause 5.7, “Additive Operators” Subclause 5.2.1, “Subscripting”
[Lockheed Martin 2005] AV Rule 96, “Arrays shall not be treated polymorphically”
[Meyers 1996] Item 3, “Never Treat Arrays Polymorphically”
[Stroustrup 2006] “What’s Wrong with Arrays?”
[Sutter 2004] Item 100, “Don’t Treat Arrays Polymorphically”

SEI CERT C++ Coding Standard > SEI CERT C++ Coding Standard > button_arrow_left.png SEI CERT C++ Coding Standard > SEI CERT C++ Coding Standard > button_arrow_up.png SEI CERT C++ Coding Standard > SEI CERT C++ Coding Standard > button_arrow_right.png