Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/FlowBarrier.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Provides classes and predicates for defining barriers.
*
* Flow barriers defined here feed into data flow configurations as follows:
*
* ```text
* data from *.model.yml | QL extensions of FlowBarrier::Range
* v v
* FlowBarrier (associated with a models-as-data kind string)
* v
* barrierNode predicate | other QL defined barriers, for example using concepts
* v v
* various Barrier classes for specific data flow configurations
* ```
*
* New barriers should be defined using models-as-data, QL extensions of
* `FlowBarrier::Range`, or concepts. Data flow configurations should use the
* `barrierNode` predicate and/or concepts to define their barriers.
*/

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

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

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

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

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

final class FlowBarrier = FlowBarrier::Range;

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

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

/**
* Holds if this element is a flow barrier guard of kind `kind`, for data
* flowing in as described by `input`, when `this` evaluates to `branch`.
*/
predicate isBarrierGuard(string input, string branch, string kind) { none() }
}
}

final class FlowBarrierGuard = FlowBarrierGuard::Range;

predicate barrierNode = DataFlowImpl::barrierNode/2;
89 changes: 89 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,64 @@ private module Cached {
cached
predicate sinkNode(Node n, string kind) { n.(FlowSummaryNode).isSink(kind, _) }

private newtype TKindModelPair =
TMkPair(string kind, string model) {
FlowSummaryImpl::Private::barrierGuardSpec(_, _, _, kind, model)
}

private boolean convertAcceptingValue(FlowSummaryImpl::Public::AcceptingValue av) {
av.isTrue() and result = true
or
av.isFalse() and result = false
// Remaining cases are not supported yet, they depend on the shared Guards library.
// or
// av.isNoException() and result.getDualValue().isThrowsException()
// or
// av.isZero() and result.asIntValue() = 0
// or
// av.isNotZero() and result.getDualValue().asIntValue() = 0
// or
// av.isNull() and result.isNullValue()
// or
// av.isNotNull() and result.isNonNullValue()
}

private predicate barrierGuardChecks(AstNode g, Expr e, boolean gv, TKindModelPair kmp) {
exists(
FlowSummaryImpl::Public::BarrierGuardElement b,
FlowSummaryImpl::Private::SummaryComponentStack stack,
FlowSummaryImpl::Public::AcceptingValue acceptingvalue, string kind, string model
|
FlowSummaryImpl::Private::barrierGuardSpec(b, stack, acceptingvalue, kind, model) and
e = FlowSummaryImpl::StepsInput::getSinkNode(b, stack.headOfSingleton()).asExpr() and
kmp = TMkPair(kind, model) and
gv = convertAcceptingValue(acceptingvalue) and
g = b.getCall()
)
}

/** Holds if `n` is a flow barrier of kind `kind` and model `model`. */
cached
predicate barrierNode(Node n, string kind, string model) {
exists(
FlowSummaryImpl::Public::BarrierElement b,
FlowSummaryImpl::Private::SummaryComponentStack stack
|
FlowSummaryImpl::Private::barrierSpec(b, stack, kind, model)
|
n = FlowSummaryImpl::StepsInput::getSourceNode(b, stack, false)
or
// For barriers like `Argument[0]` we want to target the pre-update node
n =
FlowSummaryImpl::StepsInput::getSourceNode(b, stack, true)
.(PostUpdateNode)
.getPreUpdateNode()
)
or
ParameterizedBarrierGuard<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
model)) = n
}

/**
* A step in a flow summary defined using `OptionalStep[name]`. An `OptionalStep` is "opt-in", which means
* that by default the step is not present in the flow summary and needs to be explicitly enabled by defining
Expand All @@ -1180,3 +1238,34 @@ private module Cached {
}

import Cached

/** Holds if `n` is a flow barrier of kind `kind`. */
predicate barrierNode(Node n, string kind) { barrierNode(n, kind, _) }

bindingset[this]
private signature class ParamSig;

