Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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

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]

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++ Stardard, [expr.sub], paragraph 1, defines array subscripting as being identical to pointer arithmetic. Specifically, it states:

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 loop.

Code Block

Because pointer arithmetic does not take account of polymorphism, a major problem with arrays is that they do not interact well with polymorphism (see Stroustrup 06, Meyers 06), as the following example illustrates:

Non-Compliant Code Example

Code Block
bgColor#FFCCCC
langcpp
class Base {
public:
	virtual void func(void) {
		cout << "Base" << endl;
	#include <iostream>

int GlobI;
double GlobD;

struct S {
  int I;
  
  S() : I(GlobI++) {}
};

classstruct DerivedT : public BaseS {
public:
	int i;
	Derived() { idouble = 0; }

	void func(void) {
		cout << "Derived " << ++i << endl;
	D;
  
  T() : S(), D(GlobD++) {}
};

void walkf(classconst BaseS *barSomeSes, int countstd::size_t Count) { 
	  for (int iconst S *End = SomeSes 0+ Count; iSomeSes <!= countEnd; i++SomeSes) {
		bar[i].func();
	    std::cout << SomeSes->I << std::endl;
  }
}

int main(void) {
	Base dis[3  T Test[5];
	Derived dat[3];

	walk(dis, 3);
	walk(dat, 3); // crashes
}

In the last call to walk(), dat[] is treated as a Base[] and the subscripting no longer works correctly when sizeof(Derived) != sizeof(Base). This is because walk() incorrectly believes that the size of each element in bar[] is sizeof(Base). To locate the second element in the array (located at bar[1]), walk() adds the sizeof(Base) to the address bar. Assuming the derived object is larger (which is often the case), the resulting pointer refers to a point within the first element and not to the start of the second element located at bar + sizeof(Derived).

 f(Test, 5);
}

This example would still be noncompliant if the for loop had instead been written to use array subscripting, like:

Code Block
for (std::size_t i = 0; i < Count; ++i) {
  std::cout << SomeSes[i].I << std::endl;
}

Compliant Solution (Array)

...

Instead of having an array of objects, an array of pointers solves the problem of the objects being of different sizes. With the Base and Derived classes as in the Non-Compliant Code Example, we can define the walk and main methods as follows., as in this compliant solution:

Code Block
bgColor#ccccff
langcpp
void walk(class Base *bar [], int count) {
	for (int i = 0; i < count; i++) {
		(bar[i])->func();
	#include <iostream>

int GlobI;
double GlobD;

struct S {
  int I;
  
  S() : I(GlobI++) {}
};

struct T : S {
  double D;
  
  T() : S(), D(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(void) {
	Base* dis[3  S *Test[] = {new BaseT, new BaseT, new Base};
	Base* dat[3] = {new DerivedT, new DerivedT, new DerivedT};

	walk(dis, 3);
	walk(dat, 3  f(Test, 5);

  for (intauto iV = 0; i < 3; i++: Test) {
    delete dis[i];
    delete dat[i]V;
  }
}

The elements in the arrays are now all the same size (because no longer polymorphic objects (instead, they are pointers to Base or Derived polymorphic objects), and so there is no problem undefined behavior with the array indexingpointer arithmetic.

Compliant Solution

...

(std::vector)

Another approach is to use an STL container instead of an array and have f() accept iterators as parameters, as in this compliant solution. However, since STL containers require homogeneous elements, pointers are still required within the container.A better approach would be to use vectors and iterators, instead of arrays, as follows. (Note, however, that we have to have vectors of pointers because containers must be homogeneous.)

Code Block
bgColor#ccccff
langcpp
void walk(vector<Base*>bar) {
	for_each (bar.begin(), bar.end(), mem_fun(&Base::func));
}

int main(void) {
	vector<Base*> dis(3);
        for (int i=0; i<3; i++) dis[i] = new Base;

	vector<Base*> dat(3);
        for (int i=0; i<3; i++) dat[i] = new Derived;

	walk(dis);
	walk(dat);

  for (int i = 0; i < 3; i++) {
    delete dis[i];#include <iostream>
#include <vector>

int GlobI;
double GlobD;

struct S {
  int I;
  
  S() : I(GlobI++) {}
};

struct T : S {
  double D;
  
  T() : S(), D(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 dat[i]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

ARR39CTR39-CPP

highHigh

likelyLikely

highHigh

P9

L2

Automated Detection

 

Tool

Version

Checker

Description

 PRQA QA-C++
Include Page
PRQA QA-C++_v
PRQA QA-C++_v
3072,3073 

Related Vulnerabilities

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

Related Guidelines

Bibliography

[Sutter 04] Item 100: Don't treat arrays polymorphically.

Bibliography

...

[ISO/IEC 14882-2014]

5.7, "Additive Operators"
5.2.1, "Subscripting" 

[Stroustrup 06]What's wrong with arrays?
[Meyers 06]
Item 3: Never treat arrays polymorphically

...

[Lockheed Martin 05]AV Rule 96 Arrays shall not be treated polymorphically.
[Sutter 04]Item 100: Don't treat arrays polymorphically

 Stroustrup 06] What's wrong with arrays?