Skip to content

Commit 7b5b2fd

Browse files
committed
C++: Modernize cpp/system-data-exposure as a path-problem using IR taint, RemoteFlowSinkFunction.
1 parent 5490809 commit 7b5b2fd

File tree

5 files changed

+59
-114
lines changed

5 files changed

+59
-114
lines changed

cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql

Lines changed: 19 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @description Exposing system data or debugging information helps
44
* an adversary learn about the system and form an
55
* attack plan.
6-
* @kind problem
6+
* @kind path-problem
77
* @problem.severity warning
88
* @security-severity 6.5
99
* @precision medium
@@ -14,7 +14,9 @@
1414

1515
import cpp
1616
import semmle.code.cpp.commons.Environment
17-
import semmle.code.cpp.security.OutputWrite
17+
import semmle.code.cpp.dataflow.TaintTracking
18+
import semmle.code.cpp.models.interfaces.FlowSource
19+
import DataFlow::PathGraph
1820

1921
/**
2022
* An element that should not be exposed to an adversary.
@@ -24,35 +26,6 @@ abstract class SystemData extends Element {
2426
* Gets an expression that is part of this `SystemData`.
2527
*/
2628
abstract Expr getAnExpr();
27-
28-
/**
29-
* Gets an expression whose value originates from, or is used by,
30-
* this `SystemData`.
31-
*/
32-
Expr getAnExprIndirect() {
33-
// direct SystemData
34-
result = this.getAnExpr() or
35-
// flow via global or member variable (conservative approximation)
36-
result = this.getAnAffectedVar().getAnAccess() or
37-
// flow via stack variable
38-
definitionUsePair(_, this.getAnExprIndirect(), result) or
39-
useUsePair(_, this.getAnExprIndirect(), result) or
40-
useUsePair(_, result, this.getAnExprIndirect()) or
41-
// flow from assigned value to assignment expression
42-
result.(AssignExpr).getRValue() = this.getAnExprIndirect()
43-
}
44-
45-
/**
46-
* Gets a global or member variable that may be affected by this system
47-
* data (conservative approximation).
48-
*/
49-
private Variable getAnAffectedVar() {
50-
(
51-
result.getAnAssignedValue() = this.getAnExprIndirect() or
52-
result.getAnAccess() = this.getAnExprIndirect()
53-
) and
54-
not result instanceof LocalScopeVariable
55-
}
5629
}
5730

