Skip to content

Commit 79c305c

Browse files
authored
Merge pull request #14124 from alexrford/rb/dataflow-query-refactor
Ruby: Use the new dataflow API for checked in queries
2 parents bb7ba78 + b5ec99c commit 79c305c

File tree

74 files changed

+1044
-357
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1044
-357
lines changed

ruby/ql/lib/codeql/ruby/experimental/UnicodeBypassValidationQuery.qll

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,64 @@ class PostValidation extends DataFlow::FlowState {
1919
PostValidation() { this = "PostValidation" }
2020
}
2121

22+
/**
23+
* A state signifying if a logical validation has been performed or not.
24+
*/
25+
private newtype ValidationState =
26+
// A state signifying that a logical validation has not been performed.
27+
PreValidationState() or
28+
// A state signifying that a logical validation has been performed.
29+
PostValidationState()
30+
2231
/**
2332
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
2433
*
2534
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
2635
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
36+
* DEPRECATED: Use `UnicodeBypassValidationFlow`
2737
*/
28-
class Configuration extends TaintTracking::Configuration {
38+
deprecated class Configuration extends TaintTracking::Configuration {
2939
Configuration() { this = "UnicodeBypassValidation" }
3040

41+
private ValidationState convertState(DataFlow::FlowState state) {
42+
state instanceof PreValidation and result = PreValidationState()
43+
or
44+
state instanceof PostValidation and result = PostValidationState()
45+
}
46+
3147
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
32-
source instanceof RemoteFlowSource and state instanceof PreValidation
48+
UnicodeBypassValidationConfig::isSource(source, this.convertState(state))
3349
}
3450

3551
override predicate isAdditionalTaintStep(
3652
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
3753
DataFlow::FlowState stateTo
54+
) {
55+
UnicodeBypassValidationConfig::isAdditionalFlowStep(nodeFrom, this.convertState(stateFrom),
56+
nodeTo, this.convertState(stateTo))
57+
}
58+
59+
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
60+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
61+
UnicodeBypassValidationConfig::isSink(sink, this.convertState(state))
62+
}
63+
}
64+
65+
/**
66+
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
67+
*
68+
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
69+
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
70+
*/
71+
private module UnicodeBypassValidationConfig implements DataFlow::StateConfigSig {
72+
class FlowState = ValidationState;
73+
74+
predicate isSource(DataFlow::Node source, FlowState state) {
75+
source instanceof RemoteFlowSource and state = PreValidationState()
76+
}
77+
78+
predicate isAdditionalFlowStep(
79+
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
3880
) {
3981
(
4082
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
@@ -75,12 +117,12 @@ class Configuration extends TaintTracking::Configuration {
75117
nodeTo = cn
76118
)
77119
) and
78-
stateFrom instanceof PreValidation and
79-
stateTo instanceof PostValidation
120+
stateFrom = PreValidationState() and
121+
stateTo = PostValidationState()
80122
}
81123

82124
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
83-
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
125+
predicate isSink(DataFlow::Node sink, FlowState state) {
84126
(
85127
exists(DataFlow::CallNode cn |
86128
cn.getMethodName() = "unicode_normalize" and
@@ -118,6 +160,11 @@ class Configuration extends TaintTracking::Configuration {
118160
sink = cn.getArgument(0)
119161
)
120162
) and
121-
state instanceof PostValidation
163+
state = PostValidationState()
122164
}
123165
}
166+
167+
/**
168+
* Taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
169+
*/
170+
module UnicodeBypassValidationFlow = TaintTracking::GlobalWithState<UnicodeBypassValidationConfig>;

ruby/ql/lib/codeql/ruby/experimental/ZipSlipQuery.qll

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ private import codeql.ruby.ApiGraphs
1212
/**
1313
* A taint-tracking configuration for reasoning about zip slip
1414
* vulnerabilities.
15+
* DEPRECATED: Use `ZipSlipFlow`
1516
*/
16-
class Configuration extends TaintTracking::Configuration {
17+
deprecated class Configuration extends TaintTracking::Configuration {
1718
Configuration() { this = "ZipSlip" }
1819

1920
override predicate isSource(DataFlow::Node source) { source instanceof ZipSlip::Source }
@@ -36,3 +37,30 @@ class Configuration extends TaintTracking::Configuration {
3637

3738
override predicate isSanitizer(DataFlow::Node node) { node instanceof ZipSlip::Sanitizer }
3839
}
40+
41+
private module ZipSlipConfig implements DataFlow::ConfigSig {
42+
predicate isSource(DataFlow::Node source) { source instanceof ZipSlip::Source }
43+
44+
predicate isSink(DataFlow::Node sink) { sink instanceof ZipSlip::Sink }
45+
46+
/**
47+
* This should actually be
48+
* `and cn = API::getTopLevelMember("Gem").getMember("Package").getMember("TarReader").getMember("Entry").getAMethodCall("full_name")` and similar for other classes
49+
* but I couldn't make it work so there's only check for the method name called on the entry. It is `full_name` for `Gem::Package::TarReader::Entry` and `Zlib`
50+
* and `name` for `Zip::File`
51+
*/
52+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
53+
exists(DataFlow::CallNode cn |
54+
cn.getReceiver() = nodeFrom and
55+
cn.getMethodName() in ["full_name", "name"] and
56+
cn = nodeTo
57+
)
58+
}
59+
60+
predicate isBarrier(DataFlow::Node node) { node instanceof ZipSlip::Sanitizer }
61+
}
62+
63+
/**
64+
* Taint-tracking for reasoning about zip slip vulnerabilities.
65+
*/
66+
module ZipSlipFlow = TaintTracking::Global<ZipSlipConfig>;

ruby/ql/lib/codeql/ruby/security/CleartextLoggingQuery.qll

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,51 @@
22
* Provides a taint-tracking configuration for "Clear-text logging of sensitive information".
33
*
44
* Note, for performance reasons: only import this file if
5-
* `CleartextLogging::Configuration` is needed, otherwise
5+
* `CleartextLoggingFlow` is needed, otherwise
66
* `CleartextLoggingCustomizations` should be imported instead.
77
*/
88

99
private import codeql.ruby.AST
1010
private import codeql.ruby.DataFlow
1111
private import codeql.ruby.TaintTracking
1212
import CleartextLoggingCustomizations::CleartextLogging
13-
private import CleartextLoggingCustomizations::CleartextLogging as CleartextLogging
13+
private import CleartextLoggingCustomizations::CleartextLogging as CL
1414

1515
/**
1616
* A taint-tracking configuration for detecting "Clear-text logging of sensitive information".
17+
* DEPRECATED: Use `CleartextLoggingFlow` instead
1718
*/
18-
class Configuration extends TaintTracking::Configuration {
19+
deprecated class Configuration extends TaintTracking::Configuration {
1920
Configuration() { this = "CleartextLogging" }
2021

21-
override predicate isSource(DataFlow::Node source) { source instanceof CleartextLogging::Source }
22+
override predicate isSource(DataFlow::Node source) { source instanceof CL::Source }
2223

23-
override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextLogging::Sink }
24+
override predicate isSink(DataFlow::Node sink) { sink instanceof CL::Sink }
2425

2526
override predicate isSanitizer(DataFlow::Node node) {
2627
super.isSanitizer(node)
2728
or
28-
node instanceof CleartextLogging::Sanitizer
29+
node instanceof CL::Sanitizer
2930
}
3031

3132
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
32-
CleartextLogging::isAdditionalTaintStep(nodeFrom, nodeTo)
33+
CL::isAdditionalTaintStep(nodeFrom, nodeTo)
3334
}
3435
}
36+
37+
private module Config implements DataFlow::ConfigSig {
38+
predicate isSource(DataFlow::Node source) { source instanceof CL::Source }
39+
40+
predicate isSink(DataFlow::Node sink) { sink instanceof CL::Sink }
41+
42+
predicate isBarrier(DataFlow::Node node) { node instanceof CL::Sanitizer }
43+
44+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
45+
CL::isAdditionalTaintStep(nodeFrom, nodeTo)
46+
}
47+
}
48+
49+
/**
50+
* Taint-tracking for detecting "Clear-text logging of sensitive information".
51+
*/
52+
module CleartextLoggingFlow = TaintTracking::Global<Config>;

