Skip to content

Commit de4e2a2

Browse files
committed
C++: Stub replacement for security.TaintTracking
This commit adds a `semmle.code.cpp.ir.dataflow.DefaultTaintTracking` library that's API-compatible with the `semmle.code.cpp.security.TaintTracking` library. The new library is implemented on top of the IR data flow library. The idea is to evolve this library until it can replace `semmle.code.cpp.security.TaintTracking` without decreasing our SAMATE score. Then we'll have the IR in production use, and we will have one less taint-tracking library in production.
1 parent 2806a52 commit de4e2a2

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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+
private predicate hasUpperBoundsCheck(Variable var) {
64+
exists(BinaryOperation oper, VariableAccess access |
65+
(
66+
oper.getOperator() = "<" or
67+
oper.getOperator() = "<=" or
68+
oper.getOperator() = ">" or
69+
oper.getOperator() = ">="
70+
) and
71+
oper.getLeftOperand() = access and
72+
access.getTarget() = var and
73+
// Comparing to 0 is not an upper bound check
74+
not oper.getRightOperand().getValue() = "0"
75+
)
76+
}
77+
78+
private predicate instructionTaintStep(Instruction i1, Instruction i2) {
79+
// Expressions computed from tainted data are also tainted
80+
i2 = any(CallInstruction call |
81+
isPureFunction(call.getStaticCallTarget().getName()) and
82+
call.getAnArgument() = i1 and
83+
forall(Instruction arg | arg = call.getAnArgument() | arg = i1 or predictableInstruction(arg)) and
84+
// flow through `strlen` tends to cause dubious results, if the length is
85+
// bounded.
86+
not call.getStaticCallTarget().getName() = "strlen"
87+
)
88+
or
89+
// Flow through pointer dereference
90+
i2.(LoadInstruction).getSourceAddress() = i1
91+
or
92+
i2.(UnaryInstruction).getUnary() = i1
93+
or
94+
exists(BinaryInstruction bin |
95+
bin = i2 and
96+
predictableInstruction(i2.getAnOperand().getDef()) and
97+
i1 = i2.getAnOperand().getDef()
98+
)
99+
// TODO: Check that we have flow from `a` to `a[i]`. It may work for constant
100+
// `i` because there is flow through `predictable` `BinaryInstruction` and
101+
// through `LoadInstruction`.
102+
//
103+
// TODO: Flow from argument to return of known functions: Port missing parts
104+
// of `returnArgument` to the `interfaces.Taint` library.
105+
//
106+
// TODO: Flow from input argument to output argument of known functions: Port
107+
// missing parts of `copyValueBetweenArguments` to the `interfaces.Taint`
108+
// library and implement call side-effect nodes. This will help with the test
109+
// for `ExecTainted.ql`. The test for `TaintedPath.ql` is more tricky because
110+
// the output arg is a pointer addition expression.
111+
}
112+
113+
predicate tainted(Expr source, Element tainted) {
114+
exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink |
115+
cfg.hasFlow(DataFlow::exprNode(source), sink)
116+
|
117+
sink.asExpr().getConversion*() = tainted
118+
or
119+
// For compatibility, send flow from arguments to parameters, even for
120+
// functions with no body.
121+
exists(FunctionCall call, int i |
122+
sink.asExpr() = call.getArgument(i) and
123+
tainted = resolveCall(call).getParameter(i)
124+
)
125+
or
126+
// For compatibility, send flow into a `Variable` if there is flow to any
127+
// Load or Store of that variable.
128+
exists(CopyInstruction copy |
129+
copy.getSourceValue() = sink.asInstruction() and
130+
accessesVariable(copy, tainted) and
131+
not hasUpperBoundsCheck(tainted)
132+
)
133+
or
134+
// For compatibility, send flow into a `NotExpr` even if it's part of a
135+
// short-circuiting condition and thus might get skipped.
136+
tainted.(NotExpr).getOperand() = sink.asExpr()
137+
)
138+
}
139+
140+
predicate taintedIncludingGlobalVars(Expr source, Element tainted, string globalVar) {
141+
tainted(source, tainted) and
142+
// TODO: Find a way to emulate how `security.TaintTracking` reports the last
143+
// global variable that taint has passed through. Also make sure we emulate
144+
// its behavior for interprocedural flow through globals.
145+
globalVar = ""
146+
}
147+
148+
GlobalOrNamespaceVariable globalVarFromId(string id) {
149+
// TODO: Implement this when `taintedIncludingGlobalVars` has support for
150+
// global variables.
151+
none()
152+
}
153+
154+
Function resolveCall(Call call) {
155+
// TODO: improve virtual dispatch. This will help in the test for
156+
// `UncontrolledProcessOperation.ql`.
157+
result = call.getTarget()
158+
}

0 commit comments

Comments
 (0)