@@ -227,6 +227,9 @@ private module Cached {
227
227
} or
228
228
TSelfParameterNode ( MethodBase m ) or
229
229
TBlockParameterNode ( MethodBase m ) or
230
+ TSynthHashSplatParameterNode ( DataFlowCallable c ) {
231
+ isParameterNode ( _, c , any ( ParameterPosition p | p .isKeyword ( _) ) )
232
+ } or
230
233
TExprPostUpdateNode ( CfgNodes:: ExprCfgNode n ) {
231
234
n instanceof Argument or
232
235
n = any ( CfgNodes:: ExprNodes:: InstanceVariableAccessCfgNode v ) .getReceiver ( )
@@ -240,12 +243,13 @@ private module Cached {
240
243
TSummaryParameterNode ( FlowSummaryImpl:: Public:: SummarizedCallable c , ParameterPosition pos ) {
241
244
FlowSummaryImpl:: Private:: summaryParameterNodeRange ( c , pos )
242
245
} or
243
- THashSplatArgumentsNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
246
+ TSynthHashSplatArgumentNode ( CfgNodes:: ExprNodes:: CallCfgNode c ) {
244
247
exists ( Argument arg | arg .isArgumentOf ( c , any ( ArgumentPosition pos | pos .isKeyword ( _) ) ) )
245
248
}
246
249
247
250
class TParameterNode =
248
- TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode ;
251
+ TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
252
+ TSynthHashSplatParameterNode or TSummaryParameterNode ;
249
253
250
254
private predicate defaultValueFlow ( NamedParameter p , ExprNode e ) {
251
255
p .( OptionalParameter ) .getDefaultValue ( ) = e .getExprNode ( ) .getExpr ( )
@@ -328,18 +332,21 @@ private module Cached {
328
332
329
333
cached
330
334
predicate isLocalSourceNode ( Node n ) {
331
- n instanceof ParameterNode
332
- or
333
- n instanceof PostUpdateNodes:: ExprPostUpdateNode
334
- or
335
- // Nodes that can't be reached from another entry definition or expression.
336
- not reachedFromExprOrEntrySsaDef ( n )
337
- or
338
- // Ensure all entry SSA definitions are local sources -- for parameters, this
339
- // is needed by type tracking. Note that when the parameter has a default value,
340
- // it will be reachable from an expression (the default value) and therefore
341
- // won't be caught by the rule above.
342
- entrySsaDefinition ( n )
335
+ not n instanceof SynthHashSplatParameterNode and
336
+ (
337
+ n instanceof ParameterNode
338
+ or
339
+ n instanceof PostUpdateNodes:: ExprPostUpdateNode
340
+ or
341
+ // Nodes that can't be reached from another entry definition or expression.
342
+ not reachedFromExprOrEntrySsaDef ( n )
343
+ or
344
+ // Ensure all entry SSA definitions are local sources -- for parameters, this
345
+ // is needed by type tracking. Note that when the parameter has a default value,
346
+ // it will be reachable from an expression (the default value) and therefore
347
+ // won't be caught by the rule above.
348
+ entrySsaDefinition ( n )
349
+ )
343
350
}
344
351
345
352
cached
@@ -415,7 +422,9 @@ predicate nodeIsHidden(Node n) {
415
422
or
416
423
n instanceof SynthReturnNode
417
424
or
418
- n instanceof HashSplatArgumentsNode
425
+ n instanceof SynthHashSplatParameterNode
426
+ or
427
+ n instanceof SynthHashSplatArgumentNode
419
428
}
420
429
421
430
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -470,10 +479,13 @@ private module ParameterNodes {
470
479
abstract class ParameterNodeImpl extends NodeImpl {
471
480
abstract Parameter getParameter ( ) ;
472
481
473
- abstract predicate isSourceParameterOf ( Callable c , ParameterPosition pos ) ;
482
+ abstract predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) ;
474
483
475
- predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
476
- this .isSourceParameterOf ( c .asCallable ( ) , pos )
484
+ final predicate isSourceParameterOf ( Callable c , ParameterPosition pos ) {
485
+ exists ( DataFlowCallable callable |
486
+ this .isParameterOf ( callable , pos ) and
487
+ c = callable .asCallable ( )
488
+ )
477
489
}
478
490
}
479
491
@@ -488,21 +500,23 @@ private module ParameterNodes {
488
500
489
501
override Parameter getParameter ( ) { result = parameter }
490
502
491
- override predicate isSourceParameterOf ( Callable c , ParameterPosition pos ) {
492
- exists ( int i | pos .isPositional ( i ) and c .getParameter ( i ) = parameter |
493
- parameter instanceof SimpleParameter
503
+ override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
504
+ exists ( Callable callable | callable = c .asCallable ( ) |
505
+ exists ( int i | pos .isPositional ( i ) and callable .getParameter ( i ) = parameter |
506
+ parameter instanceof SimpleParameter
507
+ or
508
+ parameter instanceof OptionalParameter
509
+ )
510
+ or
511
+ parameter =
512
+ any ( KeywordParameter kp |
513
+ callable .getAParameter ( ) = kp and
514
+ pos .isKeyword ( kp .getName ( ) )
515
+ )
494
516
or
495
- parameter instanceof OptionalParameter
517
+ parameter = callable .getAParameter ( ) .( HashSplatParameter ) and
518
+ pos .isHashSplat ( )
496
519
)
497
- or
498
- parameter =
499
- any ( KeywordParameter kp |
500
- c .getAParameter ( ) = kp and
501
- pos .isKeyword ( kp .getName ( ) )
502
- )
503
- or
504
- parameter = c .getAParameter ( ) .( HashSplatParameter ) and
505
- pos .isHashSplat ( )
506
520
}
507
521
508
522
override CfgScope getCfgScope ( ) { result = parameter .getCallable ( ) }
@@ -525,8 +539,8 @@ private module ParameterNodes {
525
539
526
540
override Parameter getParameter ( ) { none ( ) }
527
541
528
- override predicate isSourceParameterOf ( Callable c , ParameterPosition pos ) {
529
- method = c and pos .isSelf ( )
542
+ override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
543
+ method = c . asCallable ( ) and pos .isSelf ( )
530
544
}
531
545
532
546
override CfgScope getCfgScope ( ) { result = method }
@@ -551,8 +565,8 @@ private module ParameterNodes {
551
565
result = method .getAParameter ( ) and result instanceof BlockParameter
552
566
}
553
567
554
- override predicate isSourceParameterOf ( Callable c , ParameterPosition pos ) {
555
- c = method and pos .isBlock ( )
568
+ override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
569
+ c . asCallable ( ) = method and pos .isBlock ( )
556
570
}
557
571
558
572
override CfgScope getCfgScope ( ) { result = method }
@@ -570,6 +584,73 @@ private module ParameterNodes {
570
584
}
571
585
}
572
586
587
+ /**
588
+ * For all methods containing keyword parameters, we construct a synthesized
589
+ * (hidden) parameter node to contain all keyword arguments. This allows us
590
+ * to handle cases like
591
+ *
592
+ * ```rb
593
+ * def foo(p1:, p2:)
594
+ * sink(p1)
595
+ * sink(p2)
596
+ * end
597
+ *
598
+ * args = {:p1 => taint(1), :p2 => taint(2) }
599
+ * foo(**args)
600
+ * ```
601
+ *
602
+ * by adding read steps out of the synthesized parameter node to the relevant
603
+ * keyword parameters.
604
+ *
605
+ * Note that this will introduce a bit of redundancy in cases like
606
+ *
607
+ * ```rb
608
+ * foo(p1: taint(1), p2: taint(2))
609
+ * ```
610
+ *
611
+ * where direct keyword matching is possible, since we construct a synthesized hash
612
+ * splat argument (`SynthHashSplatArgumentNode`) at the call site, which means that
613
+ * `taint(1)` will flow into `p1` both via normal keyword matching and via the synthesized
614
+ * nodes (and similarly for `p2`). However, this redunancy is OK since
615
+ * (a) it means that type-tracking through keyword arguments also works in most cases,
616
+ * (b) read/store steps can be avoided when direct keyword matching is possible, and
617
+ * hence access path limits are not a concern, and
618
+ * (c) since the synthesized nodes are hidden, the reported data-flow paths will be
619
+ * collapsed anyway.
620
+ */
621
+ class SynthHashSplatParameterNode extends ParameterNodeImpl , TSynthHashSplatParameterNode {
622
+ private DataFlowCallable callable ;
623
+
624
+ SynthHashSplatParameterNode ( ) { this = TSynthHashSplatParameterNode ( callable ) }
625
+
626
+ /**
627
+ * Gets a keyword parameter that will be the result of reading `c` out of this
628
+ * synthesized node.
629
+ */
630
+ ParameterNode getAKeywordParameter ( ContentSet c ) {
631
+ exists ( string name |
632
+ isParameterNode ( result , callable , any ( ParameterPosition p | p .isKeyword ( name ) ) )
633
+ |
634
+ c = getKeywordContent ( name ) or
635
+ c .isSingleton ( TUnknownElementContent ( ) )
636
+ )
637
+ }
638
+
639
+ final override Parameter getParameter ( ) { none ( ) }
640
+
641
+ final override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
642
+ c = callable and pos .isHashSplat ( )
643
+ }
644
+
645
+ final override CfgScope getCfgScope ( ) { result = callable .asCallable ( ) }
646
+
647
+ final override DataFlowCallable getEnclosingCallable ( ) { result = callable }
648
+
649
+ final override Location getLocationImpl ( ) { result = callable .getLocation ( ) }
650
+
651
+ final override string toStringImpl ( ) { result = "**kwargs" }
652
+ }
653
+
573
654
/** A parameter for a library callable with a flow summary. */
574
655
class SummaryParameterNode extends ParameterNodeImpl , TSummaryParameterNode {
575
656
private FlowSummaryImpl:: Public:: SummarizedCallable sc ;
@@ -579,8 +660,6 @@ private module ParameterNodes {
579
660
580
661
override Parameter getParameter ( ) { none ( ) }
581
662
582
- override predicate isSourceParameterOf ( Callable c , ParameterPosition pos ) { none ( ) }
583
-
584
663
override predicate isParameterOf ( DataFlowCallable c , ParameterPosition pos ) {
585
664
sc = c .asLibraryCallable ( ) and pos = pos_
586
665
}
@@ -689,10 +768,10 @@ private module ArgumentNodes {
689
768
* part of the method signature, such that those cannot end up in the hash-splat
690
769
* parameter.
691
770
*/
692
- class HashSplatArgumentsNode extends ArgumentNode , THashSplatArgumentsNode {
771
+ class SynthHashSplatArgumentNode extends ArgumentNode , TSynthHashSplatArgumentNode {
693
772
CfgNodes:: ExprNodes:: CallCfgNode c ;
694
773
695
- HashSplatArgumentsNode ( ) { this = THashSplatArgumentsNode ( c ) }
774
+ SynthHashSplatArgumentNode ( ) { this = TSynthHashSplatArgumentNode ( c ) }
696
775
697
776
override predicate argumentOf ( DataFlowCall call , ArgumentPosition pos ) {
698
777
this .sourceArgumentOf ( call .asCall ( ) , pos )
@@ -704,10 +783,10 @@ private module ArgumentNodes {
704
783
}
705
784
}
706
785
707
- private class HashSplatArgumentsNodeImpl extends NodeImpl , THashSplatArgumentsNode {
786
+ private class SynthHashSplatArgumentNodeImpl extends NodeImpl , TSynthHashSplatArgumentNode {
708
787
CfgNodes:: ExprNodes:: CallCfgNode c ;
709
788
710
- HashSplatArgumentsNodeImpl ( ) { this = THashSplatArgumentsNode ( c ) }
789
+ SynthHashSplatArgumentNodeImpl ( ) { this = TSynthHashSplatArgumentNode ( c ) }
711
790
712
791
override CfgScope getCfgScope ( ) { result = c .getExpr ( ) .getCfgScope ( ) }
713
792
@@ -929,7 +1008,7 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
929
1008
or
930
1009
// Wrap all keyword arguments in a synthesized hash-splat argument node
931
1010
exists ( CfgNodes:: ExprNodes:: CallCfgNode call , ArgumentPosition keywordPos , string name |
932
- node2 = THashSplatArgumentsNode ( call ) and
1011
+ node2 = TSynthHashSplatArgumentNode ( call ) and
933
1012
node1 .asExpr ( ) .( Argument ) .isArgumentOf ( call , keywordPos ) and
934
1013
keywordPos .isKeyword ( name ) and
935
1014
c = getKeywordContent ( name )
@@ -962,6 +1041,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
962
1041
) )
963
1042
)
964
1043
or
1044
+ node2 = node1 .( SynthHashSplatParameterNode ) .getAKeywordParameter ( c )
1045
+ or
965
1046
FlowSummaryImpl:: Private:: Steps:: summaryReadStep ( node1 , c , node2 )
966
1047
}
967
1048
0 commit comments