Skip to content

Commit

Permalink
Rust: Add support for MaD sources and sinks with access paths
Browse files Browse the repository at this point in the history
  • Loading branch information
hvitved committed Dec 18, 2024
1 parent 8efd870 commit 2a44dd2
Show file tree
Hide file tree
Showing 12 changed files with 780 additions and 44 deletions.
36 changes: 36 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/FlowSink.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** Provides classes and predicates for defining flow sinks. */

private import rust
private import internal.FlowSummaryImpl as Impl
private import internal.DataFlowImpl as DataFlowImpl

// import all instances below
private module Sinks {
private import codeql.rust.Frameworks
private import codeql.rust.dataflow.internal.ModelsAsData
}

/** Provides the `Range` class used to define the extent of `FlowSink`. */
module FlowSink {
/** A flow source. */
abstract class Range extends Impl::Public::SinkNode {
bindingset[this]
Range() { any() }

override predicate isSink(
string input, string kind, Impl::Public::Provenance provenance, string model
) {
this.isSink(input, kind) and provenance = "manual" and model = ""
}

/**
* Holds is this element is a flow sink of kind `kind`, where data
* flows in as described by `input`.
*/
predicate isSink(string output, string kind) { none() }
}
}

final class FlowSink = FlowSink::Range;

predicate sinkNode = DataFlowImpl::sinkNode/2;
36 changes: 36 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/FlowSource.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** Provides classes and predicates for defining flow sources. */

private import rust
private import internal.FlowSummaryImpl as Impl
private import internal.DataFlowImpl as DataFlowImpl

// import all instances below
private module Sources {
private import codeql.rust.Frameworks
private import codeql.rust.dataflow.internal.ModelsAsData
}

/** Provides the `Range` class used to define the extent of `FlowSource`. */
module FlowSource {
/** A flow source. */
abstract class Range extends Impl::Public::SourceNode {
bindingset[this]
Range() { any() }

override predicate isSource(
string output, string kind, Impl::Public::Provenance provenance, string model
) {
this.isSource(output, kind) and provenance = "manual" and model = ""
}

/**
* Holds is this element is a flow source of kind `kind`, where data
* flows out as described by `output`.
*/
predicate isSource(string output, string kind) { none() }
}
}

final class FlowSource = FlowSource::Range;

predicate sourceNode = DataFlowImpl::sourceNode/2;
2 changes: 2 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/FlowSummary.qll
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ module SummarizedCallable {
}

final class SummarizedCallable = SummarizedCallable::Range;

final class Provenance = Impl::Public::Provenance;
74 changes: 62 additions & 12 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -186,18 +186,49 @@ module Node {
class FlowSummaryNode extends Node, TFlowSummaryNode {
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }

/** Gets the summarized callable that this node belongs to. */
/** Gets the summarized callable that this node belongs to, if any. */
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
result = this.getSummaryNode().getSummarizedCallable()
}

override CfgScope getCfgScope() { none() }
/** Gets the source node that this node belongs to, if any */
FlowSummaryImpl::Public::SourceNode getSourceNode() {
result = this.getSummaryNode().getSourceNode()
}

/** Gets the sink node that this node belongs to, if any */
FlowSummaryImpl::Public::SinkNode getSinkNode() { result = this.getSummaryNode().getSinkNode() }

/** Holds is this node is a source node of kind `kind`. */
predicate isSource(string kind) {
this.getSummaryNode().(FlowSummaryImpl::Private::SourceOutputNode).isEntry(kind)
}

/** Holds is this node is a sink node of kind `kind`. */
predicate isSink(string kind) {
this.getSummaryNode().(FlowSummaryImpl::Private::SinkInputNode).isExit(kind)
}

override CfgScope getCfgScope() {
result = this.getSummaryNode().getSourceNode().getEnclosingCfgScope()
or
result = this.getSummaryNode().getSinkNode().getEnclosingCfgScope()
}

