Skip to content

Commit 0d7d5a3

Browse files
committed
Ruby: Use a newtype instead of DataFlow::FlowState for code-injection
1 parent dfc3b33 commit 0d7d5a3

File tree

3 files changed

+77
-20
lines changed

3 files changed

+77
-20
lines changed

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

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,92 @@ 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" }
27+
28+
private newtype TState =
29+
TFull() or
30+
TSubString()
31+
32+
/** Flow states used to distinguish whether an attacker controls the entire string. */
33+
class State extends TState {
34+
string toString() {
35+
this = TSubString() and result = "substring"
36+
or
37+
this = TFull() and result = "full"
38+
}
39+
}
40+
41+
/**
42+
* Flow state used for normal tainted data, where an attacker might only control a substring.
43+
*/
44+
class SubString extends State, TSubString { }
1845

19-
/** Flow state used for data that is entirely controlled by the attacker. */
20-
DataFlow::FlowState full() { result = "full" }
46+
/**
47+
* Flow state used for data that is entirely controlled by the attacker.
48+
*/
49+
class Full extends State, TFull { }
2150
}
2251

2352
/**
2453
* A data flow source for "Code injection" vulnerabilities.
2554
*/
2655
abstract class Source extends DataFlow::Node {
56+
/**
57+
* Gets a flow state for which this is a source.
58+
* DEPRECATED: Use `getAState()`
59+
*/
60+
deprecated DataFlow::FlowState getAFlowState() {
61+
result = [FlowState::substring(), FlowState::full()]
62+
}
63+
2764
/** Gets a flow state for which this is a source. */
28-
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
65+
FlowState::State getAState() {
66+
result instanceof FlowState::SubString or result instanceof FlowState::Full
67+
}
2968
}
3069

3170
/**
3271
* A data flow sink for "Code injection" vulnerabilities.
3372
*/
3473
abstract class Sink extends DataFlow::Node {
74+
/**
75+
* Holds if this sink is safe for an attacker that only controls a substring.
76+
* DEPRECATED: Use `getAState()`
77+
*/
78+
deprecated DataFlow::FlowState getAFlowState() {
79+
result = [FlowState::substring(), FlowState::full()]
80+
}
81+
3582
/** Holds if this sink is safe for an attacker that only controls a substring. */
36-
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] }
83+
FlowState::State getAState() { any() }
3784
}
3885

3986
/**
4087
* A sanitizer for "Code injection" vulnerabilities.
4188
*/
4289
abstract class Sanitizer extends DataFlow::Node {
90+
/**
91+
* Gets a flow state for which this is a sanitizer.
92+
* Sanitizes all states if the result is empty.
93+
* DEPRECATED: Use `getAState()`
94+
*/
95+
deprecated DataFlow::FlowState getAFlowState() { none() }
96+
4397
/**
4498
* Gets a flow state for which this is a sanitizer.
4599
* Sanitizes all states if the result is empty.
46100
*/
47-
DataFlow::FlowState getAFlowState() { none() }
101+
FlowState::State getAState() { none() }
48102
}
49103

50104
/**
@@ -67,12 +121,17 @@ module CodeInjection {
67121

68122
CodeExecutionAsSink() { this = c.getCode() }
69123

70-
/** Gets a flow state for which this is a sink. */
71-
override DataFlow::FlowState getAFlowState() {
124+
deprecated override DataFlow::FlowState getAFlowState() {
72125
if c.runsArbitraryCode()
73126
then result = [FlowState::substring(), FlowState::full()] // If it runs arbitrary code then it's always vulnerable.
74127
else result = FlowState::full() // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
75128
}
129+
130+
override FlowState::State getAState() {
131+
if c.runsArbitraryCode()
132+
then any() // If it runs arbitrary code then it's always vulnerable.
133+
else result instanceof FlowState::Full // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string.
134+
}
76135
}
77136

78137
private import codeql.ruby.AST as Ast
@@ -92,6 +151,8 @@ module CodeInjection {
92151
)
93152
}
94153

95-
override DataFlow::FlowState getAFlowState() { result = FlowState::full() }
154+
deprecated override DataFlow::FlowState getAFlowState() { result = FlowState::full() }
155+
156+
override FlowState::State getAState() { result instanceof FlowState::Full }
96157
}
97158
}

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

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,21 @@ deprecated class Configuration extends TaintTracking::Configuration {
4444
}
4545

4646
private module Config implements DataFlow::StateConfigSig {
47-
class FlowState = DataFlow::FlowState;
47+
class FlowState = FlowState::State;
4848

49-
predicate isSource(DataFlow::Node source, FlowState state) {
50-
state = source.(Source).getAFlowState()
51-
}
49+
predicate isSource(DataFlow::Node source, FlowState state) { state = source.(Source).getAState() }
5250

53-
predicate isSink(DataFlow::Node sink, FlowState state) { state = sink.(Sink).getAFlowState() }
51+
predicate isSink(DataFlow::Node sink, FlowState state) { state = sink.(Sink).getAState() }
5452

5553
predicate isBarrier(DataFlow::Node node) {
56-
node instanceof Sanitizer and not exists(node.(Sanitizer).getAFlowState())
54+
node instanceof Sanitizer and not exists(node.(Sanitizer).getAState())
5755
or
5856
node instanceof StringConstCompareBarrier
5957
or
6058
node instanceof StringConstArrayInclusionCallBarrier
6159
}
6260

63-
predicate isBarrier(DataFlow::Node node, DataFlow::FlowState state) {
64-
node.(Sanitizer).getAFlowState() = state
65-
}
61+
predicate isBarrier(DataFlow::Node node, FlowState state) { node.(Sanitizer).getAState() = state }
6662
}
6763

6864
/**

ruby/ql/src/queries/security/cwe-094/CodeInjection.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ where
2929
otherSink) and
3030
otherSink.getNode() = sink.getNode()
3131
|
32-
otherSink order by otherSink.getState()
32+
otherSink order by otherSink.getState().toString()
3333
)
3434
select sink.getNode(), source, sink, "This code execution depends on a $@.", sourceNode,
3535
"user-provided value"

0 commit comments

Comments
 (0)