Skip to content

Commit d828bc5

Browse files
authored
Merge pull request #4251 from yoff/SharedDataflow_BarrierGuards
Python: Implement `BarrierGuard`
2 parents c8a3baf + e46ae9b commit d828bc5

File tree

7 files changed

+90
-20
lines changed

7 files changed

+90
-20
lines changed

python/ql/src/experimental/dataflow/internal/DataFlowPrivate.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ module EssaFlow {
130130
}
131131

132132
predicate useToNextUse(NameNode nodeFrom, NameNode nodeTo) {
133-
AdjacentUses::adjacentUseUseSameVar(nodeFrom, nodeTo)
133+
AdjacentUses::adjacentUseUse(nodeFrom, nodeTo)
134134
}
135135

136136
predicate defToFirstUse(EssaVariable var, NameNode nodeTo) {

python/ql/src/experimental/dataflow/internal/DataFlowPublic.qll

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
private import python
66
private import DataFlowPrivate
77
import experimental.dataflow.TypeTracker
8+
private import semmle.python.essa.SsaCompute
89

910
/**
1011
* IPA type for data flow nodes.
@@ -148,6 +149,18 @@ class ParameterNode extends EssaNode {
148149
override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) }
149150
}
150151

152+
/**
153+
* A node that controls whether other nodes are evaluated.
154+
*/
155+
class GuardNode extends ControlFlowNode {
156+
ConditionBlock conditionBlock;
157+
158+
GuardNode() { this = conditionBlock.getLastNode() }
159+
160+
/** Holds if this guard controls block `b` upon evaluating to `branch`. */
161+
predicate controlsBlock(BasicBlock b, boolean branch) { conditionBlock.controls(b, branch) }
162+
}
163+
151164
/**
152165
* A guard that validates some expression.
153166
*
@@ -157,16 +170,18 @@ class ParameterNode extends EssaNode {
157170
*
158171
* It is important that all extending classes in scope are disjoint.
159172
*/
160-
class BarrierGuard extends Expr {
161-
// /** Holds if this guard validates `e` upon evaluating to `v`. */
162-
// abstract predicate checks(Expr e, AbstractValue v);
173+
class BarrierGuard extends GuardNode {
174+
/** Holds if this guard validates `node` upon evaluating to `branch`. */
175+
abstract predicate checks(ControlFlowNode node, boolean branch);
176+
163177
/** Gets a node guarded by this guard. */
164178
final ExprNode getAGuardedNode() {
165-
none()
166-
// exists(Expr e, AbstractValue v |
167-
// this.checks(e, v) and
168-
// this.controlsNode(result.getControlFlowNode(), e, v)
169-
// )
179+
exists(EssaDefinition def, ControlFlowNode node, boolean branch |
180+
AdjacentUses::useOfDef(def, node) and
181+
this.checks(node, branch) and
182+
AdjacentUses::useOfDef(def, result.asCfgNode()) and
183+
this.controlsBlock(result.asCfgNode().getBasicBlock(), branch)
184+
)
170185
}
171186
}
172187

python/ql/src/semmle/python/essa/SsaCompute.qll

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,9 +443,28 @@ private module SsaComputeImpl {
443443
)
444444
}
445445

446+
/**
447+
* Holds if `use1` and `use2` form an adjacent use-use-pair of the same
448+
* `SsaSourceVariable`, that is, the value read in `use1` can reach `use2`
449+
* without passing through any other use or any SSA definition of the variable
450+
* except for phi nodes.
451+
*/
452+
cached
453+
predicate adjacentUseUse(ControlFlowNode use1, ControlFlowNode use2) {
454+
adjacentUseUseSameVar(use1, use2)
455+
or
456+
exists(SsaSourceVariable v, EssaDefinition def, BasicBlock b1, int i1, BasicBlock b2, int i2 |
457+
adjacentVarRefs(v, b1, i1, b2, i2) and
458+
variableUse(v, use1, b1, i1) and
459+
definesAt(def, v, b2, i2) and
460+
firstUse(def, use2) and
461+
def instanceof PhiFunction
462+
)
463+
}
464+
446465
/**
447466
* Holds if the value defined at `def` can reach `use` without passing through
448-
* any other uses, but possibly through phi nodes and uncertain implicit updates.
467+
* any other uses, but possibly through phi nodes.
449468
*/
450469
cached
451470
predicate firstUse(EssaDefinition def, ControlFlowNode use) {
@@ -482,6 +501,17 @@ private module SsaComputeImpl {
482501
b = def.(PhiFunction).getBasicBlock() and
483502
i = -1
484503
}
504+
505+
/**
506+
* Holds if the value defined at `def` can reach `use`, possibly through phi nodes.
507+
*/
508+
cached
509+
predicate useOfDef(EssaDefinition def, ControlFlowNode use) {
510+
exists(ControlFlowNode firstUse |
511+
firstUse(def, firstUse) and
512+
adjacentUseUse*(firstUse, use)
513+
)
514+
}
485515
}
486516
}
487517

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
test_taint
22
| test.py:22 | ok | test_custom_sanitizer | s |
3-
| test.py:36 | fail | test_custom_sanitizer_guard | s |
3+
| test.py:36 | ok | test_custom_sanitizer_guard | s |
44
| test.py:38 | ok | test_custom_sanitizer_guard | s |
5-
| test.py:49 | ok | test_escape | s2 |
5+
| test.py:40 | ok | test_custom_sanitizer_guard | s |
6+
| test.py:51 | ok | test_escape | s2 |
67
isSanitizer
78
| TestTaintTrackingConfiguration | test.py:21:39:21:39 | ControlFlowNode for s |
8-
| TestTaintTrackingConfiguration | test.py:48:10:48:29 | ControlFlowNode for emulated_escaping() |
9+
| TestTaintTrackingConfiguration | test.py:50:10:50:29 | ControlFlowNode for emulated_escaping() |
910
isSanitizerGuard
11+
| TestTaintTrackingConfiguration | test.py:35:8:35:26 | ControlFlowNode for emulated_is_safe() |