private module WithParam<ParamSig P> {
/**
* Holds if the guard `g` validates the expression `e` upon evaluating to `gv`.
*
* The expression `e` is expected to be a syntactic part of the guard `g`.
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
* the argument `x`.
*/
signature predicate guardChecksSig(AstNode g, Expr e, boolean branch, P param);
}

/**
* Provides a set of barrier nodes for a guard that validates an expression.
*
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
* in data flow and taint tracking.
*/
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
/** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode(P param) {
SsaFlow::asNode(result) =
SsaImpl::DataFlowIntegration::ParameterizedBarrierGuard<P, guardChecks/4>::getABarrierNode(param)
}
}
19 changes: 14 additions & 5 deletions rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ module Input implements InputSig<Location, RustDataFlow> {

private import Make<Location, RustDataFlow, Input> as Impl

private module StepsInput implements Impl::Private::StepsInputSig {
module StepsInput implements Impl::Private::StepsInputSig {
DataFlowCall getACall(Public::SummarizedCallable sc) { result.asCall().getStaticTarget() = sc }

/** Gets the argument of `source` described by `sc`, if any. */
Expand Down Expand Up @@ -171,18 +171,27 @@ private module StepsInput implements Impl::Private::StepsInputSig {
result.asCfgScope() = source.getEnclosingCfgScope()
}