override DataFlowCallable getEnclosingCallable() {
result.asLibraryCallable() = this.getSummarizedCallable()
or
result.asCfgScope() = this.getCfgScope()
}

override EmptyLocation getLocation() { any() }
override Location getLocation() {
exists(this.getSummarizedCallable()) and
result instanceof EmptyLocation
or
result = this.getSourceNode().getLocation()
or
result = this.getSinkNode().getLocation()
}

override string toString() { result = this.getSummaryNode().toString() }
}
Expand Down Expand Up @@ -526,13 +557,20 @@ 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()
predicate flowSummaryLocalStep(Node nodeFrom, Node nodeTo, string model) {
exists(FlowSummaryImpl::Public::SummarizedCallable c |
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom
.(Node::FlowSummaryNode)
.getSummaryNode(), nodeTo.(Node::FlowSummaryNode).getSummaryNode(), true, model) and
c = nodeFrom.(Node::FlowSummaryNode).getSummarizedCallable()
)
or
FlowSummaryImpl::Private::Steps::sourceLocalStep(nodeFrom
.(Node::FlowSummaryNode)
.getSummaryNode(), nodeTo, model)
or
FlowSummaryImpl::Private::Steps::sinkLocalStep(nodeFrom,
nodeTo.(Node::FlowSummaryNode).getSummaryNode(), model)
}

pragma[nomagic]
Expand Down Expand Up @@ -848,7 +886,7 @@ module RustDataFlow implements InputSig<Location> {
predicate nodeIsHidden(Node node) {
node instanceof Node::SsaNode
or
node instanceof Node::FlowSummaryNode
node.(Node::FlowSummaryNode).getSummaryNode().isHidden()
or
node instanceof Node::CaptureNode
or
Expand All @@ -864,6 +902,10 @@ module RustDataFlow implements InputSig<Location> {
node.asExpr() = match.getScrutinee() or
node.asExpr() = match.getArmPat(_)
)
or
FlowSummaryImpl::Private::Steps::sourceLocalStep(_, node, _)
or
FlowSummaryImpl::Private::Steps::sinkLocalStep(node, _, _)
}

class DataFlowExpr = ExprCfgNode;
Expand Down Expand Up @@ -944,7 +986,7 @@ module RustDataFlow implements InputSig<Location> {
) and
model = ""
or
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, _, model)
LocalFlow::flowSummaryLocalStep(nodeFrom, nodeTo, model)
}

/**
Expand Down Expand Up @@ -1499,6 +1541,14 @@ private module Cached {

cached
newtype TContentSet = TSingletonContentSet(Content c)

/** Holds if `n` is a flow source of kind `kind`. */
cached
predicate sourceNode(Node n, string kind) { n.(Node::FlowSummaryNode).isSource(kind) }

/** Holds if `n` is a flow sink of kind `kind`. */
cached
predicate sinkNode(Node n, string kind) { n.(Node::FlowSummaryNode).isSink(kind) }
}

import Cached
60 changes: 60 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,48 @@ private import codeql.rust.dataflow.internal.DataFlowImpl
private import codeql.rust.dataflow.FlowSummary

