Skip to content

Commit 0eab54d

Browse files
authored
Merge pull request #8491 from jketema/command-line-injection-with-flow-state
C++: Use flow states in `cpp/command-line-injection`
2 parents bbf60b8 + a84ee50 commit 0eab54d

File tree

5 files changed

+115
-152
lines changed

5 files changed

+115
-152
lines changed

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

Lines changed: 45 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import semmle.code.cpp.security.Security
1919
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
2020
import semmle.code.cpp.ir.IR
2121
import semmle.code.cpp.ir.dataflow.TaintTracking
22-
import semmle.code.cpp.ir.dataflow.TaintTracking2
2322
import semmle.code.cpp.security.FlowSources
2423
import semmle.code.cpp.models.implementations.Strcat
24+
import DataFlow::PathGraph
2525

2626
Expr sinkAsArgumentIndirection(DataFlow::Node sink) {
2727
result =
@@ -66,154 +66,70 @@ predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) {
6666
)
6767
}
6868

69-
class TaintToConcatenationConfiguration extends TaintTracking::Configuration {
70-
TaintToConcatenationConfiguration() { this = "TaintToConcatenationConfiguration" }
71-
72-
override predicate isSource(DataFlow::Node source) { source instanceof FlowSource }
73-
74-
override predicate isSink(DataFlow::Node sink) { interestingConcatenation(sink, _) }
75-
76-
override predicate isSanitizer(DataFlow::Node node) {
77-
node.asInstruction().getResultType() instanceof IntegralType
78-
or
79-
node.asInstruction().getResultType() instanceof FloatingPointType
80-
}
69+
class ConcatState extends DataFlow::FlowState {
70+
ConcatState() { this = "ConcatState" }
8171
}
8272

83-
class ExecTaintConfiguration extends TaintTracking2::Configuration {
84-
ExecTaintConfiguration() { this = "ExecTaintConfiguration" }
73+
class ExecState extends DataFlow::FlowState {
74+
DataFlow::Node fst;
75+
DataFlow::Node snd;
8576

86-
override predicate isSource(DataFlow::Node source) {
87-
exists(DataFlow::Node prevSink, TaintToConcatenationConfiguration conf |
88-
conf.hasFlow(_, prevSink) and
89-
interestingConcatenation(prevSink, source)
90-
)
77+
ExecState() {
78+
this =
79+
"ExecState (" + fst.getLocation() + " | " + fst + ", " + snd.getLocation() + " | " + snd + ")" and
80+
interestingConcatenation(fst, snd)
9181
}
9282

93-
override predicate isSink(DataFlow::Node sink) {
94-
shellCommand(sinkAsArgumentIndirection(sink), _)
95-
}
83+
DataFlow::Node getFstNode() { result = fst }
9684

97-
override predicate isSanitizerOut(DataFlow::Node node) {
98-
isSink(node) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
99-
}
85+
DataFlow::Node getSndNode() { result = snd }
10086
}
10187

102-
module StitchedPathGraph {
103-
// There's a different PathNode class for each DataFlowImplN.qll, so we can't simply combine the
104-
// PathGraph predicates directly. Instead, we use a newtype so there's a single type that
105-
// contains both sets of PathNodes.
106-
newtype TMergedPathNode =
107-
TPathNode1(DataFlow::PathNode node) or
108-
TPathNode2(DataFlow2::PathNode node)
109-
110-
// this wraps the toString and location predicates so we can use the merged node type in a
111-
// selection
112-
class MergedPathNode extends TMergedPathNode {
113-
string toString() {
114-
exists(DataFlow::PathNode n |
115-
this = TPathNode1(n) and
116-
result = n.toString()
117-
)
118-
or
119-
exists(DataFlow2::PathNode n |
120-
this = TPathNode2(n) and
121-
result = n.toString()
122-
)
123-
}
124-
125-
DataFlow::Node getNode() {
126-
exists(DataFlow::PathNode n |
127-
this = TPathNode1(n) and
128-
result = n.getNode()
129-
)
130-
or
131-
exists(DataFlow2::PathNode n |
132-
this = TPathNode2(n) and
133-
result = n.getNode()
134-
)
135-
}
136-
137-
DataFlow::PathNode getPathNode1() { this = TPathNode1(result) }
88+
class ExecTaintConfiguration extends TaintTracking::Configuration {
89+
ExecTaintConfiguration() { this = "ExecTaintConfiguration" }
13890

139-
DataFlow2::PathNode getPathNode2() { this = TPathNode2(result) }
91+
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
92+
source instanceof FlowSource and
93+
state instanceof ConcatState
94+
}
14095

141-
predicate hasLocationInfo(
142-
string filepath, int startline, int startcolumn, int endline, int endcolumn
143-
) {
144-
exists(DataFlow::PathNode n |
145-
this = TPathNode1(n) and
146-
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
147-
)
148-
or
149-
exists(DataFlow2::PathNode n |
150-
this = TPathNode2(n) and
151-
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
152-
)
153-
}
96+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
97+
shellCommand(sinkAsArgumentIndirection(sink), _) and
98+
state instanceof ExecState
15499
}
155100

156-
query predicate edges(MergedPathNode a, MergedPathNode b) {
157-
exists(DataFlow::PathNode an, DataFlow::PathNode bn |
158-
a = TPathNode1(an) and
159-
b = TPathNode1(bn) and
160-
DataFlow::PathGraph::edges(an, bn)
161-
)
162-
or
163-
exists(DataFlow2::PathNode an, DataFlow2::PathNode bn |
164-
a = TPathNode2(an) and
165-
b = TPathNode2(bn) and
166-
DataFlow2::PathGraph::edges(an, bn)
167-
)
168-
or
169-
// This is where paths from the two configurations are connected. `interestingConcatenation`
170-
// is the only thing in this module that's actually specific to the query - everything else is
171-
// just using types and predicates from the DataFlow library.
172-
interestingConcatenation(a.getNode(), b.getNode()) and
173-
a instanceof TPathNode1 and
174-
b instanceof TPathNode2
101+
override predicate isAdditionalTaintStep(
102+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
103+
DataFlow::FlowState state2
104+
) {
105+
state1 instanceof ConcatState and
106+
state2.(ExecState).getFstNode() = node1 and
107+
state2.(ExecState).getSndNode() = node2
175108
}
176109

177-
query predicate nodes(MergedPathNode mpn, string key, string val) {
178-
// here we just need the union of the underlying `nodes` predicates
179-
exists(DataFlow::PathNode n |
180-
mpn = TPathNode1(n) and
181-
DataFlow::PathGraph::nodes(n, key, val)
182-
)
183-
or
184-
exists(DataFlow2::PathNode n |
185-
mpn = TPathNode2(n) and
186-
DataFlow2::PathGraph::nodes(n, key, val)
187-
)
110+
override predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) {
111+
(
112+
node.asInstruction().getResultType() instanceof IntegralType
113+
or
114+
node.asInstruction().getResultType() instanceof FloatingPointType
115+
) and
116+
state instanceof ConcatState
188117
}
189118

190-
query predicate subpaths(
191-
MergedPathNode arg, MergedPathNode par, MergedPathNode ret, MergedPathNode out
192-
) {
193-
// just forward subpaths from the underlying libraries. This might be slightly awkward when
194-
// the concatenation is deep in a call chain.
195-
DataFlow::PathGraph::subpaths(arg.getPathNode1(), par.getPathNode1(), ret.getPathNode1(),
196-
out.getPathNode1())
197-
or
198-
DataFlow2::PathGraph::subpaths(arg.getPathNode2(), par.getPathNode2(), ret.getPathNode2(),
199-
out.getPathNode2())
119+
override predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowState state) {
120+
isSink(node, state) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
200121
}
201122
}
202123

203-
import StitchedPathGraph
204-
205124
from
206-
DataFlow::PathNode sourceNode, DataFlow::PathNode concatSink, DataFlow2::PathNode concatSource,
207-
DataFlow2::PathNode sinkNode, string taintCause, string callChain,
208-
TaintToConcatenationConfiguration conf1, ExecTaintConfiguration conf2
125+
ExecTaintConfiguration conf, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode,
126+
string taintCause, string callChain, DataFlow::Node concatResult
209127
where
128+
conf.hasFlowPath(sourceNode, sinkNode) and
210129
taintCause = sourceNode.getNode().(FlowSource).getSourceType() and
211-
conf1.hasFlowPath(sourceNode, concatSink) and
212-
interestingConcatenation(concatSink.getNode(), concatSource.getNode()) and // this loses call context
213-
conf2.hasFlowPath(concatSource, sinkNode) and
214-
shellCommand(sinkAsArgumentIndirection(sinkNode.getNode()), callChain)
215-
select sinkAsArgumentIndirection(sinkNode.getNode()), TPathNode1(sourceNode).(MergedPathNode),
216-
TPathNode2(sinkNode).(MergedPathNode),
130+
shellCommand(sinkAsArgumentIndirection(sinkNode.getNode()), callChain) and
131+
concatResult = sinkNode.getState().(ExecState).getSndNode()
132+
select sinkAsArgumentIndirection(sinkNode.getNode()), sourceNode, sinkNode,
217133
"This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to "
218-
+ callChain, sourceNode, "user input (" + taintCause + ")", concatSource,
219-
concatSource.toString()
134+
+ callChain, sourceNode, "user input (" + taintCause + ")", concatResult,
135+
concatResult.toString()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `cpp/command-line-injection` query now takes into account calling contexts across string concatenations. This removes false positives due to mismatched calling contexts before and after string concatenations.

cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ edges
33
| tests.cpp:33:34:33:39 | call to getenv | tests.cpp:38:39:38:49 | environment indirection |
44
| tests.cpp:38:25:38:36 | strncat output argument | tests.cpp:26:15:26:23 | ReturnValue |
55
| tests.cpp:38:39:38:49 | environment indirection | tests.cpp:38:25:38:36 | strncat output argument |
6-
| tests.cpp:38:39:38:49 | environment indirection | tests.cpp:38:25:38:36 | strncat output argument |
76
| tests.cpp:51:12:51:20 | call to badSource | tests.cpp:53:16:53:19 | data indirection |
87
nodes
98
| tests.cpp:26:15:26:23 | ReturnValue | semmle.label | ReturnValue |

0 commit comments

Comments
 (0)