@@ -8,23 +8,218 @@ private import codeql.ruby.Concepts
88private import codeql.ruby.DataFlow
99private import codeql.ruby.dataflow.FlowSummary
1010private import codeql.ruby.frameworks.data.ModelsAsData
11+ private import codeql.ruby.frameworks.ActiveRecord
1112
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+ }
1827 }
19- }
2028
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 ) }
29224 }
30225}
0 commit comments