Skip to content

Commit bd129ed

Browse files
authored
Merge pull request github#11136 from hmac/json-flow-summaries
Ruby: JSON flow summaries
2 parents 6ae10c5 + 9142152 commit bd129ed

File tree

11 files changed

+165
-1
lines changed

11 files changed

+165
-1
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+
* Taint flow is now tracked through many common JSON parsing and generation methods.

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ private import codeql.ruby.frameworks.XmlParsing
2424
private import codeql.ruby.frameworks.ActionDispatch
2525
private import codeql.ruby.frameworks.PosixSpawn
2626
private import codeql.ruby.frameworks.StringFormatters
27+
private import codeql.ruby.frameworks.Json

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ module ActiveSupport {
104104

105105
override predicate runsArbitraryCode() { none() }
106106
}
107+
108+
/** Flow summary for `Object#to_json`, which serializes the receiver as a JSON string. */
109+
private class ToJsonSummary extends SimpleSummarizedCallable {
110+
ToJsonSummary() { this = "to_json" }
111+
112+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
113+
input = ["Argument[self]", "Argument[self].Element[any]"] and
114+
output = "ReturnValue" and
115+
preservesValue = false
116+
}
117+
}
107118
}
108119

109120
/**
@@ -374,4 +385,17 @@ module ActiveSupport {
374385
]
375386
}
376387
}
388+
389+
/** `ActiveSupport::JSON` */
390+
module Json {
391+
private class JsonSummary extends ModelInput::SummaryModelCsv {
392+
override predicate row(string row) {
393+
row =
394+
[
395+
"activesupport;;Member[ActiveSupport].Member[JSON].Method[encode,dump];Argument[0];ReturnValue;taint",
396+
"activesupport;;Member[ActiveSupport].Member[JSON].Method[decode,load];Argument[0];ReturnValue;taint",
397+
]
398+
}
399+
}
400+
}
377401
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/** Provides modeling for the `json` gem. */
2+
3+
private import codeql.ruby.frameworks.data.ModelsAsData
4+
5+
/** Provides modeling for the `json` gem. */
6+
module Json {
7+
/**
8+
* Flow summaries for common `JSON` methods.
9+
* Not all of these methods are strictly defined in the `json` gem.
10+
* The `JSON` namespace is heavily overloaded by other JSON parsing gems such as `oj`, `json_pure`, `multi_json` etc.
11+
* This summary covers common methods we've seen called on `JSON` in the wild.
12+
*/
13+
private class JsonSummary extends ModelInput::SummaryModelCsv {
14+
override predicate row(string row) {
15+
row =
16+
[
17+
"json;;Member[JSON].Method[parse,parse!,load,restore];Argument[0];ReturnValue;taint",
18+
"json;;Member[JSON].Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse];Argument[0];ReturnValue;taint",
19+
]
20+
}
21+
}
22+
}

