Merge pull request #11136 from hmac/json-flow-summaries

Ruby: JSON flow summaries
This commit is contained in:
Harry Maclean
2022-12-01 14:19:47 +13:00
committed by GitHub
11 changed files with 165 additions and 1 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Taint flow is now tracked through many common JSON parsing and generation methods.

View File

@@ -24,3 +24,4 @@ private import codeql.ruby.frameworks.XmlParsing
private import codeql.ruby.frameworks.ActionDispatch
private import codeql.ruby.frameworks.PosixSpawn
private import codeql.ruby.frameworks.StringFormatters
private import codeql.ruby.frameworks.Json

View File

@@ -104,6 +104,17 @@ module ActiveSupport {
override predicate runsArbitraryCode() { none() }
}
/** Flow summary for `Object#to_json`, which serializes the receiver as a JSON string. */
private class ToJsonSummary extends SimpleSummarizedCallable {
ToJsonSummary() { this = "to_json" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = ["Argument[self]", "Argument[self].Element[any]"] and
output = "ReturnValue" and
preservesValue = false
}
}
}
/**
@@ -374,4 +385,17 @@ module ActiveSupport {
]
}
}
/** `ActiveSupport::JSON` */
module Json {
private class JsonSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
"activesupport;;Member[ActiveSupport].Member[JSON].Method[encode,dump];Argument[0];ReturnValue;taint",
"activesupport;;Member[ActiveSupport].Member[JSON].Method[decode,load];Argument[0];ReturnValue;taint",
]
}
}
}
}

View File

@@ -0,0 +1,22 @@
/** Provides modeling for the `json` gem. */
private import codeql.ruby.frameworks.data.ModelsAsData
/** Provides modeling for the `json` gem. */
module Json {
/**
* Flow summaries for common `JSON` methods.
* Not all of these methods are strictly defined in the `json` gem.
* The `JSON` namespace is heavily overloaded by other JSON parsing gems such as `oj`, `json_pure`, `multi_json` etc.
* This summary covers common methods we've seen called on `JSON` in the wild.
*/
private class JsonSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
"json;;Member[JSON].Method[parse,parse!,load,restore];Argument[0];ReturnValue;taint",
"json;;Member[JSON].Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse];Argument[0];ReturnValue;taint",
]
}
}
}

View File