python/ql/test/experimental/dataflow/tainttracking/customSanitizer/TestTaint.ql

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import experimental.dataflow.tainttracking.TestTaintLib
22

3+
class IsSafeCheck extends DataFlow::BarrierGuard {
4+
IsSafeCheck() { this.(CallNode).getNode().getFunc().(Name).getId() = "emulated_is_safe" }
5+
6+
override predicate checks(ControlFlowNode node, boolean branch) {
7+
node = this.(CallNode).getAnArg() and
8+
branch = true
9+
}
10+
}
11+
312
class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {
413
override predicate isSanitizer(DataFlow::Node node) {
514
exists(Call call |
@@ -10,13 +19,7 @@ class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {
1019
node.asExpr().(Call).getFunc().(Name).getId() = "emulated_escaping"
1120
}
1221

13-
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
14-
// TODO: Future work for when BarrierGuard is implemented properly
15-
// exists(Call call |
16-
// call.getFunc().(Name).getId() = "emulated_is_safe" and
17-
// )
18-
none()
19-
}
22+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { guard instanceof IsSafeCheck }
2023
}
2124

2225
query predicate isSanitizer(TestTaintTrackingConfiguration conf, DataFlow::Node node) {

python/ql/test/experimental/dataflow/tainttracking/customSanitizer/test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def test_custom_sanitizer_guard():
3434

3535
if emulated_is_safe(s):
3636
ensure_not_tainted(s)
37+
s = TAINTED_STRING
38+
ensure_tainted(s)
3739
else:
3840
ensure_tainted(s)
3941

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import experimental.dataflow.tainttracking.TestTaintLib
2+
3+
query predicate sanitizerGuardControls(
4+
TestTaintTrackingConfiguration conf, DataFlow::BarrierGuard guard, ControlFlowNode node,
5+
boolean branch
6+
) {
7+
exists(guard.getLocation().getFile().getRelativePath()) and
8+
conf.isSanitizerGuard(guard) and
9+
guard.controlsBlock(node.getBasicBlock(), branch)
10+
}
11+
12+
query predicate sanitizerGuardedNode(
13+
TestTaintTrackingConfiguration conf, DataFlow::BarrierGuard guard, DataFlow::ExprNode node
14+
) {
15+
exists(guard.getLocation().getFile().getRelativePath()) and
16+
conf.isSanitizerGuard(guard) and
17+
node = guard.getAGuardedNode()
18+
}

0 commit comments

Comments
 (0)