Skip to content

Commit e79b8f3

Browse files
committed
Python: Treat os.exec*, os.spawn*, and os.posix_spawn* as FileSystemAccess
1 parent d2d5cce commit e79b8f3

File tree

2 files changed

+38
-29
lines changed

2 files changed

+38
-29
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,8 @@ private module StdlibPrivate {
467467
* A call to any of the `os.exec*` functions
468468
* See https://docs.python.org/3.8/library/os.html#os.execl
469469
*/
470-
private class OsExecCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
470+
private class OsExecCall extends SystemCommandExecution::Range, FileSystemAccess::Range,
471+
DataFlow::CallCfgNode {
471472
OsExecCall() {
472473
exists(string name |
473474
name in ["execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe"] and
@@ -476,13 +477,16 @@ private module StdlibPrivate {
476477
}
477478

478479
override DataFlow::Node getCommand() { result = this.getArg(0) }
480+
481+
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
479482
}
480483

481484
/**
482485
* A call to any of the `os.spawn*` functions
483486
* See https://docs.python.org/3.8/library/os.html#os.spawnl
484487
*/
485-
private class OsSpawnCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
488+
private class OsSpawnCall extends SystemCommandExecution::Range, FileSystemAccess::Range,
489+
DataFlow::CallCfgNode {
486490
OsSpawnCall() {
487491
exists(string name |
488492
name in [
@@ -499,16 +503,21 @@ private module StdlibPrivate {
499503
// over-approximation is not hurting anyone, and is easy to implement.
500504
result = this.getArgByName("file")
501505
}
506+
507+
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
502508
}
503509

504510
/**
505511
* A call to any of the `os.posix_spawn*` functions
506512
* See https://docs.python.org/3.8/library/os.html#os.posix_spawn
507513
*/
508-
private class OsPosixSpawnCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
514+
private class OsPosixSpawnCall extends SystemCommandExecution::Range, FileSystemAccess::Range,
515+
DataFlow::CallCfgNode {
509516
OsPosixSpawnCall() { this = os().getMember(["posix_spawn", "posix_spawnp"]).getACall() }
510517

511518
override DataFlow::Node getCommand() { result in [this.getArg(0), this.getArgByName("path")] }
519+
520+
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
512521
}
513522

514523
/** An additional taint step for calls to `os.path.join` */

python/ql/test/library-tests/frameworks/stdlib/SystemCommandExecution.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,42 +44,42 @@ def os_members():
4444
# doesn't sound safe even if that is restricted to be within a certain directory.
4545
if UNKNOWN:
4646
env = {"FOO": "foo"}
47-
os.execl("path", "<progname>", "arg0") # $ getCommand="path" MISSING: getAPathArgument="path"
48-
os.execle("path", "<progname>", "arg0", env) # $ getCommand="path" MISSING: getAPathArgument="path"
49-
os.execlp("file", "<progname>", "arg0") # $ getCommand="file" MISSING: getAPathArgument="file"
50-
os.execlpe("file", "<progname>", "arg0", env) # $ getCommand="file" MISSING: getAPathArgument="file"
51-
os.execv("path", ["<progname>", "arg0"]) # $ getCommand="path" MISSING: getAPathArgument="path"
52-
os.execve("path", ["<progname>", "arg0"], env) # $ getCommand="path" MISSING: getAPathArgument="path"
53-
os.execvp("file", ["<progname>", "arg0"]) # $ getCommand="file" MISSING: getAPathArgument="file"
54-
os.execvpe("file", ["<progname>", "arg0"], env) # $ getCommand="file" MISSING: getAPathArgument="file"
47+
os.execl("path", "<progname>", "arg0") # $ getCommand="path" getAPathArgument="path"
48+
os.execle("path", "<progname>", "arg0", env) # $ getCommand="path" getAPathArgument="path"
49+
os.execlp("file", "<progname>", "arg0") # $ getCommand="file" getAPathArgument="file"
50+
os.execlpe("file", "<progname>", "arg0", env) # $ getCommand="file" getAPathArgument="file"
51+
os.execv("path", ["<progname>", "arg0"]) # $ getCommand="path" getAPathArgument="path"
52+
os.execve("path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
53+
os.execvp("file", ["<progname>", "arg0"]) # $ getCommand="file" getAPathArgument="file"
54+
os.execvpe("file", ["<progname>", "arg0"], env) # $ getCommand="file" getAPathArgument="file"
5555

5656

5757
########################################
5858
# https://docs.python.org/3.8/library/os.html#os.spawnl
5959
env = {"FOO": "foo"}
60-
os.spawnl(os.P_WAIT, "path", "<progname>", "arg0") # $ getCommand="path" MISSING: getAPathArgument="path"
61-
os.spawnle(os.P_WAIT, "path", "<progname>", "arg0", env) # $ getCommand="path" MISSING: getAPathArgument="path"
62-
os.spawnlp(os.P_WAIT, "file", "<progname>", "arg0") # $ getCommand="file" MISSING: getAPathArgument="file"
63-
os.spawnlpe(os.P_WAIT, "file", "<progname>", "arg0", env) # $ getCommand="file" MISSING: getAPathArgument="file"
64-
os.spawnv(os.P_WAIT, "path", ["<progname>", "arg0"]) # $ getCommand="path" MISSING: getAPathArgument="path"
65-
os.spawnve(os.P_WAIT, "path", ["<progname>", "arg0"], env) # $ getCommand="path" MISSING: getAPathArgument="path"
66-
os.spawnvp(os.P_WAIT, "file", ["<progname>", "arg0"]) # $ getCommand="file" MISSING: getAPathArgument="file"
67-
os.spawnvpe(os.P_WAIT, "file", ["<progname>", "arg0"], env) # $ getCommand="file" MISSING: getAPathArgument="file"
60+
os.spawnl(os.P_WAIT, "path", "<progname>", "arg0") # $ getCommand="path" getAPathArgument="path"
61+
os.spawnle(os.P_WAIT, "path", "<progname>", "arg0", env) # $ getCommand="path" getAPathArgument="path"
62+
os.spawnlp(os.P_WAIT, "file", "<progname>", "arg0") # $ getCommand="file" getAPathArgument="file"
63+
os.spawnlpe(os.P_WAIT, "file", "<progname>", "arg0", env) # $ getCommand="file" getAPathArgument="file"
64+
os.spawnv(os.P_WAIT, "path", ["<progname>", "arg0"]) # $ getCommand="path" getAPathArgument="path"
65+
os.spawnve(os.P_WAIT, "path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
66+
os.spawnvp(os.P_WAIT, "file", ["<progname>", "arg0"]) # $ getCommand="file" getAPathArgument="file"
67+
os.spawnvpe(os.P_WAIT, "file", ["<progname>", "arg0"], env) # $ getCommand="file" getAPathArgument="file"
6868

6969
# unlike os.exec*, some os.spawn* functions is usable with keyword arguments. However,
7070
# despite the docs using both `file` and `path` as the parameter name, you actually need
7171
# to use `file` in all cases.
72-
os.spawnv(mode=os.P_WAIT, file="path", args=["<progname>", "arg0"]) # $ getCommand="path" MISSING: getAPathArgument="path"
73-
os.spawnve(mode=os.P_WAIT, file="path", args=["<progname>", "arg0"], env=env) # $ getCommand="path" MISSING: getAPathArgument="path"
74-
os.spawnvp(mode=os.P_WAIT, file="file", args=["<progname>", "arg0"]) # $ getCommand="file" MISSING: getAPathArgument="file"
75-
os.spawnvpe(mode=os.P_WAIT, file="file", args=["<progname>", "arg0"], env=env) # $ getCommand="file" MISSING: getAPathArgument="file"
72+
os.spawnv(mode=os.P_WAIT, file="path", args=["<progname>", "arg0"]) # $ getCommand="path" getAPathArgument="path"
73+
os.spawnve(mode=os.P_WAIT, file="path", args=["<progname>", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path"
74+
os.spawnvp(mode=os.P_WAIT, file="file", args=["<progname>", "arg0"]) # $ getCommand="file" getAPathArgument="file"
75+
os.spawnvpe(mode=os.P_WAIT, file="file", args=["<progname>", "arg0"], env=env) # $ getCommand="file" getAPathArgument="file"
7676

7777
# `posix_spawn` Added in Python 3.8
78-
os.posix_spawn("path", ["<progname>", "arg0"], env) # $ getCommand="path" MISSING: getAPathArgument="path"
79-
os.posix_spawn(path="path", argv=["<progname>", "arg0"], env=env) # $ getCommand="path" MISSING: getAPathArgument="path"
78+
os.posix_spawn("path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
79+
os.posix_spawn(path="path", argv=["<progname>", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path"
8080

81-
os.posix_spawnp("path", ["<progname>", "arg0"], env) # $ getCommand="path" MISSING: getAPathArgument="path"
82-
os.posix_spawnp(path="path", argv=["<progname>", "arg0"], env=env) # $ getCommand="path" MISSING: getAPathArgument="path"
81+
os.posix_spawnp("path", ["<progname>", "arg0"], env) # $ getCommand="path" getAPathArgument="path"
82+
os.posix_spawnp(path="path", argv=["<progname>", "arg0"], env=env) # $ getCommand="path" getAPathArgument="path"
8383

8484
########################################
8585

@@ -126,9 +126,9 @@ def os_members():
126126
subprocess.Popen(["<progname>", "-c", "vuln"], executable="/bin/bash") # $getCommand="/bin/bash" MISSING: getCommand="vuln"
127127

128128
if UNKNOWN:
129-
os.execl("/bin/sh", "<progname>", "-c", "vuln") # $getCommand="/bin/sh" MISSING: getCommand="vuln"
129+
os.execl("/bin/sh", "<progname>", "-c", "vuln") # $getCommand="/bin/sh" getAPathArgument="/bin/sh" MISSING: getCommand="vuln"
130130

131-
os.spawnl(os.P_WAIT, "/bin/sh", "<progname>", "-c", "vuln") # $getCommand="/bin/sh" MISSING: getCommand="vuln"
131+
os.spawnl(os.P_WAIT, "/bin/sh", "<progname>", "-c", "vuln") # $getCommand="/bin/sh" getAPathArgument="/bin/sh" MISSING: getCommand="vuln"
132132

133133

134134
########################################

0 commit comments

Comments
 (0)