Skip to content

Commit e71a39f

Browse files
authored
Merge pull request github#1912 from jbj/tainttracking-ir-1
C++: Stub replacement for security.TaintTracking
2 parents 72db219 + 6912caf commit e71a39f

File tree

2 files changed

+165
-7
lines changed

2 files changed

+165
-7
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import cpp
2+
import semmle.code.cpp.security.Security
3+
private import semmle.code.cpp.ir.dataflow.DataFlow
4+
private import semmle.code.cpp.ir.IR
5+
6+
/**
7+
* A predictable expression is one where an external user can predict
8+
* the value. For example, a literal in the source code is considered
9+
* predictable.
10+
*/
11+
// TODO: Change to use Instruction instead of Expr. Naive attempt breaks
12+
// TaintedAllocationSize qltest.
13+
private predicate predictable(Expr expr) {
14+
expr instanceof Literal
15+
or
16+
exists(BinaryOperation binop | binop = expr |
17+
predictable(binop.getLeftOperand()) and predictable(binop.getRightOperand())
18+
)
19+
or
20+
exists(UnaryOperation unop | unop = expr | predictable(unop.getOperand()))
21+
}
22+
23+
// TODO: remove when `predictable` has an `Instruction` parameter instead of `Expr`.
24+
private predicate predictableInstruction(Instruction instr) {
25+
exists(DataFlow::Node node |
26+
node.asInstruction() = instr and
27+
predictable(node.asExpr())
28+
)
29+
}
30+
31+
private class DefaultTaintTrackingCfg extends DataFlow::Configuration {
32+
DefaultTaintTrackingCfg() { this = "DefaultTaintTrackingCfg" }
33+
34+
override predicate isSource(DataFlow::Node source) { isUserInput(source.asExpr(), _) }
35+
36+
override predicate isSink(DataFlow::Node sink) { any() }
37+
38+
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
39+
instructionTaintStep(n1.asInstruction(), n2.asInstruction())
40+
}
41+
42+
override predicate isBarrier(DataFlow::Node node) {
43+
exists(Variable checkedVar |
44+
accessesVariable(node.asInstruction(), checkedVar) and
45+
hasUpperBoundsCheck(checkedVar)
46+
)
47+
}
48+
}
49+
50+
private predicate accessesVariable(CopyInstruction copy, Variable var) {
51+
exists(VariableAddressInstruction va |
52+
va.getVariable().getAST() = var
53+
|
54+
copy.(StoreInstruction).getDestinationAddress() = va
55+
or
56+
copy.(LoadInstruction).getSourceAddress() = va
57+
)
58+
}
59+
60+
/**
61+
* A variable that has any kind of upper-bound check anywhere in the program
62+
*/
63+
// TODO: This coarse overapproximation, ported from the old taint tracking
64+
// library, could be replaced with an actual semantic check that a particular
65+
// variable _access_ is guarded by an upper-bound check. We probably don't want
66+
// to do this right away since it could expose a lot of FPs that were
67+
// previously suppressed by this predicate by coincidence.
68+
private predicate hasUpperBoundsCheck(Variable var) {
69+
exists(RelationalOperation oper, VariableAccess access |
70+
oper.getLeftOperand() = access and
71+
access.getTarget() = var and
72+
// Comparing to 0 is not an upper bound check
73+
not oper.getRightOperand().getValue() = "0"
74+
)
75+
}
76+
77+
private predicate instructionTaintStep(Instruction i1, Instruction i2) {
78+
// Expressions computed from tainted data are also tainted
79+
i2 = any(CallInstruction call |
80+
isPureFunction(call.getStaticCallTarget().getName()) and
81+
call.getAnArgument() = i1 and
82+
forall(Instruction arg | arg = call.getAnArgument() | arg = i1 or predictableInstruction(arg)) and
83+
// flow through `strlen` tends to cause dubious results, if the length is
84+
// bounded.
85+
not call.getStaticCallTarget().getName() = "strlen"
86+
)
87+
or
88+
// Flow through pointer dereference
89+
i2.(LoadInstruction).getSourceAddress() = i1
90+
or
91+
i2.(UnaryInstruction).getUnary() = i1
92+
or
93+
exists(BinaryInstruction bin |
94+
bin = i2 and
95+
predictableInstruction(i2.getAnOperand().getDef()) and
96+
i1 = i2.getAnOperand().getDef()
97+
)
98+
// TODO: Check that we have flow from `a` to `a[i]`. It may work for constant
99+
// `i` because there is flow through `predictable` `BinaryInstruction` and
100+
// through `LoadInstruction`.
101+
//
102+
// TODO: Flow from argument to return of known functions: Port missing parts
103+
// of `returnArgument` to the `interfaces.Taint` and `interfaces.DataFlow`
104+
// libraries.
105+
//
106+
// TODO: Flow from input argument to output argument of known functions: Port
107+
// missing parts of `copyValueBetweenArguments` to the `interfaces.Taint` and
108+
// `interfaces.DataFlow` libraries and implement call side-effect nodes. This
109+
// will help with the test for `ExecTainted.ql`. The test for
110+
// `TaintedPath.ql` is more tricky because the output arg is a pointer
111+
// addition expression.
112+
}
113+
114+
predicate tainted(Expr source, Element tainted) {
115+
exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink |
116+
cfg.hasFlow(DataFlow::exprNode(source), sink)
117+
|
118+
// TODO: is it more appropriate to use asConvertedExpr here and avoid
119+
// `getConversion*`? Or will that cause us to miss some cases where there's
120+
// flow to a conversion (like a `ReferenceDereferenceExpr`) and we want to
121+
// pretend there was flow to the converted `Expr` for the sake of
122+
// compatibility.
123+
sink.asExpr().getConversion*() = tainted
124+
or
125+
// For compatibility, send flow from arguments to parameters, even for
126+
// functions with no body.
127+
exists(FunctionCall call, int i |
128+
sink.asExpr() = call.getArgument(i) and
129+
tainted = resolveCall(call).getParameter(i)
130+
)
131+
or
132+
// For compatibility, send flow into a `Variable` if there is flow to any
133+
// Load or Store of that variable.
134+
exists(CopyInstruction copy |
135+
copy.getSourceValue() = sink.asInstruction() and
136+
accessesVariable(copy, tainted) and
137+
not hasUpperBoundsCheck(tainted)
138+
)
139+
or
140+
// For compatibility, send flow into a `NotExpr` even if it's part of a
141+
// short-circuiting condition and thus might get skipped.
142+
tainted.(NotExpr).getOperand() = sink.asExpr()
143+
)
144+
}
145+
146+
predicate taintedIncludingGlobalVars(Expr source, Element tainted, string globalVar) {
147+
tainted(source, tainted) and
148+
// TODO: Find a way to emulate how `security.TaintTracking` reports the last
149+
// global variable that taint has passed through. Also make sure we emulate
150+
// its behavior for interprocedural flow through globals.
151+
globalVar = ""
152+
}
153+
154+
GlobalOrNamespaceVariable globalVarFromId(string id) {
155+
// TODO: Implement this when `taintedIncludingGlobalVars` has support for
156+
// global variables.
157+
none()
158+
}
159+
160+
Function resolveCall(Call call) {
161+
// TODO: improve virtual dispatch. This will help in the test for
162+
// `UncontrolledProcessOperation.ql`.
163+
result = call.getTarget()
164+
}

cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -330,13 +330,7 @@ GlobalOrNamespaceVariable globalVarFromId(string id) {
330330
* A variable that has any kind of upper-bound check anywhere in the program
331331
*/
332332
private predicate hasUpperBoundsCheck(Variable var) {
333-
exists(BinaryOperation oper, VariableAccess access |
334-
(
335-
oper.getOperator() = "<" or
336-
oper.getOperator() = "<=" or
337-
oper.getOperator() = ">" or
338-
oper.getOperator() = ">="
339-
) and
333+
exists(RelationalOperation oper, VariableAccess access |
340334
oper.getLeftOperand() = access and
341335
access.getTarget() = var and
342336
// Comparing to 0 is not an upper bound check

0 commit comments

Comments
 (0)