diff --git a/rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll b/rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll new file mode 100644 index 0000000000000..a9baca2df97fc --- /dev/null +++ b/rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll @@ -0,0 +1,67 @@ +/** Provides classes and predicates for defining flow summaries. */ + +private import rust +// private import DataFlow +private import internal.FlowSummaryImpl as Impl +private import internal.DataFlowImpl + +// import all instances below +private module Summaries { + private import codeql.rust.Frameworks + // private import codeql.ruby.frameworks.data.ModelsAsData +} + +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 = "" + ) + } + } +} + +/** Gets a call to this library callable. */ +CallExprBase gesftACall(string s) { + exists(Resolvable r, string crate | + r = getCallResolvable(result) and + s = crate + r.getResolvedPath() + | + crate = r.getResolvedCrateOrigin() + "::" + or + not r.hasResolvedCrateOrigin() and + crate = "" + ) +} + +final class LibraryCallable = LibraryCallable::Range; + +/** A callable with a flow summary, identified by a unique string. */ +abstract class SummarizedCallable extends LibraryCallable::Range, Impl::Public::SummarizedCallable { + bindingset[this] + SummarizedCallable() { 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); +} diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll index 6fcb878c8ea6d..7f7b9485fdeee 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll @@ -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 } + predicate isSummaryCall( + FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver + ) { + this = TSummaryCall(c, receiver) + } + 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() } + 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 SourceArgumentNode extends ArgumentNode, ExprNode { + private CallExprBaseCfgNode call_; + private RustDataFlow::ArgumentPosition pos_; + + SourceArgumentNode() { 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 SourceReturnNode extends ExprNode, ReturnNode { + SourceReturnNode() { 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, TArgumentPostUpdateNode { + 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 ArgumentPostUpdateNode extends PostUpdateNode, TArgumentPostUpdateNode { private ExprCfgNode n; - PostUpdateNode() { this = TArgumentPostUpdateNode(n) } + ArgumentPostUpdateNode() { this = TArgumentPostUpdateNode(n) } - /** Gets the node before the state update. */ - Node getPreUpdateNode() { result = TExprNode(n) } + override Node getPreUpdateNode() { result = TExprNode(n) } + } + + final class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode { + private FlowSummaryNode pre; + + SummaryPostUpdateNode() { + FlowSummaryImpl::Private::summaryPostUpdateNode(this.getSummaryNode(), pre.getSummaryNode()) + } + + override Node getPreUpdateNode() { result = pre } - final override CfgScope getCfgScope() { result = n.getScope() } + override CfgScope getCfgScope() { result = PostUpdateNode.super.getCfgScope() } - final override Location getLocation() { result = n.getLocation() } + override EmptyLocation getLocation() { result = PostUpdateNode.super.getLocation() } - final override string toString() { result = n.toString() } + 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. */ @@ -313,6 +452,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()) @@ -326,9 +474,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 @@ -394,7 +540,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; @@ -404,6 +550,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() } @@ -490,6 +638,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 { private import Aliases @@ -513,19 +668,23 @@ module RustDataFlow implements InputSig { /** 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; @@ -538,13 +697,6 @@ module RustDataFlow implements InputSig { final class ReturnKind = ReturnKindAlias; - pragma[nomagic] - private Resolvable getCallResolvable(CallExprBase call) { - result = call.(MethodCallExpr) - or - result = call.(CallExpr).getFunction().(PathExpr).getPath() - } - /** Gets a viable implementation of the target of the given `Call`. */ DataFlowCallable viableCallable(DataFlowCall call) { exists(Resolvable r, string path, CrateOriginOption crate | @@ -552,15 +704,15 @@ module RustDataFlow implements InputSig { r = getCallResolvable(call.asCallBaseExprCfgNode().getExpr()) and resolveExtendedCanonicalPath(r, crate, path) ) + 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. @@ -597,8 +749,21 @@ module RustDataFlow implements InputSig { * 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) } /** @@ -606,7 +771,10 @@ module RustDataFlow implements InputSig { * 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 variant `v`. */ private predicate pathResolveToVariantCanonicalPath(Path p, VariantCanonicalPath v) { @@ -657,6 +825,9 @@ module RustDataFlow implements InputSig { node2.asPat() = pat.getFieldPat(field) ) ) + or + FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(Node::FlowSummaryNode).getSummaryNode(), + cs, node2.(Node::FlowSummaryNode).getSummaryNode()) } /** Holds if `ce` constructs an enum value of type `v`. */ @@ -696,6 +867,9 @@ module RustDataFlow implements InputSig { re ) ) + or + FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(Node::FlowSummaryNode).getSummaryNode(), + cs, node2.(Node::FlowSummaryNode).getSummaryNode()) } /** @@ -703,13 +877,19 @@ module RustDataFlow implements InputSig { * 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() } + predicate clearsContent(Node n, ContentSet cs) { + 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" } @@ -729,7 +909,12 @@ module RustDataFlow implements InputSig { * 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`. @@ -742,6 +927,12 @@ module RustDataFlow implements InputSig { */ 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; @@ -771,30 +962,52 @@ private module Cached { cached newtype TNode = TExprNode(ExprCfgNode n) or - TParameterNode(ParamBaseCfgNode p) or + TSourceParameterNode(ParamBaseCfgNode p) or TPatNode(PatCfgNode p) or TArgumentPostUpdateNode(ExprCfgNode e) { isArgumentForCall(e, _, _) } 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) { diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll new file mode 100644 index 0000000000000..1292a936afdce --- /dev/null +++ b/rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll @@ -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 { + 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 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; +} + +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) + } +} diff --git a/rust/ql/test/library-tests/dataflow/models/main.rs b/rust/ql/test/library-tests/dataflow/models/main.rs new file mode 100644 index 0000000000000..c77e96ecbaf8e --- /dev/null +++ b/rust/ql/test/library-tests/dataflow/models/main.rs @@ -0,0 +1,91 @@ +// Tests for intraprocedural data flow. + +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(); +} diff --git a/rust/ql/test/library-tests/dataflow/models/models.expected b/rust/ql/test/library-tests/dataflow/models/models.expected new file mode 100644 index 0000000000000..ba3421749014e --- /dev/null +++ b/rust/ql/test/library-tests/dataflow/models/models.expected @@ -0,0 +1,57 @@ +models +edges +| main.rs:17:13:17:21 | source(...) | main.rs:18:19:18:19 | s | provenance | | +| main.rs:18:19:18:19 | s | main.rs:18:10:18:20 | identity(...) | provenance | | +| main.rs:32:13:32:21 | source(...) | main.rs:33:27:33:27 | s | provenance | | +| main.rs:33:14:33:28 | ...::A(...) [A] | main.rs:34:22:34:23 | e1 [A] | provenance | | +| main.rs:33:27:33:27 | s | main.rs:33:14:33:28 | ...::A(...) [A] | provenance | | +| main.rs:34:22:34:23 | e1 [A] | main.rs:34:10:34:24 | get_var_pos(...) | provenance | | +| main.rs:45:13:45:21 | source(...) | main.rs:46:26:46:26 | s | provenance | | +| main.rs:46:14:46:27 | set_var_pos(...) [B] | main.rs:49:9:49:23 | TupleStructPat [B] | provenance | | +| main.rs:46:26:46:26 | s | main.rs:46:14:46:27 | set_var_pos(...) [B] | provenance | | +| main.rs:49:9:49:23 | TupleStructPat [B] | main.rs:49:22:49:22 | i | provenance | | +| main.rs:49:22:49:22 | i | main.rs:49:33:49:33 | i | provenance | | +| main.rs:64:13:64:21 | source(...) | main.rs:65:40:65:40 | s | provenance | | +| main.rs:65:14:65:42 | ...::C {...} [C] | main.rs:66:24:66:25 | e1 [C] | provenance | | +| main.rs:65:40:65:40 | s | main.rs:65:14:65:42 | ...::C {...} [C] | provenance | | +| main.rs:66:24:66:25 | e1 [C] | main.rs:66:10:66:26 | get_var_field(...) | provenance | | +| main.rs:77:13:77:21 | source(...) | main.rs:78:28:78:28 | s | provenance | | +| main.rs:78:14:78:29 | set_var_field(...) [D] | main.rs:81:9:81:37 | ...::D {...} [D] | provenance | | +| main.rs:78:28:78:28 | s | main.rs:78:14:78:29 | set_var_field(...) [D] | provenance | | +| main.rs:81:9:81:37 | ...::D {...} [D] | main.rs:81:35:81:35 | i | provenance | | +| main.rs:81:35:81:35 | i | main.rs:81:47:81:47 | i | provenance | | +nodes +| main.rs:17:13:17:21 | source(...) | semmle.label | source(...) | +| main.rs:18:10:18:20 | identity(...) | semmle.label | identity(...) | +| main.rs:18:19:18:19 | s | semmle.label | s | +| main.rs:32:13:32:21 | source(...) | semmle.label | source(...) | +| main.rs:33:14:33:28 | ...::A(...) [A] | semmle.label | ...::A(...) [A] | +| main.rs:33:27:33:27 | s | semmle.label | s | +| main.rs:34:10:34:24 | get_var_pos(...) | semmle.label | get_var_pos(...) | +| main.rs:34:22:34:23 | e1 [A] | semmle.label | e1 [A] | +| main.rs:45:13:45:21 | source(...) | semmle.label | source(...) | +| main.rs:46:14:46:27 | set_var_pos(...) [B] | semmle.label | set_var_pos(...) [B] | +| main.rs:46:26:46:26 | s | semmle.label | s | +| main.rs:49:9:49:23 | TupleStructPat [B] | semmle.label | TupleStructPat [B] | +| main.rs:49:22:49:22 | i | semmle.label | i | +| main.rs:49:33:49:33 | i | semmle.label | i | +| main.rs:64:13:64:21 | source(...) | semmle.label | source(...) | +| main.rs:65:14:65:42 | ...::C {...} [C] | semmle.label | ...::C {...} [C] | +| main.rs:65:40:65:40 | s | semmle.label | s | +| main.rs:66:10:66:26 | get_var_field(...) | semmle.label | get_var_field(...) | +| main.rs:66:24:66:25 | e1 [C] | semmle.label | e1 [C] | +| main.rs:77:13:77:21 | source(...) | semmle.label | source(...) | +| main.rs:78:14:78:29 | set_var_field(...) [D] | semmle.label | set_var_field(...) [D] | +| main.rs:78:28:78:28 | s | semmle.label | s | +| main.rs:81:9:81:37 | ...::D {...} [D] | semmle.label | ...::D {...} [D] | +| main.rs:81:35:81:35 | i | semmle.label | i | +| main.rs:81:47:81:47 | i | semmle.label | i | +subpaths +testFailures +invalidSpecComponent +#select +| main.rs:18:10:18:20 | identity(...) | main.rs:17:13:17:21 | source(...) | main.rs:18:10:18:20 | identity(...) | $@ | main.rs:17:13:17:21 | source(...) | source(...) | +| main.rs:34:10:34:24 | get_var_pos(...) | main.rs:32:13:32:21 | source(...) | main.rs:34:10:34:24 | get_var_pos(...) | $@ | main.rs:32:13:32:21 | source(...) | source(...) | +| main.rs:49:33:49:33 | i | main.rs:45:13:45:21 | source(...) | main.rs:49:33:49:33 | i | $@ | main.rs:45:13:45:21 | source(...) | source(...) | +| main.rs:66:10:66:26 | get_var_field(...) | main.rs:64:13:64:21 | source(...) | main.rs:66:10:66:26 | get_var_field(...) | $@ | main.rs:64:13:64:21 | source(...) | source(...) | +| main.rs:81:47:81:47 | i | main.rs:77:13:77:21 | source(...) | main.rs:81:47:81:47 | i | $@ | main.rs:77:13:77:21 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/models/models.ql b/rust/ql/test/library-tests/dataflow/models/models.ql new file mode 100644 index 0000000000000..a36e6b22f13c6 --- /dev/null +++ b/rust/ql/test/library-tests/dataflow/models/models.ql @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 + +from PathNode source, PathNode sink +where flowPath(source, sink) +select sink, source, sink, "$@", source, source.toString()