Skip to content

Commit d5ef853

Browse files
committed
Ruby: Remove ActiveStorage entry points
1 parent 54b05e4 commit d5ef853

File tree

2 files changed

+61
-84
lines changed

2 files changed

+61
-84
lines changed

ruby/ql/lib/codeql/ruby/frameworks/ActiveStorage.qll

Lines changed: 59 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,26 @@ module ActiveStorage {
5757
// "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].Argument[0].Element[any]",
5858
// ActiveStorage::Blob.find_signed(!) : Blob
5959
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[find_signed,find_signed!].ReturnValue",
60-
// ActiveStorage::Attachment#blob : Blob
61-
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Attachment].Instance.Method[blob].ReturnValue",
62-
// ActiveStorage::Attachment delegates method calls to its associated Blob
63-
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Attachment].Instance",
6460
]
6561
}
6662
}
6763

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+
6880
/**
6981
* Method calls on `ActiveStorage::Blob` that send HTTP requests.
7082
*/
@@ -78,11 +90,16 @@ module ActiveStorage {
7890
.getASubclass*()
7991
.getAMethodCall(["create_after_unfurling!", "create_and_upload!"]),
8092
// Instance methods
81-
ModelOutput::getATypeNode("activestorage", "Blob")
82-
.getAMethodCall([
83-
"upload", "upload_without_unfurling", "download", "download_chunk", "delete",
84-
"purge"
85-
])
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+
)
86103
].asExpr().getExpr()
87104
}
88105

@@ -110,7 +127,7 @@ module ActiveStorage {
110127
}
111128

