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