ruby/ql/lib/codeql/ruby/security/CleartextStorageQuery.qll

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,50 @@
22
* Provides a taint-tracking configuration for "Clear-text storage of sensitive information".
33
*
44
* Note, for performance reasons: only import this file if
5-
* `Configuration` is needed, otherwise `CleartextStorageCustomizations` should be
6-
* imported instead.
5+
* `CleartextStorageFlow` is needed, otherwise
6+
* `CleartextStorageCustomizations` should be imported instead.
77
*/
88

99
private import codeql.ruby.AST
1010
private import codeql.ruby.DataFlow
1111
private import codeql.ruby.TaintTracking
12-
private import CleartextStorageCustomizations::CleartextStorage as CleartextStorage
12+
private import CleartextStorageCustomizations::CleartextStorage as CS
1313

1414
/**
1515
* A taint-tracking configuration for detecting "Clear-text storage of sensitive information".
16+
* DEPRECATED: Use `CleartextStorageFlow` instead
1617
*/
17-
class Configuration extends TaintTracking::Configuration {
18+
deprecated class Configuration extends TaintTracking::Configuration {
1819
Configuration() { this = "CleartextStorage" }
1920

20-
override predicate isSource(DataFlow::Node source) { source instanceof CleartextStorage::Source }
21+
override predicate isSource(DataFlow::Node source) { source instanceof CS::Source }
2122

22-
override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextStorage::Sink }
23+
override predicate isSink(DataFlow::Node sink) { sink instanceof CS::Sink }
2324

2425
override predicate isSanitizer(DataFlow::Node node) {
2526
super.isSanitizer(node)
2627
or
27-
node instanceof CleartextStorage::Sanitizer
28+
node instanceof CS::Sanitizer
2829
}
2930

3031
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
31-
CleartextStorage::isAdditionalTaintStep(nodeFrom, nodeTo)
32+
CS::isAdditionalTaintStep(nodeFrom, nodeTo)
3233
}
3334
}
35+
36+
private module Config implements DataFlow::ConfigSig {
37+
predicate isSource(DataFlow::Node source) { source instanceof CS::Source }
38+
39+
predicate isSink(DataFlow::Node sink) { sink instanceof CS::Sink }
40+
41+
predicate isBarrier(DataFlow::Node node) { node instanceof CS::Sanitizer }
42+
43+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
44+
CS::isAdditionalTaintStep(nodeFrom, nodeTo)
45+
}
46+
}
47+
48+
/**
49+
* Taint-tracking for detecting "Clear-text storage of sensitive information".
50+
*/
51+
module CleartextStorageFlow = TaintTracking::Global<Config>;

ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,100 @@ private import codeql.ruby.dataflow.BarrierGuards
1313
module CodeInjection {
1414
/** Flow states used to distinguish whether an attacker controls the entire string. */
1515
module FlowState {
16-
/** Flow state used for normal tainted data, where an attacker might only control a substring. */
17-
DataFlow::FlowState substring() { result = "substring" }
16+
/**
17+
* Flow state used for normal tainted data, where an attacker might only control a substring.
18+
* DEPRECATED: Use `Full()`
19+
*/
20+
deprecated DataFlow::FlowState substring() { result = "substring" }
21+
22+
/**
23+
* Flow state used for data that is entirely controlled by the attacker.
24+
* DEPRECATED: Use `Full()`
25+
*/
26+
deprecated DataFlow::FlowState full() { result = "full" }
1827

19-
/** Flow state used for data that is entirely controlled by the attacker. */
20-
DataFlow::FlowState full() { result = "full" }
28+
private newtype TState =
29+
TFull() or
30+
TSubString()
31+
32+
/** A flow state used to distinguish whether an attacker controls the entire string. */
33+
class State extends TState {
34+
/**
35+
* Gets a string representation of this state.
36+
*/
37+
string toString() { result = this.getStringRepresentation() }
38+
39+
/**
40+
* Gets a canonical string representation of this state.
41+
*/
42+
string getStringRepresentation() {
43+
this = TSubString() and result = "substring"
44+
or
45+
this = TFull() and result = "full"
46+
}
47+
}
48+
49+
/**
50+
* A flow state used for normal tainted data, where an attacker might only control a substring.
51+
*/
52+
class SubString extends State, TSubString { }
53+
54+
/**
55+
* A flow state used for data that is entirely controlled by the attacker.
56+
*/
57+
class Full extends State, TFull { }
2158
}
2259

2360
/**
2461
* A data flow source for "Code injection" vulnerabilities.
2562
*/
2663
abstract class Source extends DataFlow::Node {
64+
/**
65+
* Gets a flow state for which this is a source.
66+
* DEPRECATED: Use `getAState()`
67+
*/
68+
deprecated DataFlow::FlowState getAFlowState() {
69+
result = [FlowState::substring(), FlowState::full()]
70+
}
71+
2772
/** Gets a flow state for which this is a source. */
28-
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
73+
FlowState::State getAState() {
74+
result instanceof FlowState::SubString or result instanceof FlowState::Full
75+
}
2976
}
3077

3178
/**
3279
* A data flow sink for "Code injection" vulnerabilities.
3380
*/
3481
abstract class Sink extends DataFlow::Node {
82+
/**
83+
* Holds if this sink is safe for an attacker that only controls a substring.
84+
* DEPRECATED: Use `getAState()`
85+
*/
86+
deprecated DataFlow::FlowState getAFlowState() {
87+
result = [FlowState::substring(), FlowState::full()]
88+
}
89+
3590
/** Holds if this sink is safe for an attacker that only controls a substring. */
36-
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
91+
FlowState::State getAState() { any() }
3792
}
3893

3994
/**
4095
* A sanitizer for "Code injection" vulnerabilities.
4196
*/
4297
abstract class Sanitizer extends DataFlow::Node {
98+
/**
99+
* Gets a flow state for which this is a sanitizer.
100+
* Sanitizes all states if the result is empty.
101+
* DEPRECATED: Use `getAState()`
102+
*/
103+
deprecated DataFlow::FlowState getAFlowState() { none() }
104+
43105
/**
44106
* Gets a flow state for which this is a sanitizer.
45107
* Sanitizes all states if the result is empty.
46108
*/
47-
DataFlow::FlowState getAFlowState() { none() }
109+
FlowState::State getAState() { none() }
48110
}
49111

50112
/**
@@ -67,12 +129,17 @@ module CodeInjection {
67129

68130
CodeExecutionAsSink() { this = c.getCode() }
69131

70-
/** Gets a flow state for which this is a sink. */
71-
override DataFlow::FlowState getAFlowState() {
132+
deprecated override DataFlow::FlowState getAFlowState() {
72133
if c.runsArbitraryCode()
73134
then result = [FlowState::substring(), FlowState::full()] // If it runs arbitrary code then it's always vulnerable.
74135
else result = FlowState::full() // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
75136
}
137+
138+
override FlowState::State getAState() {
139+
if c.runsArbitraryCode()
140+
then any() // If it runs arbitrary code then it's always vulnerable.
141+
else result instanceof FlowState::Full // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
142+
}
76143
}
77144

78145
private import codeql.ruby.AST as Ast
@@ -92,6 +159,8 @@ module CodeInjection {
92159
)
93160
}
94161

95-
override DataFlow::FlowState getAFlowState() { result = FlowState::full() }
162+
deprecated override DataFlow::FlowState getAFlowState() { result = FlowState::full() }
163+
164+
override FlowState::State getAState() { result instanceof FlowState::Full }
96165
}
97166
}

0 commit comments

Comments
 (0)