Skip to content

Commit e874fbb

Browse files
committed
C++: Add path stitching in ExecTainted.ql
1 parent 5dc6e13 commit e874fbb

File tree

1 file changed

+101
-21
lines changed

1 file changed

+101
-21
lines changed

cpp/ql/src/Security/CWE/CWE-078/ExecTainted.ql

Lines changed: 101 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import semmle.code.cpp.ir.IR
2222
import semmle.code.cpp.security.FlowSources
2323
import semmle.code.cpp.models.implementations.Strcat
2424

25-
import DataFlow::PathGraph
26-
2725
Expr sinkAsArgumentIndirection(DataFlow::Node sink) {
2826
result =
2927
sink.asOperand()
@@ -54,7 +52,7 @@ predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) {
5452
rse.getArgumentDef() = call.getArgument(strcatFunc.getParamSrc()) and
5553
fst.asOperand() = rse.getSideEffectOperand() and
5654
snd.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
57-
call.getArgument(strcatFunc.getParamDest())
55+
call.getArgument(strcatFunc.getParamDest())
5856
)
5957
or
6058
exists(CallInstruction call, Operator op, ReadSideEffectInstruction rse |
@@ -63,22 +61,17 @@ predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) {
6361
op.getType().(UserType).hasQualifiedName("std", "basic_string") and
6462
call.getArgument(1) = rse.getArgumentOperand().getAnyDef() and // left operand
6563
fst.asOperand() = rse.getSideEffectOperand() and
66-
call =
67-
snd.asInstruction()
64+
call = snd.asInstruction()
6865
)
6966
}
7067

