Pointer arithmetic must be performed only on pointers that reference elements of array objects.
The C Standard, 6.5.6 [ISO/IEC 9899:2011], states the following about pointer arithmetic:
When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression.
Noncompliant Code Example
This noncompliant code example attempts to access structure members using pointer arithmetic. This practice is dangerous because structure members are not guaranteed to be contiguous.
struct numbers { short num_a, num_b, num_c; }; int sum_numbers(const struct numbers *numb){ int total = 0; const short *numb_ptr; for (numb_ptr = &numb->num_a; numb_ptr <= &numb->num_c; numb_ptr++) { total += *(numb_ptr); } return total; } int main(void) { struct numbers my_numbers = { 1, 2, 3 }; sum_numbers(&my_numbers); return 0; }
Compliant Solution
It is possible to use the ->
operator to dereference each structure member:
total = numb->num_a + numb->num_b + numb->num_c;
However, this solution results in code that is hard to write and hard to maintain (especially if there are many more structure members), which is exactly what the author of the noncompliant code example was likely trying to avoid.
Compliant Solution
A better solution is to define the structure to contain an array member to store the numbers in an array rather than a structure, as in this compliant solution:
#include <stddef.h> struct numbers { short a[3]; }; int sum_numbers(const short *numb, size_t dim) { int total = 0; for (size_t i = 0; i < dim; ++i) { total += numb[i]; } return total; } int main(void) { struct numbers my_numbers = { .a[0]= 1, .a[1]= 2, .a[2]= 3}; sum_numbers( my_numbers.a, sizeof(my_numbers.a)/sizeof(my_numbers.a[0]) ); return 0; }
Array elements are guaranteed to be contiguous in memory, so this solution is completely portable.
Exceptions
ARR37-C-EX1: Any non-array object in memory can be considered an array consisting of one element. Adding one to a pointer for such an object yields a pointer one element past the array, and subtracting one from that pointer yields the original pointer. This allows for code such as the following:
#include <stdlib.h> #include <string.h> struct s { char *c_str; /* Other members */ }; struct s *create_s(const char *c_str) { struct s *ret; size_t len = strlen(c_str) + 1; ret = (struct s *)malloc(sizeof(struct s) + len); if (ret != NULL) { ret->c_str = (char *)(ret + 1); memcpy(ret + 1, c_str, len); } return ret; }
A more general and safer solution to this problem is to use a flexible array member that guarantees the array that follows the structure is properly aligned by inserting padding, if necessary, between it and the member that immediately precedes it.
Risk Assessment
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ARR37-C | Medium | Probable | Medium | P8 | L2 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Astrée | 24.04 | Supported indirectly via MISRA C:2004 Rule 17.4. | |
Axivion Bauhaus Suite | 7.2.0 | CertC-ARR37 | Fully implemented |
CodeSonar | 8.1p0 | LANG.MEM.BO | Buffer Overrun |
Compass/ROSE | |||
Coverity | 2017.07 | ARRAY_VS_SINGLETON | Implemented |
Helix QAC | 2024.2 | DF2930, DF2931, DF2932, DF2933 C++3705, C++3706, C++3707 | |
Klocwork | 2024.2 | MISRA.PTR.ARITH.2012 | |
LDRA tool suite | 9.7.1 | 567 S | Partially implemented |
Parasoft C/C++test | 2023.1 | CERT_C-ARR37-a | Pointer arithmetic shall not be applied to pointers that address variables of non-array type |
PC-lint Plus | 1.4 | 2662 | Partially supported |
Polyspace Bug Finder | R2024a | CERT C: Rule ARR37-C | Checks for invalid assumptions about memory organization (rule partially covered) |
RuleChecker | 24.04 | Supported indirectly via MISRA C:2004 Rule 17.4. |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|
Bibliography
[Banahan 2003] | Section 5.3, "Pointers" Section 5.7, "Expressions Involving Pointers" |
[ISO/IEC 9899:2011] | 6.5.6, "Additive Operators" |
[VU#162289] |
21 Comments
David Svoboda
I believe it is technically valid for a pointer that points to an object to be incremented, eg:
However, one can argue that if one wishes to do arithmetic on a pointer, that pointer should point to an array (or be cast to point to an array). The above code example could have been rewritten:
As for ROSE checkability, one can merely highlight arithmetic to pointers that have not been initialized (or assigned) to point to an array.
Alex Volkovitsky
Really? What about...
b
was never initialized or assigned to point to an array, and yet the arithmetic is OK. What I'm trying to say is this involves tainting variables and flow control, which we know as being super tough under ROSE.David Svoboda
I'm not sure I would call flow control analysis 'super tough'...it is merely a harder level of problem than we have currently tried to solve. But I do think others have done flow control analysis using ROSE...
Your point is valid although the example is not (it tries to modify a string literal; we have a rule against that It's pretty easy to validate a, and consequently validate b. A better example might replace your second line with:
where we don't know that get_string() returns a valid array; it might return NULL, after all.
In that case, for now, we should probably 'give up', and either ignore the code sample, or issue a warning that 'this var might not point to a sufficient array'. This might be a good argument for adding a 'sensitivity' parameter to ROSE, that indicates how willing we are to tolerate false positives...we print the warning if the sensitivity parameter is sufficiently high.
Alex Volkovitsky
My example is not invalid...
arr[]
is an initialized array, not a string literal, so writing to it is OKAs for checking whether a variable points to an array, I could imagine backtracking up a chain of assignments until we see an Sg*AssignOp with an SgArrayType on the rhs? My biggest issue is, how do we differentiate cases where the backtrace fails due to complicated code/function calls/etc, and when it fails because the var actually does not point to an array.
To flag this, we need to identify cases where a variable does not point to an array, and doing so with any reasonable certainty seems impossible.
David Svoboda
After discussion with Alex, we decided that this rule is still implementable in ROSE (tho it may not be terribly useful). The method we use is:
If the pointer is being assigned the value of another pointer, do nothing.
We need to see some more code samples to determine just how feasible this is. It may not actually catch more than the NCCE.
Masaki Kubo
yozo found this.
in the non-compliant example, the for loop should be corrected as follows:
for (numb_ptr = &numb->num1;
numb_ptr <= &numb->num9;
numb_ptr++)
Robert Seacord
Thanks, I've made this change.
Andrew Keeton
Should the title be "Do not add or subtract an integer to a pointer to an element of a non-array object?" But then does it make sense to say that a non-array object has "elements?" Maybe the title should be "Only perform pointer arithmetic on a pointer to an array element."
David Svoboda
I think the best title depends on whether you prefer technical accuracy or simplicity in your titles. In this case I vote for simplicity: "Only perform pointer arithmetic on pointers that point into arrays".
Pointer arithmetic is explained in full technical detail in C99 Section 6.5.6, paragraphs 7, 8, 9, and it really can't be expressed simply w/o sacrificing technical accuracy. The body of the rule should provide the accuracy and the title should provide the simplicity IMO.
My suggested title is technically good, but overly restrictive. The exceptions (which should prob go into the rule) are:
That last item also applies to the last item in an array. But the rule already explains that.
Comments?
Martin Sebor
I'm having trouble with this rule. According to the definition of valid pointer (quoted below), this standard doesn't distinguish between a standalone object and an element of an array of size 1.
Thus, this rule (and indeed the only non-compliant code example provided in it) is just a special case of ARR38-C. Do not add or subtract an integer to a pointer if the resulting value does not refer to a valid array element combined with ARR30-C. Guarantee that array indices are within the valid range.
In addition, not only is it perfectly valid to add or subtract 0 from any valid pointer (as it is valid to add or subtract 0 from the null pointer), but it is also valid to add 1 to a pointer to any object (with the result still being a valid pointer).
Robert Seacord
Well, I'm on the fence, put I'll argue for keeping this just to ensure we've considered this decision.
C99 states:
This looks like the same thing you quoted, but I believe this says that "For purposes of these operators" a scalar is the same as an array of one value. It doesn't say that these two things can't be differentiated for some purpose.
The intent of this guideline is to further restrict (or subset) the standard. We certainly want to be very careful in doing something like this, but I can't think of use case where it makes sense to perform arithmetic with a pointer to anything other than an array (I'm pretty sure the effective type of the object referenced by char *c = malloc(100) is array of char)
You could make an argument that this guideline is covered by the other guidelines you referenced, particularly ARR38-C, but this guideline I think makes it clear that arithmetic on non-array pointers is disallowed. The other solution would be to add this as a NCE/CS pair to ARR38-C but it might get lost a little because that guideline seems to be more about creating a pointer that lies outside the bounds of an array.
David Svoboda
Seconded, I think this rule is worthwhile. We do reserve the 'right' to prohibit things that are valid C99, if we think they still indicate security flaws. So disallowing pointer arithmetic on non-arrays seems a reasonable restriction, esp. if no one can cite a legitimate reason for not doing so.
I can think of two borderline cases. I don't think either invalidates this rule, but they should both be considered:
Martin Sebor
Here's a common use case of treating a non-array object as an array of one element which is effectively prohibited by this rule even though it is safe and doesn't violate either VOID Do not add or subtract an integer to a pointer if the resulting value does not refer to a valid array element or ARR30-C. Do not form or use out of bounds pointers or array subscripts.
You're right that C99 allows every non-bit field object to be treated as an array of bytes (
unsigned char
). This is discussed in STR00-C. Represent characters using an appropriate type.Martin Sebor
If the decision is to retain this rule I believe that, at minimum, an exception should be added to make it possible to treat an object as an array of
unsigned char
, otherwise there's would be no way to implement user-defined variants ofmemset()
ormemcpy()
without violating this standard.But I do still think this rule is overly restrictive and the decision to prohibit treating non-array objects of any type equivalently to arrays of a single element should be reconsidered. To avoid violating the rule, programmers whose code is subject to this standard would have to define all objects that might be potentially treated as arrays as arrays of size 1. That's an infeasible requirement in systems consisting of libraries or modules whose public APIs expose individual objects.
Aaron Ballman
One common use case for pointer arithmetic is for allocation locality:
I think the rule is overly restrictive as it disallows this sort of usage because
struct s
is not being used as an array. Also, as Martin points out further down, usage ofunsigned char *
should probably be specifically allowed, otherwisememset()
becomes impossible to write.David Svoboda
This rule needs an exception that every object in memory that is not part of an array can be considered an array of 1 element. So adding 1 to such a pointer puts it one past the array, and subtracting it puts it back at the object again. All valid by C11.
Jordan Brown
This rule needs a reference to offsetof(), which is a valid way to do pointer arithmetic inside a structure.
Given:
it is valid to do
This tends to be most interesting when the reference is done by code that isn't familiar with the insides of struct foo, or when it's desirable to do some kind of table-driven walk of the elements of the structure.
Robert Seacord (Manager)
The offsetof
macro is used in the related rule ARR39-C. Do not add or subtract a scaled integer to a pointer. I think your main concern of how to use pointer arithmetic within a struct is covered in ARR37-EX1. This example could have used theoffsetof
macro, but it is not necessary that it does. I think the other rule does a good job of showing the issues surrounding the use of this macro.Jordan Brown
This rule seems to say that there's no valid way to do pointer arithmetic to walk around inside a structure. My point is that offsetof() supplies such a way, and so should be listed as an exception. It must be used with care, as ARR39-C shows, but it's a valid and portable mechanism if used correctly. (Well, unless one decides that almost all pointer arithmetic and casting is too dangerous and error-prone, but personally I think that's too high a cost.)
Robert Seacord
The exception ARR37-EX1 provides a way to perform pointer arithmetic within a structure by observing that "All non-array objects in memory can be considered an array of one element." The example then shows addition to a struct pointer.
Aaron Ballman
As best I can tell, you and Jordan are both approaching the same concept from different angles. So this is me taking a stab at interpreting...
I think Jordan is discussing the fact that the title says "Do not add or subtract an integer to a pointer to a non-array object", and yet you can clearly use
offsetof()
to gain the offset of an element within a structure object.I think Robert is discussing the fact that once you have that offset within the structure, the only safe way to use it for pointer arithmetic is to treat the object as an array of
unsigned char
so that you do not get pointer scaling when performing the arithmetic.So you're both right, but I agree with Robert's assessment that a discussion of
offsetof()
doesn't really belong in this rule except perhaps as an NCCE.