RustDataFlow::Node getSourceNode(Input::SourceBase source, Impl::Private::SummaryComponentStack s) {
additional RustDataFlow::Node getSourceNode(
Input::SourceBase source, Impl::Private::SummaryComponentStack s, boolean isArgPostUpdate
) {
s.head() = Impl::Private::SummaryComponent::return(_) and
result.asExpr() = source.getCall()
result.asExpr() = source.getCall() and
isArgPostUpdate = false
or
exists(RustDataFlow::ArgumentPosition pos, Expr arg |
s.head() = Impl::Private::SummaryComponent::parameter(pos) and
arg = getSourceNodeArgument(source, s.tail().headOfSingleton()) and
result.asParameter() = getCallable(arg).getParam(pos.getPosition())
result.asParameter() = getCallable(arg).getParam(pos.getPosition()) and
isArgPostUpdate = false
)
or
result.(RustDataFlow::PostUpdateNode).getPreUpdateNode().asExpr() =
getSourceNodeArgument(source, s.headOfSingleton())
getSourceNodeArgument(source, s.headOfSingleton()) and
isArgPostUpdate = true
}

RustDataFlow::Node getSourceNode(Input::SourceBase source, Impl::Private::SummaryComponentStack s) {
result = getSourceNode(source, s, _)
}

RustDataFlow::Node getSinkNode(Input::SinkBase sink, Impl::Private::SummaryComponent sc) {
Expand Down
68 changes: 68 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
*/

private import rust
private import codeql.rust.dataflow.FlowBarrier
private import codeql.rust.dataflow.FlowSummary
private import codeql.rust.dataflow.FlowSource
private import codeql.rust.dataflow.FlowSink
Expand Down Expand Up @@ -98,6 +99,29 @@
string path, string kind, string provenance, QlBuiltins::ExtensionId madId
);

/**
* Holds if in a call to the function with canonical path `path`, the value referred
* to by `output` is a barrier of the given `kind` and `madId` is the data
* extension row number.
*/
extensible predicate barrierModel(
string path, string output, string kind, string provenance, QlBuiltins::ExtensionId madId
);

/**
* Holds if in a call to the function with canonical path `path`, the value referred
* to by `input` is a barrier guard of the given `kind` and `madId` is the data
* extension row number.
* the value referred to by `input` is assumed to lead to a parameter of a call
* (possibly `self`), and the call is guarding the parameter.
* `branch` is either `true` or `false`, indicating which branch of the guard
* is protecting the parameter.
Comment on lines +115 to +118
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The barrierGuardModel doc comment has a couple issues: the sentence at line 115 starts with a lowercase "the", and branch is documented as boolean (true/false) even though the predicate parameter is a string. Please adjust the wording to be grammatically consistent and clarify the expected string values (for example, literal strings "true"/"false").

Suggested change
* the value referred to by `input` is assumed to lead to a parameter of a call
* (possibly `self`), and the call is guarding the parameter.
* `branch` is either `true` or `false`, indicating which branch of the guard
* is protecting the parameter.
* The value referred to by `input` is assumed to lead to a parameter of a call
* (possibly `self`), and the call is guarding the parameter.
* `branch` is either `"true"` or `"false"` (as a string literal), indicating which
* branch of the guard is protecting the parameter.

Copilot uses AI. Check for mistakes.
*/
extensible predicate barrierGuardModel(

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for provenance, but the QLDoc mentions self
string path, string input, string branch, string kind, string provenance,
QlBuiltins::ExtensionId madId
);

/**
* Holds if the given extension tuple `madId` should pretty-print as `model`.
*
Expand All @@ -123,6 +147,16 @@
neutralModel(path, kind, _, madId) and
model = "Neutral: " + path + "; " + kind
)
or
exists(string path, string output, string kind |
barrierModel(path, output, kind, _, madId) and
model = "Barrier: " + path + "; " + output + "; " + kind
)
or
exists(string path, string input, string branch, string kind |
barrierGuardModel(path, input, branch, kind, _, madId) and
model = "Barrier guard: " + path + "; " + input + "; " + branch + "; " + kind
)
}

private class SummarizedCallableFromModel extends SummarizedCallable::Range {
Expand Down Expand Up @@ -206,6 +240,40 @@
}
}

private class FlowBarrierFromModel extends FlowBarrier::Range {
private string path;

FlowBarrierFromModel() {
barrierModel(path, _, _, _, _) and
this.callResolvesTo(path)
}

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

private class FlowBarrierGuardFromModel extends FlowBarrierGuard::Range {
private string path;

FlowBarrierGuardFromModel() {
barrierGuardModel(path, _, _, _, _, _) and
this.callResolvesTo(path)
}

override predicate isBarrierGuard(
string input, string branch, string kind, Provenance provenance, string model
) {
exists(QlBuiltins::ExtensionId madId |
barrierGuardModel(path, input, branch, kind, provenance, madId) and
model = "MaD:" + madId.toString()
)
}
}

private module Debug {
private import FlowSummaryImpl
private import Private
Expand Down
4 changes: 2 additions & 2 deletions rust/ql/lib/codeql/rust/dataflow/internal/Node.qll
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ class FlowSummaryNode extends Node, TFlowSummaryNode {
result = this.getSummaryNode().getSinkElement()
}

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

/** Holds is this node is a sink node of kind `kind`. */
/** Holds if this node is a sink node of kind `kind`. */
predicate isSink(string kind, string model) {
this.getSummaryNode().(FlowSummaryImpl::Private::SinkInputNode).isExit(kind, model)
}
Expand Down
25 changes: 25 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,31 @@ private module Cached {

predicate getABarrierNode = getABarrierNodeImpl/0;
}

bindingset[this]
private signature class ParamSig;

private module WithParam<ParamSig P> {
signature predicate guardChecksSig(AstNode g, Expr e, boolean branch, P param);
}

overlay[global]
cached // nothing is actually cached
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
private predicate guardChecksAdjTypes(
DataFlowIntegrationInput::Guard g, DataFlowIntegrationInput::Expr e,
DataFlowIntegrationInput::GuardValue branch, P param
) {
guardChecks(g, e, branch, param)
}

private Node getABarrierNodeImpl(P param) {
result =
DataFlowIntegrationImpl::BarrierGuardWithState<P, guardChecksAdjTypes/4>::getABarrierNode(param)
}

predicate getABarrierNode = getABarrierNodeImpl/1;
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/empty.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,13 @@ extensions:
pack: codeql/rust-all
extensible: summaryModel
data: []

- addsTo:
pack: codeql/rust-all
extensible: barrierModel
data: []

- addsTo:
pack: codeql/rust-all
extensible: barrierGuardModel
data: []
Loading
Loading