Skip to content

Commit 0cffcf3

Browse files
committed
Ruby: Extend barrier guards to handle phi inputs
1 parent 0f0acc0 commit 0cffcf3

File tree

8 files changed

+177
-60
lines changed

8 files changed

+177
-60
lines changed

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

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,31 +72,33 @@ CfgNodes::ExprCfgNode getAPostUpdateNodeForArg(Argument arg) {
7272
not exists(getALastEvalNode(result))
7373
}
7474

75-
/** Provides predicates related to local data flow. */
76-
module LocalFlow {
77-
private import codeql.ruby.dataflow.internal.SsaImpl
75+
/** An SSA definition into which another SSA definition may flow. */
76+
class SsaInputDefinitionExt extends SsaImpl::DefinitionExt {
77+
SsaInputDefinitionExt() {
78+
this instanceof Ssa::PhiNode
79+
or
80+
this instanceof SsaImpl::PhiReadNode
81+
}
7882

79-
/** An SSA definition into which another SSA definition may flow. */
80-
private class SsaInputDefinitionExtNode extends SsaDefinitionExtNode {
81-
SsaInputDefinitionExtNode() {
82-
def instanceof Ssa::PhiNode
83-
or
84-
def instanceof SsaImpl::PhiReadNode
85-
}
83+
predicate hasInputFromBlock(SsaImpl::DefinitionExt def, BasicBlock bb, int i, BasicBlock input) {
84+
SsaImpl::lastRefBeforeRedefExt(def, bb, i, input, this)
8685
}
86+
}
8787

88+
/** Provides predicates related to local data flow. */
89+
module LocalFlow {
8890
/**
8991
* Holds if `nodeFrom` is a node for SSA definition `def`, which can reach `next`.
9092
*/
9193
pragma[nomagic]
9294
private predicate localFlowSsaInputFromDef(
93-
SsaDefinitionExtNode nodeFrom, SsaImpl::DefinitionExt def, SsaInputDefinitionExtNode next
95+
SsaDefinitionExtNode nodeFrom, SsaImpl::DefinitionExt def, SsaInputNode nodeTo
9496
) {
95-
exists(BasicBlock bb, int i |
96-
lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and
97+
exists(BasicBlock bb, int i, BasicBlock input, SsaInputDefinitionExt next |
98+
next.hasInputFromBlock(def, bb, i, input) and
9799
def = nodeFrom.getDefinitionExt() and
98100
def.definesAt(_, bb, i, _) and
99-
nodeFrom != next
101+
nodeTo = TSsaInputNode(next, input)
100102
)
101103
}
102104

@@ -105,14 +107,16 @@ module LocalFlow {
105107
* can reach `next`.
106108
*/
107109
pragma[nomagic]
108-
predicate localFlowSsaInputFromRead(
109-
SsaImpl::DefinitionExt def, Node nodeFrom, SsaInputDefinitionExtNode next
110-
) {
111-
exists(BasicBlock bb, int i, CfgNodes::ExprCfgNode exprFrom |
112-
SsaImpl::lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and
110+
predicate localFlowSsaInputFromRead(SsaImpl::DefinitionExt def, Node nodeFrom, SsaInputNode nodeTo) {
111+
exists(
112+
BasicBlock bb, int i, CfgNodes::ExprCfgNode exprFrom, BasicBlock input,
113+
SsaImpl::DefinitionExt next
114+
|
115+
SsaImpl::lastRefBeforeRedefExt(def, bb, i, input, next) and
113116
exprFrom = bb.getNode(i) and
114117
exprFrom.getExpr() instanceof VariableReadAccess and
115-
exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNodeImpl).getPreUpdateNode().asExpr()]
118+
exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNodeImpl).getPreUpdateNode().asExpr()] and
119+
nodeTo = TSsaInputNode(next, input)
116120
)
117121
}
118122

@@ -181,14 +185,17 @@ module LocalFlow {
181185
or
182186
// Flow from SSA definition to first read
183187
def = nodeFrom.(SsaDefinitionExtNode).getDefinitionExt() and
184-
firstReadExt(def, nodeTo.asExpr())
188+
SsaImpl::firstReadExt(def, nodeTo.asExpr())
185189
or
186190
// Flow from post-update read to next read
187191
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNodeImpl).getPreUpdateNode(), nodeTo)
188192
or
189193
// Flow into phi (read) SSA definition node from def
190194
localFlowSsaInputFromDef(nodeFrom, def, nodeTo)
191195
or
196+
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def and
197+
def = nodeFrom.(SsaInputNode).getDefinitionExt()
198+
or
192199
localFlowSsaParamInput(nodeFrom, nodeTo) and
193200
def = nodeTo.(SsaDefinitionExtNode).getDefinitionExt()
194201
}
@@ -530,6 +537,9 @@ private module Cached {
530537
TExprNode(CfgNodes::ExprCfgNode n) or
531538
TReturningNode(CfgNodes::ReturningCfgNode n) { exists(n.getReturnedValueNode()) } or
532539
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
540+
TSsaInputNode(SsaInputDefinitionExt def, BasicBlock input) {
541+
def.hasInputFromBlock(_, _, _, input)
542+
} or
533543
TCapturedVariableNode(VariableCapture::CapturedVariable v) or
534544
TNormalParameterNode(Parameter p) {
535545
p instanceof SimpleParameter or
@@ -802,6 +812,8 @@ import Cached
802812
predicate nodeIsHidden(Node n) {
803813
n.(SsaDefinitionExtNode).isHidden()
804814
or
815+
n instanceof SsaInputNode
816+
or
805817
n = LocalFlow::getParameterDefNode(_)
806818
or
807819
exists(AstNode desug |
@@ -863,6 +875,57 @@ class SsaDefinitionExtNode extends NodeImpl, TSsaDefinitionExtNode {
863875
override string toStringImpl() { result = def.toString() }
864876
}
865877

878+
/**
879+
* A node that represents an input to an SSA phi (read) definition.
880+
*
881+
* This allows for barrier guards to filter input to phi nodes. For example, in
882+
*
883+
* ```rb
884+
* x = taint
885+
* if x != "safe" then
886+
* x = "safe"
887+
* end
888+
* sink x
889+
* ```
890+
*
891+
* the `false` edge out of `x != "safe"` guards the input from `x = taint` into the
892+
* `phi` node after the condition.
893+
*
894+
* It is also relevant to filter input into phi read nodes:
895+
*
896+
* ```rb
897+
* x = taint
898+
* if b then
899+
* if x != "safe1" then
900+
* return
901+
* end
902+
* else
903+
* if x != "safe2" then
904+
* return
905+
* end
906+
* end
907+
*
908+
* sink x
909+
* ```
910+
*
911+
* both inputs into the phi read node after the outer condition are guarded.
912+
*/
913+
class SsaInputNode extends NodeImpl, TSsaInputNode {
914+
SsaImpl::DefinitionExt def;
915+
BasicBlock input;
916+
917+
SsaInputNode() { this = TSsaInputNode(def, input) }
918+
919+
/** Gets the underlying SSA definition. */
920+
SsaImpl::DefinitionExt getDefinitionExt() { result = def }
921+
922+
override CfgScope getCfgScope() { result = input.getScope() }
923+
924+
override Location getLocationImpl() { result = input.getLastNode().getLocation() }
925+
926+
override string toStringImpl() { result = "[input] " + def }
927+
}
928+
866929
/** An SSA definition for a `self` variable. */
867930
class SsaSelfDefinitionNode extends SsaDefinitionExtNode {
868931
private SelfVariable self;

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,24 +856,52 @@ private predicate sameSourceVariable(Ssa::Definition def1, Ssa::Definition def2)
856856
* in data flow and taint tracking.
857857
*/
858858
module BarrierGuard<guardChecksSig/3 guardChecks> {
859+
private import SsaImpl as SsaImpl
860+
859861
pragma[nomagic]
860862
private predicate guardChecksSsaDef(CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def) {
861863
guardChecks(g, def.getARead(), branch)
862864
}
863865

864866
pragma[nomagic]
865-
private predicate guardControlsSsaDef(
867+
private predicate guardControlsSsaRead(
866868
CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def, Node n
867869
) {
868870
def.getARead() = n.asExpr() and
869871
guardControlsBlock(g, n.asExpr().getBasicBlock(), branch)
870872
}
871873

874+
pragma[nomagic]
875+
private predicate guardControlsPhiInput(
876+
CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def, BasicBlock input,
877+
SsaInputDefinitionExt phi
878+
) {
879+
phi.hasInputFromBlock(def, _, _, input) and
880+
(
881+
guardControlsBlock(g, input, branch)
882+
or
883+
exists(SuccessorTypes::ConditionalSuccessor s |
884+
g = input.getLastNode() and
885+
s.getValue() = branch and
886+
input.getASuccessor(s) = phi.getBasicBlock()
887+
)
888+
)
889+
}
890+
872891
/** Gets a node that is safely guarded by the given guard check. */
873892
Node getABarrierNode() {
874893
exists(CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def |
875894
guardChecksSsaDef(g, branch, def) and
876-
guardControlsSsaDef(g, branch, def, result)
895+
guardControlsSsaRead(g, branch, def, result)
896+
)
897+
or
898+
exists(
899+
CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def, BasicBlock input,
900+
SsaInputDefinitionExt phi
901+
|
902+
guardChecksSsaDef(g, branch, def) and
903+
guardControlsPhiInput(g, branch, def, input, phi) and
904+
result = TSsaInputNode(phi, input)
877905
)
878906
or
879907
result.asExpr() = getAMaybeGuardedCapturedDef().getARead()

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,14 +459,16 @@ private module Cached {
459459
* The reference is either a read of `def` or `def` itself.
460460
*/
461461
cached
462-
predicate lastRefBeforeRedefExt(DefinitionExt def, Cfg::BasicBlock bb, int i, DefinitionExt next) {
462+
predicate lastRefBeforeRedefExt(
463+
DefinitionExt def, Cfg::BasicBlock bb, int i, Cfg::BasicBlock input, DefinitionExt next
464+
) {
463465
exists(LocalVariable v |
464-
Impl::lastRefRedefExt(def, v, bb, i, next) and
466+
Impl::lastRefRedefExt(def, v, bb, i, input, next) and
465467
not SsaInput::variableRead(bb, i, v, false)
466468
)
467469
or
468470
exists(SsaInput::BasicBlock bb0, int i0 |
469-
Impl::lastRefRedefExt(def, _, bb0, i0, next) and
471+
Impl::lastRefRedefExt(def, _, bb0, i0, input, next) and
470472
adjacentDefReachesUncertainReadExt(def, bb, i, bb0, i0)
471473
)
472474
}

ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-flow.expected

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@ edges
66
| barrier_flow.rb:8:9:8:17 | call to source | barrier_flow.rb:8:5:8:5 | x | provenance | |
77
| barrier_flow.rb:24:5:24:5 | x | barrier_flow.rb:26:10:26:10 | x | provenance | |
88
| barrier_flow.rb:24:9:24:17 | call to source | barrier_flow.rb:24:5:24:5 | x | provenance | |
9-
| barrier_flow.rb:36:5:36:5 | x | barrier_flow.rb:42:10:42:10 | x | provenance | |
10-
| barrier_flow.rb:36:9:36:17 | call to source | barrier_flow.rb:36:5:36:5 | x | provenance | |
11-
| barrier_flow.rb:46:5:46:5 | x | barrier_flow.rb:50:10:50:10 | x | provenance | |
12-
| barrier_flow.rb:46:9:46:17 | call to source | barrier_flow.rb:46:5:46:5 | x | provenance | |
13-
| barrier_flow.rb:54:5:54:5 | x | barrier_flow.rb:62:10:62:10 | x | provenance | |
14-
| barrier_flow.rb:54:9:54:17 | call to source | barrier_flow.rb:54:5:54:5 | x | provenance | |
15-
| barrier_flow.rb:66:5:66:5 | x | barrier_flow.rb:78:10:78:10 | x | provenance | |
16-
| barrier_flow.rb:66:9:66:17 | call to source | barrier_flow.rb:66:5:66:5 | x | provenance | |
179
nodes
1810
| barrier_flow.rb:2:5:2:5 | x | semmle.label | x |
1911
| barrier_flow.rb:2:9:2:17 | call to source | semmle.label | call to source |
@@ -24,24 +16,8 @@ nodes
2416
| barrier_flow.rb:24:5:24:5 | x | semmle.label | x |
2517
| barrier_flow.rb:24:9:24:17 | call to source | semmle.label | call to source |
2618
| barrier_flow.rb:26:10:26:10 | x | semmle.label | x |
27-
| barrier_flow.rb:36:5:36:5 | x | semmle.label | x |
28-
| barrier_flow.rb:36:9:36:17 | call to source | semmle.label | call to source |
29-
| barrier_flow.rb:42:10:42:10 | x | semmle.label | x |
30-
| barrier_flow.rb:46:5:46:5 | x | semmle.label | x |
31-
| barrier_flow.rb:46:9:46:17 | call to source | semmle.label | call to source |
32-
| barrier_flow.rb:50:10:50:10 | x | semmle.label | x |
33-
| barrier_flow.rb:54:5:54:5 | x | semmle.label | x |
34-
| barrier_flow.rb:54:9:54:17 | call to source | semmle.label | call to source |
35-
| barrier_flow.rb:62:10:62:10 | x | semmle.label | x |
36-
| barrier_flow.rb:66:5:66:5 | x | semmle.label | x |
37-
| barrier_flow.rb:66:9:66:17 | call to source | semmle.label | call to source |
38-
| barrier_flow.rb:78:10:78:10 | x | semmle.label | x |
3919
subpaths
4020
#select
4121
| barrier_flow.rb:4:10:4:10 | x | barrier_flow.rb:2:9:2:17 | call to source | barrier_flow.rb:4:10:4:10 | x | $@ | barrier_flow.rb:2:9:2:17 | call to source | call to source |
4222
| barrier_flow.rb:11:14:11:14 | x | barrier_flow.rb:8:9:8:17 | call to source | barrier_flow.rb:11:14:11:14 | x | $@ | barrier_flow.rb:8:9:8:17 | call to source | call to source |
4323
| barrier_flow.rb:26:10:26:10 | x | barrier_flow.rb:24:9:24:17 | call to source | barrier_flow.rb:26:10:26:10 | x | $@ | barrier_flow.rb:24:9:24:17 | call to source | call to source |
44-
| barrier_flow.rb:42:10:42:10 | x | barrier_flow.rb:36:9:36:17 | call to source | barrier_flow.rb:42:10:42:10 | x | $@ | barrier_flow.rb:36:9:36:17 | call to source | call to source |
45-
| barrier_flow.rb:50:10:50:10 | x | barrier_flow.rb:46:9:46:17 | call to source | barrier_flow.rb:50:10:50:10 | x | $@ | barrier_flow.rb:46:9:46:17 | call to source | call to source |
46-
| barrier_flow.rb:62:10:62:10 | x | barrier_flow.rb:54:9:54:17 | call to source | barrier_flow.rb:62:10:62:10 | x | $@ | barrier_flow.rb:54:9:54:17 | call to source | call to source |
47-
| barrier_flow.rb:78:10:78:10 | x | barrier_flow.rb:66:9:66:17 | call to source | barrier_flow.rb:78:10:78:10 | x | $@ | barrier_flow.rb:66:9:66:17 | call to source | call to source |

ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
testFailures
22
failures
33
newStyleBarrierGuards
4+
| barrier-guards.rb:3:16:4:19 | [input] SSA phi read(foo) |
45
| barrier-guards.rb:4:5:4:7 | foo |
56
| barrier-guards.rb:10:5:10:7 | foo |
67
| barrier-guards.rb:18:5:18:7 | foo |
78
| barrier-guards.rb:24:5:24:7 | foo |
89
| barrier-guards.rb:28:5:28:7 | foo |
910
| barrier-guards.rb:38:5:38:7 | foo |
1011
| barrier-guards.rb:45:9:45:11 | foo |
12+
| barrier-guards.rb:70:22:71:19 | [input] SSA phi read(foo) |
1113
| barrier-guards.rb:71:5:71:7 | foo |
1214
| barrier-guards.rb:83:5:83:7 | foo |
1315
| barrier-guards.rb:91:5:91:7 | foo |
@@ -38,6 +40,12 @@ newStyleBarrierGuards
3840
| barrier-guards.rb:292:5:292:7 | foo |
3941
| barrier_flow.rb:19:14:19:14 | x |
4042
| barrier_flow.rb:32:10:32:10 | x |
43+
| barrier_flow.rb:38:8:38:18 | [input] phi |
44+
| barrier_flow.rb:48:23:48:33 | [input] phi |
45+
| barrier_flow.rb:56:10:57:34 | [input] SSA phi read(x) |
46+
| barrier_flow.rb:58:5:59:34 | [input] SSA phi read(x) |
47+
| barrier_flow.rb:68:10:71:11 | [input] SSA phi read(x) |
48+
| barrier_flow.rb:72:5:75:11 | [input] SSA phi read(x) |
4149
controls
4250
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | true |
4351
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:6:5:6:7 | foo | false |

ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import codeql.ruby.dataflow.internal.DataFlowPrivate
12
import codeql.ruby.dataflow.internal.DataFlowPublic
23
import codeql.ruby.dataflow.BarrierGuards
34
import codeql.ruby.controlflow.CfgNodes
@@ -25,6 +26,7 @@ module BarrierGuardTest implements TestSig {
2526
tag = "guarded" and
2627
exists(DataFlow::Node n |
2728
newStyleBarrierGuards(n) and
29+
not n instanceof SsaInputNode and
2830
location = n.getLocation() and
2931
element = n.toString() and
3032
value = ""

ruby/ql/test/library-tests/dataflow/barrier-guards/barrier_flow.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ def m6
3939
x = "safe"
4040
end
4141

42-
sink x # $ SPURIOUS hasValueFlow=6
42+
sink x
4343
end
4444

4545
def m7
4646
x = source(7)
4747

4848
x = "safe" unless x == "safe"
4949

50-
sink x # $ SPURIOUS hasValueFlow=7
50+
sink x
5151
end
5252

5353
def m8(b)
@@ -59,7 +59,7 @@ def m8(b)
5959
return unless x == "safe2"
6060
end
6161

62-
sink x # $ SPURIOUS hasValueFlow=8
62+
sink x
6363
end
6464

6565
def m9(b)
@@ -75,5 +75,5 @@ def m9(b)
7575
end
7676
end
7777

78-
sink x # $ SPURIOUS hasValueFlow=9
78+
sink x
7979
end

0 commit comments

Comments
 (0)