Skip to content

Commit 4177c38

Browse files
Merge pull request #15907 from joefarebrother/ruby-uploaded-file
Ruby: Model ActiveDispatch::Http::UploadedFile
2 parents dbf1682 + 8c5fff2 commit 4177c38

File tree

9 files changed

+197
-2
lines changed

9 files changed

+197
-2
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Modeled instances of `ActionDispatch::Http::UploadedFile` that can be obtained from element reads of `ActionController::Parameters`, with calls to `original_filename`, `content_type`, and `read` now propagating taint from their receiver.

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,11 @@ private class Argument extends CfgNodes::ExprCfgNode {
322322

323323
/** Holds if `n` is not a constant expression. */
324324
predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) {
325-
not exists(n.getConstantValue()) and
325+
not exists(ConstantValue cv |
326+
cv = n.getConstantValue() and
327+
// strings are mutable in Ruby
328+
not cv.isString(_)
329+
) and
326330
not n.getExpr() instanceof ConstantAccess
327331
}
328332

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import codeql.ruby.controlflow.CfgNodes
88
private import codeql.ruby.DataFlow
99
private import codeql.ruby.dataflow.RemoteFlowSources
1010
private import codeql.ruby.ApiGraphs
11+
private import codeql.ruby.typetracking.TypeTracking
1112
private import codeql.ruby.frameworks.ActionDispatch
1213
private import codeql.ruby.frameworks.ActionView
1314
private import codeql.ruby.frameworks.Rails
@@ -505,6 +506,27 @@ private module ParamsSummaries {
505506
]
506507
}
507508

509+
/** Gets a node that may be tainted from an `ActionController::Parameters` instance, through field accesses and hash/array element reads. */
510+
private DataFlow::LocalSourceNode taintFromParamsBase() {
511+
result =
512+
[
513+
paramsInstance(),
514+
paramsInstance().getAMethodCall(methodReturnsTaintFromSelf()).getAnElementRead*()
515+
]
516+
}
517+
518+
private DataFlow::LocalSourceNode taintFromParamsType(TypeTracker t) {
519+
t.start() and
520+
result = taintFromParamsBase()
521+
or
522+
exists(TypeTracker t2 | result = taintFromParamsType(t2).track(t2, t))
523+
}
524+
525+
/** Gets a node with a type that may be tainted from an `ActionController::Parameters` instance. */
526+
private DataFlow::LocalSourceNode taintFromParamsType() {
527+
taintFromParamsType(TypeTracker::end()).flowsTo(result)
528+
}
529+
508530
/**
509531
* A flow summary for methods on `ActionController::Parameters` which
510532
* propagate taint from receiver to return value.
@@ -569,6 +591,48 @@ private module ParamsSummaries {
569591
preservesValue = false
570592
}
571593
}
594+
595+
/** Flow summaries for `ActiveDispatch::Http::UploadedFile`, which can be an field of `ActionController::Parameters`. */
596+
module UploadedFileSummaries {
597+
/** Flow summary for various string attributes of `UploadedFile`, including `original_filename`, `content_type`, and `headers`. */
598+
private class UploadedFileStringAttributeSummary extends SummarizedCallable {
599+
UploadedFileStringAttributeSummary() {
600+
this = "ActionDispatch::Http::UploadedFile#[original_filename,content_type,headers]"
601+
}
602+
603+
override MethodCall getACall() {
604+
result =
605+
taintFromParamsType()
606+
.getAMethodCall(["original_filename", "content_type", "headers"])
607+
.asExpr()
608+
.getExpr() and
609+
result.getNumberOfArguments() = 0
610+
}
611+
612+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
613+
input = "Argument[self]" and output = "ReturnValue" and preservesValue = false
614+
}
615+
}
616+
617+
/**
618+
* Flow summary for `ActiveDispatch::Http::UploadedFile#read`,
619+
* which propagates taint from the receiver to the return value or to the second (out string) argument
620+
*/
621+
private class UploadedFileReadSummary extends SummarizedCallable {
622+
UploadedFileReadSummary() { this = "ActionDispatch::Http::UploadedFile#read" }
623+
624+
override MethodCall getACall() {
625+
result = taintFromParamsType().getAMethodCall("read").asExpr().getExpr() and
626+
result.getNumberOfArguments() in [0 .. 2]
627+
}
628+
629+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
630+
input = "Argument[self]" and
631+
output = ["ReturnValue", "Argument[1]"] and
632+
preservesValue = false
633+
}
634+
}
635+
}
572636
}
573637

