Skip to content

Commit aa92fe8

Browse files
authored
Merge pull request #7338 from geoffw0/clrtxt2
C++: Improvements to cpp/cleartext-transmission
2 parents f18492e + b142a79 commit aa92fe8

File tree

4 files changed

+562
-95
lines changed

4 files changed

+562
-95
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* The "Cleartext transmission of sensitive information" (`cpp/cleartext-transmission`) query has been improved, reducing the number of false positive results when encryption is present.

cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql

Lines changed: 132 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,105 +14,188 @@
1414
import cpp
1515
import semmle.code.cpp.security.SensitiveExprs
1616
import semmle.code.cpp.dataflow.TaintTracking
17+
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
1718
import semmle.code.cpp.models.interfaces.FlowSource
1819
import DataFlow::PathGraph
1920

2021
/**
21-
* A function call that sends or receives data over a network.
22+
* A DataFlow node corresponding to a variable or function call that
23+
* might contain or return a password or other sensitive information.
2224
*/
23-
abstract class NetworkSendRecv extends FunctionCall {
25+
class SensitiveNode extends DataFlow::Node {
26+
SensitiveNode() {
27+
this.asExpr() = any(SensitiveVariable sv).getInitializer().getExpr() or
28+
this.asExpr().(VariableAccess).getTarget() =
29+
any(SensitiveVariable sv).(GlobalOrNamespaceVariable) or
30+
this.asUninitialized() instanceof SensitiveVariable or
31+
this.asParameter() instanceof SensitiveVariable or
32+
this.asExpr().(FunctionCall).getTarget() instanceof SensitiveFunction
33+
}
34+
}
35+
36+
/**
37+
* A function that sends or receives data over a network.
38+
*/
39+
abstract class SendRecv extends Function {
2440
/**
2541
* Gets the expression for the socket or similar object used for sending or
26-
* receiving data (if any).
42+
* receiving data through the function call `call` (if any).
2743
*/
28-
abstract Expr getSocketExpr();
44+
abstract Expr getSocketExpr(Call call);
2945

3046
/**
31-
* Gets the expression for the buffer to be sent from / received into.
47+
* Gets the expression for the buffer to be sent from / received into through
48+
* the function call `call`.
3249
*/
33-
abstract Expr getDataExpr();
50+
abstract Expr getDataExpr(Call call);
3451
}
3552

3653
/**
37-
* A function call that sends data over a network.
38-
*
39-
* note: functions such as `write` may be writing to a network source or a file. We could attempt to determine which, and sort results into `cpp/cleartext-transmission` and perhaps `cpp/cleartext-storage-file`. In practice it usually isn't very important which query reports a result as long as its reported exactly once.
54+
* A function that sends data over a network.
4055
*/
41-
class NetworkSend extends NetworkSendRecv {
42-
RemoteFlowSinkFunction target;
43-
44-
NetworkSend() { target = this.getTarget() }
45-
46-
override Expr getSocketExpr() {
56+
class Send extends SendRecv instanceof RemoteFlowSinkFunction {
57+
override Expr getSocketExpr(Call call) {
58+
call.getTarget() = this and
4759
exists(FunctionInput input, int arg |
48-
target.hasSocketInput(input) and
60+
super.hasSocketInput(input) and
4961
input.isParameter(arg) and
50-
result = this.getArgument(arg)
62+
result = call.getArgument(arg)
5163
)
5264
}
5365

54-
override Expr getDataExpr() {
66+
override Expr getDataExpr(Call call) {
67+
call.getTarget() = this and
5568
exists(FunctionInput input, int arg |
56-
target.hasRemoteFlowSink(input, _) and
69+
super.hasRemoteFlowSink(input, _) and
5770
input.isParameterDeref(arg) and
58-
result = this.getArgument(arg)
71+
result = call.getArgument(arg)
5972
)
6073
}
6174
}
6275

6376
/**
64-
* A function call that receives data over a network.
77+
* A function that receives data over a network.
6578
*/
66-
class NetworkRecv extends NetworkSendRecv {
67-
RemoteFlowSourceFunction target;
68-
69-
NetworkRecv() { target = this.getTarget() }
70-
71-
override Expr getSocketExpr() {
79+
class Recv extends SendRecv instanceof RemoteFlowSourceFunction {
80+
override Expr getSocketExpr(Call call) {
81+
call.getTarget() = this and
7282
exists(FunctionInput input, int arg |
73-
target.hasSocketInput(input) and
83+
super.hasSocketInput(input) and
7484
input.isParameter(arg) and
75-
result = this.getArgument(arg)
85+
result = call.getArgument(arg)
7686
)
7787
}
7888

79-
override Expr getDataExpr() {
89+
override Expr getDataExpr(Call call) {
90+
call.getTarget() = this and
8091
exists(FunctionOutput output, int arg |
81-
target.hasRemoteFlowSource(output, _) and
92+
super.hasRemoteFlowSource(output, _) and
8293
output.isParameterDeref(arg) and
83-
result = this.getArgument(arg)
94+
result = call.getArgument(arg)
8495
)
8596
}
8697
}
8798

8899
/**
89-
* Taint flow from a sensitive expression to a network operation with data
90-
* tainted by that expression.
100+
* A function call that sends or receives data over a network.
101+
*
102+
* note: function calls such as `write` may be writing to a network source
103+
* or a file. We could attempt to determine which, and sort results into
104+
* `cpp/cleartext-transmission` and perhaps `cpp/cleartext-storage-file`. In
105+
* practice it usually isn't very important which query reports a result as
106+
* long as its reported exactly once.
107+
*
108+
* We do exclude function calls that specify a constant socket, which is
109+
* likely to mean standard input, standard output or a similar channel.
91110
*/
92-
class SensitiveSendRecvConfiguration extends TaintTracking::Configuration {
93-
SensitiveSendRecvConfiguration() { this = "SensitiveSendRecvConfiguration" }
111+
abstract class NetworkSendRecv extends FunctionCall {
112+
SendRecv target;
94113

95-
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveExpr }
114+
NetworkSendRecv() {
115+
this.getTarget() = target and
116+
// exclude calls based on the socket...
117+
not exists(GVN g |
118+
g = globalValueNumber(target.getSocketExpr(this)) and
119+
(
120+
// literal constant
121+
globalValueNumber(any(Literal l)) = g
122+
or
123+
// variable (such as a global) initialized to a literal constant
124+
exists(Variable v |
125+
v.getInitializer().getExpr() instanceof Literal and
126+
g = globalValueNumber(v.getAnAccess())
127+
)
128+
)
129+
)
130+
}
96131

97-
override predicate isSink(DataFlow::Node sink) {
98-
exists(NetworkSendRecv transmission |
99-
sink.asExpr() = transmission.getDataExpr() and
100-
// a zero socket descriptor is standard input, which is not interesting for this query.
101-
not exists(Zero zero |
102-
DataFlow::localFlow(DataFlow::exprNode(zero),
103-
DataFlow::exprNode(transmission.getSocketExpr()))
132+
final Expr getDataExpr() { result = target.getDataExpr(this) }
133+
}
134+
135+
/**
136+
* A function call that sends data over a network.
137+
*/
138+
class NetworkSend extends NetworkSendRecv {
139+
override Send target;
140+
}
141+
142+
/**
143+
* A function call that receives data over a network.
144+
*/
145+
class NetworkRecv extends NetworkSendRecv {
146+
override Recv target;
147+
}
148+
149+
/**
150+
* An expression that is an argument or return value from an encryption or
151+
* decryption call.
152+
*/
153+
class Encrypted extends Expr {
154+
Encrypted() {
155+
exists(FunctionCall fc |
156+
fc.getTarget().getName().toLowerCase().regexpMatch(".*(crypt|encode|decode).*") and
157+
(
158+
this = fc or
159+
this = fc.getAnArgument()
104160
)
105161
)
106162
}
107163
}
108164

165+
/**
166+
* Taint flow from a sensitive expression.
167+
*/
168+
class FromSensitiveConfiguration extends TaintTracking::Configuration {
169+
FromSensitiveConfiguration() { this = "FromSensitiveConfiguration" }
170+
171+
override predicate isSource(DataFlow::Node source) { source instanceof SensitiveNode }
172+
173+
override predicate isSink(DataFlow::Node sink) {
174+
sink.asExpr() = any(NetworkSendRecv nsr).getDataExpr()
175+
or
176+
sink.asExpr() instanceof Encrypted
177+
}
178+
179+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
180+
// flow through encryption functions to the return value (in case we can reach other sinks)
181+
node2.asExpr().(Encrypted).(FunctionCall).getAnArgument() = node1.asExpr()
182+
}
183+
}
184+
109185
from
110-
SensitiveSendRecvConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink,
111-
NetworkSendRecv transmission, string msg
186+
FromSensitiveConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink,
187+
NetworkSendRecv networkSendRecv, string msg
112188
where
189+
// flow from sensitive -> network data
113190
config.hasFlowPath(source, sink) and
114-
sink.getNode().asExpr() = transmission.getDataExpr() and
115-
if transmission instanceof NetworkSend
191+
sink.getNode().asExpr() = networkSendRecv.getDataExpr() and
192+
// no flow from sensitive -> evidence of encryption
193+
not exists(DataFlow::Node encrypted |
194+
config.hasFlow(source.getNode(), encrypted) and
195+
encrypted.asExpr() instanceof Encrypted
196+
) and
197+
// construct result
198+
if networkSendRecv instanceof NetworkSend
116199
then
117200
msg =
118201
"This operation transmits '" + sink.toString() +
@@ -121,4 +204,4 @@ where
121204
msg =
122205
"This operation receives into '" + sink.toString() +
123206
"', which may put unencrypted sensitive data into $@"
124-
select transmission, source, sink, msg, source, source.getNode().asExpr().toString()
207+
select networkSendRecv, source, sink, msg, source, source.getNode().toString()

0 commit comments

Comments
 (0)