66private import ruby
77private import codeql.ruby.Concepts
88private import codeql.ruby.DataFlow
9+ private import codeql.ruby.dataflow.FlowSummary
10+ private import codeql.ruby.Concepts
11+ private import codeql.ruby.ApiGraphs
12+ private import codeql.ruby.frameworks.stdlib.Logger:: Logger as StdlibLogger
913
1014/**
1115 * Modeling for `ActiveSupport`.
@@ -32,6 +36,107 @@ module ActiveSupport {
3236
3337 override DataFlow:: Node getCode ( ) { result = this .getReceiver ( ) }
3438 }
39+
40+ /**
41+ * Flow summary for methods which transform the receiver in some way, possibly preserving taint.
42+ */
43+ private class StringTransformSummary extends SummarizedCallable {
44+ // We're modelling a lot of different methods, so we make up a name for this summary.
45+ StringTransformSummary ( ) { this = "ActiveSupportStringTransform" }
46+
47+ override MethodCall getACall ( ) {
48+ result .getMethodName ( ) =
49+ [
50+ "camelize" , "camelcase" , "classify" , "dasherize" , "deconstantize" , "demodulize" ,
51+ "foreign_key" , "humanize" , "indent" , "parameterize" , "pluralize" , "singularize" ,
52+ "squish" , "strip_heredoc" , "tableize" , "titlecase" , "titleize" , "underscore" ,
53+ "upcase_first"
54+ ]
55+ }
56+
57+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
58+ input = "Argument[self]" and output = "ReturnValue" and preservesValue = false
59+ }
60+ }
61+ }
62+
63+ /**
64+ * Extensions to the `Enumerable` module.
65+ */
66+ module Enumerable {
67+ private class ArrayIndex extends int {
68+ ArrayIndex ( ) { this = any ( DataFlow:: Content:: KnownElementContent c ) .getIndex ( ) .getInt ( ) }
69+ }
70+
71+ private class CompactBlankSummary extends SimpleSummarizedCallable {
72+ CompactBlankSummary ( ) { this = "compact_blank" }
73+
74+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
75+ input = "Argument[self].Element[any]" and
76+ output = "ReturnValue.Element[?]" and
77+ preservesValue = true
78+ }
79+ }
80+
81+ private class ExcludingSummary extends SimpleSummarizedCallable {
82+ ExcludingSummary ( ) { this = [ "excluding" , "without" ] }
83+
84+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
85+ input = "Argument[self].Element[any]" and
86+ output = "ReturnValue.Element[?]" and
87+ preservesValue = true
88+ }
89+ }
90+
91+ private class InOrderOfSummary extends SimpleSummarizedCallable {
92+ InOrderOfSummary ( ) { this = "in_order_of" }
93+
94+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
95+ input = "Argument[self].Element[any]" and
96+ output = "ReturnValue.Element[?]" and
97+ preservesValue = true
98+ }
99+ }
100+
101+ /**
102+ * Like `Array#push` but doesn't update the receiver.
103+ */
104+ private class IncludingSummary extends SimpleSummarizedCallable {
105+ IncludingSummary ( ) { this = "including" }
106+
107+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
108+ (
109+ exists ( ArrayIndex i |
110+ input = "Argument[self].Element[" + i + "]" and
111+ output = "ReturnValue.Element[" + i + "]"
112+ )
113+ or
114+ input = "Argument[self].Element[?]" and
115+ output = "ReturnValue.Element[?]"
116+ or
117+ exists ( int i | i in [ 0 .. ( mc .getNumberOfArguments ( ) - 1 ) ] |
118+ input = "Argument[" + i + "]" and
119+ output = "ReturnValue.Element[?]"
120+ )
121+ ) and
122+ preservesValue = true
123+ }
124+ }
125+ // TODO: index_by, index_with, pick, pluck (they require Hash dataflow)
126+ }
127+ }
128+
129+ /**
130+ * `ActiveSupport::Logger`
131+ */
132+ module Logger {
133+ private class ActiveSupportLoggerInstantiation extends StdlibLogger:: LoggerInstantiation {
134+ ActiveSupportLoggerInstantiation ( ) {
135+ this =
136+ API:: getTopLevelMember ( "ActiveSupport" )
137+ .getMember ( [ "Logger" , "TaggedLogging" ] )
138+ .getAnInstantiation ( )
139+ }
35140 }
36141 }
37142}
0 commit comments