ruby/ql/test/TestUtilities/InlineFlowTest.qll

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*
44
* Example for a test.ql:
55
* ```ql
6-
* *import codeql.ruby.AST
76
* import TestUtilities.InlineFlowTest
87
* import PathGraph
98
*

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] | file://:0:0:0:0 | [summary] to write: argument self in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] |
2222
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] |
2323
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Method[new] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Method[new] |
24+
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActiveSupport].Member[JSON].Method[decode,load] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActiveSupport].Member[JSON].Method[decode,load] |
25+
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActiveSupport].Member[JSON].Method[encode,dump] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActiveSupport].Member[JSON].Method[encode,dump] |
26+
| file://:0:0:0:0 | parameter position 0 of json;;Member[JSON].Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse] | file://:0:0:0:0 | [summary] to write: return (return) in json;;Member[JSON].Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse] |
27+
| file://:0:0:0:0 | parameter position 0 of json;;Member[JSON].Method[parse,parse!,load,restore] | file://:0:0:0:0 | [summary] to write: return (return) in json;;Member[JSON].Method[parse,parse!,load,restore] |
2428
| file://:0:0:0:0 | parameter position 0.. of File.join | file://:0:0:0:0 | [summary] to write: return (return) in File.join |
2529
| file://:0:0:0:0 | parameter self of & | file://:0:0:0:0 | [summary] read: argument self.any element in & |
2630
| file://:0:0:0:0 | parameter self of * | file://:0:0:0:0 | [summary] read: argument self.any element in * |

ruby/ql/test/library-tests/frameworks/active_support/ActiveSupportDataFlow.expected

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@ edges
189189
| active_support.rb:303:7:303:16 | call to source : | active_support.rb:304:19:304:19 | a : |
190190
| active_support.rb:304:7:304:19 | call to json_escape : | active_support.rb:305:8:305:8 | b |
191191
| active_support.rb:304:19:304:19 | a : | active_support.rb:304:7:304:19 | call to json_escape : |
192+
| active_support.rb:309:9:309:18 | call to source : | active_support.rb:310:37:310:37 | x : |
193+
| active_support.rb:310:37:310:37 | x : | active_support.rb:310:10:310:38 | call to encode |
194+
| active_support.rb:314:9:314:18 | call to source : | active_support.rb:315:37:315:37 | x : |
195+
| active_support.rb:315:37:315:37 | x : | active_support.rb:315:10:315:38 | call to decode |
196+
| active_support.rb:319:9:319:18 | call to source : | active_support.rb:320:35:320:35 | x : |
197+
| active_support.rb:320:35:320:35 | x : | active_support.rb:320:10:320:36 | call to dump |
198+
| active_support.rb:324:9:324:18 | call to source : | active_support.rb:325:35:325:35 | x : |
199+
| active_support.rb:325:35:325:35 | x : | active_support.rb:325:10:325:36 | call to load |
200+
| active_support.rb:329:9:329:18 | call to source : | active_support.rb:330:10:330:10 | x : |
201+
| active_support.rb:329:9:329:18 | call to source : | active_support.rb:331:10:331:10 | x : |
202+
| active_support.rb:330:10:330:10 | x : | active_support.rb:332:10:332:10 | y [element 0] : |
203+
| active_support.rb:331:10:331:10 | x : | active_support.rb:331:10:331:18 | call to to_json |
204+
| active_support.rb:332:10:332:10 | y [element 0] : | active_support.rb:332:10:332:18 | call to to_json |
192205
| hash_extensions.rb:2:14:2:24 | call to source : | hash_extensions.rb:3:9:3:9 | h [element :a] : |
193206
| hash_extensions.rb:2:14:2:24 | call to source : | hash_extensions.rb:3:9:3:9 | h [element :a] : |
194207
| hash_extensions.rb:3:9:3:9 | h [element :a] : | hash_extensions.rb:3:9:3:24 | call to stringify_keys [element] : |
@@ -539,6 +552,24 @@ nodes
539552
| active_support.rb:304:7:304:19 | call to json_escape : | semmle.label | call to json_escape : |
540553
| active_support.rb:304:19:304:19 | a : | semmle.label | a : |
541554
| active_support.rb:305:8:305:8 | b | semmle.label | b |
555+
| active_support.rb:309:9:309:18 | call to source : | semmle.label | call to source : |
556+
| active_support.rb:310:10:310:38 | call to encode | semmle.label | call to encode |
557+
| active_support.rb:310:37:310:37 | x : | semmle.label | x : |
558+
| active_support.rb:314:9:314:18 | call to source : | semmle.label | call to source : |
559+
| active_support.rb:315:10:315:38 | call to decode | semmle.label | call to decode |
560+
| active_support.rb:315:37:315:37 | x : | semmle.label | x : |
561+
| active_support.rb:319:9:319:18 | call to source : | semmle.label | call to source : |
562+
| active_support.rb:320:10:320:36 | call to dump | semmle.label | call to dump |
563+
| active_support.rb:320:35:320:35 | x : | semmle.label | x : |
564+
| active_support.rb:324:9:324:18 | call to source : | semmle.label | call to source : |
565+
| active_support.rb:325:10:325:36 | call to load | semmle.label | call to load |
566+
| active_support.rb:325:35:325:35 | x : | semmle.label | x : |
567+
| active_support.rb:329:9:329:18 | call to source : | semmle.label | call to source : |
568+
| active_support.rb:330:10:330:10 | x : | semmle.label | x : |
569+
| active_support.rb:331:10:331:10 | x : | semmle.label | x : |
570+
| active_support.rb:331:10:331:18 | call to to_json | semmle.label | call to to_json |
571+
| active_support.rb:332:10:332:10 | y [element 0] : | semmle.label | y [element 0] : |
572+
| active_support.rb:332:10:332:18 | call to to_json | semmle.label | call to to_json |
542573
| hash_extensions.rb:2:14:2:24 | call to source : | semmle.label | call to source : |
543574
| hash_extensions.rb:2:14:2:24 | call to source : | semmle.label | call to source : |
544575
| hash_extensions.rb:3:9:3:9 | h [element :a] : | semmle.label | h [element :a] : |

ruby/ql/test/library-tests/frameworks/active_support/active_support.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,30 @@ def m_json_escape
304304
b = json_escape a
305305
sink b # $hasTaintFlow=a
306306
end
307+
308+
def m_json_encode
309+
x = source "a"
310+
sink ActiveSupport::JSON.encode(x) # $hasTaintFlow=a
311+
end
312+
313+
def m_json_decode
314+
x = source "a"
315+
sink ActiveSupport::JSON.decode(x) # $hasTaintFlow=a
316+
end
317+
318+
def m_json_dump
319+
x = source "a"
320+
sink ActiveSupport::JSON.dump(x) # $hasTaintFlow=a
321+
end
322+
323+
def m_json_load
324+
x = source "a"
325+
sink ActiveSupport::JSON.load(x) # $hasTaintFlow=a
326+
end
327+
328+
def m_to_json
329+
x = source "a"
330+
y = [x]
331+
sink x.to_json # $hasTaintFlow=a
332+
sink y.to_json # $hasTaintFlow=a
333+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
failures
2+
edges
3+
| json.rb:1:17:1:26 | call to source : | json.rb:1:6:1:27 | call to parse |
4+
| json.rb:2:18:2:27 | call to source : | json.rb:2:6:2:28 | call to parse! |
5+
| json.rb:3:16:3:25 | call to source : | json.rb:3:6:3:26 | call to load |
6+
| json.rb:4:19:4:28 | call to source : | json.rb:4:6:4:29 | call to restore |
7+
| json.rb:6:20:6:29 | call to source : | json.rb:6:6:6:30 | call to generate |
8+
| json.rb:7:25:7:34 | call to source : | json.rb:7:6:7:35 | call to fast_generate |
9+
| json.rb:8:27:8:36 | call to source : | json.rb:8:6:8:37 | call to pretty_generate |
10+
| json.rb:9:16:9:25 | call to source : | json.rb:9:6:9:26 | call to dump |
11+
| json.rb:10:19:10:28 | call to source : | json.rb:10:6:10:29 | call to unparse |
12+
| json.rb:11:24:11:33 | call to source : | json.rb:11:6:11:34 | call to fast_unparse |
13+
nodes
14+
| json.rb:1:6:1:27 | call to parse | semmle.label | call to parse |
15+
| json.rb:1:17:1:26 | call to source : | semmle.label | call to source : |
16+
| json.rb:2:6:2:28 | call to parse! | semmle.label | call to parse! |
17+
| json.rb:2:18:2:27 | call to source : | semmle.label | call to source : |
18+
| json.rb:3:6:3:26 | call to load | semmle.label | call to load |
19+
| json.rb:3:16:3:25 | call to source : | semmle.label | call to source : |
20+
| json.rb:4:6:4:29 | call to restore | semmle.label | call to restore |
21+
| json.rb:4:19:4:28 | call to source : | semmle.label | call to source : |
22+
| json.rb:6:6:6:30 | call to generate | semmle.label | call to generate |
23+
| json.rb:6:20:6:29 | call to source : | semmle.label | call to source : |
24+
| json.rb:7:6:7:35 | call to fast_generate | semmle.label | call to fast_generate |
25+
| json.rb:7:25:7:34 | call to source : | semmle.label | call to source : |
26+
| json.rb:8:6:8:37 | call to pretty_generate | semmle.label | call to pretty_generate |
27+
| json.rb:8:27:8:36 | call to source : | semmle.label | call to source : |
28+
| json.rb:9:6:9:26 | call to dump | semmle.label | call to dump |
29+
| json.rb:9:16:9:25 | call to source : | semmle.label | call to source : |
30+
| json.rb:10:6:10:29 | call to unparse | semmle.label | call to unparse |
31+
| json.rb:10:19:10:28 | call to source : | semmle.label | call to source : |
32+
| json.rb:11:6:11:34 | call to fast_unparse | semmle.label | call to fast_unparse |
33+
| json.rb:11:24:11:33 | call to source : | semmle.label | call to source : |
34+
subpaths
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @kind path-problem
3+
*/
4+
5+
import TestUtilities.InlineFlowTest
6+
import codeql.ruby.Frameworks
7+
import PathGraph

0 commit comments

Comments
 (0)