Rust: Add basic skeleton setup for data flow

This commit is contained in:
Simon Friis Vindum
2024-10-30 13:16:26 +01:00
parent 4c73c6284e
commit 44bebedae6
10 changed files with 446 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
/**
* Provides a module for performing local (intra-procedural) and global
* (inter-procedural) data flow analyses.
*/
private import rust
private import codeql.dataflow.DataFlow
private import internal.DataFlowImpl as DataFlowImpl
private import DataFlowImpl::Node as Node
/**
* Provides classes for performing local (intra-procedural) and global
* (inter-procedural) data flow analyses.
*/
module DataFlow {
final class Node = Node::Node;
final class ParameterNode = Node::ParameterNode;
final class PostUpdateNode = Node::PostUpdateNode;
predicate localFlowStep = DataFlowImpl::localFlowStep/2;
import DataFlowMake<Location, DataFlowImpl::RustDataFlow>
}

View File

@@ -0,0 +1,17 @@
/**
* Provides the module `TaintTracking` for performing local (intra-procedural)
* and global (inter-procedural) taint-tracking analyses.
*/
private import rust
/**
* Provides a library for performing local (intra-procedural) and global
* (inter-procedural) taint-tracking analyses.
*/
module TaintTracking {
private import codeql.dataflow.TaintTracking
private import internal.DataFlowImpl
private import internal.TaintTrackingImpl
import TaintFlowMake<Location, RustDataFlow, RustTaintTracking>
}

View File

@@ -0,0 +1,324 @@
/**
* Provides Rust-specific definitions for use in the data flow library.
*/
private import codeql.util.Void
private import codeql.dataflow.DataFlow
private import codeql.dataflow.internal.DataFlowImpl
private import rust
private import codeql.rust.controlflow.ControlFlowGraph
private import codeql.rust.dataflow.Ssa
module Node {
/**
* An element, viewed as a node in a data flow graph. Either an expression
* (`ExprNode`) or a parameter (`ParameterNode`).
*/
abstract class Node extends TNode {
/** Gets the location of this node. */
abstract Location getLocation();
/** Gets a textual representation of this node. */
abstract string toString();
/**
* Gets the expression that corresponds to this node, if any.
*/
Expr asExpr() { none() }
/**
* Gets this node's underlying pattern, if any.
*/
Pat asPattern() { none() }
/**
* Gets the control flow node that corresponds to this data flow node.
*/
CfgNode getCfgNode() { none() }
/**
* Gets this node's underlying SSA definition, if any.
*/
Ssa::Definition asDefinition() { none() }
/**
* Gets the parameter that corresponds to this node, if any.
*/
Param asParameter() { none() }
}
/** A node type that is not implemented. */
final class NaNode extends Node {
NaNode() { none() }
override string toString() { result = "N/A" }
override Location getLocation() { none() }
}
/**
* The value of a parameter at function entry, viewed as a node in a data
* flow graph.
*/
final class ParameterNode extends Node {
Param param;
ParameterNode() { this = TSourceParameterNode(param) }
override Location getLocation() { result = param.getLocation() }
override string toString() { result = param.toString() }
}
final class ArgumentNode = NaNode;
final class ReturnNode extends NaNode {
ReturnKind getKind() { none() }
}
final class OutNode = NaNode;
/**
* A node associated with an object after an operation that might have
* changed its state.
*
* This can be either the argument to a callable after the callable returns
* (which might have mutated the argument), or the qualifier of a field after
* an update to the field.
*
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
* to the value before the update.
*/
final class PostUpdateNode extends Node::NaNode {
/** Gets the node before the state update. */
Node getPreUpdateNode() { none() }
}
final class CastNode = NaNode;
}
module RustDataFlow implements InputSig<Location> {
/**
* An element, viewed as a node in a data flow graph. Either an expression
* (`ExprNode`) or a parameter (`ParameterNode`).
*/
final class Node = Node::Node;
final class ParameterNode = Node::ParameterNode;
final class ArgumentNode = Node::ArgumentNode;
final class ReturnNode = Node::ReturnNode;
final class OutNode = Node::OutNode;
final class PostUpdateNode = Node::PostUpdateNode;
final class CastNode = Node::NaNode;
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) { none() }
predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) { none() }
DataFlowCallable nodeGetEnclosingCallable(Node node) { none() }
DataFlowType getNodeType(Node node) { none() }
predicate nodeIsHidden(Node node) { none() }
class DataFlowExpr = Void;
/** Gets the node corresponding to `e`. */
Node exprNode(DataFlowExpr e) { none() }
final class DataFlowCall extends TNormalCall {
private CallExpr c;
DataFlowCall() { this = TNormalCall(c) }
DataFlowCallable getEnclosingCallable() { none() }
string toString() { result = c.toString() }
Location getLocation() { result = c.getLocation() }
}
final class DataFlowCallable = CfgScope;
final class ReturnKind = Void;
/** Gets a viable implementation of the target of the given `Call`. */
DataFlowCallable viableCallable(DataFlowCall c) { none() }
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { none() }
final class DataFlowType = Void;
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() }
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
final class Content = Void;
predicate forceHighPrecision(Content c) { none() }
class ContentSet extends TContentSet {
/** Gets a textual representation of this element. */
string toString() { result = "ContentSet" }
/** Gets a content that may be stored into when storing into this set. */
Content getAStoreContent() { none() }
/** Gets a content that may be read from when reading from this set. */
Content getAReadContent() { none() }
}
final class ContentApprox = Void;
ContentApprox getContentApprox(Content c) { any() }
class ParameterPosition extends string {
ParameterPosition() { this = "pos" }
}
class ArgumentPosition extends string {
ArgumentPosition() { this = "pos" }
}
/**
* Holds if the parameter position `ppos` matches the argument position
* `apos`.
*/
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { none() }
/**
* Holds if there is a simple local flow step from `node1` to `node2`. These
* are the value-preserving intra-callable flow steps.
*/
predicate simpleLocalFlowStep(Node node1, Node node2, string model) { none() }
/**
* Holds if data can flow from `node1` to `node2` through a non-local step
* that does not follow a call edge. For example, a step through a global
* variable.
*/
predicate jumpStep(Node node1, Node node2) { none() }
/**
* Holds if data can flow from `node1` to `node2` via a read of `c`. Thus,
* `node1` references an object with a content `c.getAReadContent()` whose
* value ends up in `node2`.
*/
predicate readStep(Node node1, ContentSet c, Node node2) { none() }
/**
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
* `node2` references an object with a content `c.getAStoreContent()` that
* contains the value of `node1`.
*/
predicate storeStep(Node node1, ContentSet c, Node node2) { none() }
/**
* Holds if values stored inside content `c` are cleared at node `n`. For example,
* any value stored inside `f` is cleared at the pre-update node associated with `x`
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, ContentSet c) { none() }
/**
* Holds if the value that is being tracked is expected to be stored inside content `c`
* at node `n`.
*/
predicate expectsContent(Node n, ContentSet c) { none() }
class NodeRegion instanceof Void {
string toString() { result = "NodeRegion" }
predicate contains(Node n) { none() }
}
/**
* Holds if the nodes in `nr` are unreachable when the call context is `call`.
*/
predicate isUnreachableInCall(NodeRegion nr, DataFlowCall call) { none() }
/**
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
* side-effect, resulting in a summary from `p` to itself.
*
* One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
* by default as a heuristic.
*/
predicate allowParameterReturnInSelf(ParameterNode p) { none() }
/**
* Holds if the value of `node2` is given by `node1`.
*
* This predicate is combined with type information in the following way: If
* the data flow library is able to compute an improved type for `node1` then
* it will also conclude that this type applies to `node2`. Vice versa, if
* `node2` must be visited along a flow path, then any type known for `node2`
* must also apply to `node1`.
*/
predicate localMustFlowStep(Node node1, Node node2) { none() }
class LambdaCallKind = Void;
// class LambdaCallKind;
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
predicate knownSourceModel(Node source, string model) { none() }
predicate knownSinkModel(Node sink, string model) { none() }
class DataFlowSecondLevelScope = Void;
}
import RustDataFlow
import MakeImpl<Location, RustDataFlow>
/**
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
* (intra-procedural) step.
*/
predicate localFlowStep = localFlowStepImpl/2;
/** A collection of cached types and predicates to be evaluated in the same stage. */
cached
private module Cached {
cached
newtype TNode =
TExprNode(CfgNode n, Expr e) or
TSourceParameterNode(Param param)
cached
newtype TDataFlowCall = TNormalCall(CallExpr c)
cached
newtype TOptionalContentSet =
TAnyElementContent() or
TAnyContent()
cached
class TContentSet = TAnyElementContent or TAnyContent;
/** This is the local flow predicate that is exposed. */
cached
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) { none() }
}
import Cached
/**
* Holds if data flows from `source` to `sink` in zero or more local
* (intra-procedural) steps.
*/
pragma[inline]
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }

