@@ -1171,27 +1171,67 @@ module StdlibPrivate {
11711171 override predicate isShellInterpreted ( DataFlow:: Node arg ) { arg = this .getCommand ( ) }
11721172 }
11731173
1174+ /**
1175+ * Holds if `flag` makes `interpreter` execute the following argument as a command.
1176+ *
1177+ * See https://docs.python.org/3/library/subprocess.html#popen-constructor.
1178+ */
1179+ private predicate isShellCommandFlag ( DataFlow:: Node interpreter , DataFlow:: Node flag ) {
1180+ interpreter .asExpr ( ) .( StringLiteral ) .getText ( ) .regexpMatch ( "(.*/)?(sh|bash|dash|zsh)" ) and
1181+ flag .asExpr ( ) .( StringLiteral ) .getText ( ) = "-c"
1182+ }
1183+
1184+ /** Gets the command from separate interpreter, flag, and command arguments. */
1185+ private DataFlow:: Node getShellCommandFromArguments (
1186+ DataFlow:: Node interpreter , DataFlow:: Node flag , DataFlow:: Node command
1187+ ) {
1188+ isShellCommandFlag ( interpreter , flag ) and
1189+ result = command
1190+ }
1191+
1192+ /** Gets the command from an argument sequence passed to `interpreter`. */
1193+ private DataFlow:: Node getShellCommandFromSequence (
1194+ DataFlow:: Node interpreter , DataFlow:: Node arguments
1195+ ) {
1196+ exists ( SequenceNode sequence , DataFlow:: Node flag |
1197+ arguments .asCfgNode ( ) = sequence and
1198+ flag .asCfgNode ( ) = sequence .getElement ( 1 ) and
1199+ isShellCommandFlag ( interpreter , flag ) and
1200+ result .asCfgNode ( ) = sequence .getElement ( 2 )
1201+ )
1202+ }
1203+
11741204 /**
11751205 * A call to any of the `os.exec*` functions
11761206 * See https://docs.python.org/3.8/library/os.html#os.execl
11771207 */
11781208 private class OsExecCall extends SystemCommandExecution:: Range , FileSystemAccess:: Range ,
11791209 DataFlow:: CallCfgNode
11801210 {
1211+ string name ;
1212+
11811213 OsExecCall ( ) {
1182- exists ( string name |
1183- name in [ "execl" , "execle" , "execlp" , "execlpe" , "execv" , "execve" , "execvp" , "execvpe" ] and
1184- this = os ( ) .getMember ( name ) .getACall ( )
1185- )
1214+ name in [ "execl" , "execle" , "execlp" , "execlpe" , "execv" , "execve" , "execvp" , "execvpe" ] and
1215+ this = os ( ) .getMember ( name ) .getACall ( )
1216+ }
1217+
1218+ private DataFlow:: Node getShellCommand ( ) {
1219+ name in [ "execl" , "execlp" ] and
1220+ result = getShellCommandFromArguments ( this .getArg ( 0 ) , this .getArg ( 2 ) , this .getArg ( 3 ) )
1221+ or
1222+ name in [ "execle" , "execlpe" ] and
1223+ exists ( this .getArg ( 4 ) ) and
1224+ result = getShellCommandFromArguments ( this .getArg ( 0 ) , this .getArg ( 2 ) , this .getArg ( 3 ) )
1225+ or
1226+ name in [ "execv" , "execve" , "execvp" , "execvpe" ] and
1227+ result = getShellCommandFromSequence ( this .getArg ( 0 ) , this .getArg ( 1 ) )
11861228 }
11871229
1188- override DataFlow:: Node getCommand ( ) { result = this .getArg ( 0 ) }
1230+ override DataFlow:: Node getCommand ( ) { result in [ this .getArg ( 0 ) , this . getShellCommand ( ) ] }
11891231
1190- override DataFlow:: Node getAPathArgument ( ) { result = this .getCommand ( ) }
1232+ override DataFlow:: Node getAPathArgument ( ) { result = this .getArg ( 0 ) }
11911233
1192- override predicate isShellInterpreted ( DataFlow:: Node arg ) {
1193- none ( ) // this is a safe API.
1194- }
1234+ override predicate isShellInterpreted ( DataFlow:: Node arg ) { arg = this .getShellCommand ( ) }
11951235 }
11961236
11971237 /**
@@ -1201,28 +1241,44 @@ module StdlibPrivate {
12011241 private class OsSpawnCall extends SystemCommandExecution:: Range , FileSystemAccess:: Range ,
12021242 DataFlow:: CallCfgNode
12031243 {
1244+ string name ;
1245+
12041246 OsSpawnCall ( ) {
1205- exists ( string name |
1206- name in [
1207- "spawnl" , "spawnle" , "spawnlp" , "spawnlpe" , "spawnv" , "spawnve" , "spawnvp" , "spawnvpe"
1208- ] and
1209- this = os ( ) .getMember ( name ) .getACall ( )
1210- )
1247+ name in [
1248+ "spawnl" , "spawnle" , "spawnlp" , "spawnlpe" , "spawnv" , "spawnve" , "spawnvp" , "spawnvpe"
1249+ ] and
1250+ this = os ( ) .getMember ( name ) .getACall ( )
12111251 }
12121252
1213- override DataFlow:: Node getCommand ( ) {
1253+ private DataFlow:: Node getInterpreter ( ) {
12141254 result = this .getArg ( 1 )
12151255 or
12161256 // `file` keyword argument only valid for the `v` variants, but this
12171257 // over-approximation is not hurting anyone, and is easy to implement.
12181258 result = this .getArgByName ( "file" )
12191259 }
12201260
1221- override DataFlow:: Node getAPathArgument ( ) { result = this .getCommand ( ) }
1261+ private DataFlow:: Node getArguments ( ) { result in [ this .getArg ( 2 ) , this . getArgByName ( "args" ) ] }
12221262
1223- override predicate isShellInterpreted ( DataFlow:: Node arg ) {
1224- none ( ) // this is a safe API.
1263+ private DataFlow:: Node getShellCommand ( ) {
1264+ name in [ "spawnl" , "spawnlp" ] and
1265+ result = getShellCommandFromArguments ( this .getInterpreter ( ) , this .getArg ( 3 ) , this .getArg ( 4 ) )
1266+ or
1267+ name in [ "spawnle" , "spawnlpe" ] and
1268+ exists ( this .getArg ( 5 ) ) and
1269+ result = getShellCommandFromArguments ( this .getInterpreter ( ) , this .getArg ( 3 ) , this .getArg ( 4 ) )
1270+ or
1271+ name in [ "spawnv" , "spawnve" , "spawnvp" , "spawnvpe" ] and
1272+ result = getShellCommandFromSequence ( this .getInterpreter ( ) , this .getArguments ( ) )
1273+ }
1274+
1275+ override DataFlow:: Node getCommand ( ) {
1276+ result in [ this .getInterpreter ( ) , this .getShellCommand ( ) ]
12251277 }
1278+
1279+ override DataFlow:: Node getAPathArgument ( ) { result = this .getInterpreter ( ) }
1280+
1281+ override predicate isShellInterpreted ( DataFlow:: Node arg ) { arg = this .getShellCommand ( ) }
12261282 }
12271283
12281284 /**
@@ -1234,13 +1290,23 @@ module StdlibPrivate {
12341290 {
12351291 OsPosixSpawnCall ( ) { this = os ( ) .getMember ( [ "posix_spawn" , "posix_spawnp" ] ) .getACall ( ) }
12361292
1237- override DataFlow:: Node getCommand ( ) { result in [ this .getArg ( 0 ) , this .getArgByName ( "path" ) ] }
1293+ private DataFlow:: Node getInterpreter ( ) {
1294+ result in [ this .getArg ( 0 ) , this .getArgByName ( "path" ) ]
1295+ }
12381296
1239- override DataFlow:: Node getAPathArgument ( ) { result = this .getCommand ( ) }
1297+ private DataFlow:: Node getArguments ( ) { result in [ this .getArg ( 1 ) , this . getArgByName ( "argv" ) ] }
12401298
1241- override predicate isShellInterpreted ( DataFlow:: Node arg ) {
1242- none ( ) // this is a safe API.
1299+ private DataFlow:: Node getShellCommand ( ) {
1300+ result = getShellCommandFromSequence ( this .getInterpreter ( ) , this .getArguments ( ) )
1301+ }
1302+
1303+ override DataFlow:: Node getCommand ( ) {
1304+ result in [ this .getInterpreter ( ) , this .getShellCommand ( ) ]
12431305 }
1306+
1307+ override DataFlow:: Node getAPathArgument ( ) { result = this .getInterpreter ( ) }
1308+
1309+ override predicate isShellInterpreted ( DataFlow:: Node arg ) { arg = this .getShellCommand ( ) }
12441310 }
12451311
12461312 /** An additional taint step for calls to `os.path.join` */
@@ -1267,13 +1333,11 @@ module StdlibPrivate {
12671333 * ref: https://docs.python.org/3/library/subprocess.html#legacy-shell-invocation-functions
12681334 */
12691335 private class SubprocessPopenCall extends SystemCommandExecution:: Range , API:: CallNode {
1336+ string name ;
1337+
12701338 SubprocessPopenCall ( ) {
1271- exists ( string name |
1272- name in [
1273- "Popen" , "call" , "check_call" , "check_output" , "run" , "getoutput" , "getstatusoutput"
1274- ] and
1275- this = subprocess ( ) .getMember ( name ) .getACall ( )
1276- )
1339+ name in [ "Popen" , "call" , "check_call" , "check_output" , "run" , "getoutput" , "getstatusoutput" ] and
1340+ this = subprocess ( ) .getMember ( name ) .getACall ( )
12771341 }
12781342
12791343 /** Gets the API-node for the `args` argument, if any. */
@@ -1296,20 +1360,43 @@ module StdlibPrivate {
12961360 /** Gets the API-node for the `executable` argument, if any. */
12971361 private API:: Node get_executable_arg ( ) { result = this .getParameter ( 2 , "executable" ) }
12981362
1363+ /** Holds if the `executable` argument overrides the executable from `args`. */
1364+ private predicate hasExecutableOverride ( ) {
1365+ exists ( DataFlow:: Node executable |
1366+ executable = this .get_executable_arg ( ) .asSink ( ) and
1367+ not executable .asExpr ( ) instanceof None
1368+ )
1369+ }
1370+
1371+ /** Gets the interpreter that will execute `args`, if it can be determined. */
1372+ private DataFlow:: Node getInterpreter ( ) {
1373+ this .hasExecutableOverride ( ) and
1374+ result = this .get_executable_arg ( ) .asSink ( )
1375+ or
1376+ not this .hasExecutableOverride ( ) and
1377+ result .asCfgNode ( ) = this .get_args_arg ( ) .asSink ( ) .asCfgNode ( ) .( SequenceNode ) .getElement ( 0 )
1378+ }
1379+
1380+ /** Gets a shell command passed in the `args` sequence. */
1381+ private DataFlow:: Node getShellCommand ( ) {
1382+ name in [ "Popen" , "call" , "check_call" , "check_output" , "run" ] and
1383+ this .get_shell_arg_value ( ) = false and
1384+ result = getShellCommandFromSequence ( this .getInterpreter ( ) , this .get_args_arg ( ) .asSink ( ) )
1385+ }
1386+
12991387 override DataFlow:: Node getCommand ( ) {
1300- // TODO: Track arguments ("args" and "shell")
1301- // TODO: Handle using `args=["sh", "-c", <user-input>]`
1388+ this .hasExecutableOverride ( ) and
13021389 result = this .get_executable_arg ( ) .asSink ( )
13031390 or
13041391 exists ( DataFlow:: Node arg_args , boolean shell |
13051392 arg_args = this .get_args_arg ( ) .asSink ( ) and
13061393 shell = this .get_shell_arg_value ( )
13071394 |
1308- // When " executable" argument is set , and " shell" argument is `False`, the
1309- // " args" argument will only be used to set the program name and arguments to
1395+ // When the ` executable` argument overrides `args[0]` , and ` shell` is `False`, the
1396+ // ` args` argument will only be used to set the program name and arguments to
13101397 // the program, so we should not consider any of them as command execution.
13111398 not (
1312- exists ( this .get_executable_arg ( ) ) and
1399+ this .hasExecutableOverride ( ) and
13131400 shell = false
13141401 ) and
13151402 (
@@ -1327,11 +1414,15 @@ module StdlibPrivate {
13271414 result = arg_args
13281415 )
13291416 )
1417+ or
1418+ result = this .getShellCommand ( )
13301419 }
13311420
13321421 override predicate isShellInterpreted ( DataFlow:: Node arg ) {
13331422 arg = [ this .get_executable_arg ( ) , this .get_args_arg ( ) ] .asSink ( ) and
13341423 this .get_shell_arg_value ( ) = true
1424+ or
1425+ arg = this .getShellCommand ( )
13351426 }
13361427 }
13371428
0 commit comments