The definition of pointer arithmetic from The the C++ Standard, [expr.add], paragraph 7, states 7 [ISO/IEC 14882-2014], states the following:
For addition or subtraction, if the expressions
P
orQ
have type “pointer to cvT
”, whereT
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]
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++ StardardStandard, [expr.sub], paragraph 11 [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.
Noncompliant Code Example
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
loopThe following code examples assume the following static variables and class definitions.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iostream> int GlobIglobI; double GlobDglobD; struct S { int Ii; S() : Ii(GlobIglobI++) {} }; struct T : S { double Dd; T() : S(), Dd(GlobDglobD++) {} }; |
Noncompliant Code Example (Pointer Arithmetic)
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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iostream> // ... definitions for S, T, globI, globD ... void f(const S *SomeSessomeSes, std::size_t Countcount) { for (const S *Endend = SomeSessomeSes + Countcount; SomeSessomeSes != Endend; ++SomeSessomeSes) { std::cout << SomeSessomeSes->I>i << std::endl; } } int main() { T Testtest[5]; f(Testtest, 5); } |
This example would still be noncompliant if the for
loop had instead been written to use array subscripting, like:
Noncompliant Code Example (Array Subscripting)
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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iostream>
// ... definitions for S, T, globI, globD ...
void f(const S *someSes, | ||||
Code Block | ||||
for (std::size_t count) { for (std::size_t i = 0; i < Countcount; ++i) { std::cout << SomeSessomeSes[i].Ii << 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:.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iostream> int GlobI; double GlobD; struct S { int I; S() : I(GlobI++) {} }; struct T : S { double D; T() : S(), D(GlobD++) {} }; // ... definitions for S, T, globI, globD ... void f(const S * const *SomeSessomeSes, std::size_t Countcount) { for (const S * const *Endend = SomeSessomeSes + Countcount; SomeSessomeSes != Endend; ++SomeSessomeSes) { std::cout << (*SomeSessomeSes)->I>i << std::endl; } } int main() { S *Testtest[] = {new T, new T, new T, new T, new T}; f(Testtest, 5); for (auto Vv : Testtest) { delete Vv; } } |
The elements in the arrays are no longer polymorphic objects (instead, they are pointers to polymorphic objects), and so there is no no undefined behavior with the pointer arithmetic.
...
Another approach is to use an a standard template library (STL) container instead of an array and have f()
accept iterators as parameters, as in this compliant solution. However, since because STL containers require homogeneous elements, pointers are still required within the container.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iostream> #include <vector> int GlobI; double GlobD; struct S { int I; S() : I(GlobI++) {} }; struct T : S { double D; T() : S(), D(GlobD++) {} }; // ... definitions for S, T, globI, globD ... template <typename Iter> void f(Iter Ii, Iter Ee) { for (; Ii != Ee; ++Ii) { std::cout << (*Ii)->I>i << std::endl; } } int main() { std::vector<S *> Testtest{new T, new T, new T, new T, new T}; f(Testtest.cbegin(), Testtest.cend()); for (auto Vv : Testtest) { delete Vv; } } |
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 |
| CertC++-CTR56 | |||||||
CodeSonar |
| LANG.STRUCT.PARITH | Pointer Arithmetic | ||||||
Helix QAC |
|
C++ |
3073 | |
Parasoft C/ |
C++test |
|
| CERT_CPP-CTR56-a | Don't treat arrays polymorphically | |||||||
LDRA tool suite |
| 567 S | Enhanced Enforcement | ||||||
Polyspace Bug Finder |
| CERT C++: CTR56-CPP | Checks for pointer arithmetic on polymorphic object (rule fully covered) | ||||||
PVS-Studio |
| V777 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Bibliography
[ISO/IEC 14882-2014] | Subclause 5.7, "Additive Operators" |
[ |
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" |
...