Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4bcba16

Browse files
authored
Merge pull request #10833 from MathiasVP/repair-badly-bounded-write-2
C++: Prepare `Buffer.qll` for IR-based use-use dataflow
2 parents 2ba94f7 + c8f81bc commit 4bcba16

11 files changed

Lines changed: 204 additions & 42 deletions

File tree

cpp/ql/lib/semmle/code/cpp/commons/Buffer.qll

Lines changed: 131 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,24 @@ predicate memberMayBeVarSize(Class c, MemberVariable v) {
2525
}
2626

2727
/**
28-
* Get the size in bytes of the buffer pointed to by an expression (if this can be determined).
28+
* Gets the expression associated with `n`. Unlike `n.asExpr()` this also gets the
29+
* expression underlying an indirect dataflow node.
2930
*/
30-
language[monotonicAggregates]
31-
int getBufferSize(Expr bufferExpr, Element why) {
31+
private Expr getExpr(DataFlow::Node n, boolean isIndirect) {
32+
result = n.asExpr() and isIndirect = false
33+
or
34+
result = n.asIndirectExpr() and isIndirect = true
35+
}
36+
37+
private DataFlow::Node exprNode(Expr e, boolean isIndirect) { e = getExpr(result, isIndirect) }
38+
39+
/**
40+
* Holds if `bufferExpr` is an allocation-like expression.
41+
*
42+
* This includes both actual allocations, as well as various operations that return a pointer to
43+
* stack-allocated objects.
44+
*/
45+
private int isSource(Expr bufferExpr, Element why) {
3246
exists(Variable bufferVar | bufferVar = bufferExpr.(VariableAccess).getTarget() |
3347
// buffer is a fixed size array
3448
result = bufferVar.getUnspecifiedType().(ArrayType).getSize() and
@@ -46,42 +60,12 @@ int getBufferSize(Expr bufferExpr, Element why) {
4660
) and
4761
result = why.(Expr).getType().(ArrayType).getSize() and
4862
not exists(bufferVar.getUnspecifiedType().(ArrayType).getSize())
49-
or
50-
exists(Class parentClass, VariableAccess parentPtr, int bufferSize |
51-
// buffer is the parentPtr->bufferVar of a 'variable size struct'
52-
memberMayBeVarSize(parentClass, bufferVar) and
53-
why = bufferVar and
54-
parentPtr = bufferExpr.(VariableAccess).getQualifier() and
55-
parentPtr.getTarget().getUnspecifiedType().(PointerType).getBaseType() = parentClass and
56-
(
57-
if exists(bufferVar.getType().getSize())
58-
then bufferSize = bufferVar.getType().getSize()
59-
else bufferSize = 0
60-
) and
61-
result = getBufferSize(parentPtr, _) + bufferSize - parentClass.getSize()
62-
)
6363
)
6464
or
6565
// buffer is a fixed size dynamic allocation
6666
result = bufferExpr.(AllocationExpr).getSizeBytes() and
6767
why = bufferExpr
6868
or
69-
exists(DataFlow::ExprNode bufferExprNode |
70-
// dataflow (all sources must be the same size)
71-
bufferExprNode = DataFlow::exprNode(bufferExpr) and
72-
result =
73-
unique(Expr def |
74-
DataFlow::localFlowStep(DataFlow::exprNode(def), bufferExprNode)
75-
|
76-
getBufferSize(def, _)
77-
) and
78-
// find reason
79-
exists(Expr def | DataFlow::localFlowStep(DataFlow::exprNode(def), bufferExprNode) |
80-
why = def or
81-
exists(getBufferSize(def, why))
82-
)
83-
)
84-
or
8569
exists(Type bufferType |
8670
// buffer is the address of a variable
8771
why = bufferExpr.(AddressOfExpr).getAddressable() and
@@ -100,3 +84,117 @@ int getBufferSize(Expr bufferExpr, Element why) {
10084
result = bufferType.getSize()
10185
)
10286
}
87+
88+
/** Holds if the value of `n2 + delta` may be equal to the value of `n1`. */
89+
private predicate localFlowIncrStep(DataFlow::Node n1, DataFlow::Node n2, int delta) {
90+
DataFlow::localFlowStep(n1, n2) and
91+
(
92+
exists(IncrementOperation incr |
93+
n1.asIndirectExpr() = incr.getOperand() and
94+
delta = -1
95+
)
96+
or
97+
exists(DecrementOperation decr |
98+
n1.asIndirectExpr() = decr.getOperand() and
99+
delta = 1
100+
)
101+
or
102+
exists(AddExpr add, Expr e1, Expr e2 |
103+
add.hasOperands(e1, e2) and
104+
n1.asIndirectExpr() = e1 and
105+
delta = -e2.getValue().toInt()
106+
)
107+
or
108+
exists(SubExpr add, Expr e1, Expr e2 |
109+
add.hasOperands(e1, e2) and
110+
n1.asIndirectExpr() = e1 and
111+
delta = e2.getValue().toInt()
112+
)
113+
)
114+
}
115+
116+
/**
117+
* Holds if `n1` may flow to `n2` without passing through any back-edges.
118+
*
119+
* Back-edges are excluded to prevent infinite loops on examples like:
120+
* ```
121+
* while(true) { ++n; }
122+
* ```
123+
* which, when used in `localFlowStepRec`, would create infinite loop that continuously
124+
* increments the `delta` parameter.
125+
*/
126+
private predicate localFlowNotIncrStep(DataFlow::Node n1, DataFlow::Node n2) {
127+
not localFlowIncrStep(n1, n2, _) and
128+
DataFlow::localFlowStep(n1, n2) and
129+
not n1 = n2.(DataFlow::SsaPhiNode).getAnInput(true)
130+
}
131+
132+
private predicate localFlowToExprStep(DataFlow::Node n1, DataFlow::Node n2) {
133+
not exists([n1.asExpr(), n1.asIndirectExpr()]) and
134+
localFlowNotIncrStep(n1, n2)
135+
}
136+
137+
/** Holds if `mid2 + delta` may be equal to `n1`. */
138+
private predicate localFlowStepRec0(DataFlow::Node n1, DataFlow::Node mid2, int delta) {
139+
exists(DataFlow::Node mid1, int d1, int d2 |
140+
// Or we take a number of steps that adds `d1` to the pointer
141+
localFlowStepRec(n1, mid1, d1) and
142+
// followed by a step that adds `d2` to the pointer
143+
localFlowIncrStep(mid1, mid2, d2) and
144+
delta = d1 + d2
145+
)
146+
}
147+
148+
/** Holds if `n2 + delta` may be equal to `n1`. */
149+
private predicate localFlowStepRec(DataFlow::Node n1, DataFlow::Node n2, int delta) {
150+
// Either we take one or more steps that doesn't modify the size of the buffer
151+
localFlowNotIncrStep+(n1, n2) and
152+
delta = 0
153+
or
154+
exists(DataFlow::Node mid2 |
155+
// Or we step from `n1` to `mid2 + delta`
156+
localFlowStepRec0(n1, mid2, delta) and
157+
// and finally to the next `ExprNode`.
158+
localFlowToExprStep*(mid2, n2)
159+
)
160+
}
161+
162+
/**
163+
* Holds if `e2` is an expression that is derived from `e1` such that if `e1[n]` is a
164+
* well-defined expression for some number `n`, then `e2[n + delta]` is also a well-defined
165+
* expression.
166+
*/
167+
private predicate step(Expr e1, Expr e2, int delta) {
168+
exists(Variable bufferVar, Class parentClass, VariableAccess parentPtr, int bufferSize |
169+
e1 = parentPtr
170+
|
171+
bufferVar = e2.(VariableAccess).getTarget() and
172+
// buffer is the parentPtr->bufferVar of a 'variable size struct'
173+
memberMayBeVarSize(parentClass, bufferVar) and
174+
parentPtr = e2.(VariableAccess).getQualifier() and
175+
parentPtr.getTarget().getUnspecifiedType().(PointerType).getBaseType() = parentClass and
176+
(
177+
if exists(bufferVar.getType().getSize())
178+
then bufferSize = bufferVar.getType().getSize()
179+
else bufferSize = 0
180+
) and
181+
delta = bufferSize - parentClass.getSize()
182+
)
183+
or
184+
exists(boolean isIndirect |
185+
localFlowStepRec(exprNode(e1, isIndirect), exprNode(e2, isIndirect), delta)
186+
)
187+
}
188+
189+
/**
190+
* Get the size in bytes of the buffer pointed to by an expression (if this can be determined).
191+
*/
192+
int getBufferSize(Expr bufferExpr, Element why) {
193+
result = isSource(bufferExpr, why)
194+
or
195+
exists(Expr e0, int delta, int size |
196+
size = getBufferSize(e0, why) and
197+
delta = unique(int cand | step(e0, bufferExpr, cand) | cand) and
198+
result = size + delta
199+
)
200+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| tests.cpp:350:13:350:19 | call to strncat | This 'call to strncat' operation is limited to 100 bytes but the destination is only 50 bytes. |
2+
| tests.cpp:452:9:452:15 | call to wcsncpy | This 'call to wcsncpy' operation is limited to 396 bytes but the destination is only 200 bytes. |
3+
| tests.cpp:481:9:481:16 | call to swprintf | This 'call to swprintf' operation is limited to 400 bytes but the destination is only 200 bytes. |
4+
| tests.cpp:630:13:630:20 | call to swprintf | This 'call to swprintf' operation is limited to 400 bytes but the destination is only 200 bytes. |
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,13 @@
11
| tests.cpp:45:9:45:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:32:10:32:18 | charFirst | destination buffer |
22
| tests.cpp:60:9:60:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:32:10:32:18 | charFirst | destination buffer |
3+
| tests.cpp:171:9:171:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:164:20:164:25 | call to malloc | destination buffer |
4+
| tests.cpp:172:9:172:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:164:20:164:25 | call to malloc | array |
5+
| tests.cpp:192:9:192:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:181:10:181:22 | dataBadBuffer | destination buffer |
6+
| tests.cpp:193:9:193:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:181:10:181:22 | dataBadBuffer | array |
7+
| tests.cpp:212:9:212:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:201:36:201:41 | call to alloca | destination buffer |
8+
| tests.cpp:213:9:213:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:201:36:201:41 | call to alloca | array |
9+
| tests.cpp:237:9:237:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:221:36:221:41 | call to alloca | array |
10+
| tests.cpp:261:9:261:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:245:10:245:22 | dataBadBuffer | array |
11+
| tests.cpp:384:9:384:14 | call to memcpy | This 'memcpy' operation accesses 40 bytes but the $@ is only 10 bytes. | tests.cpp:380:19:380:24 | call to alloca | destination buffer |
12+
| tests.cpp:434:9:434:19 | access to array | This array indexing operation accesses byte offset 399 but the $@ is only 200 bytes. | tests.cpp:422:12:422:26 | new[] | array |
13+
| tests.cpp:453:9:453:19 | access to array | This array indexing operation accesses byte offset 399 but the $@ is only 200 bytes. | tests.cpp:445:12:445:26 | new[] | array |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| tests.cpp:668:9:668:14 | call to strcpy | This 'call to strcpy' operation requires 11 bytes but the destination is only 10 bytes. |

cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ void CWE121_Stack_Based_Buffer_Overflow__CWE193_char_declare_cpy_12_bad()
664664
}
665665
{
666666
char source[10+1] = SRC_STRING;
667-
/* POTENTIAL FLAW: data may not have enough space to hold source */ // [NOT DETECTED]
667+
/* POTENTIAL FLAW: data may not have enough space to hold source */
668668
strcpy(data, source);
669669
printLine(data);
670670
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
| var_size_struct.cpp:73:3:73:9 | call to strncpy | This 'call to strncpy' operation is limited to 1025 bytes but the destination is only 1024 bytes. |
12
| var_size_struct.cpp:103:3:103:9 | call to strncpy | This 'call to strncpy' operation is limited to 129 bytes but the destination is only 128 bytes. |

0 commit comments

Comments
 (0)