-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathStackAddress.qll
More file actions
303 lines (291 loc) · 11.5 KB
/
StackAddress.qll
File metadata and controls
303 lines (291 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/**
* Provides utilities for determining which expressions contain
* stack addresses.
*/
import cpp
import semmle.code.cpp.controlflow.SSA
/**
* A stack address flows to `use`.
* The simplest case is when `use` is the expression `&var`, but
* assignments are also handled. For example:
*
* x = &var;
* y = x;
* ...y... // use of &var
*
* `useType` is the type of data which we believe was allocated on the
* stack. It is particularly important when dealing with pointers. Consider
* this example:
*
* int x[10];
* int *y = new int[10];
* ... = &x[1];
* ... = &y[1];
*
* In this example, x and y are both stack variables. But &x[1] is a
* pointer to the stack and &y[1] is a pointer to the heap. The difference
* is that the type of x is int[10], but the type of y is int*. This
* information is stored in `useType`.
*
* `source` is the origin of the stack address. It is only used to improve
* the quality of the error messages.
*
* `isLocal` is true if the stack address came from the current
* function. It is false if the stack address arrived via a function
* parameter. This information is only used to improve the quality of the
* error messages.
*/
predicate stackPointerFlowsToUse(Expr use, Type useType, Expr source, boolean isLocal) {
// Arrays in C are convertible to pointers. For example, if the type of x
// is int[10] then it is convertible to int*. The same rule applies to
// multidimensional arrays: if the type of y is int[2][3][4], then it is
// convertible to int(*)[3][4]. This conversion is what happens under the
// hood when we index an array. For example, x[5] is equivalent to
// *(x+5), so x is first converted to a pointer and then the pointer
// arithmetic is applied to the pointer.
exists(ArrayType arrayType |
stackReferenceFlowsToUse(use, arrayType, source, isLocal) and
useType = getExprPtrType(use.getConversion()).getBaseType()
)
or
// Address of: &x
stackReferenceFlowsToUse(use.(AddressOfExpr).getOperand(), useType, source, isLocal)
or
// Pointer subtract: if p is a pointer to a stack address, then p-1 is
// too.
stackPointerFlowsToUse(use.(PointerSubExpr).getLeftOperand(), useType, source, isLocal)
or
// Pointer add: if p is a pointer to a stack address, then p+1 is too.
stackPointerFlowsToUse(use.(PointerAddExpr).getAnOperand(), useType, source, isLocal)
or
// Indirect use of a stack address.
exists(SsaDefinition def, StackVariable var |
stackPointerFlowsToDef(def, var, useType, source, isLocal) and
use = def.getAUse(var)
)
or
// Use of a stack address which arrived via one of the function's
// parameters. We do not use the original pointer source here because it
// could lead to an extremely large number of results if the function has
// many callers. Instead we set `source` to the use of the parameter.
exists(SsaDefinition def, Parameter param |
pointerParamFlowsToDef(def, param, useType) and
use = def.getAUse(param) and
source = use and
isLocal = false
)
or
// Similar to the parameter case above. If a member function is called
// on an object which is allocated on the stack, then the "this" pointer
// contains a stack address.
exists(ThisExpr thisExpr |
use = thisExpr and
memberFcnMightRunOnStack(thisExpr.getEnclosingFunction(), useType) and
source = use and
isLocal = false
)
}
/**
* Helper function for stackPointerFlowsToUse. Gets the type of a pointer
* expression.
*/
cached
private PointerType getExprPtrType(Expr use) { result = use.getUnspecifiedType() }
/**
* Holds if `use` has type `useType` and `source` is an access to a stack variable
* that flows to `use`. `isLocal` is `true` if `use` is accessed via a parameter, and
* `false` otherwise.
*/
predicate stackReferenceFlowsToUse(Expr use, Type useType, Expr source, boolean isLocal) {
// Stack variables
exists(StackVariable var |
use = source and
source = var.getAnAccess() and
isLocal = true and
// If the type of the variable is a reference type, such as int&, then
// we need to look at its definition to determine whether it contains a
// stack reference. This is handled by stackReferenceFlowsToDef, below.
not isReferenceVariable(var) and
// There is a subtlety relating to ArrayType which is hidden by the
// simplicity of the line below. Consider this example:
//
// int arraytest(int p[3][4][5]) {
// int x[3][4][5] = { ... };
// return x[1][2][3] + p[1][2][3];
// }
//
// The types of `x` and `p` are the same, but `x` is stack allocated
// and `p` is not. So we want `useType` to be an ArrayType for `x` and
// a PointerType for `p`. Luckily, this conversion happens
// automatically when the variable is used. So we get the correct type
// provided that we get it from `use` rather than from `var`.
useType = use.getUnspecifiedType()
)
or
// Accessing the field of a class, struct, or union.
exists(FieldAccess access, Class classType |
use = access and useType = access.getUnspecifiedType()
|
// Handle both x.f and x->f:
stackReferenceFlowsToUse(access.getQualifier(), classType, source, isLocal) or
stackPointerFlowsToUse(access.getQualifier(), classType, source, isLocal)
)
or
// Array indexing: x[i].
// Note that array types are converted to pointers in
// stackPointerFlowsToUse.
stackPointerFlowsToUse(use.(ArrayExpr).getArrayBase(), useType, source, isLocal)
or
// Pointer dereference: *x
stackPointerFlowsToUse(use.(PointerDereferenceExpr).getOperand(), useType, source, isLocal)
or
// Indirect use of a stack reference, via a reference variable.
exists(SsaDefinition def, StackVariable var |
stackReferenceFlowsToDef(def, var, useType, source, isLocal) and
use = def.getAUse(var)
)
or
// Use of a stack reference which arrived via one of the function's
// parameters. We do not use the original reference source here because
// it could lead to an extremely large number of results if the function
// has many callers. Instead we set `source` to the use of the parameter.
exists(SsaDefinition def, Parameter param |
referenceParamFlowsToDef(def, param, useType) and
use = def.getAUse(param) and
source = use and
isLocal = false
)
}
/**
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
* addresses through SSA definitions.
*/
predicate stackPointerFlowsToDef(
SsaDefinition def, StackVariable var, Type useType, Expr source, boolean isLocal
) {
stackPointerFlowsToUse(def.getDefiningValue(var), useType, source, isLocal)
or
// Increment/decrement operators.
exists(VariableAccess access |
access = def.(CrementOperation).getOperand() and
var = access.getTarget() and
stackPointerFlowsToUse(access, useType, source, isLocal)
)
or
// Transitive closure over phi definitions.
stackPointerFlowsToDef(def.getAPhiInput(var), var, useType, source, isLocal)
}
/**
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
* references through SSA definitions. This predicate is almost identical
* to stackPointerFlowsToDef, except it handles references types, such as
* int&, rather than pointers.
*/
predicate stackReferenceFlowsToDef(
SsaDefinition def, StackVariable var, Type useType, Expr source, boolean isLocal
) {
// Check that the type of the variable is a reference type and delegate
// the rest of the work to stackReferenceFlowsToDef_Impl.
isReferenceVariable(var) and
stackReferenceFlowsToDef_Impl(def, var, useType, source, isLocal)
}
/**
* stackReferenceFlowsToDef delegates most of the work to this
* predicate.
*/
predicate stackReferenceFlowsToDef_Impl(
SsaDefinition def, StackVariable var, Type useType, Expr source, boolean isLocal
) {
stackReferenceFlowsToUse(def.getDefiningValue(var), useType, source, isLocal)
or
// Increment/decrement operators.
exists(VariableAccess access |
access = def.(CrementOperation).getOperand() and
var = access.getTarget() and
stackReferenceFlowsToUse(access, useType, source, isLocal)
)
or
// Transitive closure over phi definitions.
stackReferenceFlowsToDef(def.getAPhiInput(var), var, useType, source, isLocal)
}
/** The type of the variable is a reference type, such as int&. */
predicate isReferenceVariable(StackVariable var) {
var.getUnspecifiedType() instanceof ReferenceType
}
/**
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
* addresses which arrived through one of the function's parameters. This
* predicate is very similar to stackPointerFlowsToDef but they cannot be
* merged, because we cannot identify a sensible source expression here.
*/
predicate pointerParamFlowsToDef(SsaDefinition def, Parameter param, Type useType) {
// Only include a parameter definition if we can find a call site which
// might pass it a stack address.
exists(FunctionCall call |
call.getTarget() = param.getFunction() and
stackPointerFlowsToUse(call.getArgument(param.getIndex()), useType, _, _) and
def.definedByParameter(param)
)
or
// Transitive closure over phi definitions.
pointerParamFlowsToDef(def.getAPhiInput(param), param, useType)
}
/**
* Helper predicate for stackPointerFlowsToUse. Tracks the flow of stack
* addresses which arrived through one of the function's parameters. This
* predicate is very similar to stackPointerFlowsToDef but they cannot be
* merged, because we cannot identify a sensible source expression here.
*/
predicate referenceParamFlowsToDef(SsaDefinition def, Parameter param, Type useType) {
// Only include a parameter definition if we can find a call site which
// might pass it a stack reference.
exists(FunctionCall call |
call.getTarget() = param.getFunction() and
stackReferenceFlowsToUse(call.getArgument(param.getIndex()), useType, _, _) and
def.definedByParameter(param)
)
or
// Transitive closure over phi definitions.
referenceParamFlowsToDef(def.getAPhiInput(param), param, useType)
}
/**
* Holds if this member function might be called on an object which
* is allocated on the stack.
*/
predicate memberFcnMightRunOnStack(MemberFunction fcn, Type useType) {
exists(FunctionCall call | call.getTarget() = fcn |
// Call of the form `x.f()` where `x` is allocated on the stack.
stackReferenceFlowsToUse(call.getQualifier(), useType, _, _)
or
// Call of the form `x->f()` where `x` is allocated on the stack.
stackPointerFlowsToUse(call.getQualifier(), useType, _, _)
)
or
// Constructor calls need to be treated as a special case, because
// `call.getQualifier()` is empty.
constructorMightRunOnStack(fcn) and
useType = fcn.getDeclaringType().getUnspecifiedType()
}
/**
* Helper predicate for memberFcnMightRunOnStack. Function calls
* to constructors need to be treated as a special case, because
* `call.getQualifier()` is empty. Instead, we need to check whether
* the constructor is called from an initializer. There are several
* kinds of initializers to consider.
*/
predicate constructorMightRunOnStack(Constructor constructor) {
exists(ConstructorCall call | call.getTarget() = constructor |
// Call to a constructor from a stack variable's initializer.
exists(StackVariable var | var.getInitializer().getExpr() = call)
or
// Call to a constructor from another constructor which might
// also run on the stack.
constructorMightRunOnStack(call.getEnclosingFunction()) and
(
call instanceof ConstructorDirectInit or
call instanceof ConstructorVirtualInit or
call instanceof ConstructorDelegationInit or
exists(ConstructorFieldInit init | init.getExpr() = call)
)
)
}