@@ -8,23 +8,233 @@ private import codeql.ruby.Concepts
8
8
private import codeql.ruby.DataFlow
9
9
private import codeql.ruby.dataflow.FlowSummary
10
10
private import codeql.ruby.frameworks.data.ModelsAsData
11
+ private import codeql.ruby.frameworks.ActiveRecord
11
12
12
- /** A call to `ActiveStorage::Filename#sanitized`, considered as a path sanitizer. */
13
- class ActiveStorageFilenameSanitizedCall extends Path:: PathSanitization:: Range , DataFlow:: CallNode {
14
- ActiveStorageFilenameSanitizedCall ( ) {
15
- this .getReceiver ( ) =
16
- API:: getTopLevelMember ( "ActiveStorage" ) .getMember ( "Filename" ) .getAnInstantiation ( ) and
17
- this .getMethodName ( ) = "sanitized"
13
+ module ActiveStorage {
14
+ /** A call to `ActiveStorage::Filename#sanitized`, considered as a path sanitizer. */
15
+ private class FilenameSanitizedCall extends Path:: PathSanitization:: Range , DataFlow:: CallNode {
16
+ FilenameSanitizedCall ( ) {
17
+ this =
18
+ API:: getTopLevelMember ( "ActiveStorage" )
19
+ .getMember ( "Filename" )
20
+ .getInstance ( )
21
+ .getAMethodCall ( "sanitized" )
22
+ }
18
23
}
19
- }
20
24
21
- /** Taint related to `ActiveStorage::Filename`. */
22
- private class Summaries extends ModelInput:: SummaryModelCsv {
23
- override predicate row ( string row ) {
24
- row =
25
- [
26
- "activestorage;;Member[ActiveStorage].Member[Filename].Method[new];Argument[0];ReturnValue;taint" ,
27
- "activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized];Argument[self];ReturnValue;taint" ,
28
- ]
25
+ /** Taint related to `ActiveStorage::Filename`. */
26
+ private class FilenameSummaries extends ModelInput:: SummaryModelCsv {
27
+ override predicate row ( string row ) {
28
+ row =
29
+ [
30
+ "activestorage;;Member[ActiveStorage].Member[Filename].Method[new];Argument[0];ReturnValue;taint" ,
31
+ "activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized];Argument[self];ReturnValue;taint" ,
32
+ ]
33
+ }
34
+ }
35
+
36
+ /**
37
+ * `Blob` is an instance of `ActiveStorage::Blob`.
38
+ */
39
+ private class BlobTypeSummary extends ModelInput:: TypeModelCsv {
40
+ override predicate row ( string row ) {
41
+ // package1;type1;package2;type2;path
42
+ row =
43
+ [
44
+ // ActiveStorage::Blob.new : Blob
45
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Instance" ,
46
+ // ActiveStorage::Blob.create_and_upload! : Blob
47
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[create_and_upload!].ReturnValue" ,
48
+ // ActiveStorage::Blob.create_before_direct_upload! : Blob
49
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[create_before_direct_upload!].ReturnValue" ,
50
+ // ActiveStorage::Blob.compose(blobs : [Blob]) : Blob
51
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].ReturnValue" ,
52
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].Argument[0].Element[any]" ,
53
+ // ActiveStorage::Blob.find_signed(!) : Blob
54
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[find_signed,find_signed!].ReturnValue" ,
55
+ // ActiveStorage::Attachment#blob : Blob
56
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Attachment].Instance.Method[blob].ReturnValue" ,
57
+ // ActiveStorage::Attachment delegates method calls to its associated Blob
58
+ "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Attachment].Instance" ,
59
+ ]
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Method calls on `ActiveStorage::Blob` that send HTTP requests.
65
+ */
66
+ private class BlobRequestCall extends HTTP:: Client:: Request:: Range {
67
+ BlobRequestCall ( ) {
68
+ this =
69
+ [
70
+ // Class methods
71
+ API:: getTopLevelMember ( "ActiveStorage" )
72
+ .getMember ( "Blob" )
73
+ .getASubclass * ( )
74
+ .getAMethodCall ( [ "create_after_unfurling!" , "create_and_upload!" ] ) ,
75
+ // Instance methods
76
+ ModelOutput:: getATypeNode ( "activestorage" , "Blob" )
77
+ .getAMethodCall ( [
78
+ "upload" , "upload_without_unfurling" , "download" , "download_chunk" , "delete" ,
79
+ "purge"
80
+ ] )
81
+ ] .asExpr ( ) .getExpr ( )
82
+ }
83
+
84
+ override string getFramework ( ) { result = "activestorage" }
85
+
86
+ override DataFlow:: Node getResponseBody ( ) { result .asExpr ( ) .getExpr ( ) = this }
87
+
88
+ override DataFlow:: Node getAUrlPart ( ) { none ( ) }
89
+
90
+ override predicate disablesCertificateValidation ( DataFlow:: Node disablingNode ) { none ( ) }
91
+ }
92
+
93
+ /**
94
+ * A call to `has_one_attached` or `has_many_attached`, which declares an
95
+ * association between an ActiveRecord model and an ActiveStorage attachment.
96
+ *
97
+ * ```rb
98
+ * class User < ActiveRecord::Base
99
+ * has_one_attached :avatar
100
+ * end
101
+ * ```
102
+ */
103
+ private class Association extends ActiveRecordAssociation {
104
+ Association ( ) { this .getMethodName ( ) = [ "has_one_attached" , "has_many_attached" ] }
105
+ }
106
+
107
+ /**
108
+ * An ActiveStorage attachment, instantiated via an association with an
109
+ * ActiveRecord model.
110
+ *
111
+ * ```rb
112
+ * class User < ActiveRecord::Base
113
+ * has_one_attached :avatar
114
+ * end
115
+ *
116
+ * user = User.find(id)
117
+ * user.avatar
118
+ * ```
119
+ */
120
+ private class AttachmentInstance extends DataFlow:: CallNode {
121
+ Association assoc ;
122
+
123
+ AttachmentInstance ( ) {
124
+ exists ( string model | model = assoc .getTargetModelName ( ) |
125
+ this .getReceiver ( ) .( ActiveRecordInstance ) .getClass ( ) = assoc .getSourceClass ( ) and
126
+ (
127
+ assoc .isSingular ( ) and this .getMethodName ( ) = model
128
+ or
129
+ assoc .isCollection ( ) and this .getMethodName ( ) = model
130
+ )
131
+ )
132
+ }
133
+ }
134
+
135
+ /**
136
+ * A call on an ActiveStorage object that results in an image transformation.
137
+ * Arguments to these calls may be executed as system commands.
138
+ */
139
+ private class ImageProcessingCall extends DataFlow:: CallNode , SystemCommandExecution:: Range {
140
+ ImageProcessingCall ( ) {
141
+ this =
142
+ ModelOutput:: getATypeNode ( "activestorage" , "Blob" )
143
+ .getAMethodCall ( [ "variant" , "preview" , "representation" ] ) or
144
+ this =
145
+ API:: getTopLevelMember ( "ActiveStorage" )
146
+ .getMember ( "Attachment" )
147
+ .getInstance ( )
148
+ .getAMethodCall ( [ "variant" , "preview" , "representation" ] ) or
149
+ this =
150
+ API:: getTopLevelMember ( "ActiveStorage" )
151
+ .getMember ( "Variation" )
152
+ .getAMethodCall ( [ "new" , "wrap" , "encode" ] ) or
153
+ this =
154
+ API:: getTopLevelMember ( "ActiveStorage" )
155
+ .getMember ( "Variation" )
156
+ .getInstance ( )
157
+ .getAMethodCall ( "transformations=" ) or
158
+ this =
159
+ API:: getTopLevelMember ( "ActiveStorage" )
160
+ .getMember ( "Transformers" )
161
+ .getMember ( "ImageProcessingTransformer" )
162
+ .getAMethodCall ( "new" ) or
163
+ this =
164
+ API:: getTopLevelMember ( "ActiveStorage" )
165
+ .getMember ( [ "Preview" , "VariantWithRecord" ] )
166
+ .getAMethodCall ( "new" ) or
167
+ // `ActiveStorage.paths` is a global hash whose values are passed to
168
+ // a `system` call.
169
+ this = API:: getTopLevelMember ( "ActiveStorage" ) .getAMethodCall ( "paths=" ) or
170
+ // `ActiveStorage.video_preview_arguments` is passed to a `system` call.
171
+ this = API:: getTopLevelMember ( "ActiveStorage" ) .getAMethodCall ( "video_preview_arguments=" )
172
+ }
173
+
174
+ override DataFlow:: Node getAnArgument ( ) { result = this .getArgument ( 0 ) }
175
+ }
176
+
177
+ /**
178
+ * `ActiveStorage.variant_processor` is passed to `const_get`.
179
+ */
180
+ private class VariantProcessor extends DataFlow:: CallNode , CodeExecution:: Range {
181
+ VariantProcessor ( ) {
182
+ this = API:: getTopLevelMember ( "ActiveStorage" ) .getAMethodCall ( "variant_processor=" )
183
+ }
184
+
185
+ override DataFlow:: Node getCode ( ) { result = this .getArgument ( 0 ) }
186
+ }
187
+
188
+ /**
189
+ * Adds ActiveStorage instances to the API graph.
190
+ * Source code may not mention `ActiveStorage` or `ActiveStorage::Attachment`,
191
+ * so we add synthetic nodes for them.
192
+ */
193
+ private module ApiNodes {
194
+ class ActiveStorage extends API:: EntryPoint {
195
+ ActiveStorage ( ) { this = "ActiveStorage" }
196
+
197
+ override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
198
+ pred = API:: root ( ) and lbl = API:: Label:: member ( "ActiveStorage" )
199
+ }
200
+ }
201
+
202
+ class Attachment extends API:: EntryPoint {
203
+ Attachment ( ) { this = "ActiveStorage::Attachment" }
204
+
205
+ override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
206
+ pred = API:: getTopLevelMember ( "ActiveStorage" ) and
207
+ lbl = API:: Label:: member ( "Attachment" )
208
+ }
209
+ }
210
+
211
+ class AttachmentNew extends API:: EntryPoint {
212
+ AttachmentNew ( ) { this = "ActiveStorage::Attachment.new" }
213
+
214
+ override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
215
+ pred = API:: getTopLevelMember ( "ActiveStorage" ) .getMember ( "Attachment" ) and
216
+ lbl = API:: Label:: method ( "new" )
217
+ }
218
+ }
219
+
220
+ /**
221
+ * An API entry point for instances of `ActiveStorage::Attachment`.
222
+ * These arise from calls to methods generated by `has_one_attached` and
223
+ * `has_many_attached` associations.
224
+ */
225
+ class AttachmentInstanceNode extends API:: EntryPoint {
226
+ AttachmentInstanceNode ( ) { this = "ActiveStorage::Attachment.new.ReturnValue" }
227
+
228
+ override predicate edge ( API:: Node pred , API:: Label:: ApiLabel lbl ) {
229
+ pred = API:: getTopLevelMember ( "ActiveStorage" ) .getMember ( "Attachment" ) .getMethod ( "new" ) and
230
+ lbl = API:: Label:: return ( )
231
+ }
232
+
233
+ override DataFlow:: LocalSourceNode getAUse ( ) { result = any ( AttachmentInstance i ) }
234
+
235
+ override DataFlow:: CallNode getACall ( ) {
236
+ any ( AttachmentInstance i ) .flowsTo ( result .getReceiver ( ) )
237
+ }
238
+ }
29
239
}
30
240
}
0 commit comments