Skip to content

Commit 200c125

Browse files
authored
Merge pull request #274 from kraiouchkine/Memory3
Implement Memory3 and InvalidMemory2 packages
2 parents 07b543b + b42eeb2 commit 200c125

27 files changed

+1838
-1
lines changed

.vscode/tasks.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@
242242
"Macros",
243243
"Memory1",
244244
"Memory2",
245+
"Memory3",
245246
"Misc",
246247
"MoveForward",
247248
"Naming",
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# ARR32-C: Ensure size arguments for variable length arrays are in a valid range
2+
3+
This query implements the CERT-C rule ARR32-C:
4+
5+
> Ensure size arguments for variable length arrays are in a valid range
6+
7+
8+
## Description
9+
10+
Variable length arrays (VLAs), a conditionally supported language feature, are essentially the same as traditional C arrays except that they are declared with a size that is not a constant integer expression and can be declared only at block scope or function prototype scope and no linkage. When supported, a variable length array can be declared
11+
12+
```cpp
13+
{ /* Block scope */
14+
char vla[size];
15+
}
16+
17+
```
18+
where the integer expression `size` and the declaration of `vla` are both evaluated at runtime. If the size argument supplied to a variable length array is not a positive integer value, the behavior is undefined. (See [undefined behavior 75](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_75).) Additionally, if the magnitude of the argument is excessive, the program may behave in an unexpected way. An attacker may be able to leverage this behavior to overwrite critical program data \[[Griffiths 2006](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Griffiths06)\]. The programmer must ensure that size arguments to variable length arrays, especially those derived from untrusted data, are in a valid range.
19+
20+
Because variable length arrays are a conditionally supported feature of C11, their use in portable code should be guarded by testing the value of the macro `__STDC_NO_VLA__`. Implementations that do not support variable length arrays indicate it by setting `__STDC_NO_VLA__` to the integer constant 1.
21+
22+
## Noncompliant Code Example
23+
24+
In this noncompliant code example, a variable length array of size `size` is declared. The `size` is declared as `size_t` in compliance with [INT01-C. Use rsize_t or size_t for all integer values representing the size of an object](https://wiki.sei.cmu.edu/confluence/display/c/INT01-C.+Use+rsize_t+or+size_t+for+all+integer+values+representing+the+size+of+an+object).
25+
26+
```cpp
27+
#include <stddef.h>
28+
29+
extern void do_work(int *array, size_t size);
30+
31+
void func(size_t size) {
32+
int vla[size];
33+
do_work(vla, size);
34+
}
35+
36+
```
37+
However, the value of `size` may be zero or excessive, potentially giving rise to a security [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability).
38+
39+
## Compliant Solution
40+
41+
This compliant solution ensures the `size` argument used to allocate `vla` is in a valid range (between 1 and a programmer-defined maximum); otherwise, it uses an algorithm that relies on dynamic memory allocation. The solution also avoids unsigned integer wrapping that, given a sufficiently large value of `size`, would cause `malloc` to allocate insufficient storage for the array.
42+
43+
```cpp
44+
#include <stdint.h>
45+
#include <stdlib.h>
46+
47+
enum { MAX_ARRAY = 1024 };
48+
extern void do_work(int *array, size_t size);
49+
50+
void func(size_t size) {
51+
if (0 == size || SIZE_MAX / sizeof(int) < size) {
52+
/* Handle error */
53+
return;
54+
}
55+
if (size < MAX_ARRAY) {
56+
int vla[size];
57+
do_work(vla, size);
58+
} else {
59+
int *array = (int *)malloc(size * sizeof(int));
60+
if (array == NULL) {
61+
/* Handle error */
62+
}
63+
do_work(array, size);
64+
free(array);
65+
}
66+
}
67+
68+
```
69+
70+
## Noncompliant Code Example (sizeof)
71+
72+
The following noncompliant code example defines `A` to be a variable length array and then uses the `sizeof` operator to compute its size at runtime. When the function is called with an argument greater than `SIZE_MAX / (N1 * sizeof (int))`, the runtime `sizeof` expression may wrap around, yielding a result that is smaller than the mathematical product `N1 * n2 * sizeof (int)`. The call to `malloc()`, when successful, will then allocate storage for fewer than `n2` elements of the array, causing one or more of the final `memset()` calls in the `for` loop to write past the end of that storage.
73+
74+
```cpp
75+
#include <stdlib.h>
76+
#include <string.h>
77+
78+
enum { N1 = 4096 };
79+
80+
void *func(size_t n2) {
81+
typedef int A[n2][N1];
82+
83+
A *array = malloc(sizeof(A));
84+
if (!array) {
85+
/* Handle error */
86+
return NULL;
87+
}
88+
89+
for (size_t i = 0; i != n2; ++i) {
90+
memset(array[i], 0, N1 * sizeof(int));
91+
}
92+
93+
return array;
94+
}
95+
96+
```
97+
Furthermore, this code also violates [ARR39-C. Do not add or subtract a scaled integer to a pointer](https://wiki.sei.cmu.edu/confluence/display/c/ARR39-C.+Do+not+add+or+subtract+a+scaled+integer+to+a+pointer), where `array` is a pointer to the two-dimensional array, where it should really be a pointer to the latter dimension instead. This means that the `memset() `call does out-of-bounds writes on all of its invocations except the first.
98+
99+
## Compliant Solution (sizeof)
100+
101+
This compliant solution prevents `sizeof` wrapping by detecting the condition before it occurs and avoiding the subsequent computation when the condition is detected. The code also uses an additional typedef to fix the type of `array` so that `memset()` never writes past the two-dimensional array.
102+
103+
```cpp
104+
#include <stdint.h>
105+
#include <stdlib.h>
106+
#include <string.h>
107+
108+
enum { N1 = 4096 };
109+
110+
void *func(size_t n2) {
111+
if (n2 > SIZE_MAX / (N1 * sizeof(int))) {
112+
/* Prevent sizeof wrapping */
113+
return NULL;
114+
}
115+
116+
typedef int A1[N1];
117+
typedef A1 A[n2];
118+
119+
A1 *array = (A1*) malloc(sizeof(A));
120+
121+
if (!array) {
122+
/* Handle error */
123+
return NULL;
124+
}
125+
126+
for (size_t i = 0; i != n2; ++i) {
127+
memset(array[i], 0, N1 * sizeof(int));
128+
}
129+
return array;
130+
}
131+
132+
```
133+
**Implementation Details**
134+
135+
**Microsoft**
136+
137+
Variable length arrays are not supported by Microsoft compilers.
138+
139+
## Risk Assessment
140+
141+
Failure to properly specify the size of a variable length array may allow arbitrary code execution or result in stack exhaustion.
142+
143+
<table> <tbody> <tr> <th> Rule </th> <th> Severity </th> <th> Likelihood </th> <th> Remediation Cost </th> <th> Priority </th> <th> Level </th> </tr> <tr> <td> ARR32-C </td> <td> High </td> <td> Probable </td> <td> High </td> <td> <strong>P6</strong> </td> <td> <strong>L2</strong> </td> </tr> </tbody> </table>
144+
145+
146+
## Automated Detection
147+
148+
<table> <tbody> <tr> <th> Tool </th> <th> Version </th> <th> Checker </th> <th> Description </th> </tr> <tr> <td> <a> CodeSonar </a> </td> <td> 7.2p0 </td> <td> <strong>ALLOC.SIZE.IOFLOWALLOC.SIZE.MULOFLOWMISC.MEM.SIZE.BAD</strong> </td> <td> Integer Overflow of Allocation Size Multiplication Overflow of Allocation Size Unreasonable Size Argument </td> </tr> <tr> <td> <a> Coverity </a> </td> <td> 2017.07 </td> <td> <strong>REVERSE_NEGATIVE</strong> </td> <td> Fully implemented </td> </tr> <tr> <td> <a> Helix QAC </a> </td> <td> 2022.4 </td> <td> <strong>C1051</strong> </td> <td> </td> </tr> <tr> <td> <a> Klocwork </a> </td> <td> 2022.4 </td> <td> <strong>MISRA.ARRAY.VAR_LENGTH.2012</strong> </td> <td> </td> </tr> <tr> <td> <a> LDRA tool suite </a> </td> <td> 9.7.1 </td> <td> <strong>621 S</strong> </td> <td> Enhanced enforcement </td> </tr> <tr> <td> <a> Parasoft C/C++test </a> </td> <td> 2022.2 </td> <td> <strong>CERT_C-ARR32-a</strong> </td> <td> Ensure the size of the variable length array is in valid range </td> </tr> <tr> <td> <a> PC-lint Plus </a> </td> <td> 1.4 </td> <td> <strong>9035</strong> </td> <td> Assistance provided </td> </tr> <tr> <td> <a> Polyspace Bug Finder </a> </td> <td> R2023a </td> <td> <a> CERT C: Rule ARR32-C </a> </td> <td> Checks for: Memory allocation with tainted sizeemory allocation with tainted size, tainted size of variable length arrayainted size of variable length array. Rule fully covered. </td> </tr> <tr> <td> <a> PRQA QA-C </a> </td> <td> 9.7 </td> <td> <strong>1051</strong> </td> <td> Partially implemented </td> </tr> <tr> <td> <a> Cppcheck </a> </td> <td> 1.66 </td> <td> <strong>negativeArraySize</strong> </td> <td> Context sensitive analysis Will warn only if given size is negative </td> </tr> <tr> <td> <a> TrustInSoft Analyzer </a> </td> <td> 1.38 </td> <td> <strong>alloca_bounds</strong> </td> <td> Exhaustively verified. </td> </tr> </tbody> </table>
149+
150+
151+
## Related Vulnerabilities
152+
153+
Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ARR32-C).
154+
155+
## Related Guidelines
156+
157+
[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
158+
159+
<table> <tbody> <tr> <th> Taxonomy </th> <th> Taxonomy item </th> <th> Relationship </th> </tr> <tr> <td> <a> CERT C Secure Coding Standard </a> </td> <td> <a> INT01-C. Use rsize_t or size_t for all integer values representing the size of an object </a> </td> <td> Prior to 2018-01-12: CERT: Unspecified Relationship </td> </tr> <tr> <td> <a> ISO/IEC TR 24772:2013 </a> </td> <td> Unchecked Array Indexing \[XYZ\] </td> <td> Prior to 2018-01-12: CERT: Unspecified Relationship </td> </tr> <tr> <td> <a> ISO/IEC TS 17961:2013 </a> </td> <td> Tainted, potentially mutilated, or out-of-domain integer values are used in a restricted sink \[taintsink\] </td> <td> Prior to 2018-01-12: CERT: Unspecified Relationship </td> </tr> <tr> <td> <a> CWE 2.11 </a> </td> <td> <a> CWE-758 </a> </td> <td> 2017-06-29: CERT: Rule subset of CWE </td> </tr> </tbody> </table>
160+
161+
162+
## CERT-CWE Mapping Notes
163+
164+
[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
165+
166+
**CWE-129 and ARR32-C**
167+
168+
Intersection( CWE-188, EXP39-C) = Ø
169+
170+
ARR32-C addresses specifying the size of a variable-length array (VLA). CWE-129 addresses invalid array indices, not array sizes.
171+
172+
**CWE-758 and ARR32-C**
173+
174+
Independent( INT34-C, INT36-C, MSC37-C, FLP32-C, EXP33-C, EXP30-C, ERR34-C, ARR32-C)
175+
176+
CWE-758 = Union( ARR32-C, list) where list =
177+
178+
* Undefined behavior that results from anything other than too large a VLA dimension.
179+
**CWE-119 and ARR32-C**
180+
* Intersection( CWE-119, ARR32-C) = Ø
181+
* ARR32-C is not about providing a valid buffer but reading/writing outside it. It is about providing an invalid buffer, or one that exhausts the stack.
182+
183+
## Bibliography
184+
185+
<table> <tbody> <tr> <td> \[ <a> Griffiths 2006 </a> \] </td> </tr> </tbody> </table>
186+
187+
188+
## Implementation notes
189+
190+
None
191+
192+
## References
193+
194+
* CERT-C: [ARR32-C: Ensure size arguments for variable length arrays are in a valid range](https://wiki.sei.cmu.edu/confluence/display/c)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* @id c/cert/variable-length-array-size-not-in-valid-range
3+
* @name ARR32-C: Ensure size arguments for variable length arrays are in a valid range
4+
* @description A variable-length array size that is zero, negative, overflowed, wrapped around, or
5+
* excessively large may lead to undefined behaviour.
6+
* @kind problem
7+
* @precision high
8+
* @problem.severity error
9+
* @tags external/cert/id/arr32-c
10+
* correctness
11+
* security
12+
* external/cert/obligation/rule
13+
*/
14+
15+
import cpp
16+
import codingstandards.c.cert
17+
import codingstandards.cpp.Overflow
18+
19+
/**
20+
* Gets the maximum size (in bytes) a variable-length array
21+
* should be to not be deemed excessively large persuant to this rule.
22+
* This value has been arbitrarily chosen to be 2^16 - 1 bytes.
23+
*/
24+
private int maximumTotalVlaSize() { result = 65535 }
25+
26+
/**
27+
* Gets the base type of a pointer or array type. In the case of an array of
28+
* arrays, the inner base type is returned.
29+
*
30+
* Copied from IncorrectPointerScalingCommon.qll.
31+
*/
32+
private Type baseType(Type t) {
33+
(
34+
exists(PointerType dt |
35+
dt = t.getUnspecifiedType() and
36+
result = dt.getBaseType().getUnspecifiedType()
37+
)
38+
or
39+
exists(ArrayType at |
40+
at = t.getUnspecifiedType() and
41+
not at.getBaseType().getUnspecifiedType() instanceof ArrayType and
42+
result = at.getBaseType().getUnspecifiedType()
43+
)
44+
or
45+
exists(ArrayType at, ArrayType at2 |
46+
at = t.getUnspecifiedType() and
47+
at2 = at.getBaseType().getUnspecifiedType() and
48+
result = baseType(at2)
49+
)
50+
) and
51+
// Make sure that the type has a size and that it isn't ambiguous.
52+
strictcount(result.getSize()) = 1
53+
}
54+
55+
/**
56+
* The `SimpleRangeAnalysis` analysis over-zealously expands upper bounds of
57+
* `SubExpr`s to account for potential wrapping even when no wrapping can occur.
58+
*
59+
* This class represents a `SubExpr` that is safe from wrapping.
60+
*/
61+
class SafeSubExprWithErroneouslyWrappedUpperBound extends SubExpr {
62+
SafeSubExprWithErroneouslyWrappedUpperBound() {
63+
lowerBound(this.getLeftOperand().getFullyConverted()) -
64+
upperBound(this.getRightOperand().getFullyConverted()) >= 0 and
65+
upperBound(this.getFullyConverted()) = exprMaxVal(this.getFullyConverted())
66+
}
67+
68+
/**
69+
* Gets the lower bound of the difference.
70+
*/
71+
float getlowerBoundOfDifference() {
72+
result =
73+
lowerBound(this.getLeftOperand().getFullyConverted()) -
74+
upperBound(this.getRightOperand().getFullyConverted())
75+
}
76+
}
77+
78+
/**
79+
* Holds if `e` is an expression that is not in a valid range due to it
80+
* being partially or fully derived from an overflowing arithmetic operation.
81+
*/
82+
predicate isExprTaintedByOverflowingExpr(Expr e) {
83+
exists(InterestingOverflowingOperation bop |
84+
// `bop` is not pre-checked to prevent overflow/wrapping
85+
not bop.hasValidPreCheck() and
86+
// and the destination is tainted by `bop`
87+
TaintTracking::localExprTaint(bop, e.getAChild*()) and
88+
// and there does not exist a post-wrapping-check before `e`
89+
not exists(GuardCondition gc |
90+
gc = bop.getAValidPostCheck() and
91+
gc.controls(e.getBasicBlock(), _)
92+
)
93+
)
94+
}
95+
96+
predicate getVlaSizeExprBounds(Expr e, float lower, float upper) {
97+
lower = lowerBound(e) and
98+
upper =
99+
// upper is the smallest of either a `SubExpr` which flows to `e` and does
100+
// not wrap, or the upper bound of `e` derived from the range-analysis library
101+
min(float f |
102+
f =
103+
any(SafeSubExprWithErroneouslyWrappedUpperBound sub |
104+
DataFlow::localExprFlow(sub, e)
105+
|
106+
sub.getlowerBoundOfDifference()
107+
) or
108+
f = upperBound(e)
109+
)
110+
}
111+
112+
/**
113+
* Holds if `e` is not bounded to a valid range, (0 .. maximumTotalVlaSize()], for
114+
* a element count of an individual variable-length array dimension.
115+
*/
116+
predicate isVlaSizeExprOutOfRange(VlaDeclStmt vla, Expr e) {
117+
vla.getVlaDimensionStmt(_).getDimensionExpr() = e and
118+
exists(float lower, float upper |
119+
getVlaSizeExprBounds(e.getFullyConverted(), lower, upper) and
120+
(
121+
lower <= 0
122+
or
123+
upper > maximumTotalVlaSize() / baseType(vla.getVariable().getType()).getSize()
124+
)
125+
)
126+
}
127+
128+
/**
129+
* Returns the upper bound of `e.getFullyConverted()`.
130+
*/
131+
float getVlaSizeExprUpperBound(Expr e) { getVlaSizeExprBounds(e.getFullyConverted(), _, result) }
132+
133+
/**
134+
* Returns the upper bound of `vla`'s dimension expression at `index`.
135+
*
136+
* If `index` does not exist, then the result is `1`.
137+
*/
138+
bindingset[index]
139+
private float getVlaSizeExprUpperBoundAtIndexOrOne(VlaDeclStmt vla, float index) {
140+
if vla.getNumberOfVlaDimensionStmts() > index
141+
then result = getVlaSizeExprUpperBound(vla.getVlaDimensionStmt(index).getDimensionExpr())
142+
else result = 1
143+
}
144+
145+
predicate vlaupper = getVlaSizeExprUpperBoundAtIndexOrOne/2;
146+
147+
/**
148+
* Gets the upper bound of the total size of `vla`.
149+
*/
150+
float getTotalVlaSizeUpperBound(VlaDeclStmt vla) {
151+
result =
152+
vlaupper(vla, 0) * vlaupper(vla, 1) * vlaupper(vla, 2) * vlaupper(vla, 3) * vlaupper(vla, 4) *
153+
vlaupper(vla, 5) * vlaupper(vla, 6) * vlaupper(vla, 7) * vlaupper(vla, 8) * vlaupper(vla, 9)
154+
}
155+
156+
from VlaDeclStmt vla, string message
157+
where
158+
not isExcluded(vla, InvalidMemory2Package::variableLengthArraySizeNotInValidRangeQuery()) and
159+
(
160+
if isExprTaintedByOverflowingExpr(vla.getVlaDimensionStmt(_).getDimensionExpr())
161+
then message = "Variable-length array size derives from an overflowing or wrapping expression."
162+
else (
163+
if isVlaSizeExprOutOfRange(vla, vla.getVlaDimensionStmt(_).getDimensionExpr())
164+
then message = "Variable-length array dimension size may be in an invalid range."
165+
else (
166+
getTotalVlaSizeUpperBound(vla) > maximumTotalVlaSize() and
167+
message = "Variable-length array total size may be excessively large."
168+
)
169+
)
170+
)
171+
select vla, message

0 commit comments

Comments
 (0)