112129
/**
113-
* An ActiveStorage attachment, instantiated via an association with an
130+
* An ActiveStorage attachment, instantiated directly or via an association with an
114131
* ActiveRecord model.
115132
*
116133
* ```rb
@@ -120,18 +137,30 @@ module ActiveStorage {
120137
*
121138
* user = User.find(id)
122139
* user.avatar
140+
* ActiveStorage::Attachment.new
123141
* ```
124142
*/
125-
private class AttachmentInstance extends DataFlow::CallNode {
143+
class AttachmentInstance extends DataFlow::Node {
126144
AttachmentInstance() {
127-
exists(Association assoc, string model | model = assoc.getTargetModelName() |
128-
this.getReceiver().(ActiveRecordInstance).getClass() = assoc.getSourceClass() and
145+
this =
146+
API::getTopLevelMember("ActiveStorage")
147+
.getMember("Attachment")
148+
.getInstance()
149+
.getAValueReachableFromSource()
150+
or
151+
exists(Association assoc, string model, DataFlow::CallNode call |
152+
model = assoc.getTargetModelName()
153+
|
154+
call = this and
155+
call.getReceiver().(ActiveRecordInstance).getClass() = assoc.getSourceClass() and
129156
(
130-
assoc.isSingular() and this.getMethodName() = model
157+
assoc.isSingular() and call.getMethodName() = model
131158
or
132-
assoc.isCollection() and this.getMethodName() = model
159+
assoc.isCollection() and call.getMethodName() = model
133160
)
134161
)
162+
or
163+
any(AttachmentInstance i).(DataFlow::LocalSourceNode).flowsTo(this)
135164
}
136165
}
137166

@@ -141,35 +170,41 @@ module ActiveStorage {
141170
*/
142171
private class ImageProcessingCall extends DataFlow::CallNode, SystemCommandExecution::Range {
143172
ImageProcessingCall() {
144-
this =
145-
ModelOutput::getATypeNode("activestorage", "Blob")
146-
.getAMethodCall(["variant", "preview", "representation"]) or
173+
this.getReceiver() instanceof BlobInstance and
174+
this.getMethodName() = ["variant", "preview", "representation"]
175+
or
147176
this =
148177
API::getTopLevelMember("ActiveStorage")
149178
.getMember("Attachment")
150179
.getInstance()
151-
.getAMethodCall(["variant", "preview", "representation"]) or
180+
.getAMethodCall(["variant", "preview", "representation"])
181+
or
152182
this =
153183
API::getTopLevelMember("ActiveStorage")
154184
.getMember("Variation")
155-
.getAMethodCall(["new", "wrap", "encode"]) or
185+
.getAMethodCall(["new", "wrap", "encode"])
186+
or
156187
this =
157188
API::getTopLevelMember("ActiveStorage")
158189
.getMember("Variation")
159190
.getInstance()
160-
.getAMethodCall("transformations=") or
191+
.getAMethodCall("transformations=")
192+
or
161193
this =
162194
API::getTopLevelMember("ActiveStorage")
163195
.getMember("Transformers")
164196
.getMember("ImageProcessingTransformer")
165-
.getAMethodCall("new") or
197+
.getAMethodCall("new")
198+
or
166199
this =
167200
API::getTopLevelMember("ActiveStorage")
168201
.getMember(["Preview", "VariantWithRecord"])
169-
.getAMethodCall("new") or
202+
.getAMethodCall("new")
203+
or
170204
// `ActiveStorage.paths` is a global hash whose values are passed to
171205
// a `system` call.
172-
this = API::getTopLevelMember("ActiveStorage").getAMethodCall("paths=") or
206+
this = API::getTopLevelMember("ActiveStorage").getAMethodCall("paths=")
207+
or
173208
// `ActiveStorage.video_preview_arguments` is passed to a `system` call.
174209
this = API::getTopLevelMember("ActiveStorage").getAMethodCall("video_preview_arguments=")
175210
}
@@ -187,57 +222,4 @@ module ActiveStorage {
187222

188223
override DataFlow::Node getCode() { result = this.getArgument(0) }
189224
}
190-
191-
/**
192-
* Adds ActiveStorage instances to the API graph.
193-
* Source code may not mention `ActiveStorage` or `ActiveStorage::Attachment`,
194-
* so we add synthetic nodes for them.
195-
*/
196-
private module ApiNodes {
197-
class ActiveStorage extends API::EntryPoint {
198-
ActiveStorage() { this = "ActiveStorage" }
199-
200-
override predicate edge(API::Node pred, API::Label::ApiLabel lbl) {
201-
pred = API::root() and lbl = API::Label::member("ActiveStorage")
202-
}
203-
}
204-
205-
class Attachment extends API::EntryPoint {
206-
Attachment() { this = "ActiveStorage::Attachment" }
207-
208-
override predicate edge(API::Node pred, API::Label::ApiLabel lbl) {
209-
pred = API::getTopLevelMember("ActiveStorage") and
210-
lbl = API::Label::member("Attachment")
211-
}
212-
}
213-
214-
class AttachmentNew extends API::EntryPoint {
215-
AttachmentNew() { this = "ActiveStorage::Attachment.new" }
216-
217-
override predicate edge(API::Node pred, API::Label::ApiLabel lbl) {
218-
pred = API::getTopLevelMember("ActiveStorage").getMember("Attachment") and
219-
lbl = API::Label::method("new")
220-
}
221-
}
222-
223-
/**
224-
* An API entry point for instances of `ActiveStorage::Attachment`.
225-
* These arise from calls to methods generated by `has_one_attached` and
226-
* `has_many_attached` associations.
227-
*/
228-
class AttachmentInstanceNode extends API::EntryPoint {
229-
AttachmentInstanceNode() { this = "ActiveStorage::Attachment.new.ReturnValue" }
230-
231-
override predicate edge(API::Node pred, API::Label::ApiLabel lbl) {
232-
pred = API::getTopLevelMember("ActiveStorage").getMember("Attachment").getMethod("new") and
233-
lbl = API::Label::return()
234-
}
235-
236-
override DataFlow::LocalSourceNode getAUse() { result = any(AttachmentInstance i) }
237-
238-
override DataFlow::CallNode getACall() {
239-
any(AttachmentInstance i).flowsTo(result.getReceiver())
240-
}
241-
}
242-
}
243225
}

ruby/ql/test/library-tests/frameworks/active_storage/ActiveStorage.ql

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@ import ruby
22
import codeql.ruby.ApiGraphs
33
import codeql.ruby.DataFlow
44
import codeql.ruby.Concepts
5+
import codeql.ruby.frameworks.ActiveStorage
56

6-
query predicate attachmentInstances(DataFlow::Node n) {
7-
n =
8-
API::getTopLevelMember("ActiveStorage")
9-
.getMember("Attachment")
10-
.getInstance()
11-
.getAValueReachableFromSource()
12-
}
7+
query predicate attachmentInstances(ActiveStorage::AttachmentInstance n) { any() }
138

149
query predicate httpRequests(HTTP::Client::Request r, string framework, DataFlow::Node responseBody) {
1510
r.getFramework() = framework and r.getResponseBody() = responseBody

0 commit comments

Comments
 (0)