574638
/**

ruby/ql/test/library-tests/dataflow/global/instance_variables.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def initialize x
7070
foo3.set_field(taint(22))
7171
sink(foo3.field) # $ hasValueFlow=22
7272

73-
foo4 = "hello"
73+
foo4 = 4
7474
foo4.other = taint(23)
7575
sink(foo4.other) # no field flow for constants
7676

ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2676,6 +2676,7 @@
26762676
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
26772677
| local_dataflow.rb:132:6:132:11 | [post] self | local_dataflow.rb:133:8:133:13 | self |
26782678
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:133:8:133:13 | self |
2679+
| local_dataflow.rb:132:10:132:10 | [post] x | local_dataflow.rb:133:12:133:12 | x |
26792680
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:133:12:133:12 | x |
26802681
| local_dataflow.rb:132:12:148:10 | then ... | local_dataflow.rb:132:3:149:5 | if ... |
26812682
| local_dataflow.rb:133:5:139:7 | SSA phi read(self) | local_dataflow.rb:141:9:141:14 | self |
@@ -2686,17 +2687,20 @@
26862687
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:18:133:23 | self |
26872688
| local_dataflow.rb:133:8:133:23 | SSA phi read(self) | local_dataflow.rb:134:7:134:12 | self |
26882689
| local_dataflow.rb:133:8:133:23 | SSA phi read(x) | local_dataflow.rb:134:11:134:11 | x |
2690+
| local_dataflow.rb:133:12:133:12 | [post] x | local_dataflow.rb:133:22:133:22 | x |
26892691
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:22:133:22 | x |
26902692
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:136:7:136:12 | self |
26912693
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
26922694
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
26932695
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:136:7:136:12 | self |
2696+
| local_dataflow.rb:133:22:133:22 | [post] x | local_dataflow.rb:136:11:136:11 | x |
26942697
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:136:11:136:11 | x |
26952698
| local_dataflow.rb:133:24:134:12 | then ... | local_dataflow.rb:133:5:139:7 | if ... |
26962699
| local_dataflow.rb:134:7:134:12 | call to use | local_dataflow.rb:133:24:134:12 | then ... |
26972700
| local_dataflow.rb:135:5:138:9 | else ... | local_dataflow.rb:133:5:139:7 | if ... |
26982701
| local_dataflow.rb:136:7:136:12 | [post] self | local_dataflow.rb:137:10:137:15 | self |
26992702
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:137:10:137:15 | self |
2703+
| local_dataflow.rb:136:11:136:11 | [post] x | local_dataflow.rb:137:14:137:14 | x |
27002704
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:137:14:137:14 | x |
27012705
| local_dataflow.rb:137:7:138:9 | SSA phi read(self) | local_dataflow.rb:133:5:139:7 | SSA phi read(self) |
27022706
| local_dataflow.rb:137:7:138:9 | SSA phi read(x) | local_dataflow.rb:133:5:139:7 | SSA phi read(x) |
@@ -2705,6 +2709,7 @@
27052709
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:21:137:26 | self |
27062710
| local_dataflow.rb:137:10:137:26 | SSA phi read(self) | local_dataflow.rb:137:7:138:9 | SSA phi read(self) |
27072711
| local_dataflow.rb:137:10:137:26 | SSA phi read(x) | local_dataflow.rb:137:7:138:9 | SSA phi read(x) |
2712+
| local_dataflow.rb:137:14:137:14 | [post] x | local_dataflow.rb:137:25:137:25 | x |
27082713
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:25:137:25 | x |
27092714
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
27102715
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
@@ -2717,6 +2722,7 @@
27172722
| local_dataflow.rb:141:8:141:37 | SSA phi read(x) | local_dataflow.rb:141:5:145:7 | SSA phi read(x) |
27182723
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:141:20:141:25 | self |
27192724
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:20:141:25 | self |
2725+
| local_dataflow.rb:141:13:141:13 | [post] x | local_dataflow.rb:141:24:141:24 | x |
27202726
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:24:141:24 | x |
27212727
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
27222728
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
@@ -2726,6 +2732,7 @@
27262732
| local_dataflow.rb:141:20:141:36 | SSA phi read(x) | local_dataflow.rb:143:15:143:15 | x |
27272733
| local_dataflow.rb:141:20:141:36 | [false] ... && ... | local_dataflow.rb:141:19:141:37 | [false] ( ... ) |
27282734
| local_dataflow.rb:141:20:141:36 | [true] ... && ... | local_dataflow.rb:141:19:141:37 | [true] ( ... ) |
2735+
| local_dataflow.rb:141:24:141:24 | [post] x | local_dataflow.rb:141:35:141:35 | x |
27292736
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:35:141:35 | x |
27302737
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
27312738
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
@@ -2740,12 +2747,14 @@
27402747
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:21:143:26 | self |
27412748
| local_dataflow.rb:143:11:143:26 | SSA phi read(self) | local_dataflow.rb:144:11:144:16 | self |
27422749
| local_dataflow.rb:143:11:143:26 | SSA phi read(x) | local_dataflow.rb:144:15:144:15 | x |
2750+
| local_dataflow.rb:143:15:143:15 | [post] x | local_dataflow.rb:143:25:143:25 | x |
27432751
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:25:143:25 | x |
27442752
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
27452753
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
27462754
| local_dataflow.rb:143:27:144:16 | then ... | local_dataflow.rb:143:5:144:16 | elsif ... |
27472755
| local_dataflow.rb:144:11:144:16 | call to use | local_dataflow.rb:143:27:144:16 | then ... |
27482756
| local_dataflow.rb:147:5:147:10 | [post] self | local_dataflow.rb:148:5:148:10 | self |
27492757
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:148:5:148:10 | self |
2758+
| local_dataflow.rb:147:9:147:9 | [post] x | local_dataflow.rb:148:9:148:9 | x |
27502759
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:148:9:148:9 | x |
27512760
| local_dataflow.rb:148:5:148:10 | call to use | local_dataflow.rb:132:12:148:10 | then ... |

ruby/ql/test/library-tests/dataflow/local/TaintStep.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2835,6 +2835,9 @@
28352835
| file://:0:0:0:0 | [summary param] self in ActionController::Parameters#merge | file://:0:0:0:0 | [summary] to write: ReturnValue in ActionController::Parameters#merge |
28362836
| file://:0:0:0:0 | [summary param] self in ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: Argument[self] in ActionController::Parameters#merge! |
28372837
| file://:0:0:0:0 | [summary param] self in ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: ReturnValue in ActionController::Parameters#merge! |
2838+
| file://:0:0:0:0 | [summary param] self in ActionDispatch::Http::UploadedFile#[original_filename,content_type,headers] | file://:0:0:0:0 | [summary] to write: ReturnValue in ActionDispatch::Http::UploadedFile#[original_filename,content_type,headers] |
2839+
| file://:0:0:0:0 | [summary param] self in ActionDispatch::Http::UploadedFile#read | file://:0:0:0:0 | [summary] to write: Argument[1] in ActionDispatch::Http::UploadedFile#read |
2840+
| file://:0:0:0:0 | [summary param] self in ActionDispatch::Http::UploadedFile#read | file://:0:0:0:0 | [summary] to write: ReturnValue in ActionDispatch::Http::UploadedFile#read |
28382841
| file://:0:0:0:0 | [summary param] self in ActiveSupportStringTransform | file://:0:0:0:0 | [summary] to write: ReturnValue in ActiveSupportStringTransform |
28392842
| file://:0:0:0:0 | [summary param] self in [] | file://:0:0:0:0 | [summary] to write: ReturnValue in [] |
28402843
| file://:0:0:0:0 | [summary param] self in \| | file://:0:0:0:0 | [summary] read: Argument[self].Element[any] in \| |
@@ -3164,6 +3167,7 @@
31643167
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
31653168
| local_dataflow.rb:132:6:132:11 | [post] self | local_dataflow.rb:133:8:133:13 | self |
31663169
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:133:8:133:13 | self |
3170+
| local_dataflow.rb:132:10:132:10 | [post] x | local_dataflow.rb:133:12:133:12 | x |
31673171
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:133:12:133:12 | x |
31683172
| local_dataflow.rb:132:12:148:10 | then ... | local_dataflow.rb:132:3:149:5 | if ... |
31693173
| local_dataflow.rb:133:5:139:7 | SSA phi read(self) | local_dataflow.rb:141:9:141:14 | self |
@@ -3174,17 +3178,20 @@
31743178
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:18:133:23 | self |
31753179
| local_dataflow.rb:133:8:133:23 | SSA phi read(self) | local_dataflow.rb:134:7:134:12 | self |
31763180
| local_dataflow.rb:133:8:133:23 | SSA phi read(x) | local_dataflow.rb:134:11:134:11 | x |
3181+
| local_dataflow.rb:133:12:133:12 | [post] x | local_dataflow.rb:133:22:133:22 | x |
31773182
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:22:133:22 | x |
31783183
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:136:7:136:12 | self |
31793184
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
31803185
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
31813186
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:136:7:136:12 | self |
3187+
| local_dataflow.rb:133:22:133:22 | [post] x | local_dataflow.rb:136:11:136:11 | x |
31823188
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:136:11:136:11 | x |
31833189
| local_dataflow.rb:133:24:134:12 | then ... | local_dataflow.rb:133:5:139:7 | if ... |
31843190
| local_dataflow.rb:134:7:134:12 | call to use | local_dataflow.rb:133:24:134:12 | then ... |
31853191
| local_dataflow.rb:135:5:138:9 | else ... | local_dataflow.rb:133:5:139:7 | if ... |
31863192
| local_dataflow.rb:136:7:136:12 | [post] self | local_dataflow.rb:137:10:137:15 | self |
31873193
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:137:10:137:15 | self |
3194+
| local_dataflow.rb:136:11:136:11 | [post] x | local_dataflow.rb:137:14:137:14 | x |
31883195
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:137:14:137:14 | x |
31893196
| local_dataflow.rb:137:7:138:9 | SSA phi read(self) | local_dataflow.rb:133:5:139:7 | SSA phi read(self) |
31903197
| local_dataflow.rb:137:7:138:9 | SSA phi read(x) | local_dataflow.rb:133:5:139:7 | SSA phi read(x) |
@@ -3193,6 +3200,7 @@
31933200
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:21:137:26 | self |
31943201
| local_dataflow.rb:137:10:137:26 | SSA phi read(self) | local_dataflow.rb:137:7:138:9 | SSA phi read(self) |
31953202
| local_dataflow.rb:137:10:137:26 | SSA phi read(x) | local_dataflow.rb:137:7:138:9 | SSA phi read(x) |
3203+
| local_dataflow.rb:137:14:137:14 | [post] x | local_dataflow.rb:137:25:137:25 | x |
31963204
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:25:137:25 | x |
31973205
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
31983206
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
@@ -3209,6 +3217,7 @@
32093217
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [false] ! ... |
32103218
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [true] ! ... |
32113219
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:20:141:25 | self |
3220+
| local_dataflow.rb:141:13:141:13 | [post] x | local_dataflow.rb:141:24:141:24 | x |
32123221
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:24:141:24 | x |
32133222
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
32143223
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
@@ -3218,6 +3227,7 @@
32183227
| local_dataflow.rb:141:20:141:36 | SSA phi read(x) | local_dataflow.rb:143:15:143:15 | x |
32193228
| local_dataflow.rb:141:20:141:36 | [false] ... && ... | local_dataflow.rb:141:19:141:37 | [false] ( ... ) |
32203229
| local_dataflow.rb:141:20:141:36 | [true] ... && ... | local_dataflow.rb:141:19:141:37 | [true] ( ... ) |
3230+
| local_dataflow.rb:141:24:141:24 | [post] x | local_dataflow.rb:141:35:141:35 | x |
32213231
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:35:141:35 | x |
32223232
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
32233233
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
@@ -3234,12 +3244,14 @@
32343244
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:21:143:26 | self |
32353245
| local_dataflow.rb:143:11:143:26 | SSA phi read(self) | local_dataflow.rb:144:11:144:16 | self |
32363246
| local_dataflow.rb:143:11:143:26 | SSA phi read(x) | local_dataflow.rb:144:15:144:15 | x |
3247+
| local_dataflow.rb:143:15:143:15 | [post] x | local_dataflow.rb:143:25:143:25 | x |
32373248
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:25:143:25 | x |
32383249
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
32393250
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
32403251
| local_dataflow.rb:143:27:144:16 | then ... | local_dataflow.rb:143:5:144:16 | elsif ... |
32413252
| local_dataflow.rb:144:11:144:16 | call to use | local_dataflow.rb:143:27:144:16 | then ... |
32423253
| local_dataflow.rb:147:5:147:10 | [post] self | local_dataflow.rb:148:5:148:10 | self |
32433254
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:148:5:148:10 | self |
3255+
| local_dataflow.rb:147:9:147:9 | [post] x | local_dataflow.rb:148:9:148:9 | x |
32443256
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:148:9:148:9 | x |
32453257
| local_dataflow.rb:148:5:148:10 | call to use | local_dataflow.rb:132:12:148:10 | then ... |

0 commit comments

Comments
 (0)