Merge branch 'main' into redsun82/kotlin-language-version-min-1.9

This commit is contained in:
Paolo Tranquilli
2026-03-24 14:24:07 +01:00
committed by GitHub
37 changed files with 931 additions and 21983 deletions

View File

@@ -0,0 +1,4 @@
---
category: feature
---
* Added a class `IndirectUninitializedNode` to represent the indirection of an uninitialized local variable as a dataflow node.

View File

@@ -0,0 +1,5 @@
---
category: feature
---
* Added a class `DataFlow::IndirectParameterNode` to represent the indirection of a parameter as a dataflow node.
* Added a predicate `Node::asIndirectInstruction` which returns the `Instruction` that defines the indirect dataflow node, if any.

View File

@@ -163,12 +163,23 @@ predicate primitiveVariadicFormatter(
)
}
/**
* Gets a function call whose target is a variadic formatter with the given
* `type`, `format` parameter index and `output` parameter index.
*
* Join-order helper for `callsVariadicFormatter`.
*/
pragma[nomagic]
private predicate callsVariadicFormatterCall(FunctionCall fc, string type, int format, int output) {
variadicFormatter(fc.getTarget(), type, format, output)
}
private predicate callsVariadicFormatter(
Function f, string type, int formatParamIndex, int outputParamIndex
) {
// calls a variadic formatter with `formatParamIndex`, `outputParamIndex` linked
exists(FunctionCall fc, int format, int output |
variadicFormatter(pragma[only_bind_into](fc.getTarget()), type, format, output) and
callsVariadicFormatterCall(fc, type, format, output) and
fc.getEnclosingFunction() = f and
fc.getArgument(format) = f.getParameter(formatParamIndex).getAnAccess() and
fc.getArgument(output) = f.getParameter(outputParamIndex).getAnAccess()
@@ -176,7 +187,7 @@ private predicate callsVariadicFormatter(
or
// calls a variadic formatter with only `formatParamIndex` linked
exists(FunctionCall fc, string calledType, int format, int output |
variadicFormatter(pragma[only_bind_into](fc.getTarget()), calledType, format, output) and
callsVariadicFormatterCall(fc, calledType, format, output) and
fc.getEnclosingFunction() = f and
fc.getArgument(format) = f.getParameter(formatParamIndex).getAnAccess() and
not fc.getArgument(output) = f.getParameter(_).getAnAccess() and

View File

@@ -321,6 +321,12 @@ module Public {
*/
Operand asIndirectOperand(int index) { hasOperandAndIndex(this, result, index) }
/**
* Gets the instruction that is indirectly tracked by this node behind
* `index` number of indirections.
*/
Instruction asIndirectInstruction(int index) { hasInstructionAndIndex(this, result, index) }
/**
* Holds if this node is at index `i` in basic block `block`.
*
@@ -617,6 +623,25 @@ module Public {
*/
LocalVariable asUninitialized() { result = this.(UninitializedNode).getLocalVariable() }
/**
* Gets the uninitialized local variable corresponding to this node behind
* `index` number of indirections, if any.
*/
LocalVariable asIndirectUninitialized(int index) {
exists(IndirectUninitializedNode indirectUninitializedNode |
this = indirectUninitializedNode and
indirectUninitializedNode.getIndirectionIndex() = index
|
result = indirectUninitializedNode.getLocalVariable()
)
}
/**
* Gets the uninitialized local variable corresponding to this node behind
* a number indirections, if any.
*/
LocalVariable asIndirectUninitialized() { result = this.asIndirectUninitialized(_) }
/**
* Gets the positional parameter corresponding to the node that represents
* the value of the parameter after `index` number of loads, if any. For
@@ -761,16 +786,13 @@ module Public {
final override Type getType() { result = this.getPreUpdateNode().getType() }
}
/**
* The value of an uninitialized local variable, viewed as a node in a data
* flow graph.
*/
class UninitializedNode extends Node {
abstract private class AbstractUninitializedNode extends Node {
LocalVariable v;
int indirectionIndex;
UninitializedNode() {
AbstractUninitializedNode() {
exists(SsaImpl::Definition def, SsaImpl::SourceVariable sv |
def.getIndirectionIndex() = 0 and
def.getIndirectionIndex() = indirectionIndex and
def.getValue().asInstruction() instanceof UninitializedInstruction and
SsaImpl::defToNode(this, def, sv) and
v = sv.getBaseVariable().(SsaImpl::BaseIRVariable).getIRVariable().getAst()
@@ -781,6 +803,25 @@ module Public {
LocalVariable getLocalVariable() { result = v }
}
/**
* The value of an uninitialized local variable, viewed as a node in a data
* flow graph.
*/
class UninitializedNode extends AbstractUninitializedNode {
UninitializedNode() { indirectionIndex = 0 }
}
/**
* The value of an uninitialized local variable behind one or more levels of
* indirection, viewed as a node in a data flow graph.
*/
class IndirectUninitializedNode extends AbstractUninitializedNode {
IndirectUninitializedNode() { indirectionIndex > 0 }
/** Gets the indirection index of this node. */
int getIndirectionIndex() { result = indirectionIndex }
}
/**
* The value of a parameter at function entry, viewed as a node in a data
* flow graph. This includes both explicit parameters such as `x` in `f(x)`
@@ -795,6 +836,12 @@ module Public {
/** An explicit positional parameter, including `this`, but not `...`. */
final class DirectParameterNode = AbstractDirectParameterNode;
/**
* A node representing an indirection of a positional parameter,
* including `*this`, but not `*...`.
*/
final class IndirectParameterNode = AbstractIndirectParameterNode;
final class ExplicitParameterNode = AbstractExplicitParameterNode;
/** An implicit `this` parameter. */
@@ -954,11 +1001,6 @@ module Public {
private import Public
/**
* A node representing an indirection of a parameter.
*/
final class IndirectParameterNode = AbstractIndirectParameterNode;
/**
* A class that lifts pre-SSA dataflow nodes to regular dataflow nodes.
*/

View File

@@ -0,0 +1,7 @@
---
category: minorAnalysis
---
* The `cs/log-forging` query no longer treats arguments to extension methods with
source code on `ILogger` types as sinks. Instead, taint is tracked interprocedurally
through extension method bodies, reducing false positives when extension methods
sanitize input internally.

View File

@@ -52,9 +52,17 @@ private class HtmlSanitizer extends Sanitizer {
}
/**
* An argument to a call to a method on a logger class.
* An argument to a call to a method on a logger class, excluding extension methods
* with source code which are analyzed interprocedurally.
*/
private class LogForgingLogMessageSink extends Sink, LogMessageSink { }
private class LogForgingLogMessageSink extends Sink, LogMessageSink {
LogForgingLogMessageSink() {
not exists(ExtensionMethodCall mc |
this.getExpr() = mc.getAnArgument() and
mc.getTarget().fromSource()
)
}
}
/**
* An argument to a call to a method on a trace class.

View File

@@ -33,6 +33,11 @@ public class LogForgingHandler : IHttpHandler
Microsoft.Extensions.Logging.ILogger logger2 = null;
// BAD: Logged as-is
logger2.LogError(username); // $ Alert
// GOOD: uses safe extension method that sanitizes internally
logger.WarnSafe(username + " logged in");
// BAD: uses unsafe extension method that does not sanitize
logger.WarnUnsafe(username + " logged in");
}
public bool IsReusable
@@ -43,3 +48,16 @@ public class LogForgingHandler : IHttpHandler
}
}
}
static class UserLoggerExtensions
{
public static void WarnSafe(this ILogger logger, string message)
{
logger.Warn(message.ReplaceLineEndings(""));
}
public static void WarnUnsafe(this ILogger logger, string message)
{
logger.Warn(message); // $ Alert
}
}

View File

@@ -2,14 +2,18 @@
| LogForging.cs:21:21:21:43 | ... + ... | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:21:21:21:43 | ... + ... | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
| LogForging.cs:31:50:31:72 | ... + ... | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:31:50:31:72 | ... + ... | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
| LogForging.cs:35:26:35:33 | access to local variable username | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:35:26:35:33 | access to local variable username | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
| LogForging.cs:61:21:61:27 | access to parameter message | LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:61:21:61:27 | access to parameter message | This log entry depends on a $@. | LogForging.cs:18:27:18:49 | access to property QueryString | user-provided value |
| LogForgingAsp.cs:17:21:17:43 | ... + ... | LogForgingAsp.cs:13:32:13:39 | username : String | LogForgingAsp.cs:17:21:17:43 | ... + ... | This log entry depends on a $@. | LogForgingAsp.cs:13:32:13:39 | username | user-provided value |
edges
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:21:21:21:43 | ... + ... | provenance | |
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:31:50:31:72 | ... + ... | provenance | |
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:35:26:35:33 | access to local variable username | provenance | |
| LogForging.cs:18:16:18:23 | access to local variable username : String | LogForging.cs:40:27:40:49 | ... + ... : String | provenance | |
| LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:18:16:18:23 | access to local variable username : String | provenance | |
| LogForging.cs:18:27:18:49 | access to property QueryString : NameValueCollection | LogForging.cs:18:27:18:61 | access to indexer : String | provenance | MaD:1 |
| LogForging.cs:18:27:18:61 | access to indexer : String | LogForging.cs:18:16:18:23 | access to local variable username : String | provenance | |
| LogForging.cs:40:27:40:49 | ... + ... : String | LogForging.cs:59:63:59:69 | message : String | provenance | |
| LogForging.cs:59:63:59:69 | message : String | LogForging.cs:61:21:61:27 | access to parameter message | provenance | |
| LogForgingAsp.cs:13:32:13:39 | username : String | LogForgingAsp.cs:17:21:17:43 | ... + ... | provenance | |
models
| 1 | Summary: System.Collections.Specialized; NameValueCollection; false; get_Item; (System.String); ; Argument[this]; ReturnValue; taint; df-generated |
@@ -20,6 +24,9 @@ nodes
| LogForging.cs:21:21:21:43 | ... + ... | semmle.label | ... + ... |
| LogForging.cs:31:50:31:72 | ... + ... | semmle.label | ... + ... |
| LogForging.cs:35:26:35:33 | access to local variable username | semmle.label | access to local variable username |
| LogForging.cs:40:27:40:49 | ... + ... : String | semmle.label | ... + ... : String |
| LogForging.cs:59:63:59:69 | message : String | semmle.label | message : String |
| LogForging.cs:61:21:61:27 | access to parameter message | semmle.label | access to parameter message |
| LogForgingAsp.cs:13:32:13:39 | username : String | semmle.label | username : String |
| LogForgingAsp.cs:17:21:17:43 | ... + ... | semmle.label | ... + ... |
subpaths

View File

@@ -129,7 +129,7 @@ Java/Kotlin
"""""""""""
* The Java extractor and QL libraries now support Java 23.
* Kotlin versions up to 2.1.0\ *x* are now supported.
* Kotlin versions up to 2.1.0*x* are now supported.
Python
""""""

View File

@@ -144,7 +144,7 @@ New Features
Java/Kotlin
"""""""""""
* Kotlin versions up to 2.2.0\ *x* are now supported. Support for the Kotlin 1.5.x series is dropped (so the minimum Kotlin version is now 1.6.0).
* Kotlin versions up to 2.2.0*x* are now supported. Support for the Kotlin 1.5.x series is dropped (so the minimum Kotlin version is now 1.6.0).
Swift
"""""

View File

@@ -98,4 +98,4 @@ C/C++
Java/Kotlin
"""""""""""
* Kotlin versions up to 2.2.2\ *x* are now supported.
* Kotlin versions up to 2.2.2*x* are now supported.

View File

@@ -0,0 +1,121 @@
.. _codeql-cli-2.24.3:
==========================
CodeQL 2.24.3 (2026-03-05)
==========================
.. contents:: Contents
:depth: 2
:local:
:backlinks: none
This is an overview of changes in the CodeQL CLI and relevant CodeQL query and library packs. For additional updates on changes to the CodeQL code scanning experience, check out the `code scanning section on the GitHub blog <https://github.blog/tag/code-scanning/>`__, `relevant GitHub Changelog updates <https://github.blog/changelog/label/application-security/>`__, `changes in the CodeQL extension for Visual Studio Code <https://marketplace.visualstudio.com/items/GitHub.vscode-codeql/changelog>`__, and the `CodeQL Action changelog <https://github.com/github/codeql-action/blob/main/CHANGELOG.md>`__.
Security Coverage
-----------------
CodeQL 2.24.3 runs a total of 491 security queries when configured with the Default suite (covering 166 CWE). The Extended suite enables an additional 135 queries (covering 35 more CWE).
CodeQL CLI
----------
Bug Fixes
~~~~~~~~~
* Fixed a race condition that could cause flaky failures in overlay CodeQL tests. Test extraction now skips :code:`*.testproj` directories by name, preventing interference from concurrently cleaned-up test databases.
* Fixed spurious "OOPS" warnings that could appear in help output for commands using mutually exclusive option groups, such as :code:`codeql query run`.
Query Packs
-----------
Minor Analysis Improvements
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Java/Kotlin
"""""""""""
* The Java extractor and QL libraries now support Java 26.
* Java analysis now selects the Java version to use informed by Maven POM files across all project modules. It also tries to use Java 17 or higher for all Maven projects if possible, for improved build compatibility.
Rust
""""
* The macro resolution metric has been removed from :code:`rust/diagnostic/database-quality`. This metric was found to be an unreliable indicator of database quality in many cases, leading to false alarms on the tool status page.
Language Libraries
------------------
Bug Fixes
~~~~~~~~~
C/C++
"""""
* The :code:`allowInterproceduralFlow` predicate of must-flow data flow configurations now correctly handles direct recursion.
C#
""
* Fixed an issue where the body of a partial member could be extracted twice. When both a *defining* and an *implementing* declaration exist, only the *implementing* declaration is now extracted.
Breaking Changes
~~~~~~~~~~~~~~~~
C/C++
"""""
* CodeQL version 2.24.2 accidentally introduced a syntactical breaking change to :code:`BarrierGuard<...>::getAnIndirectBarrierNode` and :code:`InstructionBarrierGuard<...>::getAnIndirectBarrierNode`. These breaking changes have now been reverted so that the original code compiles again.
* :code:`MustFlow`, the inter-procedural must-flow data flow analysis library, has been re-worked to use parameterized modules. Like in the case of data flow and taint tracking, instead of extending the :code:`MustFlowConfiguration` class, the user should now implement a module with the :code:`MustFlow::ConfigSig` signature, and instantiate the :code:`MustFlow::Global` parameterized module with the implemented module.
Python
""""""
* The :code:`Metrics` library no longer contains code that depends on the points-to analysis. The removed functionality has instead been moved to the :code:`LegacyPointsTo` module, to classes like :code:`ModuleMetricsWithPointsTo` etc. If you depend on any of these classes, you must now remember to import :code:`LegacyPointsTo`, and use the appropriate types in order to use the points-to-based functionality.
Major Analysis Improvements
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Python
""""""
* The CodeQL Python libraries have been updated to be compatible with overlay evaluation. This should result in a significant speedup on analyses for which a base database already exists. Note that it may be necessary to add :code:`overlay[local?] module;` to user-managed libraries that extend classes that are now marked as :code:`overlay[local]`.
Minor Analysis Improvements
~~~~~~~~~~~~~~~~~~~~~~~~~~~
C/C++
"""""
* Refactored the "Year field changed using an arithmetic operation without checking for leap year" query (:code:`cpp/leap-year/unchecked-after-arithmetic-year-modification`) to address large numbers of false positive results.
C#
""
* C# 14: Added support for partial events.
* C# 14: Added support for the :code:`field` keyword in properties.
Java/Kotlin
"""""""""""
* Some modelling which previously only worked for Java EE packages beginning with "javax" will now also work for Java EE packages beginning with "jakarta" as well. This may lead to some alert changes.
JavaScript/TypeScript
"""""""""""""""""""""
* Added support for React components wrapped by :code:`observer` from :code:`mobx-react` and :code:`mobx-react-lite`.
Python
""""""
* Added new full SSRF sanitization barrier from the new AntiSSRF library.
* When a guard such as :code:`isSafe(x)` is defined, we now also automatically handle :code:`isSafe(x) == true` and :code:`isSafe(x) != false`.
Ruby
""""
* We now track taint flow through :code:`Shellwords.escape` and :code:`Shellwords.shellescape` for all queries except command injection, for which they are sanitizers.
Rust
""""
* Added support for neutral models (:code:`extensible: neutralModel`) to control where generated source, sink and flow summary models apply.

View File

@@ -0,0 +1,131 @@
.. _codeql-cli-2.25.0:
==========================
CodeQL 2.25.0 (2026-03-19)
==========================
.. contents:: Contents
:depth: 2
:local:
:backlinks: none
This is an overview of changes in the CodeQL CLI and relevant CodeQL query and library packs. For additional updates on changes to the CodeQL code scanning experience, check out the `code scanning section on the GitHub blog <https://github.blog/tag/code-scanning/>`__, `relevant GitHub Changelog updates <https://github.blog/changelog/label/application-security/>`__, `changes in the CodeQL extension for Visual Studio Code <https://marketplace.visualstudio.com/items/GitHub.vscode-codeql/changelog>`__, and the `CodeQL Action changelog <https://github.com/github/codeql-action/blob/main/CHANGELOG.md>`__.
Security Coverage
-----------------
CodeQL 2.25.0 runs a total of 491 security queries when configured with the Default suite (covering 166 CWE). The Extended suite enables an additional 135 queries (covering 35 more CWE).
CodeQL CLI
----------
Breaking Changes
~~~~~~~~~~~~~~~~
* :code:`codeql database interpret-results` and :code:`codeql database analyze` no longer attempt to reconstruct file baseline information from databases created with CLI versions before 2.11.2.
Bug Fixes
~~~~~~~~~
* Upgraded Jackson library from 2.16.1 to 2.18.6 to address a high-severity denial of service vulnerability (GHSA-72hv-8253-57qq) in jackson-core's async JSON parser.
* Upgraded snakeyaml (which is a dependency of jackson-dataformat-yaml) from 2.2 to 2.3.
Language Libraries
------------------
Breaking Changes
~~~~~~~~~~~~~~~~
Java/Kotlin
"""""""""""
* The Java control flow graph (CFG) implementation has been completely rewritten. The CFG now includes additional nodes to more accurately represent certain constructs. This also means that any existing code that implicitly relies on very specific details about the CFG may need to be updated.
The CFG now only includes the nodes that are reachable from the entry point.
Additionally, the following breaking changes have been made:
* :code:`ControlFlowNode.asCall` has been removed - use :code:`Call.getControlFlowNode` instead.
* :code:`ControlFlowNode.getEnclosingStmt` has been removed.
* :code:`ControlFlow::ExprNode` has been removed.
* :code:`ControlFlow::StmtNode` has been removed.
* :code:`ControlFlow::Node` has been removed - this was merely an alias of
:code:`ControlFlowNode`, which is still available.
* Previously deprecated predicates on :code:`BasicBlock` have been removed.
Major Analysis Improvements
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Swift
"""""
* Upgraded to allow analysis of Swift 6.2.4.
Minor Analysis Improvements
~~~~~~~~~~~~~~~~~~~~~~~~~~~
C/C++
"""""
* Inline expectations test comments, which are of the form :code:`// $ tag` or :code:`// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
C#
""
* Inline expectations test comments, which are of the form :code:`// $ tag` or :code:`// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
* Added :code:`System.Net.WebSockets::ReceiveAsync` as a remote flow source.
* Added reverse taint flow from implicit conversion operator calls to their arguments.
* Added post-update nodes for struct-type arguments, allowing data flow out of method calls via those arguments.
* C# 14: Added support for partial constructors.
Golang
""""""
* Inline expectations test comments, which are of the form :code:`// $ tag` or :code:`// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
Java/Kotlin
"""""""""""
* Inline expectations test comments, which are of the form :code:`// $ tag` or :code:`// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
* The class :code:`Assignment` now extends :code:`BinaryExpr`. Uses of :code:`BinaryExpr` may in some cases need slight adjustment.
JavaScript/TypeScript
"""""""""""""""""""""
* Added support for browser-specific source kinds (:code:`browser`, :code:`browser-url-query`, :code:`browser-url-fragment`, :code:`browser-url-path`, :code:`browser-url`, :code:`browser-window-name`, :code:`browser-message-event`) that can be used in data extensions to model sources in browser environments.
* Inline expectations test comments, which are of the form :code:`// $ tag` or :code:`// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
Python
""""""
* The call graph resolution no longer considers methods marked using |link-code-typing-overload-1|_ as valid targets. This ensures that only the method that contains the actual implementation gets resolved as a target.
* Inline expectations test comments, which are of the form :code:`# $ tag` or :code:`# $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
Ruby
""""
* Inline expectations test comments, which are of the form :code:`# $ tag` or :code:`# $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
Swift
"""""
* Inline expectations test comments, which are of the form :code:`// $ tag` or :code:`// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
Rust
""""
* Inline expectations test comments, which are of the form :code:`// $ tag` or :code:`// $ tag=value`, are now parsed more strictly and will not be recognized if there isn't a space after the :code:`$` symbol.
* Added neutral models to inhibit spurious generated sink models for :code:`map` and :code:`from`. This fixes some false positive query results.
Shared Libraries
----------------
New Features
~~~~~~~~~~~~
Dataflow Analysis
"""""""""""""""""
* Two new flow features :code:`FeatureEscapesSourceCallContext` and :code:`FeatureEscapesSourceCallContextOrEqualSourceSinkCallContext` have been added. The former implies that the sink must be reached from the source by escaping the source call context, that is, flow must either return from the callable containing the source or use a jump-step before reaching the sink. The latter is the disjunction of the former and the existing :code:`FeatureEqualSourceSinkCallContext` flow feature.
.. |link-code-typing-overload-1| replace:: :code:`@typing.overload`\
.. _link-code-typing-overload-1: https://typing.python.org/en/latest/spec/overload.html#overloads

View File

@@ -11,6 +11,8 @@ A list of queries for each suite and language `is available here <https://docs.g
.. toctree::
:maxdepth: 1
codeql-cli-2.25.0
codeql-cli-2.24.3
codeql-cli-2.24.2
codeql-cli-2.24.1
codeql-cli-2.24.0

View File

@@ -9,8 +9,12 @@ private import codeql.ruby.ast.internal.Scope
private import codeql.ruby.controlflow.CfgNodes
private import codeql.util.Numbers
bindingset[t]
pragma[inline_late]
private string getTokenValue(Ruby::Token t) { result = t.getValue() }
int parseInteger(Ruby::Integer i) {
exists(string s | s = i.getValue().toLowerCase().replaceAll("_", "") |
exists(string s | s = getTokenValue(i).toLowerCase().replaceAll("_", "") |
s.charAt(0) != "0" and
result = s.toInt()
or
@@ -38,7 +42,7 @@ class IntegerLiteralReal extends IntegerLiteralImpl, TIntegerLiteralReal {
final override int getValue() { result = parseInteger(g) }
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
}
class IntegerLiteralSynth extends IntegerLiteralImpl, TIntegerLiteralSynth {
@@ -52,7 +56,7 @@ class IntegerLiteralSynth extends IntegerLiteralImpl, TIntegerLiteralSynth {
}
// TODO: implement properly
float parseFloat(Ruby::Float f) { result = f.getValue().toFloat() }
float parseFloat(Ruby::Float f) { result = getTokenValue(f).toFloat() }
class FloatLiteralImpl extends Expr, TFloatLiteral {
private Ruby::Float g;
@@ -61,7 +65,7 @@ class FloatLiteralImpl extends Expr, TFloatLiteral {
final float getValue() { result = parseFloat(g) }
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
}
predicate isRationalValue(Ruby::Rational r, int numerator, int denominator) {
@@ -71,8 +75,8 @@ predicate isRationalValue(Ruby::Rational r, int numerator, int denominator) {
exists(Ruby::Float f, string regex, string before, string after |
f = r.getChild() and
regex = "([^.]*)\\.(.*)" and
before = f.getValue().regexpCapture(regex, 1) and
after = f.getValue().regexpCapture(regex, 2) and
before = getTokenValue(f).regexpCapture(regex, 1) and
after = getTokenValue(f).regexpCapture(regex, 2) and
numerator = before.toInt() * denominator + after.toInt() and
denominator = 10.pow(after.length())
)
@@ -87,14 +91,14 @@ class RationalLiteralImpl extends Expr, TRationalLiteral {
isRationalValue(g, numerator, denominator)
}
final override string toString() { result = g.getChild().(Ruby::Token).getValue() + "r" }
final override string toString() { result = getTokenValue(g.getChild()) + "r" }
}
float getComplexValue(Ruby::Complex c) {
exists(int n, int d | isRationalValue(c.getChild(), n, d) and result = n.(float) / d.(float))
or
exists(string s |
s = c.getChild().(Ruby::Token).getValue() and
s = getTokenValue(c.getChild()) and
result = s.prefix(s.length()).toFloat()
)
}
@@ -109,8 +113,8 @@ class ComplexLiteralImpl extends Expr, TComplexLiteral {
}
final override string toString() {
result = g.getChild().(Ruby::Token).getValue() + "i" or
result = g.getChild().(Ruby::Rational).getChild().(Ruby::Token).getValue() + "ri"
result = getTokenValue(g.getChild()) + "i" or
result = getTokenValue(g.getChild().(Ruby::Rational).getChild()) + "ri"
}
}
@@ -137,7 +141,7 @@ class TrueLiteral extends BooleanLiteralImpl, TTrueLiteral {
TrueLiteral() { this = TTrueLiteral(g) }
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
final override boolean getValue() { result = true }
}
@@ -147,7 +151,7 @@ class FalseLiteral extends BooleanLiteralImpl, TFalseLiteral {
FalseLiteral() { this = TFalseLiteral(g) }
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
final override boolean getValue() { result = false }
}
@@ -290,9 +294,9 @@ class RangeLiteralSynth extends RangeLiteralImpl, TRangeLiteralSynth {
}
private string getMethodName(MethodName::Token t) {
result = t.(Ruby::Token).getValue()
result = getTokenValue(t)
or
result = t.(Ruby::Setter).getName().getValue() + "="
result = getTokenValue(t.(Ruby::Setter).getName()) + "="
}
class TokenMethodName extends Expr, TTokenMethodName {
@@ -339,9 +343,9 @@ class StringTextComponentStringOrHeredocContent extends StringTextComponentImpl,
final override string getValue() { result = this.getUnescapedText() }
final override string getRawTextImpl() { result = g.getValue() }
final override string getRawTextImpl() { result = getTokenValue(g) }
final private string getUnescapedText() { result = unescapeTextComponent(g.getValue()) }
final private string getUnescapedText() { result = unescapeTextComponent(getTokenValue(g)) }
}
private class StringTextComponentSimpleSymbol extends StringTextComponentImpl,
@@ -352,7 +356,7 @@ private class StringTextComponentSimpleSymbol extends StringTextComponentImpl,
StringTextComponentSimpleSymbol() { this = TStringTextComponentNonRegexpSimpleSymbol(g) }
// Tree-sitter gives us value text including the colon, which we skip.
private string getSimpleSymbolValue() { result = g.getValue().suffix(1) }
private string getSimpleSymbolValue() { result = getTokenValue(g).suffix(1) }
final override string getValue() { result = this.getSimpleSymbolValue() }
@@ -366,9 +370,9 @@ private class StringTextComponentHashKeySymbol extends StringTextComponentImpl,
StringTextComponentHashKeySymbol() { this = TStringTextComponentNonRegexpHashKeySymbol(g) }
final override string getValue() { result = g.getValue() }
final override string getValue() { result = getTokenValue(g) }
final override string getRawTextImpl() { result = g.getValue() }
final override string getRawTextImpl() { result = getTokenValue(g) }
}
bindingset[escaped]
@@ -438,9 +442,9 @@ class StringEscapeSequenceComponentImpl extends StringComponentImpl,
final override string getValue() { result = this.getUnescapedText() }
final string getRawTextImpl() { result = g.getValue() }
final string getRawTextImpl() { result = getTokenValue(g) }
final private string getUnescapedText() { result = unescapeEscapeSequence(g.getValue()) }
final private string getUnescapedText() { result = unescapeEscapeSequence(getTokenValue(g)) }
final override string toString() { result = this.getRawTextImpl() }
}
@@ -473,10 +477,10 @@ class RegExpTextComponentImpl extends RegExpComponentImpl, TStringTextComponentR
// Exclude components that are children of a free-spacing regex.
// We do this because `ParseRegExp.qll` cannot handle free-spacing regexes.
not this.getParent().(RegExpLiteral).hasFreeSpacingFlag() and
result = g.getValue()
result = getTokenValue(g)
}
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
}
class RegExpEscapeSequenceComponentImpl extends RegExpComponentImpl,
@@ -490,10 +494,10 @@ class RegExpEscapeSequenceComponentImpl extends RegExpComponentImpl,
// Exclude components that are children of a free-spacing regex.
// We do this because `ParseRegExp.qll` cannot handle free-spacing regexes.
not this.getParent().(RegExpLiteral).hasFreeSpacingFlag() and
result = g.getValue()
result = getTokenValue(g)
}
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
}
class RegExpInterpolationComponentImpl extends RegExpComponentImpl,
@@ -564,7 +568,7 @@ abstract class StringlikeLiteralImpl extends Expr, TStringlikeLiteral {
concat(StringComponent c, int i, string s |
c = this.getComponentImpl(i) and
(
s = toGenerated(c).(Ruby::Token).getValue()
s = getTokenValue(toGenerated(c))
or
not toGenerated(c) instanceof Ruby::Token and
s = "#{...}"
@@ -635,7 +639,7 @@ class SimpleSymbolLiteralReal extends SimpleSymbolLiteralImpl, TSimpleSymbolLite
final override StringComponent getComponentImpl(int n) { n = 0 and toGenerated(result) = g }
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
}
class SimpleSymbolLiteralSynth extends SimpleSymbolLiteralImpl, TSimpleSymbolLiteralSynth,
@@ -677,7 +681,7 @@ private class HashKeySymbolLiteral extends SymbolLiteralImpl, THashKeySymbolLite
final override StringComponent getComponentImpl(int n) { n = 0 and toGenerated(result) = g }
final override string toString() { result = ":" + g.getValue() }
final override string toString() { result = ":" + getTokenValue(g) }
}
class RegExpLiteralImpl extends StringlikeLiteralImpl, TRegExpLiteral {
@@ -701,9 +705,9 @@ class CharacterLiteralImpl extends Expr, TCharacterLiteral {
CharacterLiteralImpl() { this = TCharacterLiteral(g) }
final string getValue() { result = g.getValue() }
final string getValue() { result = getTokenValue(g) }
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
}
class HereDocImpl extends StringlikeLiteralImpl, THereDoc {
@@ -715,5 +719,5 @@ class HereDocImpl extends StringlikeLiteralImpl, THereDoc {
toGenerated(result) = getHereDocBody(g).getChild(n)
}
final override string toString() { result = g.getValue() }
final override string toString() { result = getTokenValue(g) }
}

View File

@@ -639,7 +639,7 @@ private TMethodOrExpr lookupMethodOrConst(Module m, string name) {
// For now, we restrict the scope of top-level declarations to their file.
// This may remove some plausible targets, but also removes a lot of
// implausible targets
if getNode(result).getEnclosingModule() instanceof Toplevel
then getNode(result).getFile() = m.getADeclaration().getFile()
else any()
forall(File file | file = getNode(result).getEnclosingModule().(Toplevel).getFile() |
file = m.getADeclaration().getFile()
)
}

View File

@@ -149,11 +149,11 @@ cached
private module Cached {
/** Gets the enclosing scope of a node */
cached
Scope::Range scopeOf(Ruby::AstNode n) {
Scope::Range scopeOfImpl(Ruby::AstNode n) {
exists(Ruby::AstNode p | p = parentOf(n) |
p = result
or
not p instanceof Scope::Range and result = scopeOf(p)
not p instanceof Scope::Range and result = scopeOfImpl(p)
)
}
@@ -163,16 +163,22 @@ private module Cached {
* and synthesized scopes into account.
*/
cached
Scope scopeOfInclSynth(AstNode n) {
Scope scopeOfInclSynthImpl(AstNode n) {
exists(AstNode p | p = parentOfInclSynth(n) |
p = result
or
not p instanceof Scope and result = scopeOfInclSynth(p)
not p instanceof Scope and result = scopeOfInclSynthImpl(p)
)
}
}
import Cached
bindingset[n]
pragma[inline_late]
Scope::Range scopeOf(Ruby::AstNode n) { result = Cached::scopeOfImpl(n) }
bindingset[n]
pragma[inline_late]
Scope scopeOfInclSynth(AstNode n) { result = Cached::scopeOfInclSynthImpl(n) }
abstract class ScopeImpl extends AstNode, TScopeType {
final Scope getOuterScopeImpl() { result = scopeOfInclSynth(this) }

View File

@@ -687,26 +687,30 @@ pragma[nomagic]
private CfgScope getTargetInstance(DataFlowCall call, string method) {
exists(boolean exact |
result = lookupInstanceMethodCall(call, method, exact) and
(
if result.(Method).isPrivate()
then
call.asCall().getReceiver().getExpr() instanceof SelfVariableAccess and
// For now, we restrict the scope of top-level declarations to their file.
// This may remove some plausible targets, but also removes a lot of
// implausible targets
(
isToplevelMethodInFile(result, call.asCall().getFile()) or
not isToplevelMethodInFile(result, _)
)
else any()
) and
if result.(Method).isProtected()
then
result = lookupMethod(call.asCall().getExpr().getEnclosingModule().getModule(), method, exact)
else any()
(if result.(Method).isPrivate() then result = privateFilter(call) else any()) and
if result.(Method).isProtected() then result = protectedFilter(call, method, exact) else any()
)
}
bindingset[call, result]
pragma[inline_late]
private CfgScope privateFilter(DataFlowCall call) {
call.asCall().getReceiver().getExpr() instanceof SelfVariableAccess and
// For now, we restrict the scope of top-level declarations to their file.
// This may remove some plausible targets, but also removes a lot of
// implausible targets
(
isToplevelMethodInFile(result, call.asCall().getFile()) or
not isToplevelMethodInFile(result, _)
)
}
bindingset[call, method, exact, result]
pragma[inline_late]
private CfgScope protectedFilter(DataFlowCall call, string method, boolean exact) {
result = lookupMethod(call.asCall().getExpr().getEnclosingModule().getModule(), method, exact)
}
private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig {
class State = Block;

View File

@@ -28,7 +28,13 @@ abstract class NodeImpl extends Node {
DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.getCfgScope()) }
/** Do not call: use `getEnclosingCallable()` instead. */
abstract CfgScope getCfgScope();
abstract CfgScope getCfgScopeImpl();
/** Do not call: use `getEnclosingCallable()` instead. */
pragma[inline]
final CfgScope getCfgScope() {
pragma[only_bind_into](result) = pragma[only_bind_out](this).getCfgScopeImpl()
}
/** Do not call: use `getLocation()` instead. */
abstract Location getLocationImpl();
@@ -38,7 +44,7 @@ abstract class NodeImpl extends Node {
}
private class ExprNodeImpl extends ExprNode, NodeImpl {
override CfgScope getCfgScope() { result = this.getExprNode().getExpr().getCfgScope() }
override CfgScope getCfgScopeImpl() { result = this.getExprNode().getExpr().getCfgScope() }
override Location getLocationImpl() { result = this.getExprNode().getLocation() }
@@ -780,7 +786,7 @@ class SsaNode extends NodeImpl, TSsaNode {
/** Holds if this node should be hidden from path explanations. */
predicate isHidden() { any() }
override CfgScope getCfgScope() { result = node.getBasicBlock().getScope() }
override CfgScope getCfgScopeImpl() { result = node.getBasicBlock().getScope() }
override Location getLocationImpl() { result = node.getLocation() }
@@ -827,7 +833,7 @@ class CapturedVariableNode extends NodeImpl, TCapturedVariableNode {
/** Gets the captured variable represented by this node. */
VariableCapture::CapturedVariable getVariable() { result = variable }
override CfgScope getCfgScope() { result = variable.getCallable() }
override CfgScope getCfgScopeImpl() { result = variable.getCallable() }
override Location getLocationImpl() { result = variable.getLocation() }
@@ -849,7 +855,7 @@ class ReturningStatementNode extends NodeImpl, TReturningNode {
/** Gets the expression corresponding to this node. */
CfgNodes::ReturningCfgNode getReturningNode() { result = n }
override CfgScope getCfgScope() { result = n.getScope() }
override CfgScope getCfgScopeImpl() { result = n.getScope() }
override Location getLocationImpl() { result = n.getLocation() }
@@ -867,7 +873,7 @@ class CaptureNode extends NodeImpl, TCaptureNode {
VariableCapture::Flow::SynthesizedCaptureNode getSynthesizedCaptureNode() { result = cn }
override CfgScope getCfgScope() { result = cn.getEnclosingCallable() }
override CfgScope getCfgScopeImpl() { result = cn.getEnclosingCallable() }
override Location getLocationImpl() { result = cn.getLocation() }
@@ -935,7 +941,7 @@ private module ParameterNodes {
)
}
override CfgScope getCfgScope() { result = parameter.getCallable() }
override CfgScope getCfgScopeImpl() { result = parameter.getCallable() }
override Location getLocationImpl() { result = parameter.getLocation() }
@@ -979,7 +985,7 @@ private module ParameterNodes {
final override SelfVariable getSelfVariable() { result.getDeclaringScope() = method }
override CfgScope getCfgScope() { result = method }
override CfgScope getCfgScopeImpl() { result = method }
override Location getLocationImpl() { result = method.getLocation() }
}
@@ -1001,7 +1007,7 @@ private module ParameterNodes {
final override SelfVariable getSelfVariable() { result.getDeclaringScope() = t }
override CfgScope getCfgScope() { result = t }
override CfgScope getCfgScopeImpl() { result = t }
override Location getLocationImpl() { result = t.getLocation() }
}
@@ -1028,7 +1034,7 @@ private module ParameterNodes {
callable = c.asCfgScope() and pos.isLambdaSelf()
}
override CfgScope getCfgScope() { result = callable }
override CfgScope getCfgScopeImpl() { result = callable }
override Location getLocationImpl() { result = callable.getLocation() }
@@ -1071,7 +1077,7 @@ private module ParameterNodes {
c.asCfgScope() = method and pos.isBlock()
}
override CfgScope getCfgScope() { result = method }
override CfgScope getCfgScopeImpl() { result = method }
override Location getLocationImpl() {
result = this.getParameter().getLocation()
@@ -1130,7 +1136,7 @@ private module ParameterNodes {
c = callable and pos.isSynthHashSplat()
}
final override CfgScope getCfgScope() { result = callable.asCfgScope() }
final override CfgScope getCfgScopeImpl() { result = callable.asCfgScope() }
final override DataFlowCallable getEnclosingCallable() { result = callable }
@@ -1193,7 +1199,7 @@ private module ParameterNodes {
)
}
final override CfgScope getCfgScope() { result = callable.asCfgScope() }
final override CfgScope getCfgScopeImpl() { result = callable.asCfgScope() }
final override DataFlowCallable getEnclosingCallable() { result = callable }
@@ -1240,7 +1246,7 @@ private module ParameterNodes {
cs = getArrayContent(pos)
}
final override CfgScope getCfgScope() { result = callable.asCfgScope() }
final override CfgScope getCfgScopeImpl() { result = callable.asCfgScope() }
final override DataFlowCallable getEnclosingCallable() { result = callable }
@@ -1278,7 +1284,7 @@ class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
result = this.getSummaryNode().getSummarizedCallable()
}
override CfgScope getCfgScope() { none() }
override CfgScope getCfgScopeImpl() { none() }
override DataFlowCallable getEnclosingCallable() {
result.asLibraryCallable() = this.getSummarizedCallable()
@@ -1349,7 +1355,7 @@ module ArgumentNodes {
this.sourceArgumentOf(call.asCall(), pos)
}
override CfgScope getCfgScope() { result = yield.getScope() }
override CfgScope getCfgScopeImpl() { result = yield.getScope() }
override Location getLocationImpl() { result = yield.getLocation() }
}
@@ -1379,7 +1385,7 @@ module ArgumentNodes {
this.sourceArgumentOf(call.asCall(), pos)
}
override CfgScope getCfgScope() { result = sup.getScope() }
override CfgScope getCfgScopeImpl() { result = sup.getScope() }
override Location getLocationImpl() { result = sup.getLocation() }
}
@@ -1415,7 +1421,7 @@ module ArgumentNodes {
this.sourceArgumentOf(call.asCall(), pos)
}
final override CfgScope getCfgScope() { result = call_.getExpr().getCfgScope() }
final override CfgScope getCfgScopeImpl() { result = call_.getExpr().getCfgScope() }
final override Location getLocationImpl() { result = call_.getLocation() }
}
@@ -1563,7 +1569,7 @@ module ArgumentNodes {
)
}
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
override CfgScope getCfgScopeImpl() { result = c.getExpr().getCfgScope() }
override Location getLocationImpl() { result = c.getLocation() }
@@ -2037,16 +2043,21 @@ private predicate compatibleTypesNonSymRefl(DataFlowType t1, DataFlowType t2) {
}
pragma[nomagic]
private predicate compatibleModuleTypes(TModuleDataFlowType t1, TModuleDataFlowType t2) {
exists(Module m1, Module m2, Module m3 |
t1 = TModuleDataFlowType(m1) and
t2 = TModuleDataFlowType(m2)
|
private predicate compatibleModules(Module m1, Module m2) {
exists(Module m3 |
m3.getAnAncestor() = m1 and
m3.getAnAncestor() = m2
)
}
private predicate compatibleModuleTypes(TModuleDataFlowType t1, TModuleDataFlowType t2) {
exists(Module m1, Module m2 |
compatibleModules(m1, m2) and
t1 = TModuleDataFlowType(m1) and
t2 = TModuleDataFlowType(m2)
)
}
/**
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
* a node of type `t1` to a node of type `t2`.
@@ -2074,7 +2085,7 @@ private module PostUpdateNodes {
override ExprNode getPreUpdateNode() { e = result.getExprNode() }
override CfgScope getCfgScope() { result = e.getExpr().getCfgScope() }
override CfgScope getCfgScopeImpl() { result = e.getExpr().getCfgScope() }
override Location getLocationImpl() { result = e.getLocation() }

View File

@@ -500,6 +500,7 @@ private module Persistence {
class ActiveRecordAssociation extends DataFlow::CallNode {
private ActiveRecordModelClass modelClass;
pragma[nomagic]
ActiveRecordAssociation() {
not exists(this.asExpr().getExpr().getEnclosingMethod()) and
this.asExpr().getExpr().getEnclosingModule() = modelClass and
@@ -583,6 +584,14 @@ private string pluralize(string input) {
exists(string stem | stem + "s" = input) and result = input
}
pragma[nomagic]
private predicate activeRecordAssociationMethodCall(
DataFlow::CallNode call, ActiveRecordAssociation assoc, string model
) {
call.getReceiver().(ActiveRecordInstance).getClass() = assoc.getSourceClass() and
model = assoc.getTargetModelName()
}
/**
* A call to a method generated by an ActiveRecord association.
* These yield ActiveRecord collection proxies, which act like collections but
@@ -595,24 +604,21 @@ private class ActiveRecordAssociationMethodCall extends DataFlow::CallNode {
ActiveRecordAssociation assoc;
ActiveRecordAssociationMethodCall() {
exists(string model | model = assoc.getTargetModelName() |
this.getReceiver().(ActiveRecordInstance).getClass() = assoc.getSourceClass() and
exists(string model | activeRecordAssociationMethodCall(this, assoc, model) |
assoc.isCollection() and
(
assoc.isCollection() and
(
this.getMethodName() = pluralize(model) + ["", "="]
or
this.getMethodName() = "<<"
or
this.getMethodName() = model + ["_ids", "_ids="]
)
this.getMethodName() = pluralize(model) + ["", "="]
or
assoc.isSingular() and
(
this.getMethodName() = model + ["", "="] or
this.getMethodName() = ["build_", "reload_"] + model or
this.getMethodName() = "create_" + model + ["!", ""]
)
this.getMethodName() = "<<"
or
this.getMethodName() = model + ["_ids", "_ids="]
)
or
assoc.isSingular() and
(
this.getMethodName() = model + ["", "="] or
this.getMethodName() = ["build_", "reload_"] + model or
this.getMethodName() = "create_" + model + ["!", ""]
)
)
}

View File

@@ -46,6 +46,10 @@ module Filters {
)
}
bindingset[m, name]
pragma[inline_late]
private Method lookupMethodInlineLate(Module m, string name) { result = lookupMethod(m, name) }
/**
* A call to a class method that adds or removes a filter from the callback chain.
* This class exists to encapsulate common behavior between calls that
@@ -140,7 +144,8 @@ module Filters {
*/
Callable getAFilterCallable() {
result =
lookupMethod(this.getExpr().getEnclosingModule().getModule(), this.getFilterArgumentName())
lookupMethodInlineLate(this.getExpr().getEnclosingModule().getModule(),
this.getFilterArgumentName())
}
}

View File

@@ -943,6 +943,7 @@ module Routing {
* Note: All-uppercase words like `CONSTANT` are not handled correctly.
*/
bindingset[base]
pragma[inline_late]
string underscore(string base) {
base = "" and result = ""
or

View File

@@ -526,7 +526,7 @@ module Array {
MethodCall mc;
bindingset[this]
DeleteSummary() { mc.getMethodName() = "delete" }
DeleteSummary() { pragma[only_bind_into](mc).getMethodName() = "delete" }
final override MethodCall getACallSimple() { result = mc }
@@ -790,7 +790,7 @@ module Array {
MethodCall mc;
bindingset[this]
FetchSummary() { mc.getMethodName() = "fetch" }
FetchSummary() { pragma[only_bind_into](mc).getMethodName() = "fetch" }
override MethodCall getACallSimple() { result = mc }
}

View File

@@ -286,7 +286,7 @@ abstract private class FetchValuesSummary extends SummarizedCallable::Range {
MethodCall mc;
bindingset[this]
FetchValuesSummary() { mc.getMethodName() = "fetch_values" }
FetchValuesSummary() { pragma[only_bind_into](mc).getMethodName() = "fetch_values" }
final override MethodCall getACallSimple() { result = mc }
@@ -390,7 +390,7 @@ abstract private class SliceSummary extends SummarizedCallable::Range {
MethodCall mc;
bindingset[this]
SliceSummary() { mc.getMethodName() = "slice" }
SliceSummary() { pragma[only_bind_into](mc).getMethodName() = "slice" }
final override MethodCall getACallSimple() { result = mc }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
/**
* Provides classes and predicates for defining barriers and barrier guards.
*
* Flow barriers and barrier guards 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
*
* data from *.model.yml | QL extensions of FlowBarrierGuard::Range
* v v
* FlowBarrierGuard (associated with a models-as-data kind string)
* v
* barrierNode predicate | other QL defined barrier guards, for example using concepts
* v v
* various Barrier classes for specific data flow configurations
* ```
*
* New barriers and barrier guards should be defined using models-as-data, QL extensions of
* `FlowBarrier::Range`/`FlowBarrierGuard::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;

View File

@@ -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
@@ -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)
}
}

View File

@@ -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. */
@@ -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) {

View File

@@ -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
@@ -98,6 +99,29 @@ extensible predicate neutralModel(
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 an argument of a call
* (possibly `self`), and the call is guarding the argument. `branch` is either `true`
* or `false`, indicating which branch of the guard is protecting the argument.
*/
extensible predicate barrierGuardModel(
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`.
*
@@ -123,6 +147,16 @@ predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
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 {
@@ -206,6 +240,40 @@ private class FlowSinkFromModel extends FlowSink::Range {
}
}
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

View File

@@ -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)
}

View File

@@ -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;
}
}
}

View File

@@ -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: []

View File

@@ -1,41 +1,20 @@
models
edges
| main.rs:9:13:9:19 | ...: ... | main.rs:10:11:10:11 | s | provenance | |
| main.rs:10:11:10:11 | s | main.rs:12:9:12:9 | s | provenance | |
| main.rs:12:9:12:9 | s | main.rs:9:30:14:1 | { ... } | provenance | |
| main.rs:21:9:21:9 | s | main.rs:22:10:22:10 | s | provenance | |
| main.rs:21:13:21:21 | source(...) | main.rs:21:9:21:9 | s | provenance | |
| main.rs:26:9:26:9 | s | main.rs:27:22:27:22 | s | provenance | |
| main.rs:26:13:26:21 | source(...) | main.rs:26:9:26:9 | s | provenance | |
| main.rs:27:9:27:9 | s | main.rs:28:10:28:10 | s | provenance | |
| main.rs:27:13:27:23 | sanitize(...) | main.rs:27:9:27:9 | s | provenance | |
| main.rs:27:22:27:22 | s | main.rs:9:13:9:19 | ...: ... | provenance | |
| main.rs:27:22:27:22 | s | main.rs:27:13:27:23 | sanitize(...) | provenance | |
| main.rs:32:9:32:9 | s | main.rs:33:10:33:10 | s | provenance | |
| main.rs:32:13:32:21 | source(...) | main.rs:32:9:32:9 | s | provenance | |
nodes
| main.rs:9:13:9:19 | ...: ... | semmle.label | ...: ... |
| main.rs:9:30:14:1 | { ... } | semmle.label | { ... } |
| main.rs:10:11:10:11 | s | semmle.label | s |
| main.rs:12:9:12:9 | s | semmle.label | s |
| main.rs:17:10:17:18 | source(...) | semmle.label | source(...) |
| main.rs:21:9:21:9 | s | semmle.label | s |
| main.rs:21:13:21:21 | source(...) | semmle.label | source(...) |
| main.rs:22:10:22:10 | s | semmle.label | s |
| main.rs:26:9:26:9 | s | semmle.label | s |
| main.rs:26:13:26:21 | source(...) | semmle.label | source(...) |
| main.rs:27:9:27:9 | s | semmle.label | s |
| main.rs:27:13:27:23 | sanitize(...) | semmle.label | sanitize(...) |
| main.rs:27:22:27:22 | s | semmle.label | s |
| main.rs:28:10:28:10 | s | semmle.label | s |
| main.rs:32:9:32:9 | s | semmle.label | s |
| main.rs:32:13:32:21 | source(...) | semmle.label | source(...) |
| main.rs:33:10:33:10 | s | semmle.label | s |
subpaths
| main.rs:27:22:27:22 | s | main.rs:9:13:9:19 | ...: ... | main.rs:9:30:14:1 | { ... } | main.rs:27:13:27:23 | sanitize(...) |
testFailures
#select
| main.rs:17:10:17:18 | source(...) | main.rs:17:10:17:18 | source(...) | main.rs:17:10:17:18 | source(...) | $@ | main.rs:17:10:17:18 | source(...) | source(...) |
| main.rs:22:10:22:10 | s | main.rs:21:13:21:21 | source(...) | main.rs:22:10:22:10 | s | $@ | main.rs:21:13:21:21 | source(...) | source(...) |
| main.rs:28:10:28:10 | s | main.rs:26:13:26:21 | source(...) | main.rs:28:10:28:10 | s | $@ | main.rs:26:13:26:21 | source(...) | source(...) |
| main.rs:33:10:33:10 | s | main.rs:32:13:32:21 | source(...) | main.rs:33:10:33:10 | s | $@ | main.rs:32:13:32:21 | source(...) | source(...) |

View File

@@ -0,0 +1,11 @@
extensions:
- addsTo:
pack: codeql/rust-all
extensible: barrierModel
data:
- ["main::sanitize", "ReturnValue", "test-barrier", "manual"]
- addsTo:
pack: codeql/rust-all
extensible: barrierGuardModel
data:
- ["main::verify_safe", "Argument[0]", "true", "test-barrier", "manual"]

View File

@@ -3,8 +3,19 @@
*/
import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.FlowBarrier
import utils.test.InlineFlowTest
import DefaultFlowTest
module CustomConfig implements DataFlow::ConfigSig {
predicate isSource = DefaultFlowConfig::isSource/1;
predicate isSink = DefaultFlowConfig::isSink/1;
predicate isBarrier(DataFlow::Node n) { barrierNode(n, "test-barrier") }
}
import FlowTest<CustomConfig, CustomConfig>
import TaintFlow::PathGraph
from TaintFlow::PathNode source, TaintFlow::PathNode sink

View File

@@ -25,10 +25,24 @@ fn through_variable() {
fn with_barrier() {
let s = source(1);
let s = sanitize(s);
sink(s); // $ SPURIOUS: hasValueFlow=1
sink(s);
}
fn main() {
let s = source(1);
sink(s); // $ hasValueFlow=1
}
fn verify_safe(s: &str) -> bool {
match s {
"dangerous" => false,
_ => true,
}
}
fn with_barrier_guard() {
let s = source(1);
if verify_safe(s) {
sink(s);
}
}

View File

@@ -368,6 +368,34 @@ module Make<
abstract predicate isSink(string input, string kind, Provenance provenance, string model);
}
/** A barrier element. */
abstract class BarrierElement extends SourceBaseFinal {
bindingset[this]
BarrierElement() { any() }
/**
* Holds if this element is a flow barrier of kind `kind`, where data
* flows out as described by `output`.
*/
pragma[nomagic]
abstract predicate isBarrier(string output, string kind, Provenance provenance, string model);
}
/** A barrier guard element. */
abstract class BarrierGuardElement extends SinkBaseFinal {
bindingset[this]
BarrierGuardElement() { any() }
/**
* 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`.
*/
pragma[nomagic]
abstract predicate isBarrierGuard(
string input, string branch, string kind, Provenance provenance, string model
);
}
private signature predicate hasKindSig(string kind);
signature class NeutralCallableSig extends SummarizedCallableBaseFinal {
@@ -723,7 +751,32 @@ module Make<
)
}
private predicate summarySpec(string spec) {
private predicate isRelevantBarrier(
BarrierElement e, string output, string kind, Provenance provenance, string model
) {
e.isBarrier(output, kind, provenance, model) and
(
provenance.isManual()
or
provenance.isGenerated() and
not exists(Provenance p | p.isManual() and e.isBarrier(_, kind, p, _))
)
}
private predicate isRelevantBarrierGuard(
BarrierGuardElement e, string input, string branch, string kind, Provenance provenance,
string model
) {
e.isBarrierGuard(input, branch, kind, provenance, model) and
(
provenance.isManual()
or
provenance.isGenerated() and
not exists(Provenance p | p.isManual() and e.isBarrierGuard(_, _, kind, p, _))
)
}
private predicate flowSpec(string spec) {
exists(SummarizedCallable c |
c.propagatesFlow(spec, _, _, _, _, _)
or
@@ -732,10 +785,14 @@ module Make<
or
isRelevantSource(_, spec, _, _, _)
or
isRelevantBarrier(_, spec, _, _, _)
or
isRelevantBarrierGuard(_, spec, _, _, _, _)
or
isRelevantSink(_, spec, _, _, _)
}
import AccessPathSyntax::AccessPath<summarySpec/1>
import AccessPathSyntax::AccessPath<flowSpec/1>
/** Holds if specification component `token` parses as parameter `pos`. */
predicate parseParam(AccessPathToken token, ArgumentPosition pos) {
@@ -1515,6 +1572,31 @@ module Make<
)
}
/**
* Holds if `barrier` is a relevant barrier element with output specification `outSpec`.
*/
predicate barrierSpec(
BarrierElement barrier, SummaryComponentStack outSpec, string kind, string model
) {
exists(string output |
isRelevantBarrier(barrier, output, kind, _, model) and
External::interpretSpec(output, outSpec)
)
}
/**
* Holds if `barrierGuard` is a relevant barrier guard element with input specification `inSpec`.
*/
predicate barrierGuardSpec(
BarrierGuardElement barrierGuard, SummaryComponentStack inSpec, string branch, string kind,
string model
) {
exists(string input |
isRelevantBarrierGuard(barrierGuard, input, branch, kind, _, model) and
External::interpretSpec(input, inSpec)
)
}
signature module TypesInputSig {
/** Gets the type of content `c`. */
DataFlowType getContentType(ContentSet c);