Skip to content

Commit 757f40c

Browse files
authored
Merge pull request github#13116 from rdmarsh2/rdmarsh2/cpp/cobo-array-vars
C++: include stack-allocated arrays in off-by-one query
2 parents 9c5aff3 + d68b060 commit 757f40c

File tree

3 files changed

+108
-37
lines changed

3 files changed

+108
-37
lines changed

cpp/ql/src/experimental/Security/CWE/CWE-193/ConstantSizeArrayOffByOne.ql

Lines changed: 66 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.RangeAnalysi
1414
import semmle.code.cpp.rangeanalysis.new.internal.semantic.SemanticExprSpecific
1515
import semmle.code.cpp.ir.IR
1616
import semmle.code.cpp.ir.dataflow.DataFlow
17-
import FieldAddressToDerefFlow::PathGraph
17+
import ArrayAddressToDerefFlow::PathGraph
1818

1919
pragma[nomagic]
2020
Instruction getABoundIn(SemBound b, IRFunction func) {
@@ -78,28 +78,45 @@ predicate isInvalidPointerDerefSink2(DataFlow::Node sink, Instruction i, string
7878
)
7979
}
8080

81+
predicate arrayTypeCand(ArrayType arrayType) {
82+
any(Variable v).getUnspecifiedType() = arrayType and
83+
exists(arrayType.getArraySize())
84+
}
85+
8186
pragma[nomagic]
8287
predicate arrayTypeHasSizes(ArrayType arr, int baseTypeSize, int arraySize) {
88+
arrayTypeCand(arr) and
8389
arr.getBaseType().getSize() = baseTypeSize and
8490
arr.getArraySize() = arraySize
8591
}
8692

87-
predicate pointerArithOverflow0(
88-
PointerArithmeticInstruction pai, Field f, int size, int bound, int delta
89-
) {
90-
not f.getNamespace() instanceof StdNamespace and
91-
arrayTypeHasSizes(f.getUnspecifiedType(), pai.getElementSize(), size) and
92-
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), bound, true, _) and
93-
delta = bound - size and
94-
delta >= 0 and
95-
size != 0 and
96-
size != 1
93+
bindingset[pai]
94+
pragma[inline_late]
95+
predicate constantUpperBounded(PointerArithmeticInstruction pai, int delta) {
96+
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), delta, true, _)
97+
}
98+
99+
bindingset[pai, size]
100+
predicate pointerArithOverflow0Impl(PointerArithmeticInstruction pai, int size, int delta) {
101+
exists(int bound |
102+
constantUpperBounded(pai, bound) and
103+
delta = bound - size and
104+
delta >= 0 and
105+
size != 0 and
106+
size != 1
107+
)
108+
}
109+
110+
pragma[nomagic]
111+
predicate pointerArithOverflow0(PointerArithmeticInstruction pai, int delta) {
112+
exists(int size |
113+
arrayTypeHasSizes(_, pai.getElementSize(), size) and
114+
pointerArithOverflow0Impl(pai, size, delta)
115+
)
97116
}
98117

