Skip to content

Commit a856bc8

Browse files
authored
Merge pull request #10562 from rdmarsh2/rdmarsh2/cpp/field-off-by-one
C++: prototype for off-by-one in array-typed field
2 parents 5b67ba2 + 32d0b58 commit a856bc8

File tree

6 files changed

+271
-0
lines changed

6 files changed

+271
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#define MAX_SIZE 1024
2+
3+
struct FixedArray {
4+
int buf[MAX_SIZE];
5+
};
6+
7+
int main(){
8+
FixedArray arr;
9+
10+
for(int i = 0; i <= MAX_SIZE; i++) {
11+
arr.buf[i] = 0; // BAD
12+
}
13+
14+
for(int i = 0; i < MAX_SIZE; i++) {
15+
arr.buf[i] = 0; // GOOD
16+
}
17+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The program performs an out-of-bounds read or write operation. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
7+
8+
</overview>
9+
<recommendation>
10+
11+
<p>Ensure that pointer dereferences are properly guarded to ensure that they cannot be used to read or write past the end of the allocation.</p>
12+
13+
</recommendation>
14+
<example>
15+
<p>The first example uses a for loop which is improperly bounded by a non-strict less-than operation and will write one position past the end of the array. The second example bounds the for loop properly with a strict less-than operation.</p>
16+
<sample src="ConstantSizeArrayOffByOne.cpp" />
17+
18+
</example>
19+
<references>
20+
21+
<li>CERT C Coding Standard:
22+
<a href="https://wiki.sei.cmu.edu/confluence/display/c/ARR30-C.+Do+not+form+or+use+out-of-bounds+pointers+or+array+subscripts">ARR30-C. Do not form or use out-of-bounds pointers or array subscripts</a>.</li>
23+
<li>
24+
OWASP:
25+
<a href="https://owasp.org/www-community/vulnerabilities/Buffer_Overflow">Buffer Overflow</a>.
26+
</li>
27+
28+
</references>
29+
</qhelp>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @name Constant array overflow
3+
* @description Dereferencing a pointer that points past a statically-sized array is undefined behavior
4+
* and may lead to security vulnerabilities
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @id cpp/constant-array-overflow
8+
* @tags reliability
9+
* security
10+
*/
11+
12+
import experimental.semmle.code.cpp.semantic.analysis.RangeAnalysis
13+
import experimental.semmle.code.cpp.semantic.SemanticBound
14+
import experimental.semmle.code.cpp.semantic.SemanticExprSpecific
15+
import semmle.code.cpp.ir.IR
16+
import experimental.semmle.code.cpp.ir.dataflow.DataFlow
17+
import experimental.semmle.code.cpp.ir.dataflow.DataFlow2
18+
import DataFlow2::PathGraph
19+
20+
pragma[nomagic]
21+
Instruction getABoundIn(SemBound b, IRFunction func) {
22+
result = b.getExpr(0) and
23+
result.getEnclosingIRFunction() = func
24+
}
25+
26+
/**
27+
* Holds if `i <= b + delta`.
28+
*/
29+
pragma[nomagic]
30+
predicate bounded(Instruction i, Instruction b, int delta) {
31+
exists(SemBound bound, IRFunction func |
32+
semBounded(getSemanticExpr(i), bound, delta, true, _) and
33+
b = getABoundIn(bound, func) and
34+
i.getEnclosingIRFunction() = func
35+
)
36+
}
37+
38+
class FieldAddressToPointerArithmeticConf extends DataFlow::Configuration {
39+
FieldAddressToPointerArithmeticConf() { this = "FieldAddressToPointerArithmeticConf" }
40+
41+
override predicate isSource(DataFlow::Node source) { isFieldAddressSource(_, source) }
42+
43+
override predicate isSink(DataFlow::Node sink) {
44+
exists(PointerAddInstruction pai | pai.getLeft() = sink.asInstruction())
45+
}
46+
}
47+
48+
predicate isFieldAddressSource(Field f, DataFlow::Node source) {
49+
source.asInstruction().(FieldAddressInstruction).getField() = f
50+
}
51+
52+
/**
53+
* Holds if `sink` is a sink for `InvalidPointerToDerefConf` and `i` is a `StoreInstruction` that
54+
* writes to an address that non-strictly upper-bounds `sink`, or `i` is a `LoadInstruction` that
55+
* reads from an address that non-strictly upper-bounds `sink`.
56+
*/
57+
predicate isInvalidPointerDerefSink(DataFlow::Node sink, Instruction i, string operation) {
58+
exists(AddressOperand addr, int delta |
59+
bounded(addr.getDef(), sink.asInstruction(), delta) and
60+
delta >= 0 and
61+
i.getAnOperand() = addr
62+
|
63+
i instanceof StoreInstruction and
64+
operation = "write"
65+
or
66+
i instanceof LoadInstruction and
67+
operation = "read"
68+
)
69+
}
70+
71+
predicate isConstantSizeOverflowSource(Field f, PointerAddInstruction pai, int delta) {
72+
exists(
73+
int size, int bound, FieldAddressToPointerArithmeticConf conf, DataFlow::Node source,
74+
DataFlow::InstructionNode sink
75+
|
76+
conf.hasFlow(source, sink) and
77+
isFieldAddressSource(f, source) and
78+
pai.getLeft() = sink.asInstruction() and
79+
f.getUnspecifiedType().(ArrayType).getArraySize() = size and
80+
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), bound, true, _) and
81+
delta = bound - size and
82+
delta >= 0 and
83+
size != 0 and
84+
size != 1
85+
)
86+
}
87+
88+
class PointerArithmeticToDerefConf extends DataFlow2::Configuration {
89+
PointerArithmeticToDerefConf() { this = "PointerArithmeticToDerefConf" }
90+
91+
override predicate isSource(DataFlow::Node source) {
92+
isConstantSizeOverflowSource(_, source.asInstruction(), _)
93+
}
94+
95+
override predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _) }
96+
}
97+
98+
from
99+
Field f, DataFlow2::PathNode source, DataFlow2::PathNode sink, Instruction deref,
100+
PointerArithmeticToDerefConf conf, string operation, int delta
101+
where
102+
conf.hasFlowPath(source, sink) and
103+
isInvalidPointerDerefSink(sink.getNode(), deref, operation) and
104+
isConstantSizeOverflowSource(f, source.getNode().asInstruction(), delta)
105+
select source, source, sink,
106+
"This pointer arithmetic may have an off-by-" + (delta + 1) +
107+
" error allowing it to overrun $@ at this $@.", f, f.getName(), deref, operation
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
edges
2+
| test.cpp:66:32:66:32 | p | test.cpp:66:32:66:32 | Load |
3+
| test.cpp:66:32:66:32 | p | test.cpp:67:5:67:6 | * ... |
4+
| test.cpp:66:32:66:32 | p | test.cpp:67:6:67:6 | Load |
5+
| test.cpp:77:26:77:44 | & ... | test.cpp:66:32:66:32 | p |
6+
| test.cpp:77:26:77:44 | & ... | test.cpp:66:32:66:32 | p |
7+
| test.cpp:77:27:77:44 | access to array | test.cpp:77:26:77:44 | & ... |
8+
nodes
9+
| test.cpp:35:5:35:22 | access to array | semmle.label | access to array |
10+
| test.cpp:36:5:36:24 | access to array | semmle.label | access to array |
11+
| test.cpp:43:9:43:19 | access to array | semmle.label | access to array |
12+
| test.cpp:49:5:49:22 | access to array | semmle.label | access to array |
13+
| test.cpp:50:5:50:24 | access to array | semmle.label | access to array |
14+
| test.cpp:57:9:57:19 | access to array | semmle.label | access to array |
15+
| test.cpp:61:9:61:19 | access to array | semmle.label | access to array |
16+
| test.cpp:66:32:66:32 | Load | semmle.label | Load |
17+
| test.cpp:66:32:66:32 | p | semmle.label | p |
18+
| test.cpp:66:32:66:32 | p | semmle.label | p |
19+
| test.cpp:67:5:67:6 | * ... | semmle.label | * ... |
20+
| test.cpp:67:6:67:6 | Load | semmle.label | Load |
21+
| test.cpp:72:5:72:15 | access to array | semmle.label | access to array |
22+
| test.cpp:77:26:77:44 | & ... | semmle.label | & ... |
23+
| test.cpp:77:27:77:44 | access to array | semmle.label | access to array |
24+
subpaths
25+
#select
26+
| test.cpp:35:5:35:22 | access to array | test.cpp:35:5:35:22 | access to array | test.cpp:35:5:35:22 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:35:5:35:26 | Store: ... = ... | write |
27+
| test.cpp:36:5:36:24 | access to array | test.cpp:36:5:36:24 | access to array | test.cpp:36:5:36:24 | access to array | This pointer arithmetic may have an off-by-2 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:36:5:36:28 | Store: ... = ... | write |
28+
| test.cpp:43:9:43:19 | access to array | test.cpp:43:9:43:19 | access to array | test.cpp:43:9:43:19 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:43:9:43:23 | Store: ... = ... | write |
29+
| test.cpp:49:5:49:22 | access to array | test.cpp:49:5:49:22 | access to array | test.cpp:49:5:49:22 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:19:9:19:11 | buf | buf | test.cpp:49:5:49:26 | Store: ... = ... | write |
30+
| test.cpp:50:5:50:24 | access to array | test.cpp:50:5:50:24 | access to array | test.cpp:50:5:50:24 | access to array | This pointer arithmetic may have an off-by-2 error allowing it to overrun $@ at this $@. | test.cpp:19:9:19:11 | buf | buf | test.cpp:50:5:50:28 | Store: ... = ... | write |
31+
| test.cpp:57:9:57:19 | access to array | test.cpp:57:9:57:19 | access to array | test.cpp:57:9:57:19 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:19:9:19:11 | buf | buf | test.cpp:57:9:57:23 | Store: ... = ... | write |
32+
| test.cpp:61:9:61:19 | access to array | test.cpp:61:9:61:19 | access to array | test.cpp:61:9:61:19 | access to array | This pointer arithmetic may have an off-by-2 error allowing it to overrun $@ at this $@. | test.cpp:19:9:19:11 | buf | buf | test.cpp:61:9:61:23 | Store: ... = ... | write |
33+
| test.cpp:72:5:72:15 | access to array | test.cpp:72:5:72:15 | access to array | test.cpp:72:5:72:15 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:72:5:72:19 | Store: ... = ... | write |
34+
| test.cpp:77:27:77:44 | access to array | test.cpp:77:27:77:44 | access to array | test.cpp:66:32:66:32 | Load | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:67:5:67:10 | Store: ... = ... | write |
35+
| test.cpp:77:27:77:44 | access to array | test.cpp:77:27:77:44 | access to array | test.cpp:66:32:66:32 | p | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:67:5:67:10 | Store: ... = ... | write |
36+
| test.cpp:77:27:77:44 | access to array | test.cpp:77:27:77:44 | access to array | test.cpp:67:5:67:6 | * ... | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:67:5:67:10 | Store: ... = ... | write |
37+
| test.cpp:77:27:77:44 | access to array | test.cpp:77:27:77:44 | access to array | test.cpp:67:6:67:6 | Load | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:15:9:15:11 | buf | buf | test.cpp:67:5:67:10 | Store: ... = ... | write |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-193/ConstantSizeArrayOffByOne.ql
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#define MAX_SIZE 1024
2+
3+
struct ZeroArray {
4+
int size;
5+
int buf[0];
6+
};
7+
8+
struct OneArray {
9+
int size;
10+
int buf[1];
11+
};
12+
13+
struct BigArray {
14+
int size;
15+
int buf[MAX_SIZE];
16+
};
17+
18+
struct ArrayAndFields {
19+
int buf[MAX_SIZE];
20+
int field1;
21+
int field2;
22+
};
23+
24+
// tests for dynamic-size trailing arrays
25+
void testZeroArray(ZeroArray *arr) {
26+
arr->buf[0] = 0;
27+
}
28+
29+
void testOneArray(OneArray *arr) {
30+
arr->buf[1] = 0;
31+
}
32+
33+
void testBig(BigArray *arr) {
34+
arr->buf[MAX_SIZE-1] = 0; // GOOD
35+
arr->buf[MAX_SIZE] = 0; // BAD
36+
arr->buf[MAX_SIZE+1] = 0; // BAD
37+
38+
for(int i = 0; i < MAX_SIZE; i++) {
39+
arr->buf[i] = 0; // GOOD
40+
}
41+
42+
for(int i = 0; i <= MAX_SIZE; i++) {
43+
arr->buf[i] = 0; // BAD
44+
}
45+
}
46+
47+
void testFields(ArrayAndFields *arr) {
48+
arr->buf[MAX_SIZE-1] = 0; // GOOD
49+
arr->buf[MAX_SIZE] = 0; // BAD?
50+
arr->buf[MAX_SIZE+1] = 0; // BAD?
51+
52+
for(int i = 0; i < MAX_SIZE; i++) {
53+
arr->buf[i] = 0; // GOOD
54+
}
55+
56+
for(int i = 0; i <= MAX_SIZE; i++) {
57+
arr->buf[i] = 0; // BAD?
58+
}
59+
60+
for(int i = 0; i < MAX_SIZE+2; i++) {
61+
arr->buf[i] = 0; // BAD?
62+
}
63+
// is this different if it's a memcpy?
64+
}
65+
66+
void assignThroughPointer(int *p) {
67+
*p = 0; // ??? should the result go at a flow source?
68+
}
69+
70+
void addToPointerAndAssign(int *p) {
71+
p[MAX_SIZE-1] = 0; // GOOD
72+
p[MAX_SIZE] = 0; // BAD
73+
}
74+
75+
void testInterproc(BigArray *arr) {
76+
assignThroughPointer(&arr->buf[MAX_SIZE-1]); // GOOD
77+
assignThroughPointer(&arr->buf[MAX_SIZE]); // BAD
78+
79+
addToPointerAndAssign(arr->buf);
80+
}

0 commit comments

Comments
 (0)