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 [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]

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.

Code Block
languagecpp
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)

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
bgColor#FFCCCC
langcpp
#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;
  

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

class Base {
public:
	virtual void func(void) {
		cout << "Base" << endl;
	}
};

class Derived : public Base {
public:
	int i;
	Derived() { i = 0; }

	void func(void) {
		cout << "Derived " << ++i << endl;
	}
};

void walk(class Base *bar, int count) {
	for (int i = 0; i < count; i++) {
		bar[i].func();
	}
}

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

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

Wiki Markup
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);
}

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
bgColor#FFCCCC
langcpp
#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. With the Base and Derived classes as above, we can define the walk and main methods as followsas in this compliant solution.

Code Block
bgColor#ccccff
langcpp
#include <iostream>

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

void walkf(classconst BaseS *bar [], int const *someSes, std::size_t count) { 
	  for (int iconst S * const *end = 0someSes + count; isomeSes <!= countend; i++someSes) {
		(bar[i])->func();
	    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  f(test, 35);
	walk(dat, 3);
}
  for (auto v : test) {
    delete 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 no undefined behavior with the array indexingpointer arithmetic.

Compliant Solution

...

(std::vector)

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.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
#include <iostream>
#include <vector>

// ... definitions for S, T, globI, globD ...
template <typename Iter>
void f(Iter i, Iter e) {

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<3e; i++i) dis[i] = new Base;

	vector<Base*> dat(3){
    std::cout << (*i)->i << std::endl;
  }
}

int main() {
  std::vector<S *> test{new forT, (int i=0; i<3; i++) dat[i] = new Derived;

	walk(dis);
	walk(dat);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

OBJ31

CTR56-

C

3 (high)

3 (likely)

1 (high)

CPP

High

Likely

High

P9

L2

References

Wiki Markup
\[[Sutter 04|AA. C References#Sutter 04]\] Item 100: Don't treat arrays polymorphically.

Wiki Markup
\[[Meyers 96|AA. C References#Meyers 96]\] Item 3: Never treat arrays polymorphically.

Wiki Markup
\[[Lockheed Martin 05|AA. C References#Lockheed Martin 05]\] AV Rule 96 Arrays shall not be treated polymorphically.

Automated Detection

Tool

Version

Checker

Description

Axivion Bauhaus Suite

Include Page
Axivion Bauhaus Suite_V
Axivion Bauhaus Suite_V

CertC++-CTR56
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V

LANG.STRUCT.PARITH

Pointer Arithmetic

Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C++3073
Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V

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
Include Page
LDRA_V
LDRA_V

567 S

Enhanced Enforcement

Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: CTR56-CPPChecks for pointer arithmetic on polymorphic object (rule fully covered)
PVS-Studio

Include Page
PVS-Studio_V
PVS-Studio_V

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"
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"


...

Image Added Image Added Image Added Wiki Markup\[[Stroustrup 06|AA. C References#Stroustrup 06]\] What's wrong with arrays?