Skip to content

Commit d228cf0

Browse files
committed
use more API-nodes to model subprocess.run (and friends)
1 parent bce83bf commit d228cf0

File tree

3 files changed

+25
-29
lines changed

3 files changed

+25
-29
lines changed

python/ql/lib/semmle/python/frameworks/Stdlib.qll

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,7 +1185,7 @@ private module StdlibPrivate {
11851185
* See https://docs.python.org/3.8/library/subprocess.html#subprocess.Popen
11861186
* ref: https://docs.python.org/3/library/subprocess.html#legacy-shell-invocation-functions
11871187
*/
1188-
private class SubprocessPopenCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
1188+
private class SubprocessPopenCall extends SystemCommandExecution::Range, API::CallNode {
11891189
SubprocessPopenCall() {
11901190
exists(string name |
11911191
name in [
@@ -1195,44 +1195,33 @@ private module StdlibPrivate {
11951195
)
11961196
}
11971197

1198-
/** Gets the ControlFlowNode for the `args` argument, if any. */
1199-
private DataFlow::Node get_args_arg() { result in [this.getArg(0), this.getArgByName("args")] }
1198+
/** Gets the API-node for the `args` argument, if any. */
1199+
private API::Node get_args_arg() { result = this.getParameter(0, "args") }
12001200

1201-
/** Gets the ControlFlowNode for the `shell` argument, if any. */
1202-
private DataFlow::Node get_shell_arg() {
1203-
result in [this.getArg(8), this.getArgByName("shell")]
1204-
}
1201+
/** Gets the API-node for the `shell` argument, if any. */
1202+
private API::Node get_shell_arg() { result = this.getParameter(8, "shell") }
12051203

12061204
private boolean get_shell_arg_value() {
1207-
// TODO: API-node this thing - with added tests for unsafe-shell-command-construction
12081205
not exists(this.get_shell_arg()) and
12091206
result = false
12101207
or
1211-
exists(DataFlow::Node shell_arg | shell_arg = this.get_shell_arg() |
1212-
result = shell_arg.asCfgNode().getNode().(ImmutableLiteral).booleanValue()
1213-
or
1214-
// TODO: Track the "shell" argument to determine possible values
1215-
not shell_arg.asCfgNode().getNode() instanceof ImmutableLiteral and
1216-
(
1217-
result = true
1218-
or
1219-
result = false
1220-
)
1221-
)
1208+
result =
1209+
this.get_shell_arg().getAValueReachingSink().asExpr().(ImmutableLiteral).booleanValue()
1210+
or
1211+
not this.get_shell_arg().getAValueReachingSink().asExpr() instanceof ImmutableLiteral and
1212+
result = [true, false]
12221213
}
12231214

1224-
/** Gets the ControlFlowNode for the `executable` argument, if any. */
1225-
private DataFlow::Node get_executable_arg() {
1226-
result in [this.getArg(2), this.getArgByName("executable")]
1227-
}
1215+
/** Gets the API-node for the `executable` argument, if any. */
1216+
private API::Node get_executable_arg() { result = this.getParameter(2, "executable") }
12281217

12291218
override DataFlow::Node getCommand() {
12301219
// TODO: Track arguments ("args" and "shell")
12311220
// TODO: Handle using `args=["sh", "-c", <user-input>]`
1232-
result = this.get_executable_arg()
1221+
result = this.get_executable_arg().asSink()
12331222
or
12341223
exists(DataFlow::Node arg_args, boolean shell |
1235-
arg_args = this.get_args_arg() and
1224+
arg_args = this.get_args_arg().asSink() and
12361225
shell = this.get_shell_arg_value()
12371226
|
12381227
// When "executable" argument is set, and "shell" argument is `False`, the
@@ -1260,7 +1249,7 @@ private module StdlibPrivate {
12601249
}
12611250

12621251
override predicate isShellInterpreted(DataFlow::Node arg) {
1263-
arg = [this.get_executable_arg(), this.get_args_arg()] and
1252+
arg = [this.get_executable_arg(), this.get_args_arg()].asSink() and
12641253
this.get_shell_arg_value() = true
12651254
}
12661255
}

python/ql/test/query-tests/Security/CWE-078-UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ edges
88
| src/unsafe_shell_test.py:14:34:14:39 | ControlFlowNode for List | src/unsafe_shell_test.py:14:25:14:40 | ControlFlowNode for Attribute() |
99
| src/unsafe_shell_test.py:26:20:26:23 | ControlFlowNode for name | src/unsafe_shell_test.py:29:30:29:33 | ControlFlowNode for name |
1010
| src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:39:30:39:33 | ControlFlowNode for name |
11+
| src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:44:20:44:23 | ControlFlowNode for name |
12+
| src/unsafe_shell_test.py:41:24:41:24 | ControlFlowNode for x | src/unsafe_shell_test.py:42:34:42:34 | ControlFlowNode for x |
13+
| src/unsafe_shell_test.py:44:20:44:23 | ControlFlowNode for name | src/unsafe_shell_test.py:41:24:41:24 | ControlFlowNode for x |
1114
nodes
1215
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
1316
| src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
@@ -21,6 +24,9 @@ nodes
2124
| src/unsafe_shell_test.py:29:30:29:33 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
2225
| src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
2326
| src/unsafe_shell_test.py:39:30:39:33 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
27+
| src/unsafe_shell_test.py:41:24:41:24 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
28+
| src/unsafe_shell_test.py:42:34:42:34 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
29+
| src/unsafe_shell_test.py:44:20:44:23 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
2430
subpaths
2531
#select
2632
| src/unsafe_shell_test.py:5:15:5:28 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:5:5:5:29 | ControlFlowNode for Attribute() | shell command |
@@ -31,3 +37,4 @@ subpaths
3137
| src/unsafe_shell_test.py:20:15:20:30 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:20:27:20:30 | ControlFlowNode for name | This formatted string which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:20:5:20:31 | ControlFlowNode for Attribute() | shell command |
3238
| src/unsafe_shell_test.py:29:20:29:33 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:26:20:26:23 | ControlFlowNode for name | src/unsafe_shell_test.py:29:30:29:33 | ControlFlowNode for name | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:26:20:26:23 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:29:5:29:46 | ControlFlowNode for Attribute() | shell command |
3339
| src/unsafe_shell_test.py:39:20:39:33 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:39:30:39:33 | ControlFlowNode for name | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:39:5:39:46 | ControlFlowNode for Attribute() | shell command |
40+
| src/unsafe_shell_test.py:42:24:42:34 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:42:34:42:34 | ControlFlowNode for x | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:42:9:42:47 | ControlFlowNode for Attribute() | shell command |

python/ql/test/query-tests/Security/CWE-078-UnsafeShellCommandConstruction/src/unsafe_shell_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def subprocess_flag (name):
3838

3939
subprocess.run("ping " + name, shell=True) # $result=BAD
4040

41-
def indirect(flag):
42-
subprocess.run("ping " + name, shell=flag) # $ MISSING: result=BAD
41+
def indirect(flag, x):
42+
subprocess.run("ping " + x, shell=flag) # $result=BAD
4343

44-
indirect(True)
44+
indirect(True, name)

0 commit comments

Comments
 (0)