@@ -2,7 +2,7 @@ private import codeql.ruby.AST
2
2
private import codeql.ruby.CFG
3
3
private import DataFlowPrivate
4
4
private import codeql.ruby.typetracking.internal.TypeTrackingImpl
5
- private import codeql.ruby.ast.internal.Module
5
+ private import codeql.ruby.ast.internal.Module as Module
6
6
private import FlowSummaryImpl as FlowSummaryImpl
7
7
private import codeql.ruby.dataflow.FlowSummary
8
8
private import codeql.ruby.dataflow.SSA
@@ -82,6 +82,12 @@ abstract class LibraryCallable extends string {
82
82
Call getACallSimple ( ) { none ( ) }
83
83
}
84
84
85
+ /** A callable defined in library code, which should be taken into account in type tracking. */
86
+ abstract class LibraryCallableToIncludeInTypeTracking extends LibraryCallable {
87
+ bindingset [ this ]
88
+ LibraryCallableToIncludeInTypeTracking ( ) { exists ( this ) }
89
+ }
90
+
85
91
/**
86
92
* A callable. This includes callables from source code, as well as callables
87
93
* defined in library code.
@@ -184,6 +190,91 @@ class NormalCall extends DataFlowCall, TNormalCall {
184
190
override Location getLocation ( ) { result = c .getLocation ( ) }
185
191
}
186
192
193
+ /**
194
+ * Provides modeling of flow through the `render` method of view components.
195
+ *
196
+ * ```rb
197
+ * # view.rb
198
+ * class View < ViewComponent::Base
199
+ * def initialize(x)
200
+ * @x = x
201
+ * end
202
+ *
203
+ * def foo
204
+ * sink(@x)
205
+ * end
206
+ * end
207
+ * ```
208
+ *
209
+ * ```erb
210
+ * # view.html.erb
211
+ * <%= foo() %> # 1
212
+ * ```
213
+ *
214
+ * ```rb
215
+ * # app.rb
216
+ * class App
217
+ * def run
218
+ * view = View.new(taint) # 2
219
+ * render(view) # 3
220
+ * end
221
+ * end
222
+ * ```
223
+ *
224
+ * The `render` call (3) is modeled using a flow summary. The summary specifies
225
+ * that the first argument (`view`) will have a special method invoked on it (we
226
+ * call the method `__invoke__toplevel__erb__`), which targets the top-level of the
227
+ * matching ERB file (`view.html.erb`). The `view` argument will flow into the receiver
228
+ * of the synthesized method call, from there into the implicit `self` parameter of
229
+ * the ERB file, and from there to the implicit `self` receiver of the call to `foo` (1).
230
+ *
231
+ * Since it is not actually possible to specify such flow summaries, we instead
232
+ * specify a call-back summary, and adjust the generated call to target the special
233
+ * `__invoke__toplevel__erb__` method.
234
+ *
235
+ * In order to resolve the target of the adjusted method call, we need to take
236
+ * the `render` summary into account when constructing the call graph. That is, we
237
+ * need to track the `View` instance (2) into the receiver of the adjusted method
238
+ * call, in order to figure out that the call target is in fact `view.html.erb`.
239
+ */
240
+ private module ViewComponentRenderModeling {
241
+ private import codeql.ruby.frameworks.ViewComponent
242
+
243
+ private class RenderMethod extends SummarizedCallable , LibraryCallableToIncludeInTypeTracking {
244
+ RenderMethod ( ) { this = "render view component" }
245
+
246
+ override MethodCall getACallSimple ( ) { result .getMethodName ( ) = "render" }
247
+
248
+ override predicate propagatesFlow ( string input , string output , boolean preservesValue ) {
249
+ input = "Argument[0]" and
250
+ // use a call-back summary, and adjust it to a method call below
251
+ output = "Argument[0].Parameter[self]" and
252
+ preservesValue = true
253
+ }
254
+ }
255
+
256
+ private string invokeToplevelName ( ) { result = "__invoke__toplevel__erb__" }
257
+
258
+ /** Holds if `call` should be adjusted to be a method call to `name` on `receiver`. */
259
+ predicate adjustedMethodCall ( DataFlowCall call , FlowSummaryNode receiver , string name ) {
260
+ exists ( RenderMethod render |
261
+ call = TSummaryCall ( render , receiver .getSummaryNode ( ) ) and
262
+ name = invokeToplevelName ( )
263
+ )
264
+ }
265
+
266
+ /** Holds if `self` belongs to the top-level of an ERB file with matching view class `view`. */
267
+ pragma [ nomagic]
268
+ predicate selfInErbToplevel ( SelfVariable self , ViewComponent:: ComponentClass view ) {
269
+ self .getDeclaringScope ( ) .( Toplevel ) .getFile ( ) = view .getTemplate ( )
270
+ }
271
+
272
+ Toplevel lookupMethod ( ViewComponent:: ComponentClass m , string name ) {
273
+ result .getFile ( ) = m .getTemplate ( ) and
274
+ name = invokeToplevelName ( )
275
+ }
276
+ }
277
+
187
278
/** A call for which we want to compute call targets. */
188
279
private class RelevantCall extends CfgNodes:: ExprNodes:: CallCfgNode {
189
280
pragma [ nomagic]
@@ -200,6 +291,8 @@ private predicate methodCall(DataFlowCall call, DataFlow::Node receiver, string
200
291
method = rc .getExpr ( ) .( MethodCall ) .getMethodName ( ) and
201
292
receiver .asExpr ( ) = rc .getReceiver ( )
202
293
)
294
+ or
295
+ ViewComponentRenderModeling:: adjustedMethodCall ( call , receiver , method )
203
296
}
204
297
205
298
pragma [ nomagic]
@@ -253,8 +346,11 @@ private predicate selfInMethod(SelfVariable self, MethodBase method, Module m) {
253
346
/** Holds if `self` belongs to the top-level. */
254
347
pragma [ nomagic]
255
348
private predicate selfInToplevel ( SelfVariable self , Module m ) {
349
+ ViewComponentRenderModeling:: selfInErbToplevel ( self , m )
350
+ or
351
+ not ViewComponentRenderModeling:: selfInErbToplevel ( self , _) and
256
352
self .getDeclaringScope ( ) instanceof Toplevel and
257
- m = TResolved ( "Object" )
353
+ m = Module :: TResolved ( "Object" )
258
354
}
259
355
260
356
/**
@@ -271,7 +367,7 @@ private predicate selfInToplevel(SelfVariable self, Module m) {
271
367
*/
272
368
private predicate asModulePattern ( SsaDefinitionExtNode def , Module m ) {
273
369
exists ( AsPattern ap |
274
- m = resolveConstantReadAccess ( ap .getPattern ( ) ) and
370
+ m = Module :: resolveConstantReadAccess ( ap .getPattern ( ) ) and
275
371
def .getDefinitionExt ( ) .( Ssa:: WriteDefinition ) .getWriteAccess ( ) .getAstNode ( ) =
276
372
ap .getVariableAccess ( )
277
373
)
@@ -295,7 +391,7 @@ private predicate hasAdjacentTypeCheckedReads(
295
391
exists (
296
392
CfgNodes:: ExprCfgNode pattern , ConditionBlock cb , CfgNodes:: ExprNodes:: CaseExprCfgNode case
297
393
|
298
- m = resolveConstantReadAccess ( pattern .getExpr ( ) ) and
394
+ m = Module :: resolveConstantReadAccess ( pattern .getExpr ( ) ) and
299
395
cb .getLastNode ( ) = pattern and
300
396
cb .controls ( read2 .getBasicBlock ( ) ,
301
397
any ( SuccessorTypes:: MatchingSuccessor match | match .getValue ( ) = true ) ) and
@@ -313,7 +409,7 @@ predicate isUserDefinedNew(SingletonMethod new) {
313
409
exists ( Expr object | singletonMethod ( new , "new" , object ) |
314
410
selfInModule ( object .( SelfVariableReadAccess ) .getVariable ( ) , _)
315
411
or
316
- exists ( resolveConstantReadAccess ( object ) )
412
+ exists ( Module :: resolveConstantReadAccess ( object ) )
317
413
)
318
414
}
319
415
@@ -351,7 +447,7 @@ private predicate extendCall(DataFlow::ExprNode receiver, Module m) {
351
447
extendCall .getMethodName ( ) = "extend" and
352
448
exists ( DataFlow:: LocalSourceNode sourceNode | sourceNode .flowsTo ( extendCall .getArgument ( _) ) |
353
449
selfInModule ( sourceNode .( SelfLocalSourceNode ) .getVariable ( ) , m ) or
354
- m = resolveConstantReadAccess ( sourceNode .asExpr ( ) .getExpr ( ) )
450
+ m = Module :: resolveConstantReadAccess ( sourceNode .asExpr ( ) .getExpr ( ) )
355
451
) and
356
452
receiver = extendCall .getReceiver ( )
357
453
)
@@ -364,10 +460,16 @@ private predicate extendCallModule(Module m, Module n) {
364
460
receiver .flowsTo ( e ) and extendCall ( e , n )
365
461
|
366
462
selfInModule ( receiver .( SelfLocalSourceNode ) .getVariable ( ) , m ) or
367
- m = resolveConstantReadAccess ( receiver .asExpr ( ) .getExpr ( ) )
463
+ m = Module :: resolveConstantReadAccess ( receiver .asExpr ( ) .getExpr ( ) )
368
464
)
369
465
}
370
466
467
+ private CfgScope lookupMethod ( Module m , string name ) {
468
+ result = Module:: lookupMethod ( m , name )
469
+ or
470
+ result = ViewComponentRenderModeling:: lookupMethod ( m , name )
471
+ }
472
+
371
473
/**
372
474
* Gets a method available in module `m`, or in one of `m`'s transitive
373
475
* sub classes when `exact = false`.
@@ -377,7 +479,7 @@ private CfgScope lookupMethod(Module m, string name, boolean exact) {
377
479
result = lookupMethod ( m , name ) and
378
480
exact in [ false , true ]
379
481
or
380
- result = lookupMethodInSubClasses ( m , name ) and
482
+ result = Module :: lookupMethodInSubClasses ( m , name ) and
381
483
exact = false
382
484
}
383
485
@@ -490,7 +592,7 @@ private module TrackModuleInput implements CallGraphConstruction::Simple::InputS
490
592
class State = Module ;
491
593
492
594
predicate start ( DataFlow:: Node start , Module m ) {
493
- m = resolveConstantReadAccess ( start .asExpr ( ) .getExpr ( ) )
595
+ m = Module :: resolveConstantReadAccess ( start .asExpr ( ) .getExpr ( ) )
494
596
}
495
597
496
598
// We exclude steps into `self` parameters, and instead rely on the type of the
@@ -547,55 +649,55 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig {
547
649
pragma [ nomagic]
548
650
private predicate isInstanceNoCall ( DataFlow:: Node n , Module tp , boolean exact ) {
549
651
n .asExpr ( ) .getExpr ( ) instanceof NilLiteral and
550
- tp = TResolved ( "NilClass" ) and
652
+ tp = Module :: TResolved ( "NilClass" ) and
551
653
exact = true
552
654
or
553
655
n .asExpr ( ) .getExpr ( ) .( BooleanLiteral ) .isFalse ( ) and
554
- tp = TResolved ( "FalseClass" ) and
656
+ tp = Module :: TResolved ( "FalseClass" ) and
555
657
exact = true
556
658
or
557
659
n .asExpr ( ) .getExpr ( ) .( BooleanLiteral ) .isTrue ( ) and
558
- tp = TResolved ( "TrueClass" ) and
660
+ tp = Module :: TResolved ( "TrueClass" ) and
559
661
exact = true
560
662
or
561
663
n .asExpr ( ) .getExpr ( ) instanceof IntegerLiteral and
562
- tp = TResolved ( "Integer" ) and
664
+ tp = Module :: TResolved ( "Integer" ) and
563
665
exact = true
564
666
or
565
667
n .asExpr ( ) .getExpr ( ) instanceof FloatLiteral and
566
- tp = TResolved ( "Float" ) and
668
+ tp = Module :: TResolved ( "Float" ) and
567
669
exact = true
568
670
or
569
671
n .asExpr ( ) .getExpr ( ) instanceof RationalLiteral and
570
- tp = TResolved ( "Rational" ) and
672
+ tp = Module :: TResolved ( "Rational" ) and
571
673
exact = true
572
674
or
573
675
n .asExpr ( ) .getExpr ( ) instanceof ComplexLiteral and
574
- tp = TResolved ( "Complex" ) and
676
+ tp = Module :: TResolved ( "Complex" ) and
575
677
exact = true
576
678
or
577
679
n .asExpr ( ) .getExpr ( ) instanceof StringlikeLiteral and
578
- tp = TResolved ( "String" ) and
680
+ tp = Module :: TResolved ( "String" ) and
579
681
exact = true
580
682
or
581
683
n .asExpr ( ) instanceof CfgNodes:: ExprNodes:: ArrayLiteralCfgNode and
582
- tp = TResolved ( "Array" ) and
684
+ tp = Module :: TResolved ( "Array" ) and
583
685
exact = true
584
686
or
585
687
n .asExpr ( ) instanceof CfgNodes:: ExprNodes:: HashLiteralCfgNode and
586
- tp = TResolved ( "Hash" ) and
688
+ tp = Module :: TResolved ( "Hash" ) and
587
689
exact = true
588
690
or
589
691
n .asExpr ( ) .getExpr ( ) instanceof MethodBase and
590
- tp = TResolved ( "Symbol" ) and
692
+ tp = Module :: TResolved ( "Symbol" ) and
591
693
exact = true
592
694
or
593
695
n .asParameter ( ) instanceof BlockParameter and
594
- tp = TResolved ( "Proc" ) and
696
+ tp = Module :: TResolved ( "Proc" ) and
595
697
exact = true
596
698
or
597
699
n .asExpr ( ) .getExpr ( ) instanceof Lambda and
598
- tp = TResolved ( "Proc" ) and
700
+ tp = Module :: TResolved ( "Proc" ) and
599
701
exact = true
600
702
or
601
703
// `self` reference in method or top-level (but not in module or singleton method,
@@ -646,11 +748,11 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig {
646
748
isInstance ( start , tp , exact )
647
749
or
648
750
exists ( Module m |
649
- ( if m .isClass ( ) then tp = TResolved ( "Class" ) else tp = TResolved ( "Module" ) ) and
751
+ ( if m .isClass ( ) then tp = Module :: TResolved ( "Class" ) else tp = Module :: TResolved ( "Module" ) ) and
650
752
exact = true
651
753
|
652
754
// needed for e.g. `C.new`
653
- m = resolveConstantReadAccess ( start .asExpr ( ) .getExpr ( ) )
755
+ m = Module :: resolveConstantReadAccess ( start .asExpr ( ) .getExpr ( ) )
654
756
or
655
757
// needed for e.g. `self.include`
656
758
selfInModule ( start .( SelfLocalSourceNode ) .getVariable ( ) , m )
@@ -805,7 +907,7 @@ private predicate singletonMethodOnModule(MethodBase method, string name, Module
805
907
)
806
908
or
807
909
exists ( DataFlow:: LocalSourceNode sourceNode |
808
- m = resolveConstantReadAccess ( sourceNode .asExpr ( ) .getExpr ( ) ) and
910
+ m = Module :: resolveConstantReadAccess ( sourceNode .asExpr ( ) .getExpr ( ) ) and
809
911
flowsToSingletonMethodObject ( sourceNode , method , name )
810
912
)
811
913
or
@@ -821,7 +923,7 @@ private MethodBase lookupSingletonMethodDirect(Module m, string name) {
821
923
or
822
924
exists ( DataFlow:: LocalSourceNode sourceNode |
823
925
sourceNode = trackModuleAccess ( m ) and
824
- not m = resolveConstantReadAccess ( sourceNode .asExpr ( ) .getExpr ( ) ) and
926
+ not m = Module :: resolveConstantReadAccess ( sourceNode .asExpr ( ) .getExpr ( ) ) and
825
927
flowsToSingletonMethodObject ( sourceNode , result , name )
826
928
)
827
929
}
@@ -847,7 +949,7 @@ private MethodBase lookupSingletonMethodInSubClasses(Module m, string name) {
847
949
// The 'self' inside such a singleton method could then be any class, leading to self-calls
848
950
// being resolved to arbitrary singleton methods.
849
951
// To remedy this, we do not allow following super-classes all the way to Object.
850
- not m = TResolved ( "Object" ) and
952
+ not m = Module :: TResolved ( "Object" ) and
851
953
exists ( Module sub |
852
954
sub .getSuperClass ( ) = m // not `getAnImmediateAncestor` because singleton methods cannot be included
853
955
|
@@ -901,7 +1003,7 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object)
901
1003
singletonMethod ( method , name , object ) and
902
1004
not selfInModule ( object .( SelfVariableReadAccess ) .getVariable ( ) , _) and
903
1005
// cannot use `trackModuleAccess` because of negative recursion
904
- not exists ( resolveConstantReadAccess ( object ) )
1006
+ not exists ( Module :: resolveConstantReadAccess ( object ) )
905
1007
or
906
1008
exists ( DataFlow:: ExprNode receiver , Module other |
907
1009
extendCall ( receiver , other ) and
@@ -948,7 +1050,7 @@ private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruct
948
1050
RelevantCall call , DataFlow:: Node arg , DataFlow:: ParameterNode p ,
949
1051
CfgNodes:: ExprCfgNode nodeFromPreExpr
950
1052
|
951
- callStep ( call , arg , p ) and
1053
+ sourceCallStep ( call , arg , p ) and
952
1054
nodeTo .getPreUpdateNode ( ) = arg and
953
1055
summary .toString ( ) = "return" and
954
1056
(
0 commit comments