Skip to content

Commit d7325ba

Browse files
authored
Merge pull request github#12856 from p-/p--non-constant-open-improvments
Ruby: Add additional sanitizers for Kernel.open or IO.read or similar sinks with a non-constant value
2 parents 4e60697 + 672cb92 commit d7325ba

File tree

3 files changed

+47
-16
lines changed

3 files changed

+47
-16
lines changed

ruby/ql/src/queries/security/cwe-078/NonConstantKernelOpen.ql

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,41 @@
1818
import codeql.ruby.security.KernelOpenQuery
1919
import codeql.ruby.AST
2020
import codeql.ruby.ApiGraphs
21+
import codeql.ruby.DataFlow
2122

2223
from AmbiguousPathCall call
2324
where
24-
not hasConstantPrefix(call.getPathArgument().getALocalSource().asExpr().getExpr()) and
25+
call.getNumberOfArguments() > 0 and
26+
not hasConstantPrefix(call.getPathArgument()) and
2527
not call.getPathArgument().getALocalSource() =
2628
API::getTopLevelMember("File").getAMethodCall("join")
2729
select call,
2830
"Call to " + call.getName() + " with a non-constant value. Consider replacing it with " +
2931
call.getReplacement() + "."
3032

31-
predicate hasConstantPrefix(Expr e) {
33+
predicate hasConstantPrefix(DataFlow::Node node) {
34+
hasConstantPrefix(node.getALocalSource())
35+
or
3236
// if it's a format string, then the first argument is not a constant string
33-
e.(StringlikeLiteral).getComponent(0) instanceof StringTextComponent
37+
node.asExpr().getExpr().(StringlikeLiteral).getComponent(0) instanceof StringTextComponent
3438
or
3539
// it is not a constant string argument
36-
exists(e.getConstantValue())
40+
exists(node.getConstantValue())
3741
or
3842
// not a concatenation that starts with a constant string
39-
hasConstantPrefix(e.(AddExpr).getLeftOperand())
43+
exists(DataFlow::ExprNode prefix |
44+
node.asExpr().getExpr().(AddExpr).getLeftOperand() = prefix.asExpr().getExpr() and
45+
hasConstantPrefix(prefix)
46+
)
47+
or
48+
// is a .freeze call on a constant string
49+
exists(DataFlow::CallNode call | node = call and call.getMethodName() = "freeze" |
50+
hasConstantPrefix(call.getReceiver())
51+
)
52+
or
53+
// is a constant read of a constant string
54+
exists(DataFlow::Node constant |
55+
constant.asExpr().getExpr() = node.asExpr().getExpr().(ConstantReadAccess).getValue() and
56+
hasConstantPrefix(constant)
57+
)
4058
}
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
| NonConstantKernelOpen.rb:4:5:4:14 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |
2-
| NonConstantKernelOpen.rb:5:5:5:17 | call to read | Call to IO.read with a non-constant value. Consider replacing it with File.read. |
3-
| NonConstantKernelOpen.rb:6:5:6:18 | call to write | Call to IO.write with a non-constant value. Consider replacing it with File.write. |
4-
| NonConstantKernelOpen.rb:7:5:7:20 | call to binread | Call to IO.binread with a non-constant value. Consider replacing it with File.binread. |
5-
| NonConstantKernelOpen.rb:8:5:8:21 | call to binwrite | Call to IO.binwrite with a non-constant value. Consider replacing it with File.binwrite. |
6-
| NonConstantKernelOpen.rb:9:5:9:20 | call to foreach | Call to IO.foreach with a non-constant value. Consider replacing it with File.foreach. |
7-
| NonConstantKernelOpen.rb:10:5:10:22 | call to readlines | Call to IO.readlines with a non-constant value. Consider replacing it with File.readlines. |
8-
| NonConstantKernelOpen.rb:11:5:11:18 | call to open | Call to URI.open with a non-constant value. Consider replacing it with URI(<uri>).open. |
9-
| NonConstantKernelOpen.rb:15:5:15:21 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |
10-
| NonConstantKernelOpen.rb:25:5:25:33 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |
11-
| NonConstantKernelOpen.rb:33:5:33:14 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |
1+
| NonConstantKernelOpen.rb:7:5:7:14 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |
2+
| NonConstantKernelOpen.rb:8:5:8:17 | call to read | Call to IO.read with a non-constant value. Consider replacing it with File.read. |
3+
| NonConstantKernelOpen.rb:9:5:9:18 | call to write | Call to IO.write with a non-constant value. Consider replacing it with File.write. |
4+
| NonConstantKernelOpen.rb:10:5:10:20 | call to binread | Call to IO.binread with a non-constant value. Consider replacing it with File.binread. |
5+
| NonConstantKernelOpen.rb:11:5:11:21 | call to binwrite | Call to IO.binwrite with a non-constant value. Consider replacing it with File.binwrite. |
6+
| NonConstantKernelOpen.rb:12:5:12:20 | call to foreach | Call to IO.foreach with a non-constant value. Consider replacing it with File.foreach. |
7+
| NonConstantKernelOpen.rb:13:5:13:22 | call to readlines | Call to IO.readlines with a non-constant value. Consider replacing it with File.readlines. |
8+
| NonConstantKernelOpen.rb:14:5:14:18 | call to open | Call to URI.open with a non-constant value. Consider replacing it with URI(<uri>).open. |
9+
| NonConstantKernelOpen.rb:18:5:18:21 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |
10+
| NonConstantKernelOpen.rb:28:5:28:33 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |
11+
| NonConstantKernelOpen.rb:46:5:46:14 | call to open | Call to Kernel.open with a non-constant value. Consider replacing it with File.open. |

ruby/ql/test/query-tests/security/cwe-078/NonConstantKernelOpen/NonConstantKernelOpen.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
class UsersController < ActionController::Base
2+
CONSTANT = "constant"
3+
CONSTANT_WITH_FREEZE = "constant-with-freeze".freeze
4+
25
def create
36
file = params[:file]
47
open(file) # BAD
@@ -30,6 +33,16 @@ def create
3033

3134
IO.write(File.join("foo", "bar.txt"), "bar") # GOOD
3235

36+
IO.read(CONSTANT) # GOOD
37+
38+
IO.read(CONSTANT + file) # GOOD
39+
40+
IO.read(CONSTANT_WITH_FREEZE) # GOOD
41+
42+
IO.read(CONSTANT_WITH_FREEZE + file) # GOOD
43+
44+
open.where(external: false) # GOOD - an open method is called withoout arguments
45+
3346
open(file) # BAD - sanity check to verify that file was not mistakenly marked as sanitized
3447
end
3548
end

0 commit comments

Comments
 (0)