5831
/**
@@ -311,70 +284,23 @@ class RegQuery extends SystemData {
311284
override Expr getAnExpr() { regQuery(this, result) }
312285
}
313286

314-
/**
315-
* Somewhere data is output.
316-
*/
317-
abstract class DataOutput extends Element {
318-
/**
319-
* Get an expression containing data that is output.
320-
*/
321-
abstract Expr getASource();
322-
}
287+
class ExposedSystemDataConfiguration extends TaintTracking::Configuration {
288+
ExposedSystemDataConfiguration() { this = "ExposedSystemDataConfiguration" }
323289

324-
/**
325-
* Data that is output via standard output or standard error.
326-
*/
327-
class StandardOutput extends DataOutput instanceof OutputWrite {
328-
override Expr getASource() { result = OutputWrite.super.getASource() }
329-
}
290+
override predicate isSource(DataFlow::Node source) {
291+
source.asExpr() = any(SystemData sd).getAnExpr()
292+
}
330293

331-
private predicate socketCallOrIndirect(FunctionCall call) {
332-
// direct socket call
333-
// int socket(int domain, int type, int protocol);
334-
call.getTarget().getName() = "socket"
335-
or
336-
exists(ReturnStmt rtn |
337-
// indirect socket call
338-
call.getTarget() = rtn.getEnclosingFunction() and
339-
(
340-
socketCallOrIndirect(rtn.getExpr()) or
341-
socketCallOrIndirect(rtn.getExpr().(VariableAccess).getTarget().getAnAssignedValue())
294+
override predicate isSink(DataFlow::Node sink) {
295+
exists(FunctionCall fc, FunctionInput input, int arg |
296+
fc.getTarget().(RemoteFlowSinkFunction).hasRemoteFlowSink(input, _) and
297+
input.isParameterDeref(arg) and
298+
fc.getArgument(arg) = sink.asExpr()
342299
)
343-
)
344-
}
345-
346-
private predicate socketFileDescriptor(Expr e) {
347-
exists(Variable var, FunctionCall socket |
348-
socketCallOrIndirect(socket) and
349-
var.getAnAssignedValue() = socket and
350-
e = var.getAnAccess()
351-
)
352-
}
353-
354-
private predicate socketOutput(FunctionCall call, Expr data) {
355-
(
356-
// ssize_t send(int sockfd, const void *buf, size_t len, int flags);
357-
// ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
358-
// const struct sockaddr *dest_addr, socklen_t addrlen);
359-
// ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
360-
// int write(int handle, void *buffer, int nbyte);
361-
call.getTarget().hasGlobalName(["send", "sendto", "sendmsg", "write"]) and
362-
data = call.getArgument(1) and
363-
socketFileDescriptor(call.getArgument(0))
364-
)
365-
}
366-
367-
/**
368-
* Data that is output via a socket.
369-
*/
370-
class SocketOutput extends DataOutput {
371-
SocketOutput() { socketOutput(this, _) }
372-
373-
override Expr getASource() { socketOutput(this, result) }
300+
}
374301
}
375302

376-
from SystemData sd, DataOutput ow
377-
where
378-
sd.getAnExprIndirect() = ow.getASource() or
379-
sd.getAnExprIndirect() = ow.getASource().getAChild*()
380-
select ow, "This operation exposes system data from $@.", sd, sd.toString()
303+
from ExposedSystemDataConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
304+
where config.hasFlowPath(source, sink)
305+
select sink, source, sink, "This operation exposes system data from $@.", source,
306+
source.getNode().toString()
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
| tests.c:70:9:70:15 | call to fprintf | This operation exposes system data from $@. | tests.c:54:13:54:22 | call to LogonUserA | call to LogonUserA |
1+
edges
2+
nodes
3+
subpaths
4+
#select

cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ void CWE535_Info_Exposure_Shell_Error__w32_char_01_bad()
6767
printLine("Unable to login.");
6868
}
6969
/* FLAW: Write sensitive data to stderr */
70-
fprintf(stderr, "User attempted access with password: %s\n", password);
70+
fprintf(stderr, "User attempted access with password: %s\n", password); // [NOT DETECTED]
7171
}
7272
}
Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1-
| tests2.cpp:58:12:58:12 | call to operator<< | This operation exposes system data from $@. | tests2.cpp:58:15:58:20 | call to getenv | call to getenv |
2-
| tests2.cpp:59:25:59:25 | call to operator<< | This operation exposes system data from $@. | tests2.cpp:59:28:59:33 | call to getenv | call to getenv |
3-
| tests2.cpp:63:2:63:5 | call to send | This operation exposes system data from $@. | tests2.cpp:63:13:63:18 | call to getenv | call to getenv |
4-
| tests2.cpp:64:2:64:5 | call to send | This operation exposes system data from $@. | tests2.cpp:64:13:64:18 | call to getenv | call to getenv |
5-
| tests2.cpp:65:2:65:5 | call to send | This operation exposes system data from $@. | tests2.cpp:65:13:65:18 | call to getenv | call to getenv |
6-
| tests2.cpp:66:2:66:5 | call to send | This operation exposes system data from $@. | tests2.cpp:66:13:66:18 | call to getenv | call to getenv |
7-
| tests2.cpp:78:3:78:6 | call to send | This operation exposes system data from $@. | tests2.cpp:78:14:78:34 | call to mysql_get_client_info | call to mysql_get_client_info |
8-
| tests2.cpp:80:3:80:6 | call to send | This operation exposes system data from $@. | tests2.cpp:50:23:50:43 | call to mysql_get_client_info | call to mysql_get_client_info |
9-
| tests2.cpp:91:3:91:6 | call to send | This operation exposes system data from $@. | tests2.cpp:89:3:89:20 | call to mysql_real_connect | call to mysql_real_connect |
10-
| tests2.cpp:100:3:100:6 | call to send | This operation exposes system data from $@. | tests2.cpp:99:8:99:15 | call to getpwuid | call to getpwuid |
11-
| tests2.cpp:109:3:109:6 | call to send | This operation exposes system data from $@. | tests2.cpp:107:12:107:17 | call to getenv | call to getenv |
12-
| tests2.cpp:110:3:110:6 | call to send | This operation exposes system data from $@. | tests2.cpp:107:12:107:17 | call to getenv | call to getenv |
1+
edges
2+
| tests2.cpp:76:18:76:38 | call to mysql_get_client_info | tests2.cpp:79:14:79:19 | buffer |
3+
| tests2.cpp:107:3:107:4 | c1 [post update] [ptr] | tests2.cpp:109:14:109:15 | c1 [ptr] |
4+
| tests2.cpp:107:3:107:36 | ... = ... | tests2.cpp:107:3:107:4 | c1 [post update] [ptr] |
5+
| tests2.cpp:107:12:107:17 | call to getenv | tests2.cpp:107:3:107:36 | ... = ... |
6+
| tests2.cpp:109:14:109:15 | c1 [ptr] | tests2.cpp:109:17:109:19 | ptr |
7+
nodes
8+
| tests2.cpp:63:13:63:18 | call to getenv | semmle.label | call to getenv |
9+
| tests2.cpp:64:13:64:18 | call to getenv | semmle.label | call to getenv |
10+
| tests2.cpp:65:13:65:18 | call to getenv | semmle.label | call to getenv |
11+
| tests2.cpp:66:13:66:18 | call to getenv | semmle.label | call to getenv |
12+
| tests2.cpp:76:18:76:38 | call to mysql_get_client_info | semmle.label | call to mysql_get_client_info |
13+
| tests2.cpp:78:14:78:34 | call to mysql_get_client_info | semmle.label | call to mysql_get_client_info |
14+
| tests2.cpp:79:14:79:19 | buffer | semmle.label | buffer |
15+
| tests2.cpp:107:3:107:4 | c1 [post update] [ptr] | semmle.label | c1 [post update] [ptr] |
16+
| tests2.cpp:107:3:107:36 | ... = ... | semmle.label | ... = ... |
17+
| tests2.cpp:107:12:107:17 | call to getenv | semmle.label | call to getenv |
18+
| tests2.cpp:109:14:109:15 | c1 [ptr] | semmle.label | c1 [ptr] |
19+
| tests2.cpp:109:17:109:19 | ptr | semmle.label | ptr |
20+
subpaths
21+
#select
22+
| tests2.cpp:63:13:63:18 | call to getenv | tests2.cpp:63:13:63:18 | call to getenv | tests2.cpp:63:13:63:18 | call to getenv | This operation exposes system data from $@. | tests2.cpp:63:13:63:18 | call to getenv | call to getenv |
23+
| tests2.cpp:64:13:64:18 | call to getenv | tests2.cpp:64:13:64:18 | call to getenv | tests2.cpp:64:13:64:18 | call to getenv | This operation exposes system data from $@. | tests2.cpp:64:13:64:18 | call to getenv | call to getenv |
24+
| tests2.cpp:65:13:65:18 | call to getenv | tests2.cpp:65:13:65:18 | call to getenv | tests2.cpp:65:13:65:18 | call to getenv | This operation exposes system data from $@. | tests2.cpp:65:13:65:18 | call to getenv | call to getenv |
25+
| tests2.cpp:66:13:66:18 | call to getenv | tests2.cpp:66:13:66:18 | call to getenv | tests2.cpp:66:13:66:18 | call to getenv | This operation exposes system data from $@. | tests2.cpp:66:13:66:18 | call to getenv | call to getenv |
26+
| tests2.cpp:78:14:78:34 | call to mysql_get_client_info | tests2.cpp:78:14:78:34 | call to mysql_get_client_info | tests2.cpp:78:14:78:34 | call to mysql_get_client_info | This operation exposes system data from $@. | tests2.cpp:78:14:78:34 | call to mysql_get_client_info | call to mysql_get_client_info |
27+
| tests2.cpp:79:14:79:19 | buffer | tests2.cpp:76:18:76:38 | call to mysql_get_client_info | tests2.cpp:79:14:79:19 | buffer | This operation exposes system data from $@. | tests2.cpp:76:18:76:38 | call to mysql_get_client_info | call to mysql_get_client_info |
28+
| tests2.cpp:109:17:109:19 | ptr | tests2.cpp:107:12:107:17 | call to getenv | tests2.cpp:109:17:109:19 | ptr | This operation exposes system data from $@. | tests2.cpp:107:12:107:17 | call to getenv | call to getenv |

