Skip to content

Commit f136651

Browse files
authored
Merge pull request github#11575 from erik-krogh/kernelLoad
Rb: add Kernel methods as sinks to path-injection
2 parents 55a04e7 + d0af30b commit f136651

File tree

16 files changed

+168
-87
lines changed

16 files changed

+168
-87
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Calls to `Kernel.load`, `Kernel.require`, `Kernel.autoload` are now modeled as sinks for path injection.

ruby/ql/lib/codeql/ruby/ast/Call.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ class MethodCall extends Call instanceof MethodCallImpl {
9797
* ```
9898
*
9999
* the result is `"bar"`.
100+
*
101+
* Super calls call a method with the same name as the current method, so
102+
* the result for a super call is the name of the current method.
103+
* E.g:
104+
* ```rb
105+
* def foo
106+
* super # the result for this super call is "foo"
107+
* end
108+
* ```
100109
*/
101110
final string getMethodName() { result = super.getMethodNameImpl() }
102111

@@ -201,6 +210,8 @@ class YieldCall extends Call instanceof YieldCallImpl {
201210
*/
202211
class SuperCall extends MethodCall instanceof SuperCallImpl {
203212
final override string getAPrimaryQlClass() { result = "SuperCall" }
213+
214+
override string toString() { result = "super call to " + this.getMethodName() }
204215
}
205216

206217
/**

ruby/ql/lib/codeql/ruby/ast/internal/Call.qll

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,9 @@ class RegularMethodCall extends MethodCallImpl, TRegularMethodCall {
7878
}
7979

8080
final override string getMethodNameImpl() {
81-
isRegularMethodCall(g) and
82-
(
83-
result = "call" and not exists(g.getMethod())
84-
or
85-
result = g.getMethod().(Ruby::Token).getValue()
86-
)
81+
result = "call" and not exists(g.getMethod())
82+
or
83+
result = g.getMethod().(Ruby::Token).getValue()
8784
}
8885

8986
final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getArguments().getChild(n) }
@@ -115,12 +112,26 @@ class ElementReferenceImpl extends MethodCallImpl, TElementReference {
115112

116113
abstract class SuperCallImpl extends MethodCallImpl, TSuperCall { }
117114

115+
private Ruby::AstNode getSuperParent(Ruby::Super sup) {
116+
result = sup
117+
or
118+
result = getSuperParent(sup).getParent() and
119+
not result instanceof Ruby::Method
120+
}
121+
122+
private string getSuperMethodName(Ruby::Super sup) {
123+
exists(Ruby::Method meth |
124+
meth = getSuperParent(sup).getParent() and
125+
result = any(Method c | toGenerated(c) = meth).getName()
126+
)
127+
}
128+
118129
class TokenSuperCall extends SuperCallImpl, TTokenSuperCall {
119130
private Ruby::Super g;
120131

121132
TokenSuperCall() { this = TTokenSuperCall(g) }
122133

123-
final override string getMethodNameImpl() { result = g.getValue() }
134+
final override string getMethodNameImpl() { result = getSuperMethodName(g) }
124135

125136
final override Expr getReceiverImpl() { none() }
126137

@@ -136,7 +147,7 @@ class RegularSuperCall extends SuperCallImpl, TRegularSuperCall {
136147

137148
RegularSuperCall() { this = TRegularSuperCall(g) }
138149

139-
final override string getMethodNameImpl() { result = g.getMethod().(Ruby::Super).getValue() }
150+
final override string getMethodNameImpl() { result = getSuperMethodName(g.getMethod()) }
140151

141152
final override Expr getReceiverImpl() { none() }
142153

ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ module Kernel {
2424
this.asExpr().getExpr() instanceof UnknownMethodCall and
2525
(
2626
this.getReceiver().asExpr().getExpr() instanceof SelfVariableAccess and
27-
isPrivateKernelMethod(this.getMethodName())
27+
isPrivateKernelMethod(super.getMethodName())
2828
or
29-
isPublicKernelMethod(this.getMethodName())
29+
this.asExpr().getExpr() instanceof SuperCall and
30+
isPrivateKernelMethod(super.getMethodName())
31+
or
32+
isPublicKernelMethod(super.getMethodName())
3033
)
3134
}
3235
}
@@ -92,14 +95,14 @@ module Kernel {
9295
* ```
9396
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system
9497
*/
95-
class KernelSystemCall extends SystemCommandExecution::Range, KernelMethodCall {
98+
class KernelSystemCall extends SystemCommandExecution::Range instanceof KernelMethodCall {
9699
KernelSystemCall() { this.getMethodName() = "system" }
97100

98-
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
101+
override DataFlow::Node getAnArgument() { result = super.getArgument(_) }
99102

100103
override predicate isShellInterpreted(DataFlow::Node arg) {
101104
// Kernel.system invokes a subshell if you provide a single string as argument
102-
this.getNumberOfArguments() = 1 and arg = this.getAnArgument()
105+
super.getNumberOfArguments() = 1 and arg = this.getAnArgument()
103106
}
104107
}
105108

@@ -108,14 +111,14 @@ module Kernel {
108111
* `Kernel.exec` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details.
109112
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec
110113
*/
111-
class KernelExecCall extends SystemCommandExecution::Range, KernelMethodCall {
114+
class KernelExecCall extends SystemCommandExecution::Range instanceof KernelMethodCall {
112115
KernelExecCall() { this.getMethodName() = "exec" }
113116

114-
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
117+
override DataFlow::Node getAnArgument() { result = super.getArgument(_) }
115118

116119
override predicate isShellInterpreted(DataFlow::Node arg) {
117120
// Kernel.exec invokes a subshell if you provide a single string as argument
118-
this.getNumberOfArguments() = 1 and arg = this.getAnArgument()
121+
super.getNumberOfArguments() = 1 and arg = this.getAnArgument()
119122
}
120123
}
121124

@@ -129,14 +132,14 @@ module Kernel {
129132
* spawn([env,] command... [,options]) -> pid
130133
* ```
131134
*/
132-
class KernelSpawnCall extends SystemCommandExecution::Range, KernelMethodCall {
135+
class KernelSpawnCall extends SystemCommandExecution::Range instanceof KernelMethodCall {
133136
KernelSpawnCall() { this.getMethodName() = "spawn" }
134137

135-
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
138+
override DataFlow::Node getAnArgument() { result = super.getArgument(_) }
136139

137140
override predicate isShellInterpreted(DataFlow::Node arg) {
138141
// Kernel.spawn invokes a subshell if you provide a single string as argument
139-
this.getNumberOfArguments() = 1 and arg = this.getAnArgument()
142+
super.getNumberOfArguments() = 1 and arg = this.getAnArgument()
140143
}
141144
}
142145

@@ -179,4 +182,19 @@ module Kernel {
179182
preservesValue = true
180183
}
181184
}
185+
186+
/** A call to e.g. `Kernel.load` that accesses a file. */
187+
private class KernelFileAccess extends FileSystemAccess::Range instanceof KernelMethodCall {
188+
KernelFileAccess() {
189+
super.getMethodName() = ["load", "require", "require_relative", "autoload", "autoload?"]
190+
}
191+
192+
override DataFlow::Node getAPathArgument() {
193+
result = super.getArgument(0) and
194+
super.getMethodName() = ["load", "require", "require_relative"]
195+
or
196+
result = super.getArgument(1) and
197+
super.getMethodName() = ["autoload", "autoload?"]
198+
}
199+
}
182200
}

ruby/ql/lib/codeql/ruby/security/StackTraceExposureCustomizations.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ module StackTraceExposure {
4141
/**
4242
* A call to `Kernel#caller`, considered as a flow source.
4343
*/
44-
class KernelCallerCall extends Source, Kernel::KernelMethodCall {
45-
KernelCallerCall() { this.getMethodName() = "caller" }
44+
class KernelCallerCall extends Source instanceof Kernel::KernelMethodCall {
45+
KernelCallerCall() { super.getMethodName() = "caller" }
4646
}
4747

4848
/**

ruby/ql/src/queries/meta/internal/TaintMetrics.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
private import ruby
12
private import codeql.files.FileSystem
2-
private import codeql.ruby.DataFlow
33
private import codeql.ruby.dataflow.RemoteFlowSources
44
private import codeql.ruby.security.CodeInjectionCustomizations
55
private import codeql.ruby.security.CommandInjectionCustomizations
@@ -34,6 +34,12 @@ DataFlow::Node relevantTaintSink(string kind) {
3434
kind = "UnsafeDeserialization" and result instanceof UnsafeDeserialization::Sink
3535
or
3636
kind = "UrlRedirect" and result instanceof UrlRedirect::Sink
37+
) and
38+
// the sink is not a string literal
39+
not exists(Ast::StringLiteral str |
40+
str = result.asExpr().getExpr() and
41+
// ensure there is no interpolation, as that is not a literal
42+
not str.getComponent(_) instanceof Ast::StringInterpolationComponent
3743
)
3844
}
3945

ruby/ql/src/queries/security/cwe-022/PathInjection.ql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
* external/cwe/cwe-099
1616
*/
1717

18-
import codeql.ruby.AST
18+
import ruby
1919
import codeql.ruby.security.PathInjectionQuery
20-
import codeql.ruby.DataFlow
2120
import DataFlow::PathGraph
2221

2322
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

ruby/ql/test/library-tests/ast/Ast.expected

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -496,30 +496,30 @@ calls/calls.rb:
496496
# 279| getReceiver: [ConstantReadAccess] X
497497
# 284| getStmt: [ClassDeclaration] MyClass
498498
# 285| getStmt: [Method] my_method
499-
# 286| getStmt: [SuperCall] call to super
500-
# 287| getStmt: [SuperCall] call to super
501-
# 288| getStmt: [SuperCall] call to super
499+
# 286| getStmt: [SuperCall] super call to my_method
500+
# 287| getStmt: [SuperCall] super call to my_method
501+
# 288| getStmt: [SuperCall] super call to my_method
502502
# 288| getArgument: [StringLiteral] "blah"
503503
# 288| getComponent: [StringTextComponent] blah
504-
# 289| getStmt: [SuperCall] call to super
504+
# 289| getStmt: [SuperCall] super call to my_method
505505
# 289| getArgument: [IntegerLiteral] 1
506506
# 289| getArgument: [IntegerLiteral] 2
507507
# 289| getArgument: [IntegerLiteral] 3
508-
# 290| getStmt: [SuperCall] call to super
508+
# 290| getStmt: [SuperCall] super call to my_method
509509
# 290| getBlock: [BraceBlock] { ... }
510510
# 290| getParameter: [SimpleParameter] x
511511
# 290| getDefiningAccess: [LocalVariableAccess] x
512512
# 290| getStmt: [AddExpr] ... + ...
513513
# 290| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x
514514
# 290| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
515-
# 291| getStmt: [SuperCall] call to super
515+
# 291| getStmt: [SuperCall] super call to my_method
516516
# 291| getBlock: [DoBlock] do ... end
517517
# 291| getParameter: [SimpleParameter] x
518518
# 291| getDefiningAccess: [LocalVariableAccess] x
519519
# 291| getStmt: [MulExpr] ... * ...
520520
# 291| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x
521521
# 291| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2
522-
# 292| getStmt: [SuperCall] call to super
522+
# 292| getStmt: [SuperCall] super call to my_method
523523
# 292| getArgument: [IntegerLiteral] 4
524524
# 292| getArgument: [IntegerLiteral] 5
525525
# 292| getBlock: [BraceBlock] { ... }
@@ -528,7 +528,7 @@ calls/calls.rb:
528528
# 292| getStmt: [AddExpr] ... + ...
529529
# 292| getAnOperand/getLeftOperand/getReceiver: [LocalVariableAccess] x
530530
# 292| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 100
531-
# 293| getStmt: [SuperCall] call to super
531+
# 293| getStmt: [SuperCall] super call to my_method
532532
# 293| getArgument: [IntegerLiteral] 6
533533
# 293| getArgument: [IntegerLiteral] 7
534534
# 293| getBlock: [DoBlock] do ... end
@@ -545,7 +545,7 @@ calls/calls.rb:
545545
# 304| getStmt: [MethodCall] call to super
546546
# 304| getReceiver: [SelfVariableAccess] self
547547
# 305| getStmt: [MethodCall] call to super
548-
# 305| getReceiver: [SuperCall] call to super
548+
# 305| getReceiver: [SuperCall] super call to another_method
549549
# 310| getStmt: [MethodCall] call to call
550550
# 310| getReceiver: [MethodCall] call to foo
551551
# 310| getReceiver: [SelfVariableAccess] self
@@ -646,7 +646,7 @@ calls/calls.rb:
646646
# 328| getComponent: [StringTextComponent] error
647647
# 331| getStmt: [Method] foo
648648
# 331| getParameter: [ForwardParameter] ...
649-
# 332| getStmt: [SuperCall] call to super
649+
# 332| getStmt: [SuperCall] super call to foo
650650
# 332| getArgument: [ForwardedArguments] ...
651651
# 335| getStmt: [Method] foo
652652
# 335| getParameter: [SimpleParameter] a
@@ -1293,7 +1293,7 @@ modules/classes.rb:
12931293
# 42| getStmt: [Method] length
12941294
# 43| getStmt: [MulExpr] ... * ...
12951295
# 43| getAnOperand/getLeftOperand/getReceiver: [IntegerLiteral] 100
1296-
# 43| getAnOperand/getArgument/getRightOperand: [SuperCall] call to super
1296+
# 43| getAnOperand/getArgument/getRightOperand: [SuperCall] super call to length
12971297
# 46| getStmt: [Method] wibble
12981298
# 47| getStmt: [MethodCall] call to puts
12991299
# 47| getReceiver: [SelfVariableAccess] self

0 commit comments

Comments
 (0)