99118
module PointerArithmeticToDerefConfig implements DataFlow::ConfigSig {
100-
predicate isSource(DataFlow::Node source) {
101-
pointerArithOverflow0(source.asInstruction(), _, _, _, _)
102-
}
119+
predicate isSource(DataFlow::Node source) { pointerArithOverflow0(source.asInstruction(), _) }
103120

104121
predicate isBarrierIn(DataFlow::Node node) { isSource(node) }
105122

@@ -110,25 +127,38 @@ module PointerArithmeticToDerefConfig implements DataFlow::ConfigSig {
110127

111128
module PointerArithmeticToDerefFlow = DataFlow::Global<PointerArithmeticToDerefConfig>;
112129

113-
predicate pointerArithOverflow(
114-
PointerArithmeticInstruction pai, Field f, int size, int bound, int delta
115-
) {
116-
pointerArithOverflow0(pai, f, size, bound, delta) and
130+
predicate pointerArithOverflow(PointerArithmeticInstruction pai, int delta) {
131+
pointerArithOverflow0(pai, delta) and
117132
PointerArithmeticToDerefFlow::flow(DataFlow::instructionNode(pai), _)
118133
}
119134

120-
module FieldAddressToDerefConfig implements DataFlow::StateConfigSig {
135+
bindingset[v]
136+
predicate finalPointerArithOverflow(Variable v, PointerArithmeticInstruction pai, int delta) {
137+
exists(int size |
138+
arrayTypeHasSizes(pragma[only_bind_out](v.getUnspecifiedType()), pai.getElementSize(), size) and
139+
pointerArithOverflow0Impl(pai, size, delta)
140+
)
141+
}
142+
143+
predicate isSourceImpl(DataFlow::Node source, Variable v) {
144+
(
145+
source.asInstruction().(FieldAddressInstruction).getField() = v
146+
or
147+
source.asInstruction().(VariableAddressInstruction).getAstVariable() = v
148+
) and
149+
arrayTypeCand(v.getUnspecifiedType())
150+
}
151+
152+
module ArrayAddressToDerefConfig implements DataFlow::StateConfigSig {
121153
newtype FlowState =
122-
additional TArray(Field f) { pointerArithOverflow(_, f, _, _, _) } or
154+
additional TArray() or
123155
additional TOverflowArithmetic(PointerArithmeticInstruction pai) {
124-
pointerArithOverflow(pai, _, _, _, _)
156+
pointerArithOverflow(pai, _)
125157
}
126158

127159
predicate isSource(DataFlow::Node source, FlowState state) {
128-
exists(Field f |
129-
source.asInstruction().(FieldAddressInstruction).getField() = f and
130-
state = TArray(f)
131-
)
160+
isSourceImpl(source, _) and
161+
state = TArray()
132162
}
133163

134164
predicate isSink(DataFlow::Node sink, FlowState state) {
@@ -147,27 +177,27 @@ module FieldAddressToDerefConfig implements DataFlow::StateConfigSig {
147177
predicate isAdditionalFlowStep(
148178
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
149179
) {
150-
exists(PointerArithmeticInstruction pai, Field f |
151-
state1 = TArray(f) and
180+
exists(PointerArithmeticInstruction pai |
181+
state1 = TArray() and
152182
state2 = TOverflowArithmetic(pai) and
153183
pai.getLeft() = node1.asInstruction() and
154184
node2.asInstruction() = pai and
155-
pointerArithOverflow(pai, f, _, _, _)
185+
pointerArithOverflow(pai, _)
156186
)
157187
}
158188
}
159189

160-
module FieldAddressToDerefFlow = DataFlow::GlobalWithState<FieldAddressToDerefConfig>;
190+
module ArrayAddressToDerefFlow = DataFlow::GlobalWithState<ArrayAddressToDerefConfig>;
161191

162192
from
163-
Field f, FieldAddressToDerefFlow::PathNode source, PointerArithmeticInstruction pai,
164-
FieldAddressToDerefFlow::PathNode sink, Instruction deref, string operation, int delta
193+
Variable v, ArrayAddressToDerefFlow::PathNode source, PointerArithmeticInstruction pai,
194+
ArrayAddressToDerefFlow::PathNode sink, Instruction deref, string operation, int delta
165195
where
166-
FieldAddressToDerefFlow::flowPath(source, sink) and
196+
ArrayAddressToDerefFlow::flowPath(pragma[only_bind_into](source), pragma[only_bind_into](sink)) and
167197
isInvalidPointerDerefSink2(sink.getNode(), deref, operation) and
168-
source.getState() = FieldAddressToDerefConfig::TArray(f) and
169-
sink.getState() = FieldAddressToDerefConfig::TOverflowArithmetic(pai) and
170-
pointerArithOverflow(pai, f, _, _, delta)
198+
pragma[only_bind_out](sink.getState()) = ArrayAddressToDerefConfig::TOverflowArithmetic(pai) and
199+
isSourceImpl(source.getNode(), v) and
200+
finalPointerArithOverflow(v, pai, delta)
171201
select pai, source, sink,
172202
"This pointer arithmetic may have an off-by-" + (delta + 1) +
173-
" error allowing it to overrun $@ at this $@.", f, f.getName(), deref, operation
203+
" error allowing it to overrun $@ at this $@.", v, v.getName(), deref, operation

cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/ConstantSizeArrayOffByOne.expected

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ edges
1111
| test.cpp:77:32:77:34 | buf | test.cpp:77:26:77:44 | & ... |
1212
| test.cpp:79:27:79:34 | buf | test.cpp:70:33:70:33 | p |
1313
| test.cpp:79:32:79:34 | buf | test.cpp:79:27:79:34 | buf |
14+
| test.cpp:85:34:85:36 | buf | test.cpp:87:5:87:31 | access to array |
15+
| test.cpp:85:34:85:36 | buf | test.cpp:88:5:88:27 | access to array |
16+
| test.cpp:128:9:128:11 | arr | test.cpp:128:9:128:14 | access to array |
17+
| test.cpp:134:25:134:27 | arr | test.cpp:136:9:136:16 | ... += ... |
18+
| test.cpp:136:9:136:16 | ... += ... | test.cpp:138:13:138:15 | arr |
19+
| test.cpp:143:18:143:21 | asdf | test.cpp:134:25:134:27 | arr |
20+
| test.cpp:143:18:143:21 | asdf | test.cpp:143:18:143:21 | asdf |
1421
nodes
1522
| test.cpp:35:5:35:22 | access to array | semmle.label | access to array |
1623
| test.cpp:35:10:35:12 | buf | semmle.label | buf |
@@ -33,6 +40,16 @@ nodes
3340
| test.cpp:77:32:77:34 | buf | semmle.label | buf |
3441
| test.cpp:79:27:79:34 | buf | semmle.label | buf |
3542
| test.cpp:79:32:79:34 | buf | semmle.label | buf |
43+
| test.cpp:85:34:85:36 | buf | semmle.label | buf |
44+
| test.cpp:87:5:87:31 | access to array | semmle.label | access to array |
45+
| test.cpp:88:5:88:27 | access to array | semmle.label | access to array |
46+
| test.cpp:128:9:128:11 | arr | semmle.label | arr |
47+
| test.cpp:128:9:128:14 | access to array | semmle.label | access to array |
48+
| test.cpp:134:25:134:27 | arr | semmle.label | arr |
49+
| test.cpp:136:9:136:16 | ... += ... | semmle.label | ... += ... |
50+
| test.cpp:138:13:138:15 | arr | semmle.label | arr |
51+
| test.cpp:143:18:143:21 | asdf | semmle.label | asdf |
52+
| test.cpp:143:18:143:21 | asdf | semmle.label | asdf |
3653
subpaths
3754
#select
3855
| test.cpp:35:5:35:22 | PointerAdd: access to array | test.cpp:35:10:35:12 | buf | 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 |
@@ -44,3 +61,5 @@ subpaths
4461
| test.cpp:61:9:61:19 | PointerAdd: access to array | test.cpp:61:14:61:16 | buf | 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 |
4562
| test.cpp:72:5:72:15 | PointerAdd: access to array | test.cpp:79:32:79:34 | buf | 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 |
4663
| test.cpp:77:27:77:44 | PointerAdd: access to array | test.cpp:77:32:77:34 | buf | 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 |
64+
| test.cpp:128:9:128:14 | PointerAdd: access to array | test.cpp:128:9:128:11 | arr | test.cpp:128:9:128:14 | access to array | This pointer arithmetic may have an off-by-1 error allowing it to overrun $@ at this $@. | test.cpp:125:11:125:13 | arr | arr | test.cpp:128:9:128:18 | Store: ... = ... | write |
65+
| test.cpp:136:9:136:16 | PointerAdd: ... += ... | test.cpp:143:18:143:21 | asdf | test.cpp:138:13:138:15 | arr | This pointer arithmetic may have an off-by-2 error allowing it to overrun $@ at this $@. | test.cpp:142:10:142:13 | asdf | asdf | test.cpp:138:12:138:15 | Load: * ... | read |

cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/constant-size/test.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void testCharIndex(BigArray *arr) {
8585
char *charBuf = (char*) arr->buf;
8686

8787
charBuf[MAX_SIZE_BYTES - 1] = 0; // GOOD
88-
charBuf[MAX_SIZE_BYTES] = 0; // BAD [FALSE NEGATIVE]
88+
charBuf[MAX_SIZE_BYTES] = 0; // BAD
8989
}
9090

9191
void testEqRefinement() {
@@ -120,3 +120,25 @@ void testEqRefinement2() {
120120
}
121121
}
122122
}
123+
124+
void testStackAllocated() {
125+
char *arr[MAX_SIZE];
126+
127+
for(int i = 0; i <= MAX_SIZE; i++) {
128+
arr[i] = 0; // BAD
129+
}
130+
}
131+
132+
int strncmp(const char*, const char*, int);
133+
134+
char testStrncmp2(char *arr) {
135+
if(strncmp(arr, "<test>", 6) == 0) {
136+
arr += 6;
137+
}
138+
return *arr; // GOOD [FALSE POSITIVE]
139+
}
140+
141+
void testStrncmp1() {
142+
char asdf[5];
143+
testStrncmp2(asdf);
144+
}

0 commit comments

Comments
 (0)