Skip to content

Commit 0469df1

Browse files
authored
Merge pull request github#12338 from MathiasVP/expr-sanitizer-for-exec-tainted
C++: Speedup `cpp/command-line-injection`
2 parents a96145a + 075a83c commit 0469df1

File tree

1 file changed

+32
-30
lines changed

1 file changed

+32
-30
lines changed

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

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ import semmle.code.cpp.models.implementations.Strcat
2525
import DataFlow::PathGraph
2626

2727
/**
28-
* Holds if `fst` is a string that is used in a format or concatenation function resulting in `snd`,
29-
* and is *not* placed at the start of the resulting string. This indicates that the author did not
30-
* expect `fst` to control what program is run if the resulting string is eventually interpreted as
31-
* a command line, for example as an argument to `system`.
28+
* Holds if `incoming` is a string that is used in a format or concatenation function resulting
29+
* in `outgoing`, and is *not* placed at the start of the resulting string. This indicates that
30+
* the author did not expect `incoming` to control what program is run if the resulting string
31+
* is eventually interpreted as a command line, for example as an argument to `system`.
3232
*/
33-
predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) {
33+
predicate interestingConcatenation(DataFlow::Node incoming, DataFlow::Node outgoing) {
3434
exists(FormattingFunctionCall call, int index, FormatLiteral literal |
35-
fst.asIndirectArgument() = call.getConversionArgument(index) and
36-
snd.asDefiningArgument() = call.getOutputArgument(false) and
35+
incoming.asIndirectArgument() = call.getConversionArgument(index) and
36+
outgoing.asDefiningArgument() = call.getOutputArgument(false) and
3737
literal = call.getFormat() and
3838
not literal.getConvSpecOffset(index) = 0 and
3939
literal.getConversionChar(index) = ["s", "S"]
@@ -42,16 +42,16 @@ predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) {
4242
// strcat and friends
4343
exists(StrcatFunction strcatFunc, Call call |
4444
call.getTarget() = strcatFunc and
45-
fst.asIndirectArgument() = call.getArgument(strcatFunc.getParamSrc()) and
46-
snd.asDefiningArgument() = call.getArgument(strcatFunc.getParamDest())
45+
incoming.asIndirectArgument() = call.getArgument(strcatFunc.getParamSrc()) and
46+
outgoing.asDefiningArgument() = call.getArgument(strcatFunc.getParamDest())
4747
)
4848
or
4949
exists(Call call, Operator op |
5050
call.getTarget() = op and
5151
op.hasQualifiedName("std", "operator+") and
5252
op.getType().(UserType).hasQualifiedName("std", "basic_string") and
53-
fst.asIndirectArgument() = call.getArgument(1) and // left operand
54-
call = snd.asInstruction().getUnconvertedResultExpression()
53+
incoming.asIndirectArgument() = call.getArgument(1) and // left operand
54+
call = outgoing.asInstruction().getUnconvertedResultExpression()
5555
)
5656
}
5757

@@ -60,22 +60,23 @@ class ConcatState extends DataFlow::FlowState {
6060
}
6161

6262
class ExecState extends DataFlow::FlowState {
63-
DataFlow::Node fst;
64-
DataFlow::Node snd;
63+
DataFlow::Node incoming;
64+
DataFlow::Node outgoing;
6565

6666
ExecState() {
6767
this =
68-
"ExecState (" + fst.getLocation() + " | " + fst + ", " + snd.getLocation() + " | " + snd + ")" and
69-
interestingConcatenation(pragma[only_bind_into](fst), pragma[only_bind_into](snd))
68+
"ExecState (" + incoming.getLocation() + " | " + incoming + ", " + outgoing.getLocation() +
69+
" | " + outgoing + ")" and
70+
interestingConcatenation(pragma[only_bind_into](incoming), pragma[only_bind_into](outgoing))
7071
}
7172

72-
DataFlow::Node getFstNode() { result = fst }
73+
DataFlow::Node getIncomingNode() { result = incoming }
7374

74-
DataFlow::Node getSndNode() { result = snd }
75+
DataFlow::Node getOutgoingNode() { result = outgoing }
7576

7677
/** Holds if this is a possible `ExecState` for `sink`. */
7778
predicate isFeasibleForSink(DataFlow::Node sink) {
78-
any(ExecStateConfiguration conf).hasFlow(snd, sink)
79+
any(ExecStateConfiguration conf).hasFlow(outgoing, sink)
7980
}
8081
}
8182

@@ -84,6 +85,12 @@ predicate isSinkImpl(DataFlow::Node sink, Expr command, string callChain) {
8485
shellCommand(command, callChain)
8586
}
8687

88+
predicate isSanitizerImpl(DataFlow::Node node) {
89+
node.asExpr().getUnspecifiedType() instanceof IntegralType
90+
or
91+
node.asExpr().getUnspecifiedType() instanceof FloatingPointType
92+
}
93+
8794
/**
8895
* A `TaintTracking` configuration that's used to find the relevant `ExecState`s for a
8996
* given sink. This avoids a cartesian product between all sinks and all `ExecState`s in
@@ -93,11 +100,13 @@ class ExecStateConfiguration extends TaintTracking2::Configuration {
93100
ExecStateConfiguration() { this = "ExecStateConfiguration" }
94101

95102
override predicate isSource(DataFlow::Node source) {
96-
exists(ExecState state | state.getSndNode() = source)
103+
any(ExecState state).getOutgoingNode() = source
97104
}
98105

99106
override predicate isSink(DataFlow::Node sink) { isSinkImpl(sink, _, _) }
100107

108+
override predicate isSanitizer(DataFlow::Node node) { isSanitizerImpl(node) }
109+
101110
override predicate isSanitizerOut(DataFlow::Node node) {
102111
isSink(node, _) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
103112
}
@@ -121,18 +130,11 @@ class ExecTaintConfiguration extends TaintTracking::Configuration {
121130
DataFlow::FlowState state2
122131
) {
123132
state1 instanceof ConcatState and
124-
state2.(ExecState).getFstNode() = node1 and
125-
state2.(ExecState).getSndNode() = node2
133+
state2.(ExecState).getIncomingNode() = node1 and
134+
state2.(ExecState).getOutgoingNode() = node2
126135
}
127136

128-
override predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) {
129-
(
130-
node.asInstruction().getResultType() instanceof IntegralType
131-
or
132-
node.asInstruction().getResultType() instanceof FloatingPointType
133-
) and
134-
state instanceof ConcatState
135-
}
137+
override predicate isSanitizer(DataFlow::Node node) { isSanitizerImpl(node) }
136138

137139
override predicate isSanitizerOut(DataFlow::Node node) {
138140
isSink(node, _) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
@@ -146,7 +148,7 @@ where
146148
conf.hasFlowPath(sourceNode, sinkNode) and
147149
taintCause = sourceNode.getNode().(FlowSource).getSourceType() and
148150
isSinkImpl(sinkNode.getNode(), command, callChain) and
149-
concatResult = sinkNode.getState().(ExecState).getSndNode()
151+
concatResult = sinkNode.getState().(ExecState).getOutgoingNode()
150152
select command, sourceNode, sinkNode,
151153
"This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to "
152154
+ callChain + ".", sourceNode, "user input (" + taintCause + ")", concatResult,

0 commit comments

Comments
 (0)