mirror of
https://github.com/github/codeql.git
synced 2026-05-14 11:19:27 +02:00
Merge branch 'main' into redsun82/kotlin-language-version-min-1.9
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a class `IndirectUninitializedNode` to represent the indirection of an uninitialized local variable as a dataflow node.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
""""""
|
||||
|
||||
@@ -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
|
||||
"""""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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() }
|
||||
|
||||
|
||||
@@ -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 + ["!", ""]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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
85
rust/ql/lib/codeql/rust/dataflow/FlowBarrier.qll
Normal file
85
rust/ql/lib/codeql/rust/dataflow/FlowBarrier.qll
Normal 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;
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -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(...) |
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user