module Input implements InputSig<Location, RustDataFlow> {
private import codeql.rust.elements.internal.CallExprBaseImpl::Impl as CallExprBaseImpl

class SummarizedCallableBase = string;

abstract private class SourceSinkBase extends AstNode {
/** Gets the associated call. */
abstract CallExprBase getCall();

/** Holds if the associated call resolves to `crate, path`. */
final predicate callResolvesTo(string crate, string path) {
exists(Resolvable r |
r = CallExprBaseImpl::getCallResolvable(this.getCall()) and
path = r.getResolvedPath()
|
crate = r.getResolvedCrateOrigin()
or
not r.hasResolvedCrateOrigin() and
crate = ""
)
}
}

abstract class SourceBase extends SourceSinkBase { }

abstract class SinkBase extends SourceSinkBase { }

private class CallExprFunction extends SourceBase, SinkBase {
private CallExpr call;

CallExprFunction() { this = call.getFunction() }

override CallExpr getCall() { result = call }
}

private class MethodCallExprNameRef extends SourceBase, SinkBase {
private MethodCallExpr call;

MethodCallExprNameRef() { this = call.getNameRef() }

override MethodCallExpr getCall() { result = call }
}

RustDataFlow::ArgumentPosition callbackSelfParameterPosition() { none() }

ReturnKind getStandardReturnValueKind() { result = TNormalReturnKind() }
Expand Down Expand Up @@ -86,12 +126,32 @@ private module StepsInput implements Impl::Private::StepsInputSig {
DataFlowCall getACall(Public::SummarizedCallable sc) {
result.asCallBaseExprCfgNode().getCallExprBase() = sc.(LibraryCallable).getACall()
}

Node getSourceNode(Input::SourceBase source, Impl::Private::SummaryComponent sc) {
sc = Impl::Private::SummaryComponent::return(_) and
result.asExpr().getExpr() = source.getCall()
}

Node getSinkNode(Input::SinkBase sink, Impl::Private::SummaryComponent sc) {
exists(CallExprBase call, Expr arg, ParameterPosition pos |
result.asExpr().getExpr() = arg and
sc = Impl::Private::SummaryComponent::argument(pos) and
call = sink.getCall()
|
arg = call.getArgList().getArg(pos.getPosition())
or
arg = call.(MethodCallExpr).getReceiver() and pos.isSelf()
)
}
}

module Private {
import Impl::Private

module Steps = Impl::Private::Steps<StepsInput>;

private import codeql.rust.dataflow.FlowSource
private import codeql.rust.dataflow.FlowSink
}

module Public = Impl::Public;
Expand Down
36 changes: 36 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@

private import rust
private import codeql.rust.dataflow.FlowSummary
private import codeql.rust.dataflow.FlowSource
private import codeql.rust.dataflow.FlowSink

/**
* Holds if in a call to the function with canonical path `path`, defined in the
Expand Down Expand Up @@ -138,3 +140,37 @@ private class SummarizedCallableFromModel extends SummarizedCallable::Range {
)
}
}

private class FlowSourceFromModel extends FlowSource::Range {
private string crate;
private string path;

FlowSourceFromModel() {
sourceModel(crate, path, _, _, _, _) and
this.callResolvesTo(crate, path)
}

override predicate isSource(string output, string kind, Provenance provenance, string model) {
exists(QlBuiltins::ExtensionId madId |
sourceModel(crate, path, output, kind, provenance, madId) and
model = "MaD:" + madId.toString()
)
}
}

private class FlowSinkFromModel extends FlowSink::Range {
private string crate;
private string path;

FlowSinkFromModel() {
sinkModel(crate, path, _, _, _, _) and
this.callResolvesTo(crate, path)
}

override predicate isSink(string input, string kind, Provenance provenance, string model) {
exists(QlBuiltins::ExtensionId madId |
sinkModel(crate, path, input, kind, provenance, madId) and
model = "MaD:" + madId.toString()
)
}
}
6 changes: 6 additions & 0 deletions rust/ql/lib/utils/test/InlineFlowTest.qll
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ private module FlowTestImpl implements InputSig<Location, RustDataFlow> {
private string getSourceArgString(DataFlow::Node src) {
defaultSource(src) and
result = src.asExpr().(CallExprCfgNode).getArgument(0).toString()
or
sourceNode(src, _) and
exists(CallExprBase call |
call = src.(Node::FlowSummaryNode).getSourceNode().getCall() and
result = call.getArgList().getArg(0).toString()
)
}

bindingset[src, sink]
Expand Down
Loading

0 comments on commit 2a44dd2

Please sign in to comment.