View File

@@ -0,0 +1,20 @@
private import rust
private import codeql.dataflow.TaintTracking
private import DataFlowImpl
module RustTaintTracking implements InputSig<Location, RustDataFlow> {
predicate defaultTaintSanitizer(Node node) { none() }
/**
* Holds if the additional step from `src` to `sink` should be included in all
* global taint flow configurations.
*/
predicate defaultAdditionalTaintStep(Node src, Node sink, string model) { none() }
/**
* Holds if taint flow configurations should allow implicit reads of `c` at sinks
* and inputs to additional taint steps.
*/
bindingset[node]
predicate defaultImplicitTaintRead(Node node, ContentSet c) { none() }
}

View File

@@ -0,0 +1,17 @@
import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.TaintTracking
module FlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { none() }
predicate isSink(DataFlow::Node sink) { none() }
predicate isBarrier(DataFlow::Node n) { none() }
}
module Tainted = TaintTracking::Global<FlowConfig>;
from Tainted::PathNode source, Tainted::PathNode sink
where Tainted::flowPath(source, sink)
select sink, source, sink, "$@", source, source.toString()

View File

@@ -0,0 +1,30 @@
fn source() -> &'static str {
"source"
}
fn sink(s: &str) {
println!("{}", s);
}
fn sanitize(s: &str) -> &str {
match s {
"dangerous" => "",
s => s
}
}
fn no_barrier() {
let s = source();
sink(s);
}
fn with_barrier() {
let s = source();
let s = sanitize(s);
sink(s);
}
fn main() {
let s = source();
sink(s);
}

View File

@@ -0,0 +1,5 @@
import codeql.rust.dataflow.DataFlow
from DataFlow::Node pred, DataFlow::Node succ
where DataFlow::localFlowStep(pred, succ)
select pred, succ

View File

@@ -0,0 +1,8 @@
fn variable() {
let s = "Hello";
println!("{:?} data flow!", s);
}
fn main() {
variable();
}