cpp/ql/test/query-tests/Security/CWE/CWE-497/semmle/tests/tests2.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ void test1()
5555
int sock = socket(val(), val(), val());
5656

5757
// tests for a strict implementation of CWE-497
58-
std::cout << getenv("HOME"); // BAD: outputs HOME environment variable
59-
std::cout << "PATH = " << getenv("PATH") << "."; // BAD: outputs PATH environment variable
58+
std::cout << getenv("HOME"); // BAD: outputs HOME environment variable [NOT DETECTED]
59+
std::cout << "PATH = " << getenv("PATH") << "."; // BAD: outputs PATH environment variable [NOT DETECTED]
6060
std::cout << "PATHPATHPATH"; // GOOD: not system data
6161

6262
// tests for a more pragmatic implementation of CWE-497
@@ -76,8 +76,8 @@ void test1()
7676
strcpy(buffer, mysql_get_client_info());
7777

7878
send(sock, mysql_get_client_info(), val(), val()); // BAD
79-
send(sock, buffer, val(), val()); // BAD [NOT DETECTED]
80-
send(sock, global1, val(), val()); // BAD
79+
send(sock, buffer, val(), val()); // BAD
80+
send(sock, global1, val(), val()); // BAD [NOT DETECTED]
8181
send(sock, global2, val(), val()); // GOOD: not system data
8282
}
8383

@@ -88,7 +88,7 @@ void test1()
8888

8989
mysql_real_connect(sock, val(), val(), str1, val(), val(), val(), val());
9090

91-
send(sock, str1, val(), val()); // BAD
91+
send(sock, str1, val(), val()); // BAD [NOT DETECTED]
9292
send(sock, str2, val(), val()); // GOOD: not system data
9393
}
9494

@@ -97,7 +97,7 @@ void test1()
9797
passwd *pw;
9898

9999
pw = getpwuid(val());
100-
send(sock, pw->pw_passwd, val(), val()); // BAD
100+
send(sock, pw->pw_passwd, val(), val()); // BAD [NOT DETECTED]
101101
}
102102

103103
// tests for containers
@@ -107,6 +107,6 @@ void test1()
107107
c1.ptr = getenv("MY_SECRET_TOKEN");
108108
c2.ptr = "";
109109
send(sock, c1.ptr, val(), val()); // BAD
110-
send(sock, c2.ptr, val(), val()); // GOOD: not system data [FALSE POSITIVE]
110+
send(sock, c2.ptr, val(), val()); // GOOD: not system data
111111
}
112112
}

0 commit comments

Comments
 (0)