Files
codeql/csharp/ql/lib/semmle/code/cil/DataFlow.qll
Andrew Eisenberg c9f1c98390 Packaging: C# refactoring
Split c# pack into `codeql/csharp-all` and `codeql/csharp-queries`.
2021-08-19 14:09:35 -07:00

275 lines
8.2 KiB
Plaintext

/**
* Provides a collection of building blocks and utilities for data flow.
*/
private import CIL
/**
* A node in the data flow graph.
*
* Either an instruction (`Instruction`), a method return (`Method`), or a variable (`Variable`).
*/
class DataFlowNode extends @cil_dataflow_node {
/** Gets a textual representation of this data flow node. */
abstract string toString();
/** Gets the type of this data flow node. */
Type getType() { none() }
/**
* Holds if this node flows to `sink` in one step.
* `tt` is the tainting that occurs during this step.
*/
predicate getALocalFlowSucc(DataFlowNode sink, TaintType tt) {
localExactStep(this, sink) and tt = TExactValue()
or
localTaintStep(this, sink) and tt = TTaintedValue()
}
private predicate flowsToStep(DataFlowNode sink) { this.getALocalFlowSucc(sink, TExactValue()) }
/** Holds if this node flows to `sink` in zero or more steps. */
predicate flowsTo(DataFlowNode sink) { this.flowsToStep*(sink) }
/** Gets the method that contains this dataflow node. */
Method getMethod() { none() }
/** Gets the location of this dataflow node. */
Location getLocation() { none() }
}
private newtype TTaintType =
TExactValue() or
TTaintedValue()
/** Describes how data is tainted. */
class TaintType extends TTaintType {
string toString() {
this = TExactValue() and result = "exact"
or
this = TTaintedValue() and result = "tainted"
}
}
/** A taint type where the data is untainted. */
class Untainted extends TaintType, TExactValue { }
/** A taint type where the data is tainted. */
class Tainted extends TaintType, TTaintedValue { }
private predicate localFlowPhiInput(DataFlowNode input, Ssa::PhiNode phi) {
exists(Ssa::Definition def, BasicBlock bb, int i | phi.hasLastInputRef(def, bb, i) |
def.definesAt(_, bb, i) and
input = def.getVariableUpdate().getSource()
or
input =
any(ReadAccess ra |
bb.getNode(i) = ra and
ra.getTarget() = def.getSourceVariable()
)
)
or
exists(Ssa::PhiNode mid, BasicBlock bb, int i |
localFlowPhiInput(input, mid) and
phi.hasLastInputRef(mid, bb, i) and
mid.definesAt(_, bb, i)
)
}
private predicate localExactStep(DataFlowNode src, DataFlowNode sink) {
src = sink.(Opcodes::Dup).getAnOperand()
or
exists(Ssa::Definition def, VariableUpdate vu |
vu = def.getVariableUpdate() and
src = vu.getSource() and
sink = def.getAFirstRead()
)
or
any(Ssa::Definition def).hasAdjacentReads(src, sink)
or
exists(Ssa::PhiNode phi |
localFlowPhiInput(src, phi) and
sink = phi.getAFirstRead()
)
or
src = sink.(Conversion).getExpr()
or
src = sink.(WriteAccess).getExpr()
or
src = sink.(Method).getAnImplementation().getAnInstruction().(Return)
or
src = sink.(Return).getExpr()
or
src = sink.(ConditionalBranch).getAnOperand()
}
private predicate localTaintStep(DataFlowNode src, DataFlowNode sink) {
src = sink.(BinaryArithmeticExpr).getAnOperand() or
src = sink.(Opcodes::Neg).getOperand() or
src = sink.(UnaryBitwiseOperation).getOperand()
}
deprecated module DefUse {
/**
* A classification of variable references into reads and writes.
*/
private newtype RefKind =
Read() or
Write()
/**
* Holds if the `i`th node of basic block `bb` is a reference to `v`,
* either a read (when `k` is `Read()`) or a write (when `k` is `Write()`).
*/
private predicate ref(BasicBlock bb, int i, StackVariable v, RefKind k) {
exists(ReadAccess ra | bb.getNode(i) = ra |
ra.getTarget() = v and
k = Read()
)
or
exists(VariableUpdate vu | bb.getNode(i) = vu |
vu.getVariable() = v and
k = Write()
)
}
/**
* Gets the (1-based) rank of the reference to `v` at the `i`th node of
* basic block `bb`, which has the given reference kind `k`.
*/
private int refRank(BasicBlock bb, int i, StackVariable v, RefKind k) {
i = rank[result](int j | ref(bb, j, v, _)) and
ref(bb, i, v, k)
}
/**
* Holds if stack variable `v` is live at the beginning of basic block `bb`.
*/
private predicate liveAtEntry(BasicBlock bb, StackVariable v) {
// The first reference to `v` inside `bb` is a read
refRank(bb, _, v, Read()) = 1
or
// There is no reference to `v` inside `bb`, but `v` is live at entry
// to a successor basic block of `bb`
not exists(refRank(bb, _, v, _)) and
liveAtExit(bb, v)
}
/**
* Holds if stack variable `v` is live at the end of basic block `bb`.
*/
private predicate liveAtExit(BasicBlock bb, StackVariable v) {
liveAtEntry(bb.getASuccessor(), v)
}
/**
* Holds if the variable update `vu` reaches rank index `rankix`
* in its own basic block `bb`.
*/
private predicate defReachesRank(BasicBlock bb, VariableUpdate vu, int rankix, StackVariable v) {
exists(int i |
rankix = refRank(bb, i, v, Write()) and
vu = bb.getNode(i)
)
or
defReachesRank(bb, vu, rankix - 1, v) and
rankix = refRank(bb, _, v, Read())
}
/**
* Holds if the variable update `vu` of stack variable `v` reaches the
* end of a basic block `bb`, at which point it is still live, without
* crossing another update.
*/
private predicate defReachesEndOfBlock(BasicBlock bb, VariableUpdate vu, StackVariable v) {
liveAtExit(bb, v) and
(
exists(int last | last = max(refRank(bb, _, v, _)) | defReachesRank(bb, vu, last, v))
or
defReachesStartOfBlock(bb, vu, v) and
not exists(refRank(bb, _, v, Write()))
)
}
pragma[noinline]
private predicate defReachesStartOfBlock(BasicBlock bb, VariableUpdate vu, StackVariable v) {
defReachesEndOfBlock(bb.getAPredecessor(), vu, v)
}
/**
* Holds if the variable update `vu` of stack variable `v` reaches `read` in the
* same basic block without crossing another update of `v`.
*/
private predicate defReachesReadWithinBlock(StackVariable v, VariableUpdate vu, ReadAccess read) {
exists(BasicBlock bb, int rankix, int i |
defReachesRank(bb, vu, rankix, v) and
rankix = refRank(bb, i, v, Read()) and
read = bb.getNode(i)
)
}
/** Holds if the variable update `vu` can be used at the read `use`. */
cached
deprecated predicate variableUpdateUse(StackVariable target, VariableUpdate vu, ReadAccess use) {
defReachesReadWithinBlock(target, vu, use)
or
exists(BasicBlock bb, int i |
exists(refRank(bb, i, target, Read())) and
use = bb.getNode(i) and
defReachesEndOfBlock(bb.getAPredecessor(), vu, target) and
not defReachesReadWithinBlock(target, _, use)
)
}
/** Holds if the update `def` can be used at the read `use`. */
cached
deprecated predicate defUse(StackVariable target, Expr def, ReadAccess use) {
exists(VariableUpdate vu | def = vu.getSource() | variableUpdateUse(target, vu, use))
}
}
/** A node that updates a variable. */
abstract class VariableUpdate extends DataFlowNode {
/** Gets the value assigned, if any. */
abstract DataFlowNode getSource();
/** Gets the variable that is updated. */
abstract Variable getVariable();
/** Holds if this variable update happens at index `i` in basic block `bb`. */
abstract predicate updatesAt(BasicBlock bb, int i);
}
private class MethodParameterDef extends VariableUpdate, MethodParameter {
override MethodParameter getSource() { result = this }
override MethodParameter getVariable() { result = this }
override predicate updatesAt(BasicBlock bb, int i) {
bb.(EntryBasicBlock).getANode().getImplementation().getMethod() = this.getMethod() and
i = -1
}
}
private class VariableWrite extends VariableUpdate, WriteAccess {
override Expr getSource() { result = this.getExpr() }
override Variable getVariable() { result = this.getTarget() }
override predicate updatesAt(BasicBlock bb, int i) { this = bb.getNode(i) }
}
private class MethodOutOrRefTarget extends VariableUpdate, Call {
int parameterIndex;
MethodOutOrRefTarget() { this.getTarget().getRawParameter(parameterIndex).hasOutFlag() }
override Variable getVariable() {
result = this.getRawArgument(parameterIndex).(ReadAccess).getTarget()
}
override Expr getSource() { none() }
override predicate updatesAt(BasicBlock bb, int i) { this = bb.getNode(i) }
}