7168
// TODO: maybe we can drop this?
7269
class TaintToConcatenationConfiguration extends TaintTracking::Configuration {
7370
TaintToConcatenationConfiguration() { this = "TaintToConcatenationConfiguration" }
7471

75-
override predicate isSource(DataFlow::Node source) {
76-
source instanceof FlowSource
77-
}
72+
override predicate isSource(DataFlow::Node source) { source instanceof FlowSource }
7873

79-
override predicate isSink(DataFlow::Node sink) {
80-
interestingConcatenation(sink, _)
81-
}
74+
override predicate isSink(DataFlow::Node sink) { interestingConcatenation(sink, _) }
8275

8376
override predicate isSanitizer(DataFlow::Node node) {
8477
node.asInstruction().getResultType() instanceof IntegralType
@@ -90,9 +83,7 @@ class TaintToConcatenationConfiguration extends TaintTracking::Configuration {
9083
class ExecTaintConfiguration extends TaintTracking2::Configuration {
9184
ExecTaintConfiguration() { this = "ExecTaintConfiguration" }
9285

93-
override predicate isSource(DataFlow::Node source) {
94-
interestingConcatenation(_, source)
95-
}
86+
override predicate isSource(DataFlow::Node source) { interestingConcatenation(_, source) }
9687

9788
override predicate isSink(DataFlow::Node sink) {
9889
shellCommand(sinkAsArgumentIndirection(sink), _)
@@ -103,16 +94,105 @@ class ExecTaintConfiguration extends TaintTracking2::Configuration {
10394
}
10495
}
10596

97+
module StitchedPathGraph {
98+
// There's a different PathNode class for each DataFlowImplN.qll, so we can't simply combine the
99+
// PathGraph predicates directly. Instead, we use a newtype so there's a single type that
100+
// contains both sets of PathNodes.
101+
newtype TMergedPathNode =
102+
TPathNode1(DataFlow::PathNode node) or
103+
TPathNode2(DataFlow2::PathNode node)
104+
105+
// this wraps the toString and location predicates so we can use the merged node type in a
106+
// selection
107+
class MergedPathNode extends TMergedPathNode {
108+
string toString() {
109+
exists(DataFlow::PathNode n |
110+
this = TPathNode1(n) and
111+
result = n.toString()
112+
)
113+
or
114+
exists(DataFlow2::PathNode n |
115+
this = TPathNode2(n) and
116+
result = n.toString()
117+
)
118+
}
119+
120+
DataFlow::Node getNode() {
121+
exists(DataFlow::PathNode n |
122+
this = TPathNode1(n) and
123+
result = n.getNode()
124+
)
125+
or
126+
exists(DataFlow2::PathNode n |
127+
this = TPathNode2(n) and
128+
result = n.getNode()
129+
)
130+
}
131+
132+
predicate hasLocationInfo(
133+
string filepath, int startline, int startcolumn, int endline, int endcolumn
134+
) {
135+
exists(DataFlow::PathNode n |
136+
this = TPathNode1(n) and
137+
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
138+
)
139+
or
140+
exists(DataFlow2::PathNode n |
141+
this = TPathNode2(n) and
142+
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
143+
)
144+
}
145+
}
146+
147+
query predicate edges(MergedPathNode a, MergedPathNode b) {
148+
exists(DataFlow::PathNode an, DataFlow::PathNode bn |
149+
a = TPathNode1(an) and
150+
b = TPathNode1(bn) and
151+
DataFlow::PathGraph::edges(an, bn)
152+
)
153+
or
154+
exists(DataFlow2::PathNode an, DataFlow2::PathNode bn |
155+
a = TPathNode2(an) and
156+
b = TPathNode2(bn) and
157+
DataFlow2::PathGraph::edges(an, bn)
158+
)
159+
or
160+
// This is where paths from the two configurations are connected. `interestingConcatenation`
161+
// is the only thing in this module that's actually specific to the query - everything else is
162+
// just using types and predicates from the DataFlow library.
163+
interestingConcatenation(a.getNode(), b.getNode()) and
164+
a instanceof TPathNode1 and
165+
b instanceof TPathNode2
166+
}
167+
168+
query predicate nodes(MergedPathNode mpn, string key, string val) {
169+
// here we just need the union of the underlying `nodes` predicates
170+
exists(DataFlow::PathNode n |
171+
mpn = TPathNode1(n) and
172+
DataFlow::PathGraph::nodes(n, key, val)
173+
)
174+
or
175+
exists(DataFlow2::PathNode n |
176+
mpn = TPathNode2(n) and
177+
DataFlow2::PathGraph::nodes(n, key, val)
178+
)
179+
}
180+
}
181+
182+
import StitchedPathGraph
183+
106184
from
107-
DataFlow::PathNode sourceNode, DataFlow::PathNode concatSink, DataFlow2::PathNode concatSource, DataFlow2::PathNode sinkNode, string taintCause, string callChain,
185+
DataFlow::PathNode sourceNode, DataFlow::PathNode concatSink, DataFlow2::PathNode concatSource,
186+
DataFlow2::PathNode sinkNode, string taintCause, string callChain,
108187
TaintToConcatenationConfiguration conf1, ExecTaintConfiguration conf2
109188
where
110189
taintCause = sourceNode.getNode().(FlowSource).getSourceType() and
111-
conf1.hasFlowPath(sourceNode, concatSink) and // TODO: can we link these better?
112-
interestingConcatenation(concatSink.getNode(), concatSource.getNode()) and
190+
conf1.hasFlowPath(sourceNode, concatSink) and
191+
interestingConcatenation(concatSink.getNode(), concatSource.getNode()) and // this loses call context
113192
conf2.hasFlowPath(concatSource, sinkNode) and
114193
shellCommand(sinkAsArgumentIndirection(sinkNode.getNode()), callChain)
115-
select sinkAsArgumentIndirection(sinkNode.getNode()), sourceNode, sinkNode,
116-
"This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to " + callChain, sourceNode,
117-
"user input (" + taintCause + ")", concatSource, concatSource.toString()
118-
194+
select sinkAsArgumentIndirection(sinkNode.getNode()), TPathNode1(sourceNode).(MergedPathNode),
195+
TPathNode2(sinkNode).(MergedPathNode),
196+
"This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to "
197+
+ callChain, sourceNode, "user input (" + taintCause + ")", concatSource,
198+
concatSource.toString()

0 commit comments

Comments
 (0)