Merge pull request #8737 from hmac/hmac/posix-spawn

Ruby: Model the posix-spawn gem
This commit is contained in:
Harry Maclean
2022-06-16 00:50:10 +01:00
committed by GitHub
5 changed files with 146 additions and 0 deletions

View File

@@ -15,3 +15,4 @@ private import codeql.ruby.frameworks.Files
private import codeql.ruby.frameworks.HttpClients
private import codeql.ruby.frameworks.XmlParsing
private import codeql.ruby.frameworks.ActionDispatch
private import codeql.ruby.frameworks.PosixSpawn

View File

@@ -0,0 +1,80 @@
/**
* Provides modeling for the `posix-spawn` gem.
* Version: 0.3.15
*/
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.controlflow.CfgNodes
/**
* Provides modeling for the `posix-spawn` gem.
* Version: 0.3.15
*/
module PosixSpawn {
private API::Node posixSpawnModule() {
result = API::getTopLevelMember("POSIX").getMember("Spawn")
}
/**
* A call to `POSIX::Spawn::Child.new` or `POSIX::Spawn::Child.build`.
*/
class ChildCall extends SystemCommandExecution::Range, DataFlow::CallNode {
ChildCall() {
this =
[
posixSpawnModule().getMember("Child").getAMethodCall("build"),
posixSpawnModule().getMember("Child").getAnInstantiation()
]
}
override DataFlow::Node getAnArgument() {
result = this.getArgument(_) and not result.asExpr() instanceof ExprNodes::PairCfgNode
}
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
}
/**
* A call to `POSIX::Spawn.spawn` or a related method.
*/
class SystemCall extends SystemCommandExecution::Range, DataFlow::CallNode {
SystemCall() {
this =
posixSpawnModule()
.getAMethodCall(["spawn", "fspawn", "popen4", "pspawn", "system", "_pspawn", "`"])
}
override DataFlow::Node getAnArgument() { this.argument(result) }
// From the docs:
// When only command is given and includes a space character, the command
// text is executed by the system shell interpreter.
// This means the following signatures are shell interpreted:
//
// spawn(cmd)
// spawn(cmd, opts)
// spawn(env, cmd)
// spawn(env, cmd, opts)
//
// env and opts will be hashes. We over-approximate by assuming the argument
// is shell interpreted unless there is another argument with a string
// constant value.
override predicate isShellInterpreted(DataFlow::Node arg) {
not exists(DataFlow::Node otherArg |
otherArg != arg and
this.argument(arg) and
this.argument(otherArg) and
otherArg.asExpr().getConstantValue().isString(_)
)
}
private predicate argument(DataFlow::Node arg) {
arg = this.getArgument(_) and
not arg.asExpr() instanceof ExprNodes::HashLiteralCfgNode and
not arg.asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
not arg.asExpr() instanceof ExprNodes::PairCfgNode
}
}
}

View File

@@ -0,0 +1,29 @@
systemCalls
| PosixSpawn.rb:1:1:1:32 | call to popen4 | PosixSpawn.rb:1:22:1:25 | "ls" | false |
| PosixSpawn.rb:1:1:1:32 | call to popen4 | PosixSpawn.rb:1:28:1:31 | "-l" | false |
| PosixSpawn.rb:2:1:2:31 | call to popen4 | PosixSpawn.rb:2:21:2:24 | "ls" | false |
| PosixSpawn.rb:2:1:2:31 | call to popen4 | PosixSpawn.rb:2:27:2:30 | "-l" | false |
| PosixSpawn.rb:7:1:7:40 | call to spawn | PosixSpawn.rb:7:20:7:39 | * ... | true |
| PosixSpawn.rb:8:1:8:30 | call to spawn | PosixSpawn.rb:8:21:8:29 | "sleep 5" | true |
| PosixSpawn.rb:9:1:9:23 | call to spawn | PosixSpawn.rb:9:20:9:22 | call to cmd | true |
| PosixSpawn.rb:10:1:10:29 | call to spawn | PosixSpawn.rb:10:20:10:22 | call to env | false |
| PosixSpawn.rb:10:1:10:29 | call to spawn | PosixSpawn.rb:10:25:10:28 | "ls" | true |
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:21:15:25 | "foo" | false |
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:28:15:32 | "bar" | false |
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:35:15:44 | "--a-flag" | false |
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:47:15:52 | call to before | false |
| PosixSpawn.rb:15:1:15:60 | call to system | PosixSpawn.rb:15:55:15:59 | call to after | false |
| PosixSpawn.rb:17:1:17:28 | call to fspawn | PosixSpawn.rb:17:21:17:27 | call to command | true |
| PosixSpawn.rb:18:1:18:28 | call to pspawn | PosixSpawn.rb:18:21:18:27 | call to command | true |
| PosixSpawn.rb:19:1:19:28 | call to popen4 | PosixSpawn.rb:19:21:19:27 | call to command | true |
| PosixSpawn.rb:21:1:21:28 | call to ` | PosixSpawn.rb:21:16:21:20 | "foo" | false |
| PosixSpawn.rb:21:1:21:28 | call to ` | PosixSpawn.rb:21:23:21:27 | "bar" | false |
childCalls
| PosixSpawn.rb:4:1:4:77 | call to new | PosixSpawn.rb:4:25:4:39 | call to [] | false |
| PosixSpawn.rb:4:1:4:77 | call to new | PosixSpawn.rb:4:42:4:51 | ... + ... | false |
| PosixSpawn.rb:4:1:4:77 | call to new | PosixSpawn.rb:4:54:4:58 | * ... | false |
| PosixSpawn.rb:5:1:5:80 | call to new | PosixSpawn.rb:5:25:5:32 | * ... | false |
| PosixSpawn.rb:12:1:12:35 | call to new | PosixSpawn.rb:12:25:12:28 | "ls" | false |
| PosixSpawn.rb:12:1:12:35 | call to new | PosixSpawn.rb:12:31:12:34 | "-l" | false |
| PosixSpawn.rb:13:1:13:38 | call to build | PosixSpawn.rb:13:27:13:32 | "echo" | false |
| PosixSpawn.rb:13:1:13:38 | call to build | PosixSpawn.rb:13:35:13:37 | call to msg | false |

View File

@@ -0,0 +1,15 @@
import ruby
import codeql.ruby.frameworks.PosixSpawn
import codeql.ruby.DataFlow
query predicate systemCalls(
PosixSpawn::SystemCall call, DataFlow::Node arg, boolean shellInterpreted
) {
arg = call.getAnArgument() and
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
}
query predicate childCalls(PosixSpawn::ChildCall call, DataFlow::Node arg, boolean shellInterpreted) {
arg = call.getAnArgument() and
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
}

View File

@@ -0,0 +1,21 @@
POSIX::Spawn::popen4("ls", "-l")
POSIX::Spawn.popen4("ls", "-l")
POSIX::Spawn::Child.new({'ENV' => @var}, "foo/"+cmd, *argv, :chdir=>root_dir)
POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: timeout)
POSIX::Spawn.spawn(*(argv+[{:in => f}]))
POSIX::Spawn::spawn('sleep 5')
POSIX::Spawn.spawn(cmd)
POSIX::Spawn.spawn(env, "ls")
POSIX::Spawn::Child.new("ls", "-l")
POSIX::Spawn::Child.build("echo", msg)
POSIX::Spawn.system("foo", "bar", "--a-flag", before, after)
POSIX::Spawn.fspawn(command)
POSIX::Spawn.pspawn(command)
POSIX::Spawn.popen4(command)
POSIX::Spawn.`("foo", "bar")