Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6f408f9

Browse files
Robert MarshRobert Marsh
authored andcommitted
C++: Refactor ExecTainted.ql to need concatenation
This makes ExecTainted report results only when the tainted value does not become the start of the string which is eventually run as a shell command. The theory is that those cases are likely to be deliberate, and part of the expected threat model of the program (e.g. $CC in make). This lines up better with the results I considered fixable true positives in LGTM testing
1 parent 8f4df86 commit 6f408f9

4 files changed

Lines changed: 509 additions & 11 deletions

File tree

cpp/ql/lib/semmle/code/cpp/commons/Printf.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,13 @@ class FormattingFunctionCall extends Expr {
253253
// format arguments must be known
254254
exists(getTarget().(FormattingFunction).getFirstFormatArgumentIndex())
255255
}
256+
257+
/**
258+
*
259+
*/
260+
Expr getOutputArgument(boolean isStream) {
261+
result = this.(Call).getArgument(this.(Call).getTarget().(FormattingFunction).getOutputParameterIndex(isStream))
262+
}
256263
}
257264

258265
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ int main(int argc, char** argv) {
99
system(command1);
1010
}
1111

12-
{
12+
{
1313
// GOOD: the user string is encoded by a library routine.
1414
char userNameQuoted[1000] = {0};
1515
encodeShellString(userNameQuoted, 1000, userName);

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

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @description Using user-supplied data in an OS command, without
44
* neutralizing special elements, can make code vulnerable
55
* to command injection.
6-
* @kind problem
6+
* @kind path-problem
77
* @problem.severity error
88
* @security-severity 9.8
99
* @precision low
@@ -16,13 +16,113 @@
1616
import cpp
1717
import semmle.code.cpp.security.CommandExecution
1818
import semmle.code.cpp.security.Security
19-
import semmle.code.cpp.security.TaintTracking
19+
import semmle.code.cpp.ir.dataflow.TaintTracking
20+
import semmle.code.cpp.ir.dataflow.TaintTracking2
21+
import semmle.code.cpp.ir.IR
22+
import semmle.code.cpp.security.FlowSources
23+
import semmle.code.cpp.models.implementations.Strcat
2024

21-
from Expr taintedArg, Expr taintSource, string taintCause, string callChain
25+
Expr sinkAsArgumentIndirection(DataFlow::Node sink) {
26+
result =
27+
sink.asOperand()
28+
.(SideEffectOperand)
29+
.getAddressOperand()
30+
.getAnyDef()
31+
.getUnconvertedResultExpression()
32+
}
33+
34+
predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) {
35+
exists(FormattingFunctionCall call, int index, FormatLiteral literal |
36+
sinkAsArgumentIndirection(fst) = call.getConversionArgument(index) and
37+
snd.asDefiningArgument() = call.getOutputArgument(false) and
38+
literal = call.getFormat() and
39+
not literal.getConvSpecOffset(index) = 0 and
40+
(
41+
literal.getConversionType(index) instanceof CharPointerType
42+
or
43+
literal.getConversionType(index).(PointerType).getBaseType() instanceof Wchar_t
44+
)
45+
)
46+
or
47+
// strcat and friends
48+
exists(StrcatFunction strcatFunc, CallInstruction call, ReadSideEffectInstruction rse |
49+
call.getStaticCallTarget() = strcatFunc and
50+
rse.getArgumentDef() = call.getArgument(strcatFunc.getParamSrc()) and
51+
fst.asOperand() = rse.getSideEffectOperand() and
52+
snd.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
53+
call.getArgument(strcatFunc.getParamDest())
54+
)
55+
or
56+
exists(CallInstruction call, Operator op, ReadSideEffectInstruction rse |
57+
call.getStaticCallTarget() = op and
58+
op.hasQualifiedName("std", "operator+") and
59+
op.getType().(UserType).hasQualifiedName("std", "basic_string") and
60+
call.getArgument(1) = rse.getArgumentOperand().getAnyDef() and // left operand
61+
fst.asOperand() = rse.getSideEffectOperand() and
62+
call =
63+
snd.asInstruction()
64+
)
65+
}
66+
67+
// TODO: maybe we can drop this?
68+
class TaintToConcatenationConfiguration extends TaintTracking::Configuration {
69+
TaintToConcatenationConfiguration() { this = "TaintToConcatenationConfiguration" }
70+
71+
override predicate isSource(DataFlow::Node source) {
72+
source instanceof FlowSource
73+
}
74+
75+
override predicate isSink(DataFlow::Node sink) {
76+
interestingConcatenation(sink, _)
77+
}
78+
79+
override int explorationLimit() {
80+
result = 10
81+
}
82+
}
83+
84+
class ExecTaintConfiguration extends TaintTracking::Configuration {
85+
ExecTaintConfiguration() { this = "ExecTaintConfiguration" }
86+
87+
override predicate isSource(DataFlow::Node source) {
88+
interestingConcatenation(_, source)
89+
}
90+
91+
override predicate isSink(DataFlow::Node sink) {
92+
shellCommand(sinkAsArgumentIndirection(sink), _)
93+
}
94+
95+
override predicate isSanitizerOut(DataFlow::Node node) {
96+
isSink(node) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers
97+
}
98+
}
99+
100+
query predicate nodes = DataFlow::PathGraph::nodes/3;
101+
102+
query predicate edges(DataFlow::PathNode a, DataFlow::PathNode b) {
103+
DataFlow::PathGraph::edges(a, b) or
104+
interestingConcatenation(a.getNode(), b.getNode()) and
105+
a.getConfiguration() instanceof TaintToConcatenationConfiguration and
106+
b.getConfiguration() instanceof ExecTaintConfiguration
107+
}
108+
109+
query predicate pathExplore(DataFlow::PartialPathNode source, DataFlow::PartialPathNode node, int dist) {
110+
any(TaintToConcatenationConfiguration cfg).hasPartialFlow(source, node, dist)
111+
}
112+
113+
query predicate pathExploreRev(DataFlow::PartialPathNode node, DataFlow::PartialPathNode sink, int dist) {
114+
any(TaintToConcatenationConfiguration cfg).hasPartialFlowRev(node, sink, dist)
115+
}
116+
117+
from
118+
DataFlow::PathNode sourceNode, DataFlow::PathNode concatSink, DataFlow::PathNode concatSource, DataFlow::PathNode sinkNode, string taintCause, string callChain,
119+
TaintToConcatenationConfiguration conf1, ExecTaintConfiguration conf2
22120
where
23-
shellCommand(taintedArg, callChain) and
24-
tainted(taintSource, taintedArg) and
25-
isUserInput(taintSource, taintCause)
26-
select taintedArg,
27-
"This argument to an OS command is derived from $@ and then passed to " + callChain, taintSource,
28-
"user input (" + taintCause + ")"
121+
taintCause = sourceNode.getNode().(FlowSource).getSourceType() and
122+
conf1.hasFlowPath(sourceNode, concatSink) and // TODO: can we link these better?
123+
interestingConcatenation(concatSink.getNode(), concatSource.getNode()) and
124+
conf2.hasFlowPath(concatSource, sinkNode) and
125+
shellCommand(sinkAsArgumentIndirection(sinkNode.getNode()), callChain)
126+
select sinkAsArgumentIndirection(sinkNode.getNode()), sourceNode, sinkNode,
127+
"This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to " + callChain, sourceNode,
128+
"user input (" + taintCause + ")", concatSource, concatSource.toString()

0 commit comments

Comments
 (0)