@@ -3,7 +3,6 @@
*
* Example for a test.ql:
* ```ql
* *import codeql.ruby.AST
* import TestUtilities.InlineFlowTest
* import PathGraph
*

View File

@@ -21,6 +21,10 @@
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| file://:0:0:0:0 | parameter position 0.. of File.join | file://:0:0:0:0 | [summary] to write: return (return) in File.join |
| file://:0:0:0:0 | parameter self of & | file://:0:0:0:0 | [summary] read: argument self.any element in & |
| file://:0:0:0:0 | parameter self of * | file://:0:0:0:0 | [summary] read: argument self.any element in * |

View File

@@ -189,6 +189,19 @@ edges
| active_support.rb:303:7:303:16 | call to source : | active_support.rb:304:19:304:19 | a : |
| active_support.rb:304:7:304:19 | call to json_escape : | active_support.rb:305:8:305:8 | b |
| active_support.rb:304:19:304:19 | a : | active_support.rb:304:7:304:19 | call to json_escape : |
| active_support.rb:309:9:309:18 | call to source : | active_support.rb:310:37:310:37 | x : |
| active_support.rb:310:37:310:37 | x : | active_support.rb:310:10:310:38 | call to encode |
| active_support.rb:314:9:314:18 | call to source : | active_support.rb:315:37:315:37 | x : |
| active_support.rb:315:37:315:37 | x : | active_support.rb:315:10:315:38 | call to decode |
| active_support.rb:319:9:319:18 | call to source : | active_support.rb:320:35:320:35 | x : |
| active_support.rb:320:35:320:35 | x : | active_support.rb:320:10:320:36 | call to dump |
| active_support.rb:324:9:324:18 | call to source : | active_support.rb:325:35:325:35 | x : |
| active_support.rb:325:35:325:35 | x : | active_support.rb:325:10:325:36 | call to load |
| active_support.rb:329:9:329:18 | call to source : | active_support.rb:330:10:330:10 | x : |
| active_support.rb:329:9:329:18 | call to source : | active_support.rb:331:10:331:10 | x : |
| active_support.rb:330:10:330:10 | x : | active_support.rb:332:10:332:10 | y [element 0] : |
| active_support.rb:331:10:331:10 | x : | active_support.rb:331:10:331:18 | call to to_json |
| active_support.rb:332:10:332:10 | y [element 0] : | active_support.rb:332:10:332:18 | call to to_json |
| hash_extensions.rb:2:14:2:24 | call to source : | hash_extensions.rb:3:9:3:9 | h [element :a] : |
| hash_extensions.rb:2:14:2:24 | call to source : | hash_extensions.rb:3:9:3:9 | h [element :a] : |
| 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
| active_support.rb:304:7:304:19 | call to json_escape : | semmle.label | call to json_escape : |
| active_support.rb:304:19:304:19 | a : | semmle.label | a : |
| active_support.rb:305:8:305:8 | b | semmle.label | b |
| active_support.rb:309:9:309:18 | call to source : | semmle.label | call to source : |
| active_support.rb:310:10:310:38 | call to encode | semmle.label | call to encode |
| active_support.rb:310:37:310:37 | x : | semmle.label | x : |
| active_support.rb:314:9:314:18 | call to source : | semmle.label | call to source : |
| active_support.rb:315:10:315:38 | call to decode | semmle.label | call to decode |
| active_support.rb:315:37:315:37 | x : | semmle.label | x : |
| active_support.rb:319:9:319:18 | call to source : | semmle.label | call to source : |
| active_support.rb:320:10:320:36 | call to dump | semmle.label | call to dump |
| active_support.rb:320:35:320:35 | x : | semmle.label | x : |
| active_support.rb:324:9:324:18 | call to source : | semmle.label | call to source : |
| active_support.rb:325:10:325:36 | call to load | semmle.label | call to load |
| active_support.rb:325:35:325:35 | x : | semmle.label | x : |
| active_support.rb:329:9:329:18 | call to source : | semmle.label | call to source : |
| active_support.rb:330:10:330:10 | x : | semmle.label | x : |
| active_support.rb:331:10:331:10 | x : | semmle.label | x : |
| active_support.rb:331:10:331:18 | call to to_json | semmle.label | call to to_json |
| active_support.rb:332:10:332:10 | y [element 0] : | semmle.label | y [element 0] : |
| active_support.rb:332:10:332:18 | call to to_json | semmle.label | call to to_json |
| hash_extensions.rb:2:14:2:24 | call to source : | semmle.label | call to source : |
| hash_extensions.rb:2:14:2:24 | call to source : | semmle.label | call to source : |
| hash_extensions.rb:3:9:3:9 | h [element :a] : | semmle.label | h [element :a] : |

View File

@@ -304,3 +304,30 @@ def m_json_escape
b = json_escape a
sink b # $hasTaintFlow=a
end
def m_json_encode
x = source "a"
sink ActiveSupport::JSON.encode(x) # $hasTaintFlow=a
end
def m_json_decode
x = source "a"
sink ActiveSupport::JSON.decode(x) # $hasTaintFlow=a
end
def m_json_dump
x = source "a"
sink ActiveSupport::JSON.dump(x) # $hasTaintFlow=a
end
def m_json_load
x = source "a"
sink ActiveSupport::JSON.load(x) # $hasTaintFlow=a
end
def m_to_json
x = source "a"
y = [x]
sink x.to_json # $hasTaintFlow=a
sink y.to_json # $hasTaintFlow=a
end

View File

@@ -0,0 +1,34 @@
failures
edges
| json.rb:1:17:1:26 | call to source : | json.rb:1:6:1:27 | call to parse |
| json.rb:2:18:2:27 | call to source : | json.rb:2:6:2:28 | call to parse! |
| json.rb:3:16:3:25 | call to source : | json.rb:3:6:3:26 | call to load |
| json.rb:4:19:4:28 | call to source : | json.rb:4:6:4:29 | call to restore |
| json.rb:6:20:6:29 | call to source : | json.rb:6:6:6:30 | call to generate |
| json.rb:7:25:7:34 | call to source : | json.rb:7:6:7:35 | call to fast_generate |
| json.rb:8:27:8:36 | call to source : | json.rb:8:6:8:37 | call to pretty_generate |
| json.rb:9:16:9:25 | call to source : | json.rb:9:6:9:26 | call to dump |
| json.rb:10:19:10:28 | call to source : | json.rb:10:6:10:29 | call to unparse |
| json.rb:11:24:11:33 | call to source : | json.rb:11:6:11:34 | call to fast_unparse |
nodes
| json.rb:1:6:1:27 | call to parse | semmle.label | call to parse |
| json.rb:1:17:1:26 | call to source : | semmle.label | call to source : |
| json.rb:2:6:2:28 | call to parse! | semmle.label | call to parse! |
| json.rb:2:18:2:27 | call to source : | semmle.label | call to source : |
| json.rb:3:6:3:26 | call to load | semmle.label | call to load |
| json.rb:3:16:3:25 | call to source : | semmle.label | call to source : |
| json.rb:4:6:4:29 | call to restore | semmle.label | call to restore |
| json.rb:4:19:4:28 | call to source : | semmle.label | call to source : |
| json.rb:6:6:6:30 | call to generate | semmle.label | call to generate |
| json.rb:6:20:6:29 | call to source : | semmle.label | call to source : |
| json.rb:7:6:7:35 | call to fast_generate | semmle.label | call to fast_generate |
| json.rb:7:25:7:34 | call to source : | semmle.label | call to source : |
| json.rb:8:6:8:37 | call to pretty_generate | semmle.label | call to pretty_generate |
| json.rb:8:27:8:36 | call to source : | semmle.label | call to source : |
| json.rb:9:6:9:26 | call to dump | semmle.label | call to dump |
| json.rb:9:16:9:25 | call to source : | semmle.label | call to source : |
| json.rb:10:6:10:29 | call to unparse | semmle.label | call to unparse |
| json.rb:10:19:10:28 | call to source : | semmle.label | call to source : |
| json.rb:11:6:11:34 | call to fast_unparse | semmle.label | call to fast_unparse |
| json.rb:11:24:11:33 | call to source : | semmle.label | call to source : |
subpaths

View File

@@ -0,0 +1,7 @@
/**
* @kind path-problem
*/
import TestUtilities.InlineFlowTest
import codeql.ruby.Frameworks
import PathGraph

View File

@@ -0,0 +1,11 @@
sink JSON.parse(source "a") # $hasTaintFlow=a
sink JSON.parse!(source "a") # $hasTaintFlow=a
sink JSON.load(source "a") # $hasTaintFlow=a
sink JSON.restore(source "a") # $hasTaintFlow=a
sink JSON.generate(source "a") # $hasTaintFlow=a
sink JSON.fast_generate(source "a") # $hasTaintFlow=a
sink JSON.pretty_generate(source "a") # $hasTaintFlow=a
sink JSON.dump(source "a") # $hasTaintFlow=a
sink JSON.unparse(source "a") # $hasTaintFlow=a
sink JSON.fast_unparse(source "a") # $hasTaintFlow=a