mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
Rust: Adopt shared flow summaries library
This commit is contained in:
@@ -15,7 +15,14 @@ private import DataFlowImpl::Node as Node
|
||||
module DataFlow {
|
||||
final class Node = Node::Node;
|
||||
|
||||
final class ParameterNode = Node::ParameterNode;
|
||||
/**
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
final class ParameterNode extends Node instanceof Node::SourceParameterNode {
|
||||
/** Gets the parameter that this node corresponds to. */
|
||||
ParamBase getParameter() { result = super.getParameter().getParamBase() }
|
||||
}
|
||||
|
||||
final class PostUpdateNode = Node::PostUpdateNode;
|
||||
|
||||
|
||||
58
rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll
Normal file
58
rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll
Normal file
@@ -0,0 +1,58 @@
|
||||
/** Provides classes and predicates for defining flow summaries. */
|
||||
|
||||
private import rust
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowImpl
|
||||
|
||||
// import all instances below
|
||||
private module Summaries {
|
||||
private import codeql.rust.Frameworks
|
||||
}
|
||||
|
||||
/** Provides the `Range` class used to define the extent of `LibraryCallable`. */
|
||||
module LibraryCallable {
|
||||
/** A callable defined in library code, identified by a unique string. */
|
||||
abstract class Range extends string {
|
||||
bindingset[this]
|
||||
Range() { any() }
|
||||
|
||||
/** Gets a call to this library callable. */
|
||||
CallExprBase getACall() {
|
||||
exists(Resolvable r, string crate |
|
||||
r = getCallResolvable(result) and
|
||||
this = crate + r.getResolvedPath()
|
||||
|
|
||||
crate = r.getResolvedCrateOrigin() + "::_::"
|
||||
or
|
||||
not r.hasResolvedCrateOrigin() and
|
||||
crate = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class LibraryCallable = LibraryCallable::Range;
|
||||
|
||||
/** Provides the `Range` class used to define the extent of `SummarizedCallable`. */
|
||||
module SummarizedCallable {
|
||||
/** A callable with a flow summary, identified by a unique string. */
|
||||
abstract class Range extends LibraryCallable::Range, Impl::Public::SummarizedCallable {
|
||||
bindingset[this]
|
||||
Range() { any() }
|
||||
|
||||
override predicate propagatesFlow(
|
||||
string input, string output, boolean preservesValue, string model
|
||||
) {
|
||||
this.propagatesFlow(input, output, preservesValue) and model = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `input` to `output` through this callable.
|
||||
*
|
||||
* `preservesValue` indicates whether this is a value-preserving step or a taint-step.
|
||||
*/
|
||||
abstract predicate propagatesFlow(string input, string output, boolean preservesValue);
|
||||
}
|
||||
}
|
||||
|
||||
final class SummarizedCallable = SummarizedCallable::Range;
|
||||
@@ -11,8 +11,8 @@ private import SsaImpl as SsaImpl
|
||||
private import codeql.rust.controlflow.ControlFlowGraph
|
||||
private import codeql.rust.controlflow.CfgNodes
|
||||
private import codeql.rust.dataflow.Ssa
|
||||
|
||||
private newtype TReturnKind = TNormalReturnKind()
|
||||
private import codeql.rust.dataflow.FlowSummary
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
/**
|
||||
* A return kind. A return kind describes how a value can be returned from a
|
||||
@@ -35,8 +35,13 @@ final class DataFlowCallable extends TDataFlowCallable {
|
||||
*/
|
||||
CfgScope asCfgScope() { this = TCfgScope(result) }
|
||||
|
||||
/**
|
||||
* Gets the underlying library callable, if any.
|
||||
*/
|
||||
LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) }
|
||||
|
||||
/** Gets a textual representation of this callable. */
|
||||
string toString() { result = this.asCfgScope().toString() }
|
||||
string toString() { result = [this.asCfgScope().toString(), this.asLibraryCallable().toString()] }
|
||||
|
||||
/** Gets the location of this callable. */
|
||||
Location getLocation() { result = this.asCfgScope().getLocation() }
|
||||
@@ -54,11 +59,31 @@ final class DataFlowCall extends TDataFlowCall {
|
||||
|
||||
CallExprBaseCfgNode asCallBaseExprCfgNode() { result = call }
|
||||
|
||||
DataFlowCallable getEnclosingCallable() {
|
||||
result = TCfgScope(call.getExpr().getEnclosingCfgScope())
|
||||
predicate isSummaryCall(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
|
||||
) {
|
||||
this = TSummaryCall(c, receiver)
|
||||
}
|
||||
|
||||
string toString() { result = this.asCallBaseExprCfgNode().toString() }
|
||||
DataFlowCallable getEnclosingCallable() {
|
||||
result = TCfgScope(call.getExpr().getEnclosingCfgScope())
|
||||
or
|
||||
exists(FlowSummaryImpl::Public::SummarizedCallable c |
|
||||
this.isSummaryCall(c, _) and
|
||||
result = TLibraryCallable(c)
|
||||
)
|
||||
}
|
||||
|
||||
string toString() {
|
||||
result = this.asCallBaseExprCfgNode().toString()
|
||||
or
|
||||
exists(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
|
||||
|
|
||||
this.isSummaryCall(c, receiver) and
|
||||
result = "[summary] call to " + receiver + " in " + c
|
||||
)
|
||||
}
|
||||
|
||||
Location getLocation() { result = this.asCallBaseExprCfgNode().getLocation() }
|
||||
}
|
||||
@@ -148,6 +173,26 @@ module Node {
|
||||
override Location getLocation() { none() }
|
||||
}
|
||||
|
||||
/** A data-flow node used to model flow summaries. */
|
||||
class FlowSummaryNode extends Node, TFlowSummaryNode {
|
||||
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }
|
||||
|
||||
/** Gets the summarized callable that this node belongs to. */
|
||||
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
|
||||
result = this.getSummaryNode().getSummarizedCallable()
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { none() }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() {
|
||||
result.asLibraryCallable() = this.getSummarizedCallable()
|
||||
}
|
||||
|
||||
override EmptyLocation getLocation() { any() }
|
||||
|
||||
override string toString() { result = this.getSummaryNode().toString() }
|
||||
}
|
||||
|
||||
/** A data flow node that corresponds directly to a CFG node for an AST node. */
|
||||
abstract class AstCfgFlowNode extends Node {
|
||||
AstCfgNode n;
|
||||
@@ -189,17 +234,62 @@ module Node {
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
final class ParameterNode extends AstCfgFlowNode, TParameterNode {
|
||||
abstract class ParameterNode extends Node {
|
||||
abstract predicate isParameterOf(DataFlowCallable c, ParameterPosition pos);
|
||||
}
|
||||
|
||||
final class SourceParameterNode extends AstCfgFlowNode, ParameterNode, TSourceParameterNode {
|
||||
override ParamBaseCfgNode n;
|
||||
|
||||
ParameterNode() { this = TParameterNode(n) }
|
||||
SourceParameterNode() { this = TSourceParameterNode(n) }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
n.getAstNode() = pos.getParameterIn(c.asCfgScope().(Callable).getParamList())
|
||||
}
|
||||
|
||||
/** Gets the parameter in the CFG that this node corresponds to. */
|
||||
ParamBaseCfgNode getParameter() { result = n }
|
||||
}
|
||||
|
||||
final class ArgumentNode extends ExprNode {
|
||||
ArgumentNode() { isArgumentForCall(n, _, _) }
|
||||
/** A parameter for a library callable with a flow summary. */
|
||||
final class SummaryParameterNode extends ParameterNode, FlowSummaryNode {
|
||||
private ParameterPosition pos_;
|
||||
|
||||
SummaryParameterNode() {
|
||||
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), pos_)
|
||||
}
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
this.getSummarizedCallable() = c.asLibraryCallable() and pos = pos_
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ArgumentNode extends Node {
|
||||
abstract predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos);
|
||||
}
|
||||
|
||||
final class ExprArgumentNode extends ArgumentNode, ExprNode {
|
||||
private CallExprBaseCfgNode call_;
|
||||
private RustDataFlow::ArgumentPosition pos_;
|
||||
|
||||
ExprArgumentNode() { isArgumentForCall(n, call_, pos_) }
|
||||
|
||||
override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
|
||||
call.asCallBaseExprCfgNode() = call_ and pos = pos_
|
||||
}
|
||||
}
|
||||
|
||||
final class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
|
||||
private FlowSummaryImpl::Private::SummaryNode receiver;
|
||||
private RustDataFlow::ArgumentPosition pos_;
|
||||
|
||||
SummaryArgumentNode() {
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(receiver, this.getSummaryNode(), pos_)
|
||||
}
|
||||
|
||||
override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
|
||||
call.isSummaryCall(_, receiver) and pos = pos_
|
||||
}
|
||||
}
|
||||
|
||||
/** An SSA node. */
|
||||
@@ -222,23 +312,52 @@ module Node {
|
||||
}
|
||||
|
||||
/** A data flow node that represents a value returned by a callable. */
|
||||
final class ReturnNode extends ExprNode {
|
||||
ReturnNode() { this.getCfgNode().getASuccessor() instanceof AnnotatedExitCfgNode }
|
||||
abstract class ReturnNode extends Node {
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
ReturnKind getKind() { any() }
|
||||
final class ExprReturnNode extends ExprNode, ReturnNode {
|
||||
ExprReturnNode() { this.getCfgNode().getASuccessor() instanceof AnnotatedExitCfgNode }
|
||||
|
||||
override ReturnKind getKind() { result = TNormalReturnKind() }
|
||||
}
|
||||
|
||||
final class SummaryReturnNode extends FlowSummaryNode, ReturnNode {
|
||||
private ReturnKind rk;
|
||||
|
||||
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this.getSummaryNode(), rk) }
|
||||
|
||||
override ReturnKind getKind() { result = rk }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents the output of a call. */
|
||||
abstract class OutNode extends Node, ExprNode {
|
||||
abstract class OutNode extends Node {
|
||||
/** Gets the underlying call for this node. */
|
||||
abstract DataFlowCall getCall();
|
||||
abstract DataFlowCall getCall(ReturnKind kind);
|
||||
}
|
||||
|
||||
final private class ExprOutNode extends ExprNode, OutNode {
|
||||
ExprOutNode() { this.asExpr() instanceof CallExprBaseCfgNode }
|
||||
|
||||
/** Gets the underlying call CFG node that includes this out node. */
|
||||
override DataFlowCall getCall() { result.asCallBaseExprCfgNode() = this.getCfgNode() }
|
||||
override DataFlowCall getCall(ReturnKind kind) {
|
||||
result.asCallBaseExprCfgNode() = this.getCfgNode() and
|
||||
kind = TNormalReturnKind()
|
||||
}
|
||||
}
|
||||
|
||||
final class SummaryOutNode extends FlowSummaryNode, OutNode {
|
||||
private DataFlowCall call;
|
||||
private ReturnKind kind_;
|
||||
|
||||
SummaryOutNode() {
|
||||
exists(FlowSummaryImpl::Private::SummaryNode receiver |
|
||||
call.isSummaryCall(_, receiver) and
|
||||
FlowSummaryImpl::Private::summaryOutNode(receiver, this.getSummaryNode(), kind_)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) { result = call and kind = kind_ }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,19 +371,39 @@ module Node {
|
||||
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
|
||||
* to the value before the update.
|
||||
*/
|
||||
final class PostUpdateNode extends Node, TExprPostUpdateNode {
|
||||
abstract class PostUpdateNode extends Node {
|
||||
/** Gets the node before the state update. */
|
||||
abstract Node getPreUpdateNode();
|
||||
|
||||
override CfgScope getCfgScope() { result = this.getPreUpdateNode().getCfgScope() }
|
||||
|
||||
override Location getLocation() { result = this.getPreUpdateNode().getLocation() }
|
||||
|
||||
override string toString() { result = "[post] " + this.getPreUpdateNode().toString() }
|
||||
}
|
||||
|
||||
final class ExprPostUpdateNode extends PostUpdateNode, TExprPostUpdateNode {
|
||||
private ExprCfgNode n;
|
||||
|
||||
PostUpdateNode() { this = TExprPostUpdateNode(n) }
|
||||
ExprPostUpdateNode() { this = TExprPostUpdateNode(n) }
|
||||
|
||||
/** Gets the node before the state update. */
|
||||
Node getPreUpdateNode() { result = TExprNode(n) }
|
||||
override Node getPreUpdateNode() { result = TExprNode(n) }
|
||||
}
|
||||
|
||||
final override CfgScope getCfgScope() { result = n.getScope() }
|
||||
final class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode {
|
||||
private FlowSummaryNode pre;
|
||||
|
||||
final override Location getLocation() { result = n.getLocation() }
|
||||
SummaryPostUpdateNode() {
|
||||
FlowSummaryImpl::Private::summaryPostUpdateNode(this.getSummaryNode(), pre.getSummaryNode())
|
||||
}
|
||||
|
||||
final override string toString() { result = "[post] " + n.toString() }
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
|
||||
override CfgScope getCfgScope() { result = PostUpdateNode.super.getCfgScope() }
|
||||
|
||||
override EmptyLocation getLocation() { result = PostUpdateNode.super.getLocation() }
|
||||
|
||||
final override string toString() { result = PostUpdateNode.super.toString() }
|
||||
}
|
||||
|
||||
final class CastNode = NaNode;
|
||||
@@ -277,7 +416,7 @@ module SsaFlow {
|
||||
private module SsaFlow = SsaImpl::DataFlowIntegration;
|
||||
|
||||
private Node::ParameterNode toParameterNode(ParamCfgNode p) {
|
||||
result.(Node::ParameterNode).getParameter() = p
|
||||
result.(Node::SourceParameterNode).getParameter() = p
|
||||
}
|
||||
|
||||
/** Converts a control flow node into an SSA control flow node. */
|
||||
@@ -316,6 +455,15 @@ private ExprCfgNode getALastEvalNode(ExprCfgNode e) {
|
||||
}
|
||||
|
||||
module LocalFlow {
|
||||
predicate flowSummaryLocalStep(
|
||||
Node::FlowSummaryNode nodeFrom, Node::FlowSummaryNode nodeTo,
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, string model
|
||||
) {
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.getSummaryNode(),
|
||||
nodeTo.getSummaryNode(), true, model) and
|
||||
c = nodeFrom.getSummarizedCallable()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
|
||||
nodeFrom.getCfgNode() = getALastEvalNode(nodeTo.getCfgNode())
|
||||
@@ -329,9 +477,7 @@ module LocalFlow {
|
||||
nodeFrom.(Node::AstCfgFlowNode).getCfgNode() =
|
||||
nodeTo.(Node::SsaNode).getDefinitionExt().(Ssa::WriteDefinition).getControlFlowNode()
|
||||
or
|
||||
nodeFrom.(Node::ParameterNode).getParameter().(ParamCfgNode).getPat() = nodeTo.asPat()
|
||||
or
|
||||
SsaFlow::localFlowStep(_, nodeFrom, nodeTo, _)
|
||||
nodeFrom.(Node::SourceParameterNode).getParameter().(ParamCfgNode).getPat() = nodeTo.asPat()
|
||||
or
|
||||
exists(AssignmentExprCfgNode a |
|
||||
a.getRhs() = nodeFrom.getCfgNode() and
|
||||
@@ -397,7 +543,7 @@ abstract class Content extends TContent {
|
||||
}
|
||||
|
||||
/** A canonical path pointing to an enum variant. */
|
||||
private class VariantCanonicalPath extends MkVariantCanonicalPath {
|
||||
class VariantCanonicalPath extends MkVariantCanonicalPath {
|
||||
CrateOriginOption crate;
|
||||
string path;
|
||||
string name;
|
||||
@@ -407,6 +553,8 @@ private class VariantCanonicalPath extends MkVariantCanonicalPath {
|
||||
/** Gets the underlying variant. */
|
||||
Variant getVariant() { variantHasExtendedCanonicalPath(_, result, crate, path, name) }
|
||||
|
||||
string getExtendedCanonicalPath() { result = path + "::" + name }
|
||||
|
||||
string toString() { result = name }
|
||||
|
||||
Location getLocation() { result = this.getVariant().getLocation() }
|
||||
@@ -541,6 +689,13 @@ private module Aliases {
|
||||
class ContentSetAlias = ContentSet;
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
Resolvable getCallResolvable(CallExprBase call) {
|
||||
result = call.(MethodCallExpr)
|
||||
or
|
||||
result = call.(CallExpr).getFunction().(PathExpr).getPath()
|
||||
}
|
||||
|
||||
module RustDataFlow implements InputSig<Location> {
|
||||
private import Aliases
|
||||
|
||||
@@ -564,19 +719,23 @@ module RustDataFlow implements InputSig<Location> {
|
||||
|
||||
/** Holds if `p` is a parameter of `c` at the position `pos`. */
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
p.getCfgNode().getAstNode() = pos.getParameterIn(c.asCfgScope().(Callable).getParamList())
|
||||
p.isParameterOf(c, pos)
|
||||
}
|
||||
|
||||
/** Holds if `n` is an argument of `c` at the position `pos`. */
|
||||
predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) {
|
||||
isArgumentForCall(n.getCfgNode(), call.asCallBaseExprCfgNode(), pos)
|
||||
n.isArgumentOf(call, pos)
|
||||
}
|
||||
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node node) { result = node.getEnclosingCallable() }
|
||||
|
||||
DataFlowType getNodeType(Node node) { any() }
|
||||
|
||||
predicate nodeIsHidden(Node node) { node instanceof Node::SsaNode }
|
||||
predicate nodeIsHidden(Node node) {
|
||||
node instanceof Node::SsaNode
|
||||
or
|
||||
node instanceof Node::FlowSummaryNode
|
||||
}
|
||||
|
||||
class DataFlowExpr = ExprCfgNode;
|
||||
|
||||
@@ -592,15 +751,15 @@ module RustDataFlow implements InputSig<Location> {
|
||||
/** Gets a viable implementation of the target of the given `Call`. */
|
||||
DataFlowCallable viableCallable(DataFlowCall call) {
|
||||
result.asCfgScope() = call.asCallBaseExprCfgNode().getCallExprBase().getStaticTarget()
|
||||
or
|
||||
result.asLibraryCallable().getACall() = call.asCallBaseExprCfgNode().getCallExprBase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that can read the value returned from `call` with return kind
|
||||
* `kind`.
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
call = result.getCall() and exists(kind)
|
||||
}
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
|
||||
|
||||
// NOTE: For now we use the type `Unit` and do not benefit from type
|
||||
// information in the data flow analysis.
|
||||
@@ -637,8 +796,21 @@ module RustDataFlow implements InputSig<Location> {
|
||||
* are the value-preserving intra-callable flow steps.
|
||||
*/
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo) and
|
||||
(
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
exists(SsaImpl::DefinitionExt def, boolean isUseStep |
|
||||
SsaFlow::localFlowStep(def, nodeFrom, nodeTo, isUseStep)
|
||||
|
|
||||
isUseStep = false
|
||||
or
|
||||
isUseStep = true and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
|
||||
)
|
||||
) and
|
||||
model = ""
|
||||
or
|
||||
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _, model)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -646,7 +818,10 @@ module RustDataFlow implements InputSig<Location> {
|
||||
* that does not follow a call edge. For example, a step through a global
|
||||
* variable.
|
||||
*/
|
||||
predicate jumpStep(Node node1, Node node2) { none() }
|
||||
predicate jumpStep(Node node1, Node node2) {
|
||||
FlowSummaryImpl::Private::Steps::summaryJumpStep(node1.(Node::FlowSummaryNode).getSummaryNode(),
|
||||
node2.(Node::FlowSummaryNode).getSummaryNode())
|
||||
}
|
||||
|
||||
/** Holds if path `p` resolves to struct `s`. */
|
||||
private predicate pathResolveToStructCanonicalPath(Path p, StructCanonicalPath s) {
|
||||
@@ -725,6 +900,9 @@ module RustDataFlow implements InputSig<Location> {
|
||||
node2.asExpr() = access
|
||||
)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(Node::FlowSummaryNode).getSummaryNode(),
|
||||
cs, node2.(Node::FlowSummaryNode).getSummaryNode())
|
||||
}
|
||||
|
||||
/** Holds if `ce` constructs an enum value of type `v`. */
|
||||
@@ -789,6 +967,9 @@ module RustDataFlow implements InputSig<Location> {
|
||||
or
|
||||
tupleAssignment(node1, node2.(PostUpdateNode).getPreUpdateNode(), c)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(Node::FlowSummaryNode).getSummaryNode(),
|
||||
cs, node2.(Node::FlowSummaryNode).getSummaryNode())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -798,13 +979,19 @@ module RustDataFlow implements InputSig<Location> {
|
||||
*/
|
||||
predicate clearsContent(Node n, ContentSet cs) {
|
||||
tupleAssignment(_, n, cs.(SingletonContentSet).getContent())
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(Node::FlowSummaryNode).getSummaryNode(),
|
||||
cs)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() }
|
||||
predicate expectsContent(Node n, ContentSet cs) {
|
||||
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(Node::FlowSummaryNode)
|
||||
.getSummaryNode(), cs)
|
||||
}
|
||||
|
||||
class NodeRegion instanceof Void {
|
||||
string toString() { result = "NodeRegion" }
|
||||
@@ -824,7 +1011,12 @@ module RustDataFlow implements InputSig<Location> {
|
||||
* 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() }
|
||||
predicate allowParameterReturnInSelf(ParameterNode p) {
|
||||
exists(DataFlowCallable c, ParameterPosition pos |
|
||||
p.isParameterOf(c, pos) and
|
||||
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(c.asLibraryCallable(), pos)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the value of `node2` is given by `node1`.
|
||||
@@ -837,6 +1029,12 @@ module RustDataFlow implements InputSig<Location> {
|
||||
*/
|
||||
predicate localMustFlowStep(Node node1, Node node2) {
|
||||
SsaFlow::localMustFlowStep(_, node1, node2)
|
||||
or
|
||||
node1 =
|
||||
unique(Node::FlowSummaryNode n1 |
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(n1.getSummaryNode(),
|
||||
node2.(Node::FlowSummaryNode).getSummaryNode(), true, _)
|
||||
)
|
||||
}
|
||||
|
||||
class LambdaCallKind = Void;
|
||||
@@ -866,32 +1064,54 @@ private module Cached {
|
||||
cached
|
||||
newtype TNode =
|
||||
TExprNode(ExprCfgNode n) or
|
||||
TParameterNode(ParamBaseCfgNode p) or
|
||||
TSourceParameterNode(ParamBaseCfgNode p) or
|
||||
TPatNode(PatCfgNode p) or
|
||||
TExprPostUpdateNode(ExprCfgNode e) {
|
||||
isArgumentForCall(e, _, _) or e = any(FieldExprCfgNode access).getExpr()
|
||||
} or
|
||||
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node)
|
||||
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
|
||||
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn)
|
||||
|
||||
cached
|
||||
newtype TDataFlowCall = TCall(CallExprBaseCfgNode c)
|
||||
newtype TDataFlowCall =
|
||||
TCall(CallExprBaseCfgNode c) or
|
||||
TSummaryCall(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
|
||||
) {
|
||||
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TDataFlowCallable = TCfgScope(CfgScope scope)
|
||||
newtype TDataFlowCallable =
|
||||
TCfgScope(CfgScope scope) or
|
||||
TLibraryCallable(LibraryCallable c)
|
||||
|
||||
/** This is the local flow predicate that is exposed. */
|
||||
cached
|
||||
predicate localFlowStepImpl(Node::Node nodeFrom, Node::Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
SsaFlow::localFlowStep(_, nodeFrom, nodeTo, _)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TParameterPosition =
|
||||
TPositionalParameterPosition(int i) {
|
||||
i in [0 .. max([any(ParamList l).getNumberOfParams(), any(ArgList l).getNumberOfArgs()]) - 1]
|
||||
or
|
||||
FlowSummaryImpl::ParsePositions::isParsedArgumentPosition(_, i)
|
||||
or
|
||||
FlowSummaryImpl::ParsePositions::isParsedParameterPosition(_, i)
|
||||
} or
|
||||
TSelfParameterPosition()
|
||||
|
||||
cached
|
||||
newtype TReturnKind = TNormalReturnKind()
|
||||
|
||||
cached
|
||||
newtype TVariantCanonicalPath =
|
||||
MkVariantCanonicalPath(CrateOriginOption crate, string path, string name) {
|
||||
|
||||
108
rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
Normal file
108
rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Provides classes and predicates for defining flow summaries.
|
||||
*/
|
||||
|
||||
private import rust
|
||||
private import codeql.dataflow.internal.FlowSummaryImpl
|
||||
private import codeql.dataflow.internal.AccessPathSyntax as AccessPath
|
||||
private import codeql.rust.dataflow.internal.DataFlowImpl
|
||||
private import codeql.rust.dataflow.FlowSummary
|
||||
|
||||
module Input implements InputSig<Location, RustDataFlow> {
|
||||
class SummarizedCallableBase = string;
|
||||
|
||||
RustDataFlow::ArgumentPosition callbackSelfParameterPosition() { none() }
|
||||
|
||||
ReturnKind getStandardReturnValueKind() { result = TNormalReturnKind() }
|
||||
|
||||
string encodeParameterPosition(ParameterPosition pos) {
|
||||
result = pos.getPosition().toString()
|
||||
or
|
||||
pos.isSelf() and
|
||||
result = "self"
|
||||
}
|
||||
|
||||
predicate encodeArgumentPosition = encodeParameterPosition/1;
|
||||
|
||||
string encodeContent(ContentSet cs, string arg) {
|
||||
exists(Content c | cs = TSingletonContentSet(c) |
|
||||
exists(VariantCanonicalPath v | result = "Variant" |
|
||||
exists(int pos |
|
||||
c = TVariantPositionContent(v, pos) and
|
||||
arg = v.getExtendedCanonicalPath() + "(" + pos + ")"
|
||||
)
|
||||
or
|
||||
exists(string field |
|
||||
c = TVariantFieldContent(v, field) and
|
||||
arg = v.getExtendedCanonicalPath() + "::" + field
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
string encodeReturn(ReturnKind rk, string arg) { none() }
|
||||
|
||||
string encodeWithoutContent(ContentSet c, string arg) {
|
||||
result = "Without" + encodeContent(c, arg)
|
||||
}
|
||||
|
||||
string encodeWithContent(ContentSet c, string arg) { result = "With" + encodeContent(c, arg) }
|
||||
|
||||
bindingset[token]
|
||||
ParameterPosition decodeUnknownParameterPosition(AccessPath::AccessPathTokenBase token) {
|
||||
// needed to support `Argument[x..y]` ranges
|
||||
token.getName() = "Argument" and
|
||||
result.getPosition() = AccessPath::parseInt(token.getAnArgument())
|
||||
}
|
||||
|
||||
bindingset[token]
|
||||
RustDataFlow::ArgumentPosition decodeUnknownArgumentPosition(AccessPath::AccessPathTokenBase token) {
|
||||
// needed to support `Parameter[x..y]` ranges
|
||||
token.getName() = "Parameter" and
|
||||
result.getPosition() = AccessPath::parseInt(token.getAnArgument())
|
||||
}
|
||||
|
||||
bindingset[token]
|
||||
ContentSet decodeUnknownContent(AccessPath::AccessPathTokenBase token) { none() }
|
||||
|
||||
bindingset[token]
|
||||
ContentSet decodeUnknownWithContent(AccessPath::AccessPathTokenBase token) { none() }
|
||||
}
|
||||
|
||||
private import Make<Location, RustDataFlow, Input> as Impl
|
||||
|
||||
private module StepsInput implements Impl::Private::StepsInputSig {
|
||||
DataFlowCall getACall(Public::SummarizedCallable sc) {
|
||||
result.asCallBaseExprCfgNode().getCallExprBase() = sc.(LibraryCallable).getACall()
|
||||
}
|
||||
}
|
||||
|
||||
module Private {
|
||||
import Impl::Private
|
||||
|
||||
module Steps = Impl::Private::Steps<StepsInput>;
|
||||
}
|
||||
|
||||
module Public = Impl::Public;
|
||||
|
||||
module ParsePositions {
|
||||
private import Private
|
||||
|
||||
private predicate isParamBody(string body) {
|
||||
body = any(AccessPathToken tok).getAnArgument("Parameter")
|
||||
}
|
||||
|
||||
private predicate isArgBody(string body) {
|
||||
body = any(AccessPathToken tok).getAnArgument("Argument")
|
||||
}
|
||||
|
||||
predicate isParsedParameterPosition(string c, int i) {
|
||||
isParamBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
}
|
||||
|
||||
predicate isParsedArgumentPosition(string c, int i) {
|
||||
isArgBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
}
|
||||
}
|
||||
89
rust/ql/test/library-tests/dataflow/models/main.rs
Normal file
89
rust/ql/test/library-tests/dataflow/models/main.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
fn source(i: i64) -> i64 {
|
||||
1000 + i
|
||||
}
|
||||
|
||||
fn sink(s: i64) {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
// has a flow model
|
||||
fn identity(i: i64) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
fn test_identify() {
|
||||
let s = source(1);
|
||||
sink(identity(s)); // $ hasValueFlow=1
|
||||
}
|
||||
|
||||
enum MyPosEnum {
|
||||
A(i64),
|
||||
B(i64),
|
||||
}
|
||||
|
||||
// has a flow model
|
||||
fn get_var_pos(e: MyPosEnum) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
fn test_get_var_pos() {
|
||||
let s = source(2);
|
||||
let e1 = MyPosEnum::A(s);
|
||||
sink(get_var_pos(e1)); // $ hasValueFlow=2
|
||||
let e2 = MyPosEnum::B(s);
|
||||
sink(get_var_pos(e2));
|
||||
}
|
||||
|
||||
// has a flow model
|
||||
fn set_var_pos(i: i64) -> MyPosEnum {
|
||||
MyPosEnum::A(0)
|
||||
}
|
||||
|
||||
fn test_set_var_pos() {
|
||||
let s = source(3);
|
||||
let e1 = set_var_pos(s);
|
||||
match e1 {
|
||||
MyPosEnum::A(i) => sink(i),
|
||||
MyPosEnum::B(i) => sink(i), // $ hasValueFlow=3
|
||||
}
|
||||
}
|
||||
|
||||
enum MyFieldEnum {
|
||||
C { field_c: i64 },
|
||||
D { field_d: i64 },
|
||||
}
|
||||
|
||||
// has a flow model
|
||||
fn get_var_field(e: MyFieldEnum) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
fn test_get_var_field() {
|
||||
let s = source(4);
|
||||
let e1 = MyFieldEnum::C { field_c: s };
|
||||
sink(get_var_field(e1)); // $ hasValueFlow=4
|
||||
let e2 = MyFieldEnum::D { field_d: s };
|
||||
sink(get_var_field(e2));
|
||||
}
|
||||
|
||||
// has a flow model
|
||||
fn set_var_field(i: i64) -> MyFieldEnum {
|
||||
MyFieldEnum::C { field_c: 0 }
|
||||
}
|
||||
|
||||
fn test_set_var_field() {
|
||||
let s = source(5);
|
||||
let e1 = set_var_field(s);
|
||||
match e1 {
|
||||
MyFieldEnum::C { field_c: i } => sink(i),
|
||||
MyFieldEnum::D { field_d: i } => sink(i), // $ hasValueFlow=5
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_identify();
|
||||
test_get_var_pos();
|
||||
test_set_var_pos();
|
||||
test_get_var_field();
|
||||
test_set_var_field();
|
||||
}
|
||||
57
rust/ql/test/library-tests/dataflow/models/models.expected
Normal file
57
rust/ql/test/library-tests/dataflow/models/models.expected
Normal file
@@ -0,0 +1,57 @@
|
||||
models
|
||||
edges
|
||||
| main.rs:15:13:15:21 | source(...) | main.rs:16:19:16:19 | s | provenance | |
|
||||
| main.rs:16:19:16:19 | s | main.rs:16:10:16:20 | identity(...) | provenance | |
|
||||
| main.rs:30:13:30:21 | source(...) | main.rs:31:27:31:27 | s | provenance | |
|
||||
| main.rs:31:14:31:28 | ...::A(...) [A] | main.rs:32:22:32:23 | e1 [A] | provenance | |
|
||||
| main.rs:31:27:31:27 | s | main.rs:31:14:31:28 | ...::A(...) [A] | provenance | |
|
||||
| main.rs:32:22:32:23 | e1 [A] | main.rs:32:10:32:24 | get_var_pos(...) | provenance | |
|
||||
| main.rs:43:13:43:21 | source(...) | main.rs:44:26:44:26 | s | provenance | |
|
||||
| main.rs:44:14:44:27 | set_var_pos(...) [B] | main.rs:47:9:47:23 | TupleStructPat [B] | provenance | |
|
||||
| main.rs:44:26:44:26 | s | main.rs:44:14:44:27 | set_var_pos(...) [B] | provenance | |
|
||||
| main.rs:47:9:47:23 | TupleStructPat [B] | main.rs:47:22:47:22 | i | provenance | |
|
||||
| main.rs:47:22:47:22 | i | main.rs:47:33:47:33 | i | provenance | |
|
||||
| main.rs:62:13:62:21 | source(...) | main.rs:63:40:63:40 | s | provenance | |
|
||||
| main.rs:63:14:63:42 | ...::C {...} [C] | main.rs:64:24:64:25 | e1 [C] | provenance | |
|
||||
| main.rs:63:40:63:40 | s | main.rs:63:14:63:42 | ...::C {...} [C] | provenance | |
|
||||
| main.rs:64:24:64:25 | e1 [C] | main.rs:64:10:64:26 | get_var_field(...) | provenance | |
|
||||
| main.rs:75:13:75:21 | source(...) | main.rs:76:28:76:28 | s | provenance | |
|
||||
| main.rs:76:14:76:29 | set_var_field(...) [D] | main.rs:79:9:79:37 | ...::D {...} [D] | provenance | |
|
||||
| main.rs:76:28:76:28 | s | main.rs:76:14:76:29 | set_var_field(...) [D] | provenance | |
|
||||
| main.rs:79:9:79:37 | ...::D {...} [D] | main.rs:79:35:79:35 | i | provenance | |
|
||||
| main.rs:79:35:79:35 | i | main.rs:79:47:79:47 | i | provenance | |
|
||||
nodes
|
||||
| main.rs:15:13:15:21 | source(...) | semmle.label | source(...) |
|
||||
| main.rs:16:10:16:20 | identity(...) | semmle.label | identity(...) |
|
||||
| main.rs:16:19:16:19 | s | semmle.label | s |
|
||||
| main.rs:30:13:30:21 | source(...) | semmle.label | source(...) |
|
||||
| main.rs:31:14:31:28 | ...::A(...) [A] | semmle.label | ...::A(...) [A] |
|
||||
| main.rs:31:27:31:27 | s | semmle.label | s |
|
||||
| main.rs:32:10:32:24 | get_var_pos(...) | semmle.label | get_var_pos(...) |
|
||||
| main.rs:32:22:32:23 | e1 [A] | semmle.label | e1 [A] |
|
||||
| main.rs:43:13:43:21 | source(...) | semmle.label | source(...) |
|
||||
| main.rs:44:14:44:27 | set_var_pos(...) [B] | semmle.label | set_var_pos(...) [B] |
|
||||
| main.rs:44:26:44:26 | s | semmle.label | s |
|
||||
| main.rs:47:9:47:23 | TupleStructPat [B] | semmle.label | TupleStructPat [B] |
|
||||
| main.rs:47:22:47:22 | i | semmle.label | i |
|
||||
| main.rs:47:33:47:33 | i | semmle.label | i |
|
||||
| main.rs:62:13:62:21 | source(...) | semmle.label | source(...) |
|
||||
| main.rs:63:14:63:42 | ...::C {...} [C] | semmle.label | ...::C {...} [C] |
|
||||
| main.rs:63:40:63:40 | s | semmle.label | s |
|
||||
| main.rs:64:10:64:26 | get_var_field(...) | semmle.label | get_var_field(...) |
|
||||
| main.rs:64:24:64:25 | e1 [C] | semmle.label | e1 [C] |
|
||||
| main.rs:75:13:75:21 | source(...) | semmle.label | source(...) |
|
||||
| main.rs:76:14:76:29 | set_var_field(...) [D] | semmle.label | set_var_field(...) [D] |
|
||||
| main.rs:76:28:76:28 | s | semmle.label | s |
|
||||
| main.rs:79:9:79:37 | ...::D {...} [D] | semmle.label | ...::D {...} [D] |
|
||||
| main.rs:79:35:79:35 | i | semmle.label | i |
|
||||
| main.rs:79:47:79:47 | i | semmle.label | i |
|
||||
subpaths
|
||||
testFailures
|
||||
invalidSpecComponent
|
||||
#select
|
||||
| main.rs:16:10:16:20 | identity(...) | main.rs:15:13:15:21 | source(...) | main.rs:16:10:16:20 | identity(...) | $@ | main.rs:15:13:15:21 | source(...) | source(...) |
|
||||
| main.rs:32:10:32:24 | get_var_pos(...) | main.rs:30:13:30:21 | source(...) | main.rs:32:10:32:24 | get_var_pos(...) | $@ | main.rs:30:13:30:21 | source(...) | source(...) |
|
||||
| main.rs:47:33:47:33 | i | main.rs:43:13:43:21 | source(...) | main.rs:47:33:47:33 | i | $@ | main.rs:43:13:43:21 | source(...) | source(...) |
|
||||
| main.rs:64:10:64:26 | get_var_field(...) | main.rs:62:13:62:21 | source(...) | main.rs:64:10:64:26 | get_var_field(...) | $@ | main.rs:62:13:62:21 | source(...) | source(...) |
|
||||
| main.rs:79:47:79:47 | i | main.rs:75:13:75:21 | source(...) | main.rs:79:47:79:47 | i | $@ | main.rs:75:13:75:21 | source(...) | source(...) |
|
||||
78
rust/ql/test/library-tests/dataflow/models/models.ql
Normal file
78
rust/ql/test/library-tests/dataflow/models/models.ql
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @kind path-problem
|
||||
*/
|
||||
|
||||
import rust
|
||||
import utils.InlineFlowTest
|
||||
import codeql.rust.dataflow.DataFlow
|
||||
import codeql.rust.dataflow.FlowSummary
|
||||
import codeql.rust.dataflow.TaintTracking
|
||||
import codeql.rust.dataflow.internal.FlowSummaryImpl
|
||||
import PathGraph
|
||||
|
||||
query predicate invalidSpecComponent(SummarizedCallable sc, string s, string c) {
|
||||
(sc.propagatesFlow(s, _, _) or sc.propagatesFlow(_, s, _)) and
|
||||
Private::External::invalidSpecComponent(s, c)
|
||||
}
|
||||
|
||||
private class SummarizedCallableIdentity extends SummarizedCallable::Range {
|
||||
SummarizedCallableIdentity() { this = "repo::test::_::crate::identity" }
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableGetVarPos extends SummarizedCallable::Range {
|
||||
SummarizedCallableGetVarPos() { this = "repo::test::_::crate::get_var_pos" }
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0].Variant[crate::MyPosEnum::A(0)]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableSetVarPos extends SummarizedCallable::Range {
|
||||
SummarizedCallableSetVarPos() { this = "repo::test::_::crate::set_var_pos" }
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue.Variant[crate::MyPosEnum::B(0)]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableGetVarField extends SummarizedCallable::Range {
|
||||
SummarizedCallableGetVarField() { this = "repo::test::_::crate::get_var_field" }
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0].Variant[crate::MyFieldEnum::C::field_c]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableSetVarField extends SummarizedCallable::Range {
|
||||
SummarizedCallableSetVarField() { this = "repo::test::_::crate::set_var_field" }
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue.Variant[crate::MyFieldEnum::D::field_d]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
module CustomConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { DefaultFlowConfig::isSource(source) }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { DefaultFlowConfig::isSink(sink) }
|
||||
}
|
||||
|
||||
import ValueFlowTest<CustomConfig>
|
||||
|
||||
from PathNode source, PathNode sink
|
||||
where flowPath(source, sink)
|
||||
select sink, source, sink, "$@", source, source.toString()
|
||||
Reference in New Issue
Block a user