Skip to content

Commit

Permalink
sinks
Browse files Browse the repository at this point in the history
  • Loading branch information
hvitved committed Dec 17, 2024
1 parent 6902cef commit ca2b719
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 73 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;
31 changes: 26 additions & 5 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -196,26 +196,38 @@ module Node {
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.getSummaryNode().getSourceNode().getEnclosingCfgScope()
result.asCfgScope() = this.getCfgScope()
}

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 @@ -545,14 +557,17 @@ private ExprCfgNode getALastEvalNode(ExprCfgNode e) {
}

module LocalFlow {
predicate flowSummaryLocalStep(Node::FlowSummaryNode nodeFrom, Node nodeTo, string model) {
predicate flowSummaryLocalStep(Node nodeFrom, Node nodeTo, string model) {
exists(FlowSummaryImpl::Public::SummarizedCallable c |
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.getSummaryNode(),
nodeTo.(Node::FlowSummaryNode).getSummaryNode(), true, model) and
c = nodeFrom.getSummarizedCallable()
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom
.(Node::FlowSummaryNode)
.getSummaryNode(), nodeTo.(Node::FlowSummaryNode).getSummaryNode(), true, model) and
c = nodeFrom.(Node::FlowSummaryNode).getSummarizedCallable()
)
or
FlowSummaryImpl::Private::localSourceNodeStep(nodeFrom, nodeTo, model)
or
FlowSummaryImpl::Private::localSinkNodeStep(nodeFrom, nodeTo, model)
}

pragma[nomagic]
Expand Down Expand Up @@ -886,6 +901,8 @@ module RustDataFlow implements InputSig<Location> {
)
or
FlowSummaryImpl::Private::localSourceNodeStep(_, node, _)
or
FlowSummaryImpl::Private::localSinkNodeStep(node, _, _)
}

class DataFlowExpr = ExprCfgNode;
Expand Down Expand Up @@ -1525,6 +1542,10 @@ private module Cached {
/** 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
28 changes: 23 additions & 5 deletions rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,26 @@ private import codeql.rust.dataflow.FlowSummary
module Input implements InputSig<Location, RustDataFlow> {
class SummarizedCallableBase = string;

abstract class SourceBase extends Expr {
abstract class SourceBase extends AstNode {
abstract CallExprBase getCall();
}

private class CallExprSource extends SourceBase {
abstract class SinkBase extends AstNode {
abstract CallExprBase getCall();
}

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

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

override CallExpr getCall() { result = call }
}

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

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

override MethodCallExpr getCall() { result = call }
}
Expand Down Expand Up @@ -114,6 +118,7 @@ module Private {
module Steps = Impl::Private::Steps<StepsInput>;

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

predicate localSourceNodeStep(Node::FlowSummaryNode nodeFrom, Node::ExprNode nodeTo, string model) {
exists(SummaryComponent sc, FlowSource source |
Expand All @@ -122,6 +127,19 @@ module Private {
nodeTo.asExpr().getExpr() = source.getCall()
)
}

predicate localSinkNodeStep(Node::ExprNode nodeFrom, Node::FlowSummaryNode nodeTo, string model) {
exists(CallExprBase call, Expr arg, SummaryComponent sc, FlowSink sink, ParameterPosition pos |
nodeFrom.asExpr().getExpr() = arg and
nodeTo.getSummaryNode().(SinkInputNode).isEntry(sink, sc, model) and
sc = SummaryComponent::argument(pos) and
call = sink.getCall()
|
arg = call.getArgList().getArg(pos.getPosition())
or
arg = call.(MethodCallExpr).getReceiver() and pos.isSelf()
)
}
}

module Public = Impl::Public;
Expand Down
26 changes: 26 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
private import rust
private import codeql.rust.dataflow.FlowSummary
private import codeql.rust.dataflow.FlowSource
private import codeql.rust.dataflow.FlowSink
private import codeql.rust.elements.internal.CallExprBaseImpl::Impl as CallExprBaseImpl

/**
Expand Down Expand Up @@ -165,3 +166,28 @@ private class FlowSourceFromModel extends FlowSource::Range {
)
}
}

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

FlowSinkFromModel() {
sinkModel(crate, path, _, _, _, _) and
exists(Resolvable r |
r = CallExprBaseImpl::getCallResolvable(this.getCall()) and
path = r.getResolvedPath()
|
crate = r.getResolvedCrateOrigin()
or
not r.hasResolvedCrateOrigin() and
crate = ""
)
}

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()
)
}
}
2 changes: 1 addition & 1 deletion rust/ql/lib/utils/test/InlineFlowTest.qll
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private module FlowTestImpl implements InputSig<Location, RustDataFlow> {
result = src.asExpr().(CallExprCfgNode).getArgument(0).toString()
or
sourceNode(src, _) and
exists(CallExpr call |
exists(CallExprBase call |
call = src.(Node::FlowSummaryNode).getSourceNode().getCall() and
result = call.getArgList().getArg(0).toString()
)
Expand Down
37 changes: 37 additions & 0 deletions rust/ql/test/library-tests/dataflow/models/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ fn test_set_tuple_element() {
sink(t.1); // $ hasValueFlow=11
}

impl MyFieldEnum {
// has a source model
fn source(&self, i: i64) -> MyFieldEnum {
MyFieldEnum::C { field_c: 0 }
}

// has a sink model
fn sink(self) {}
}

// has a source model
fn enum_source(i: i64) -> MyFieldEnum {
MyFieldEnum::C { field_c: 0 }
Expand All @@ -188,6 +198,30 @@ fn test_enum_source() {
}
}

fn test_enum_method_source() {
let e = MyFieldEnum::D { field_d: 0 };
let s = e.source(13);
match s {
MyFieldEnum::C { field_c: i } => sink(i), // $ hasValueFlow=13
MyFieldEnum::D { field_d: i } => sink(i),
}
}

// has a sink model
fn enum_sink(e: MyFieldEnum) {}

fn test_enum_sink() {
let s = source(14);
enum_sink(MyFieldEnum::C { field_c: s }); // $ hasValueFlow=14
enum_sink(MyFieldEnum::D { field_d: s });
}

fn test_enum_method_sink() {
let s = source(15);
let e = MyFieldEnum::D { field_d: s };
e.sink(); // $ hasValueFlow=15
}

fn main() {
test_identify();
test_get_var_pos();
Expand All @@ -201,5 +235,8 @@ fn main() {
test_get_tuple_element();
test_set_tuple_element();
test_enum_source();
test_enum_method_source();
test_enum_sink();
test_enum_method_sink();
let dummy = Some(0); // ensure that the the `lang:core` crate is extracted
}
Loading

0 comments on commit ca2b719

Please sign in to comment.