mirror of
https://github.com/github/codeql.git
synced 2026-06-03 04:40:14 +02:00
Merge commit '737dd9d4c1' into jb1/lib/dataflowstack
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
## 0.1.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.1.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.1.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/controlflow/change-notes/released/0.1.6.md
Normal file
3
shared/controlflow/change-notes/released/0.1.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.1.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/controlflow/change-notes/released/0.1.7.md
Normal file
3
shared/controlflow/change-notes/released/0.1.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.1.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.5
|
||||
lastReleaseVersion: 0.1.7
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/controlflow
|
||||
version: 0.1.5
|
||||
version: 0.1.7
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
|
||||
11
shared/cpp/BUILD.bazel
Normal file
11
shared/cpp/BUILD.bazel
Normal file
@@ -0,0 +1,11 @@
|
||||
cc_library(
|
||||
name = "extractor_shared",
|
||||
srcs = glob(["*.cpp"]),
|
||||
hdrs = glob(["*.h"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@absl//absl/strings",
|
||||
"@fmt",
|
||||
"@json",
|
||||
],
|
||||
)
|
||||
75
shared/cpp/Diagnostics.cpp
Normal file
75
shared/cpp/Diagnostics.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "Diagnostics.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace codeql {
|
||||
|
||||
namespace {
|
||||
std::string_view severityToString(Diagnostic::Severity severity) {
|
||||
using S = Diagnostic::Severity;
|
||||
switch (severity) {
|
||||
case S::note:
|
||||
return "note";
|
||||
case S::warning:
|
||||
return "warning";
|
||||
case S::error:
|
||||
return "error";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
nlohmann::json Diagnostic::json(const std::chrono::system_clock::time_point& timestamp,
|
||||
std::string_view message) const {
|
||||
nlohmann::json ret{
|
||||
{"source",
|
||||
{
|
||||
{"id", absl::StrJoin({extractorName, programName, id}, "/")},
|
||||
{"name", name},
|
||||
{"extractorName", extractorName},
|
||||
}},
|
||||
{"visibility",
|
||||
{
|
||||
{"statusPage", has(Visibility::statusPage)},
|
||||
{"cliSummaryTable", has(Visibility::cliSummaryTable)},
|
||||
{"telemetry", has(Visibility::telemetry)},
|
||||
}},
|
||||
{"severity", severityToString(severity)},
|
||||
{"markdownMessage", absl::StrCat(message, "\n\n", action)},
|
||||
{"timestamp", fmt::format("{:%FT%T%z}", timestamp)},
|
||||
};
|
||||
if (location) {
|
||||
ret["location"] = location->json();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Diagnostic::abbreviation() const {
|
||||
if (location) {
|
||||
return absl::StrCat(id, "@", location->str());
|
||||
}
|
||||
return std::string{id};
|
||||
}
|
||||
|
||||
bool Diagnostic::has(Visibility v) const {
|
||||
return (visibility & v) != Visibility::none;
|
||||
}
|
||||
|
||||
nlohmann::json DiagnosticsLocation::json() const {
|
||||
nlohmann::json ret{{"file", file}};
|
||||
if (startLine) ret["startLine"] = startLine;
|
||||
if (startColumn) ret["startColumn"] = startColumn;
|
||||
if (endLine) ret["endLine"] = endLine;
|
||||
if (endColumn) ret["endColumn"] = endColumn;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string DiagnosticsLocation::str() const {
|
||||
return absl::StrJoin(std::tuple{file, startLine, startColumn, endLine, endColumn}, ":");
|
||||
}
|
||||
} // namespace codeql
|
||||
88
shared/cpp/Diagnostics.h
Normal file
88
shared/cpp/Diagnostics.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace codeql {
|
||||
|
||||
extern const std::string_view programName;
|
||||
extern const std::string_view extractorName;
|
||||
|
||||
struct DiagnosticsLocation {
|
||||
std::string_view file;
|
||||
unsigned startLine;
|
||||
unsigned startColumn;
|
||||
unsigned endLine;
|
||||
unsigned endColumn;
|
||||
|
||||
nlohmann::json json() const;
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
// Models a diagnostic source for Swift, holding static information that goes out into a diagnostic
|
||||
// These are internally stored into a map on id's. A specific error log can use binlog's category
|
||||
// as id, which will then be used to recover the diagnostic source while dumping.
|
||||
class Diagnostic {
|
||||
public:
|
||||
enum class Visibility : unsigned char {
|
||||
none = 0b000,
|
||||
statusPage = 0b001,
|
||||
cliSummaryTable = 0b010,
|
||||
telemetry = 0b100,
|
||||
all = 0b111,
|
||||
};
|
||||
|
||||
// Notice that Tool Status Page severity is not necessarily the same as log severity, as the
|
||||
// scope is different: TSP's scope is the whole analysis, log's scope is a single run
|
||||
enum class Severity {
|
||||
note,
|
||||
warning,
|
||||
error,
|
||||
};
|
||||
|
||||
std::string_view id;
|
||||
std::string_view name;
|
||||
std::string_view action;
|
||||
|
||||
Visibility visibility{Visibility::all};
|
||||
Severity severity{Severity::error};
|
||||
|
||||
std::optional<DiagnosticsLocation> location{};
|
||||
|
||||
// create a JSON diagnostics for this source with the given `timestamp` and Markdown `message`
|
||||
// A markdownMessage is emitted that includes both the message and the action to take. The id is
|
||||
// used to construct the source id in the form `swift/<prog name>/<id>`
|
||||
nlohmann::json json(const std::chrono::system_clock::time_point& timestamp,
|
||||
std::string_view message) const;
|
||||
|
||||
// returns <id> or <id>@<location> if a location is present
|
||||
std::string abbreviation() const;
|
||||
|
||||
Diagnostic withLocation(std::string_view file,
|
||||
unsigned startLine = 0,
|
||||
unsigned startColumn = 0,
|
||||
unsigned endLine = 0,
|
||||
unsigned endColumn = 0) const {
|
||||
auto ret = *this;
|
||||
ret.location = DiagnosticsLocation{file, startLine, startColumn, endLine, endColumn};
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
bool has(Visibility v) const;
|
||||
};
|
||||
|
||||
inline constexpr Diagnostic::Visibility operator|(Diagnostic::Visibility lhs,
|
||||
Diagnostic::Visibility rhs) {
|
||||
return static_cast<Diagnostic::Visibility>(static_cast<unsigned char>(lhs) |
|
||||
static_cast<unsigned char>(rhs));
|
||||
}
|
||||
|
||||
inline constexpr Diagnostic::Visibility operator&(Diagnostic::Visibility lhs,
|
||||
Diagnostic::Visibility rhs) {
|
||||
return static_cast<Diagnostic::Visibility>(static_cast<unsigned char>(lhs) &
|
||||
static_cast<unsigned char>(rhs));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
## 0.1.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The old configuration-class based data flow api has been deprecated. The configuration-module based api should be used instead. For details, see https://github.blog/changelog/2023-08-14-new-dataflow-api-for-writing-custom-codeql-queries/.
|
||||
|
||||
## 0.1.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
5
shared/dataflow/change-notes/released/0.1.6.md
Normal file
5
shared/dataflow/change-notes/released/0.1.6.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.1.6
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The old configuration-class based data flow api has been deprecated. The configuration-module based api should be used instead. For details, see https://github.blog/changelog/2023-08-14-new-dataflow-api-for-writing-custom-codeql-queries/.
|
||||
3
shared/dataflow/change-notes/released/0.1.7.md
Normal file
3
shared/dataflow/change-notes/released/0.1.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.1.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.5
|
||||
lastReleaseVersion: 0.1.7
|
||||
|
||||
@@ -87,13 +87,13 @@ signature module InputSig {
|
||||
* Holds if the set of viable implementations that can be called by `call`
|
||||
* might be improved by knowing the call context.
|
||||
*/
|
||||
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c);
|
||||
default predicate mayBenefitFromCallContext(DataFlowCall call) { none() }
|
||||
|
||||
/**
|
||||
* Gets a viable dispatch target of `call` in the context `ctx`. This is
|
||||
* restricted to those `call`s for which a context might make a difference.
|
||||
*/
|
||||
DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx);
|
||||
default DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) { none() }
|
||||
|
||||
/**
|
||||
* Gets a node that can read the value returned from `call` with return kind
|
||||
@@ -150,6 +150,9 @@ signature module InputSig {
|
||||
* stored into (`getAStoreContent`) or read from (`getAReadContent`).
|
||||
*/
|
||||
class ContentSet {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString();
|
||||
|
||||
/** Gets a content that may be stored into when storing into this set. */
|
||||
Content getAStoreContent();
|
||||
|
||||
@@ -180,6 +183,13 @@ signature module InputSig {
|
||||
|
||||
predicate simpleLocalFlowStep(Node node1, Node node2);
|
||||
|
||||
/**
|
||||
* Holds if the data-flow step from `node1` to `node2` can be used to
|
||||
* determine where side-effects may return from a callable.
|
||||
*/
|
||||
bindingset[node1, node2]
|
||||
default predicate validParameterAliasStep(Node node1, Node node2) { any() }
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` through a non-local step
|
||||
* that does not follow a call edge. For example, a step through a global
|
||||
|
||||
220
shared/dataflow/codeql/dataflow/internal/AccessPathSyntax.qll
Normal file
220
shared/dataflow/codeql/dataflow/internal/AccessPathSyntax.qll
Normal file
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Module for parsing access paths from MaD models, both the identifying access path used
|
||||
* by dynamic languages, and the input/output specifications for summary steps.
|
||||
*
|
||||
* This file is used by the shared data flow library and by the JavaScript libraries
|
||||
* (which does not use the shared data flow libraries).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convenience-predicate for extracting two capture groups at once.
|
||||
*/
|
||||
bindingset[input, regexp]
|
||||
private predicate regexpCaptureTwo(string input, string regexp, string capture1, string capture2) {
|
||||
capture1 = input.regexpCapture(regexp, 1) and
|
||||
capture2 = input.regexpCapture(regexp, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value
|
||||
* of the constant or any value contained in the interval.
|
||||
*/
|
||||
bindingset[arg]
|
||||
int parseInt(string arg) {
|
||||
result = arg.toInt()
|
||||
or
|
||||
// Match "n1..n2"
|
||||
exists(string lo, string hi |
|
||||
regexpCaptureTwo(arg, "(-?\\d+)\\.\\.(-?\\d+)", lo, hi) and
|
||||
result = [lo.toInt() .. hi.toInt()]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a lower-bounded interval `n..` and gets the lower bound.
|
||||
*/
|
||||
bindingset[arg]
|
||||
int parseLowerBound(string arg) { result = arg.regexpCapture("(-?\\d+)\\.\\.", 1).toInt() }
|
||||
|
||||
/**
|
||||
* An access path token such as `Argument[1]` or `ReturnValue`.
|
||||
*/
|
||||
class AccessPathTokenBase extends string {
|
||||
bindingset[this]
|
||||
AccessPathTokenBase() { exists(this) }
|
||||
|
||||
bindingset[this]
|
||||
private string getPart(int part) {
|
||||
result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part)
|
||||
}
|
||||
|
||||
/** Gets the name of the token, such as `Member` from `Member[x]` */
|
||||
bindingset[this]
|
||||
string getName() { result = this.getPart(1) }
|
||||
|
||||
/**
|
||||
* Gets the argument list, such as `1,2` from `Member[1,2]`,
|
||||
* or has no result if there are no arguments.
|
||||
*/
|
||||
bindingset[this]
|
||||
string getArgumentList() { result = this.getPart(2) }
|
||||
|
||||
/** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
bindingset[this]
|
||||
string getArgument(int n) { result = this.getArgumentList().splitAt(",", n).trim() }
|
||||
|
||||
/** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
bindingset[this]
|
||||
string getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
/** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */
|
||||
bindingset[this]
|
||||
int getNumArgument() { result = count(int n | exists(this.getArgument(n))) }
|
||||
}
|
||||
|
||||
final private class AccessPathTokenBaseFinal = AccessPathTokenBase;
|
||||
|
||||
signature predicate accessPathRangeSig(string s);
|
||||
|
||||
/** Companion module to the `AccessPath` class. */
|
||||
module AccessPath<accessPathRangeSig/1 accessPathRange> {
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) that explicitly
|
||||
* references the arity, such as `N-1` or `N-3..N-1`.
|
||||
*
|
||||
* Note that expressions of form `N-x` will never resolve to a negative index,
|
||||
* even if `N` is zero (it will have no result in that case).
|
||||
*/
|
||||
bindingset[arg, arity]
|
||||
private int parseIntWithExplicitArity(string arg, int arity) {
|
||||
result >= 0 and // do not allow N-1 to resolve to a negative index
|
||||
exists(string lo |
|
||||
// N-x
|
||||
lo = arg.regexpCapture("N-(\\d+)", 1) and
|
||||
result = arity - lo.toInt()
|
||||
or
|
||||
// N-x..
|
||||
lo = arg.regexpCapture("N-(\\d+)\\.\\.", 1) and
|
||||
result = [arity - lo.toInt(), arity - 1]
|
||||
)
|
||||
or
|
||||
exists(string lo, string hi |
|
||||
// x..N-y
|
||||
regexpCaptureTwo(arg, "(-?\\d+)\\.\\.N-(\\d+)", lo, hi) and
|
||||
result = [lo.toInt() .. arity - hi.toInt()]
|
||||
or
|
||||
// N-x..N-y
|
||||
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.N-(\\d+)", lo, hi) and
|
||||
result = [arity - lo.toInt() .. arity - hi.toInt()] and
|
||||
result >= 0
|
||||
or
|
||||
// N-x..y
|
||||
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.(\\d+)", lo, hi) and
|
||||
result = [arity - lo.toInt() .. hi.toInt()] and
|
||||
result >= 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) and gets any
|
||||
* of the integers contained within (of which there may be infinitely many).
|
||||
*
|
||||
* Has no result for arguments involving an explicit arity, such as `N-1`.
|
||||
*/
|
||||
bindingset[arg, result]
|
||||
int parseIntUnbounded(string arg) {
|
||||
result = parseInt(arg)
|
||||
or
|
||||
result >= parseLowerBound(arg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) that
|
||||
* may reference the arity of a call, such as `N-1` or `N-3..N-1`.
|
||||
*
|
||||
* Note that expressions of form `N-x` will never resolve to a negative index,
|
||||
* even if `N` is zero (it will have no result in that case).
|
||||
*/
|
||||
bindingset[arg, arity]
|
||||
int parseIntWithArity(string arg, int arity) {
|
||||
result = parseInt(arg)
|
||||
or
|
||||
result in [parseLowerBound(arg) .. arity - 1]
|
||||
or
|
||||
result = parseIntWithExplicitArity(arg, arity)
|
||||
}
|
||||
|
||||
/** Gets the `n`th token on the access path as a string. */
|
||||
private string getRawToken(AccessPath path, int n) {
|
||||
// Avoid splitting by '.' since tokens may contain dots, e.g. `Field[foo.Bar.x]`.
|
||||
// Instead use regexpFind to match valid tokens, and supplement with a final length
|
||||
// check (in `AccessPath.hasSyntaxError`) to ensure all characters were included in a token.
|
||||
result = path.regexpFind("\\w+(?:\\[[^\\]]*\\])?(?=\\.|$)", n, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* A string that occurs as an access path (either identifying or input/output spec)
|
||||
* which might be relevant for this database.
|
||||
*/
|
||||
final class AccessPath extends string {
|
||||
AccessPath() { accessPathRange(this) }
|
||||
|
||||
/** Holds if this string is not a syntactically valid access path. */
|
||||
predicate hasSyntaxError() {
|
||||
// If the lengths match, all characters must haven been included in a token
|
||||
// or seen by the `.` lookahead pattern.
|
||||
this != "" and
|
||||
not this.length() = sum(int n | | getRawToken(this, n).length() + 1) - 1
|
||||
}
|
||||
|
||||
/** Gets the `n`th token on the access path (if there are no syntax errors). */
|
||||
AccessPathToken getToken(int n) {
|
||||
result = getRawToken(this, n) and
|
||||
not this.hasSyntaxError()
|
||||
}
|
||||
|
||||
/** Gets the number of tokens on the path (if there are no syntax errors). */
|
||||
int getNumToken() {
|
||||
result = count(int n | exists(getRawToken(this, n))) and
|
||||
not this.hasSyntaxError()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An access path token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths.
|
||||
*/
|
||||
class AccessPathToken extends AccessPathTokenBaseFinal {
|
||||
AccessPathToken() { this = getRawToken(_, _) }
|
||||
|
||||
/** Gets the name of the token, such as `Member` from `Member[x]` */
|
||||
pragma[nomagic]
|
||||
string getName() { result = super.getName() }
|
||||
|
||||
/**
|
||||
* Gets the argument list, such as `1,2` from `Member[1,2]`,
|
||||
* or has no result if there are no arguments.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
string getArgumentList() { result = super.getArgumentList() }
|
||||
|
||||
/** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
pragma[nomagic]
|
||||
string getArgument(int n) { result = super.getArgument(n) }
|
||||
|
||||
/** Gets the `n`th argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */
|
||||
pragma[nomagic]
|
||||
string getArgument(string name, int n) {
|
||||
name = this.getName() and result = this.getArgument(n)
|
||||
}
|
||||
|
||||
/** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
string getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
/** Gets an argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */
|
||||
string getAnArgument(string name) { result = this.getArgument(name, _) }
|
||||
|
||||
/** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */
|
||||
pragma[nomagic]
|
||||
int getNumArgument() { result = count(int n | exists(this.getArgument(n))) }
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
private import codeql.util.Unit
|
||||
private import codeql.util.Option
|
||||
private import codeql.util.Boolean
|
||||
private import codeql.dataflow.DataFlow
|
||||
|
||||
module MakeImpl<InputSig Lang> {
|
||||
@@ -1183,7 +1184,9 @@ module MakeImpl<InputSig Lang> {
|
||||
string toString();
|
||||
}
|
||||
|
||||
class Ap;
|
||||
class Ap {
|
||||
string toString();
|
||||
}
|
||||
|
||||
class ApNil extends Ap;
|
||||
|
||||
@@ -1464,10 +1467,11 @@ module MakeImpl<InputSig Lang> {
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowIntoArg(
|
||||
ArgNodeEx arg, FlowState state, Cc outercc, ParamNodeOption summaryCtx, TypOption argT,
|
||||
ApOption argAp, Typ t, Ap ap, ApApprox apa, boolean cc
|
||||
ApOption argAp, Typ t, Ap ap, boolean emptyAp, ApApprox apa, boolean cc
|
||||
) {
|
||||
fwdFlow(arg, state, outercc, summaryCtx, argT, argAp, t, ap, apa) and
|
||||
if outercc instanceof CcCall then cc = true else cc = false
|
||||
(if outercc instanceof CcCall then cc = true else cc = false) and
|
||||
if ap instanceof ApNil then emptyAp = true else emptyAp = false
|
||||
}
|
||||
|
||||
private signature module FwdFlowInInputSig {
|
||||
@@ -1549,26 +1553,59 @@ module MakeImpl<InputSig Lang> {
|
||||
viableImplNotCallContextReducedInlineLate(call, outercc)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
pragma[inline]
|
||||
private predicate fwdFlowInCand(
|
||||
DataFlowCall call, ArgNodeEx arg, Cc outercc, DataFlowCallable inner, ParamNodeEx p,
|
||||
ApApprox apa, boolean allowsFieldFlow, boolean cc
|
||||
DataFlowCall call, ArgNodeEx arg, FlowState state, Cc outercc, DataFlowCallable inner,
|
||||
ParamNodeEx p, ParamNodeOption summaryCtx, TypOption argT, ApOption argAp, Typ t, Ap ap,
|
||||
boolean emptyAp, ApApprox apa, boolean cc
|
||||
) {
|
||||
fwdFlowIntoArg(arg, _, outercc, _, _, _, _, _, apa, cc) and
|
||||
(
|
||||
inner = viableImplCallContextReducedInlineLate(call, arg, outercc)
|
||||
or
|
||||
viableImplArgNotCallContextReduced(call, arg, outercc)
|
||||
) and
|
||||
callEdgeArgParamRestrictedInlineLate(call, inner, arg, p, allowsFieldFlow, apa)
|
||||
exists(boolean allowsFieldFlow |
|
||||
fwdFlowIntoArg(arg, state, outercc, summaryCtx, argT, argAp, t, ap, emptyAp, apa, cc) and
|
||||
(
|
||||
inner = viableImplCallContextReducedInlineLate(call, arg, outercc)
|
||||
or
|
||||
viableImplArgNotCallContextReduced(call, arg, outercc)
|
||||
) and
|
||||
callEdgeArgParamRestrictedInlineLate(call, inner, arg, p, allowsFieldFlow, apa) and
|
||||
if allowsFieldFlow = false then emptyAp = true else any()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
private predicate fwdFlowInCandTypeFlowDisabled(
|
||||
DataFlowCall call, ArgNodeEx arg, FlowState state, Cc outercc, DataFlowCallable inner,
|
||||
ParamNodeEx p, ParamNodeOption summaryCtx, TypOption argT, ApOption argAp, Typ t, Ap ap,
|
||||
ApApprox apa, boolean cc
|
||||
) {
|
||||
not enableTypeFlow() and
|
||||
fwdFlowInCand(call, arg, state, outercc, inner, p, summaryCtx, argT, argAp, t, ap, _,
|
||||
apa, cc)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowInValidEdge(
|
||||
private predicate fwdFlowInCandTypeFlowEnabled(
|
||||
DataFlowCall call, ArgNodeEx arg, Cc outercc, DataFlowCallable inner, ParamNodeEx p,
|
||||
CcCall innercc, ApApprox apa, boolean allowsFieldFlow, boolean cc
|
||||
boolean emptyAp, ApApprox apa, boolean cc
|
||||
) {
|
||||
fwdFlowInCand(call, arg, outercc, inner, p, apa, allowsFieldFlow, cc) and
|
||||
enableTypeFlow() and
|
||||
fwdFlowInCand(call, arg, _, outercc, inner, p, _, _, _, _, _, emptyAp, apa, cc)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowInValidEdgeTypeFlowDisabled(
|
||||
DataFlowCall call, DataFlowCallable inner, CcCall innercc, boolean cc
|
||||
) {
|
||||
not enableTypeFlow() and
|
||||
FwdTypeFlow::typeFlowValidEdgeIn(call, inner, cc) and
|
||||
innercc = getCallContextCall(call, inner)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowInValidEdgeTypeFlowEnabled(
|
||||
DataFlowCall call, ArgNodeEx arg, Cc outercc, DataFlowCallable inner, ParamNodeEx p,
|
||||
CcCall innercc, boolean emptyAp, ApApprox apa, boolean cc
|
||||
) {
|
||||
fwdFlowInCandTypeFlowEnabled(call, arg, outercc, inner, p, emptyAp, apa, cc) and
|
||||
FwdTypeFlow::typeFlowValidEdgeIn(call, inner, cc) and
|
||||
innercc = getCallContextCall(call, inner)
|
||||
}
|
||||
@@ -1579,10 +1616,18 @@ module MakeImpl<InputSig Lang> {
|
||||
CcCall innercc, ParamNodeOption summaryCtx, TypOption argT, ApOption argAp, Typ t,
|
||||
Ap ap, ApApprox apa, boolean cc
|
||||
) {
|
||||
exists(ArgNodeEx arg, boolean allowsFieldFlow |
|
||||
fwdFlowIntoArg(arg, state, outercc, summaryCtx, argT, argAp, t, ap, apa, cc) and
|
||||
fwdFlowInValidEdge(call, arg, outercc, inner, p, innercc, apa, allowsFieldFlow, cc) and
|
||||
if allowsFieldFlow = false then ap instanceof ApNil else any()
|
||||
exists(ArgNodeEx arg |
|
||||
// type flow disabled: linear recursion
|
||||
fwdFlowInCandTypeFlowDisabled(call, arg, state, outercc, inner, p, summaryCtx, argT,
|
||||
argAp, t, ap, apa, cc) and
|
||||
fwdFlowInValidEdgeTypeFlowDisabled(call, inner, innercc, cc)
|
||||
or
|
||||
// type flow enabled: non-linear recursion
|
||||
exists(boolean emptyAp |
|
||||
fwdFlowIntoArg(arg, state, outercc, summaryCtx, argT, argAp, t, ap, emptyAp, apa, cc) and
|
||||
fwdFlowInValidEdgeTypeFlowEnabled(call, arg, outercc, inner, p, innercc, emptyAp,
|
||||
apa, cc)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2432,9 +2477,7 @@ module MakeImpl<InputSig Lang> {
|
||||
|
||||
class Typ = Unit;
|
||||
|
||||
class Ap extends boolean {
|
||||
Ap() { this in [true, false] }
|
||||
}
|
||||
class Ap = Boolean;
|
||||
|
||||
class ApNil extends Ap {
|
||||
ApNil() { this = false }
|
||||
@@ -2724,7 +2767,7 @@ module MakeImpl<InputSig Lang> {
|
||||
pragma[noinline]
|
||||
ApHeadContent getHeadContent(Ap ap) { result = ap.getHead() }
|
||||
|
||||
predicate projectToHeadContent = getContentApprox/1;
|
||||
predicate projectToHeadContent = getContentApproxCached/1;
|
||||
|
||||
class ApOption = ApproxAccessPathFrontOption;
|
||||
|
||||
|
||||
@@ -7,16 +7,22 @@ module MakeImplCommon<InputSig Lang> {
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
/** Provides `FlowState = string`. */
|
||||
module FlowStateString {
|
||||
/**
|
||||
* DEPRECATED: Generally, a custom `FlowState` type should be used instead,
|
||||
* but `string` can of course still be used without referring to this
|
||||
* module.
|
||||
*
|
||||
* Provides `FlowState = string`.
|
||||
*/
|
||||
deprecated module FlowStateString {
|
||||
/** A state value to track during data flow. */
|
||||
class FlowState = string;
|
||||
deprecated class FlowState = string;
|
||||
|
||||
/**
|
||||
* The default state, which is used when the state is unspecified for a source
|
||||
* or a sink.
|
||||
*/
|
||||
class FlowStateEmpty extends FlowState {
|
||||
deprecated class FlowStateEmpty extends FlowState {
|
||||
FlowStateEmpty() { this = "" }
|
||||
}
|
||||
}
|
||||
@@ -551,7 +557,8 @@ module MakeImplCommon<InputSig Lang> {
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlowCand(p, mid, read) and
|
||||
simpleLocalFlowStep(mid, node)
|
||||
simpleLocalFlowStep(mid, node) and
|
||||
validParameterAliasStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
@@ -670,7 +677,8 @@ module MakeImplCommon<InputSig Lang> {
|
||||
// local flow
|
||||
exists(Node mid |
|
||||
parameterValueFlow(p, mid, read) and
|
||||
simpleLocalFlowStep(mid, node)
|
||||
simpleLocalFlowStep(mid, node) and
|
||||
validParameterAliasStep(mid, node)
|
||||
)
|
||||
or
|
||||
// read
|
||||
@@ -783,10 +791,12 @@ module MakeImplCommon<InputSig Lang> {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) {
|
||||
mayBenefitFromCallContext(call, callable)
|
||||
or
|
||||
callEnclosingCallable(call, callable) and
|
||||
exists(viableCallableLambda(call, TDataFlowCallSome(_)))
|
||||
(
|
||||
mayBenefitFromCallContext(call)
|
||||
or
|
||||
exists(viableCallableLambda(call, TDataFlowCallSome(_)))
|
||||
) and
|
||||
callEnclosingCallable(call, callable)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -973,6 +983,9 @@ module MakeImplCommon<InputSig Lang> {
|
||||
cached
|
||||
predicate paramMustFlow(ParamNode p, ArgNode arg) { localMustFlowStep+(p, arg) }
|
||||
|
||||
cached
|
||||
ContentApprox getContentApproxCached(Content c) { result = getContentApprox(c) }
|
||||
|
||||
cached
|
||||
newtype TCallContext =
|
||||
TAnyCallContext() or
|
||||
@@ -1121,8 +1134,8 @@ module MakeImplCommon<InputSig Lang> {
|
||||
Input::enableTypeFlow() and
|
||||
(
|
||||
exists(ParamNode p, DataFlowType at, DataFlowType pt |
|
||||
at = getNodeType(arg) and
|
||||
pt = getNodeType(p) and
|
||||
nodeDataFlowType(arg, at) and
|
||||
nodeDataFlowType(p, pt) and
|
||||
relevantCallEdge(_, _, arg, p) and
|
||||
typeStrongerThan0(pt, at)
|
||||
)
|
||||
@@ -1131,8 +1144,8 @@ module MakeImplCommon<InputSig Lang> {
|
||||
// A call edge may implicitly strengthen a type by ensuring that a
|
||||
// specific argument node was reached if the type of that argument was
|
||||
// strengthened via a cast.
|
||||
at = getNodeType(arg) and
|
||||
pt = getNodeType(p) and
|
||||
nodeDataFlowType(arg, at) and
|
||||
nodeDataFlowType(p, pt) and
|
||||
paramMustFlow(p, arg) and
|
||||
relevantCallEdge(_, _, arg, _) and
|
||||
typeStrongerThan0(at, pt)
|
||||
@@ -1172,8 +1185,8 @@ module MakeImplCommon<InputSig Lang> {
|
||||
or
|
||||
exists(ArgNode arg, DataFlowType at, DataFlowType pt |
|
||||
trackedParamTypeCand(p) and
|
||||
at = getNodeType(arg) and
|
||||
pt = getNodeType(p) and
|
||||
nodeDataFlowType(arg, at) and
|
||||
nodeDataFlowType(p, pt) and
|
||||
relevantCallEdge(_, _, arg, p) and
|
||||
typeStrongerThan0(at, pt)
|
||||
)
|
||||
@@ -1883,7 +1896,7 @@ module MakeImplCommon<InputSig Lang> {
|
||||
Content getAHead() {
|
||||
exists(ContentApprox cont |
|
||||
this = TApproxFrontHead(cont) and
|
||||
cont = getContentApprox(result)
|
||||
cont = getContentApproxCached(result)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,4 +319,9 @@ module MakeConsistency<
|
||||
strictcount(DataFlowCall call0 | multipleArgumentCallInclude(arg, call0)) > 1 and
|
||||
msg = "Multiple calls for argument node."
|
||||
}
|
||||
|
||||
query predicate lambdaCallEnclosingCallableMismatch(DataFlowCall call, Node receiver) {
|
||||
lambdaCall(call, _, receiver) and
|
||||
not nodeGetEnclosingCallable(receiver) = call.getEnclosingCallable()
|
||||
}
|
||||
}
|
||||
|
||||
1886
shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll
Normal file
1886
shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll
Normal file
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ signature module InputSig<DF::InputSig DataFlowLang> {
|
||||
|
||||
predicate defaultSink(DataFlowLang::Node source);
|
||||
|
||||
bindingset[src, sink]
|
||||
string getArgString(DataFlowLang::Node src, DataFlowLang::Node sink);
|
||||
}
|
||||
|
||||
@@ -62,7 +63,13 @@ module InlineFlowTestMake<
|
||||
predicate isSink(DataFlowLang::Node sink) { none() }
|
||||
}
|
||||
|
||||
module FlowTest<DataFlow::ConfigSig ValueFlowConfig, DataFlow::ConfigSig TaintFlowConfig> {
|
||||
bindingset[src, sink]
|
||||
signature string getArgStringSig(DataFlowLang::Node src, DataFlowLang::Node sink);
|
||||
|
||||
module FlowTestArgString<
|
||||
DataFlow::ConfigSig ValueFlowConfig, DataFlow::ConfigSig TaintFlowConfig,
|
||||
getArgStringSig/2 getArgString>
|
||||
{
|
||||
module ValueFlow = DataFlow::Global<ValueFlowConfig>;
|
||||
|
||||
module TaintFlow = TaintTracking::Global<TaintFlowConfig>;
|
||||
@@ -82,7 +89,7 @@ module InlineFlowTestMake<
|
||||
exists(DataFlowLang::Node src, DataFlowLang::Node sink | ValueFlow::flow(src, sink) |
|
||||
hasLocationInfo(sink, location) and
|
||||
element = sink.toString() and
|
||||
value = Impl::getArgString(src, sink)
|
||||
value = getArgString(src, sink)
|
||||
)
|
||||
or
|
||||
tag = "hasTaintFlow" and
|
||||
@@ -91,7 +98,7 @@ module InlineFlowTestMake<
|
||||
|
|
||||
hasLocationInfo(sink, location) and
|
||||
element = sink.toString() and
|
||||
value = Impl::getArgString(src, sink)
|
||||
value = getArgString(src, sink)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -105,13 +112,27 @@ module InlineFlowTestMake<
|
||||
}
|
||||
}
|
||||
|
||||
module FlowTest<DataFlow::ConfigSig ValueFlowConfig, DataFlow::ConfigSig TaintFlowConfig> {
|
||||
import FlowTestArgString<ValueFlowConfig, TaintFlowConfig, Impl::getArgString/2>
|
||||
}
|
||||
|
||||
module DefaultFlowTest = FlowTest<DefaultFlowConfig, DefaultFlowConfig>;
|
||||
|
||||
module ValueFlowTestArgString<DataFlow::ConfigSig ValueFlowConfig, getArgStringSig/2 getArgString>
|
||||
{
|
||||
import FlowTestArgString<ValueFlowConfig, NoFlowConfig, getArgString/2>
|
||||
}
|
||||
|
||||
module ValueFlowTest<DataFlow::ConfigSig ValueFlowConfig> {
|
||||
import FlowTest<ValueFlowConfig, NoFlowConfig>
|
||||
import ValueFlowTestArgString<ValueFlowConfig, Impl::getArgString/2>
|
||||
}
|
||||
|
||||
module TaintFlowTestArgString<DataFlow::ConfigSig TaintFlowConfig, getArgStringSig/2 getArgString>
|
||||
{
|
||||
import FlowTestArgString<NoFlowConfig, TaintFlowConfig, getArgString/2>
|
||||
}
|
||||
|
||||
module TaintFlowTest<DataFlow::ConfigSig TaintFlowConfig> {
|
||||
import FlowTest<NoFlowConfig, TaintFlowConfig>
|
||||
import TaintFlowTestArgString<TaintFlowConfig, Impl::getArgString/2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/dataflow
|
||||
version: 0.1.5
|
||||
version: 0.1.7
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/mad/change-notes/released/0.2.6.md
Normal file
3
shared/mad/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/mad/change-notes/released/0.2.7.md
Normal file
3
shared/mad/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -26,9 +26,9 @@ module KindValidation<KindValidationConfigSig Config> {
|
||||
this =
|
||||
[
|
||||
// shared
|
||||
"code-injection", "command-injection", "file-content-store", "html-injection",
|
||||
"js-injection", "ldap-injection", "log-injection", "path-injection", "request-forgery",
|
||||
"sql-injection", "url-redirection",
|
||||
"code-injection", "command-injection", "environment-injection", "file-content-store",
|
||||
"html-injection", "js-injection", "ldap-injection", "log-injection", "path-injection",
|
||||
"request-forgery", "sql-injection", "url-redirection",
|
||||
// Java-only currently, but may be shared in the future
|
||||
"bean-validation", "fragment-injection", "groovy-injection", "hostname-verification",
|
||||
"information-leak", "intent-redirection", "jexl-injection", "jndi-injection",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/mad
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies: null
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.0.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.0.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.0.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/rangeanalysis/change-notes/released/0.0.5.md
Normal file
3
shared/rangeanalysis/change-notes/released/0.0.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.5
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/rangeanalysis/change-notes/released/0.0.6.md
Normal file
3
shared/rangeanalysis/change-notes/released/0.0.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.6
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.0.4
|
||||
lastReleaseVersion: 0.0.6
|
||||
|
||||
@@ -289,6 +289,10 @@ signature module LangSig<Semantic Sem, DeltaSig D> {
|
||||
predicate ignoreExprBound(Sem::Expr e);
|
||||
|
||||
default predicate javaCompatibility() { none() }
|
||||
|
||||
default predicate includeConstantBounds() { any() }
|
||||
|
||||
default predicate includeRelativeBounds() { any() }
|
||||
}
|
||||
|
||||
signature module BoundSig<LocationSig Location, Semantic Sem, DeltaSig D> {
|
||||
@@ -678,60 +682,65 @@ module RangeStage<
|
||||
* - `upper = false` : `e2 >= e1 + delta`
|
||||
*/
|
||||
private predicate boundFlowStep(Sem::Expr e2, Sem::Expr e1, D::Delta delta, boolean upper) {
|
||||
valueFlowStep(e2, e1, delta) and
|
||||
(upper = true or upper = false)
|
||||
or
|
||||
e2.(SafeCastExpr).getOperand() = e1 and
|
||||
delta = D::fromInt(0) and
|
||||
(upper = true or upper = false)
|
||||
or
|
||||
javaCompatibility() and
|
||||
exists(Sem::Expr x, Sem::SubExpr sub |
|
||||
e2 = sub and
|
||||
sub.getLeftOperand() = e1 and
|
||||
sub.getRightOperand() = x
|
||||
|
|
||||
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
|
||||
not x instanceof Sem::ConstantIntegerExpr and
|
||||
if strictlyPositiveIntegralExpr(x)
|
||||
then upper = true and delta = D::fromInt(-1)
|
||||
else
|
||||
if semPositive(x)
|
||||
then upper = true and delta = D::fromInt(0)
|
||||
// Constants have easy, base-case bounds, so let's not infer any recursive bounds.
|
||||
not e2 instanceof Sem::ConstantIntegerExpr and
|
||||
(
|
||||
valueFlowStep(e2, e1, delta) and
|
||||
upper = [true, false]
|
||||
or
|
||||
e2.(SafeCastExpr).getOperand() = e1 and
|
||||
delta = D::fromInt(0) and
|
||||
upper = [true, false]
|
||||
or
|
||||
javaCompatibility() and
|
||||
exists(Sem::Expr x, Sem::SubExpr sub |
|
||||
e2 = sub and
|
||||
sub.getLeftOperand() = e1 and
|
||||
sub.getRightOperand() = x
|
||||
|
|
||||
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
|
||||
not x instanceof Sem::ConstantIntegerExpr and
|
||||
if strictlyPositiveIntegralExpr(x)
|
||||
then upper = true and delta = D::fromInt(-1)
|
||||
else
|
||||
if strictlyNegativeIntegralExpr(x)
|
||||
then upper = false and delta = D::fromInt(1)
|
||||
if semPositive(x)
|
||||
then upper = true and delta = D::fromInt(0)
|
||||
else
|
||||
if semNegative(x)
|
||||
then upper = false and delta = D::fromInt(0)
|
||||
else none()
|
||||
if strictlyNegativeIntegralExpr(x)
|
||||
then upper = false and delta = D::fromInt(1)
|
||||
else
|
||||
if semNegative(x)
|
||||
then upper = false and delta = D::fromInt(0)
|
||||
else none()
|
||||
)
|
||||
or
|
||||
e2.(Sem::RemExpr).getRightOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = D::fromInt(-1) and
|
||||
upper = true
|
||||
or
|
||||
e2.(Sem::RemExpr).getLeftOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = D::fromInt(0) and
|
||||
upper = true
|
||||
or
|
||||
e2.(Sem::BitAndExpr).getAnOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = D::fromInt(0) and
|
||||
upper = true
|
||||
or
|
||||
e2.(Sem::BitOrExpr).getAnOperand() = e1 and
|
||||
semPositive(e2) and
|
||||
delta = D::fromInt(0) and
|
||||
upper = false
|
||||
or
|
||||
additionalBoundFlowStep(e2, e1, delta, upper)
|
||||
)
|
||||
or
|
||||
e2.(Sem::RemExpr).getRightOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = D::fromInt(-1) and
|
||||
upper = true
|
||||
or
|
||||
e2.(Sem::RemExpr).getLeftOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = D::fromInt(0) and
|
||||
upper = true
|
||||
or
|
||||
e2.(Sem::BitAndExpr).getAnOperand() = e1 and
|
||||
semPositive(e1) and
|
||||
delta = D::fromInt(0) and
|
||||
upper = true
|
||||
or
|
||||
e2.(Sem::BitOrExpr).getAnOperand() = e1 and
|
||||
semPositive(e2) and
|
||||
delta = D::fromInt(0) and
|
||||
upper = false
|
||||
or
|
||||
additionalBoundFlowStep(e2, e1, delta, upper)
|
||||
}
|
||||
|
||||
/** Holds if `e2 = e1 * factor` and `factor > 0`. */
|
||||
private predicate boundFlowStepMul(Sem::Expr e2, Sem::Expr e1, D::Delta factor) {
|
||||
not e2 instanceof Sem::ConstantIntegerExpr and
|
||||
exists(Sem::ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
|
||||
e2.(Sem::MulExpr).hasOperands(e1, c) and factor = D::fromInt(k)
|
||||
or
|
||||
@@ -751,6 +760,7 @@ module RangeStage<
|
||||
* therefore only valid for non-negative numbers.
|
||||
*/
|
||||
private predicate boundFlowStepDiv(Sem::Expr e2, Sem::Expr e1, D::Delta factor) {
|
||||
not e2 instanceof Sem::ConstantIntegerExpr and
|
||||
Sem::getExprType(e2) instanceof Sem::IntegerType and
|
||||
exists(Sem::ConstantIntegerExpr c, D::Delta k |
|
||||
k = D::fromInt(c.getIntValue()) and D::toFloat(k) > 0
|
||||
@@ -983,16 +993,31 @@ module RangeStage<
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` has an upper (for `upper = true`) or lower
|
||||
* (for `upper = false`) bound of `b`.
|
||||
*/
|
||||
private predicate baseBound(Sem::Expr e, D::Delta b, boolean upper) {
|
||||
hasConstantBound(e, b, upper)
|
||||
private predicate includeBound(SemBound b) {
|
||||
// always include phi bounds
|
||||
b.(SemSsaBound).getVariable() instanceof Sem::SsaPhiNode
|
||||
or
|
||||
upper = false and
|
||||
b = D::fromInt(0) and
|
||||
semPositive(e.(Sem::BitAndExpr).getAnOperand())
|
||||
if b instanceof SemZeroBound then includeConstantBounds() else includeRelativeBounds()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` has an intrinsic upper (for `upper = true`) or lower
|
||||
* (for `upper = false`) bound of `b + delta` as a base case for range analysis.
|
||||
*/
|
||||
private predicate baseBound(Sem::Expr e, SemBound b, D::Delta delta, boolean upper) {
|
||||
includeBound(b) and
|
||||
(
|
||||
e = b.getExpr(delta) and
|
||||
upper = [true, false]
|
||||
or
|
||||
hasConstantBound(e, delta, upper) and
|
||||
b instanceof SemZeroBound
|
||||
or
|
||||
upper = false and
|
||||
delta = D::fromInt(0) and
|
||||
semPositive(e.(Sem::BitAndExpr).getAnOperand()) and
|
||||
b instanceof SemZeroBound
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1107,6 +1132,84 @@ module RangeStage<
|
||||
bindingset[x, y]
|
||||
private float truncatingDiv(float x, float y) { result = (x - (x % y)) / y }
|
||||
|
||||
/**
|
||||
* Holds if `e1 + delta` is a valid bound for `e2`.
|
||||
* - `upper = true` : `e2 <= e1 + delta`
|
||||
* - `upper = false` : `e2 >= e1 + delta`
|
||||
*
|
||||
* This is restricted to simple forward-flowing steps and disregards phi-nodes.
|
||||
*/
|
||||
private predicate preBoundStep(Sem::Expr e2, Sem::Expr e1, D::Delta delta, boolean upper) {
|
||||
boundFlowStep(e2, e1, delta, upper)
|
||||
or
|
||||
exists(Sem::SsaVariable v, SsaReadPositionBlock bb |
|
||||
boundFlowStepSsa(v, bb, e1, delta, upper, _) and
|
||||
bb.getAnSsaRead(v) = e2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if simple forward-flowing steps from `e` can reach an expression that
|
||||
* has multiple incoming bound-flow edges, that is, it has multiple ways to
|
||||
* get a valid bound.
|
||||
*/
|
||||
private predicate reachesBoundMergepoint(Sem::Expr e, boolean upper) {
|
||||
2 <= strictcount(Sem::Expr mid | preBoundStep(e, mid, _, upper))
|
||||
or
|
||||
exists(Sem::SsaPhiNode v, SsaReadPositionBlock bb |
|
||||
boundFlowStepSsa(v, bb, _, _, upper, _) and
|
||||
bb.getAnSsaRead(v) = e
|
||||
)
|
||||
or
|
||||
exists(Sem::Expr e2 |
|
||||
preBoundStep(e2, e, _, upper) and
|
||||
reachesBoundMergepoint(e2, upper)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate relevantPreBoundStep(Sem::Expr e2, Sem::Expr e1, D::Delta delta, boolean upper) {
|
||||
preBoundStep(e2, e1, delta, upper) and
|
||||
reachesBoundMergepoint(e2, upper)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `e` that can be found using only
|
||||
* simple forward-flowing steps and disregarding phi-nodes.
|
||||
* - `upper = true` : `e <= b + delta`
|
||||
* - `upper = false` : `e >= b + delta`
|
||||
*
|
||||
* This predicate is used as a fast approximation for `bounded` to avoid
|
||||
* excessive computation in certain cases. In particular, this applies to
|
||||
* loop-unrolled code like
|
||||
* ```
|
||||
* if (..) x+=1; else x+=100;
|
||||
* x &= 7;
|
||||
* if (..) x+=1; else x+=100;
|
||||
* x &= 7;
|
||||
* if (..) x+=1; else x+=100;
|
||||
* x &= 7;
|
||||
* ...
|
||||
* ```
|
||||
*/
|
||||
private predicate preBounded(Sem::Expr e, SemBound b, D::Delta delta, boolean upper) {
|
||||
baseBound(e, b, delta, upper)
|
||||
or
|
||||
exists(Sem::Expr mid, D::Delta d1, D::Delta d2 |
|
||||
relevantPreBoundStep(e, mid, d1, upper) and
|
||||
preBounded(mid, b, d2, upper) and
|
||||
delta = D::fromFloat(D::toFloat(d1) + D::toFloat(d2))
|
||||
)
|
||||
}
|
||||
|
||||
private predicate bestPreBound(Sem::Expr e, SemBound b, D::Delta delta, boolean upper) {
|
||||
delta = min(D::Delta d | preBounded(e, b, d, upper) | d order by D::toFloat(d)) and
|
||||
upper = true
|
||||
or
|
||||
delta = max(D::Delta d | preBounded(e, b, d, upper) | d order by D::toFloat(d)) and
|
||||
upper = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `b + delta` is a valid bound for `e`.
|
||||
* - `upper = true` : `e <= b + delta`
|
||||
@@ -1117,15 +1220,14 @@ module RangeStage<
|
||||
D::Delta origdelta, SemReason reason
|
||||
) {
|
||||
not ignoreExprBound(e) and
|
||||
(
|
||||
e = b.getExpr(delta) and
|
||||
(upper = true or upper = false) and
|
||||
fromBackEdge = false and
|
||||
origdelta = delta and
|
||||
reason = TSemNoReason()
|
||||
// ignore poor bounds
|
||||
not exists(D::Delta d | bestPreBound(e, b, d, upper) |
|
||||
D::toFloat(delta) > D::toFloat(d) and upper = true
|
||||
or
|
||||
baseBound(e, delta, upper) and
|
||||
b instanceof SemZeroBound and
|
||||
D::toFloat(delta) < D::toFloat(d) and upper = false
|
||||
) and
|
||||
(
|
||||
baseBound(e, b, delta, upper) and
|
||||
fromBackEdge = false and
|
||||
origdelta = delta and
|
||||
reason = TSemNoReason()
|
||||
@@ -1137,8 +1239,6 @@ module RangeStage<
|
||||
or
|
||||
exists(Sem::Expr mid, D::Delta d1, D::Delta d2 |
|
||||
boundFlowStep(e, mid, d1, upper) and
|
||||
// Constants have easy, base-case bounds, so let's not infer any recursive bounds.
|
||||
not e instanceof Sem::ConstantIntegerExpr and
|
||||
bounded(mid, b, d2, upper, fromBackEdge, origdelta, reason) and
|
||||
// upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta
|
||||
// upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta
|
||||
@@ -1152,7 +1252,6 @@ module RangeStage<
|
||||
or
|
||||
exists(Sem::Expr mid, D::Delta factor, D::Delta d |
|
||||
boundFlowStepMul(e, mid, factor) and
|
||||
not e instanceof Sem::ConstantIntegerExpr and
|
||||
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
|
||||
b instanceof SemZeroBound and
|
||||
delta = D::fromFloat(D::toFloat(d) * D::toFloat(factor))
|
||||
@@ -1160,7 +1259,6 @@ module RangeStage<
|
||||
or
|
||||
exists(Sem::Expr mid, D::Delta factor, D::Delta d |
|
||||
boundFlowStepDiv(e, mid, factor) and
|
||||
not e instanceof Sem::ConstantIntegerExpr and
|
||||
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
|
||||
b instanceof SemZeroBound and
|
||||
D::toFloat(d) >= 0 and
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/rangeanalysis
|
||||
version: 0.0.4
|
||||
version: 0.0.6
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/regex/change-notes/released/0.2.6.md
Normal file
3
shared/regex/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/regex/change-notes/released/0.2.7.md
Normal file
3
shared/regex/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -129,6 +129,9 @@ module Make<RegexTreeViewSig TreeImpl> {
|
||||
or
|
||||
// starting from the zero byte is a good indication that it's purposely matching a large range.
|
||||
result.isRange(0.toUnicode(), _)
|
||||
or
|
||||
// the range 0123456789:;<=>? is intentional
|
||||
result.isRange("0", "?")
|
||||
}
|
||||
|
||||
/** Gets a char between (and including) `low` and `high`. */
|
||||
|
||||
@@ -64,9 +64,6 @@ module Make<RegexTreeViewSig TreeImpl> {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use `EmptyPositiveSubPattern` instead. */
|
||||
deprecated class EmptyPositiveSubPatttern = EmptyPositiveSubPattern;
|
||||
|
||||
final private class FinalRegExpTerm = RegExpTerm;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/regex
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 0.2.7
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Deleted the deprecated `adjacentDefNoUncertainReads`, `lastRefRedefNoUncertainReads`, and `lastRefNoUncertainReads` predicates.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/ssa/change-notes/released/0.2.6.md
Normal file
3
shared/ssa/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
5
shared/ssa/change-notes/released/0.2.7.md
Normal file
5
shared/ssa/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.2.7
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Deleted the deprecated `adjacentDefNoUncertainReads`, `lastRefRedefNoUncertainReads`, and `lastRefNoUncertainReads` predicates.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -741,43 +741,6 @@ module Make<LocationSig Location, InputSig<Location> Input> {
|
||||
defAdjacentRead(def, bb1, bb2, i2)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
deprecated private predicate adjacentDefRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
v = def.getSourceVariable()
|
||||
}
|
||||
|
||||
deprecated private predicate adjacentDefReachesRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
|
||||
ssaRef(bb1, i1, v, SsaDef())
|
||||
or
|
||||
variableRead(bb1, i1, v, true)
|
||||
)
|
||||
or
|
||||
exists(BasicBlock bb3, int i3 |
|
||||
adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
|
||||
variableRead(bb3, i3, _, false) and
|
||||
adjacentDefRead(def, bb3, i3, bb2, i2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `adjacentDefRead`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
deprecated predicate adjacentDefNoUncertainReads(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
|
||||
variableRead(bb2, i2, _, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
@@ -838,31 +801,6 @@ module Make<LocationSig Location, InputSig<Location> Input> {
|
||||
lastRefRedef(inp, _, _, def)
|
||||
}
|
||||
|
||||
deprecated private predicate adjacentDefReachesUncertainRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
|
||||
variableRead(bb2, i2, _, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `lastRefRedef`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
deprecated predicate lastRefRedefNoUncertainReads(
|
||||
Definition def, BasicBlock bb, int i, Definition next
|
||||
) {
|
||||
lastRefRedef(def, bb, i, next) and
|
||||
not variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(BasicBlock bb0, int i0 |
|
||||
lastRefRedef(def, bb0, i0, next) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
@@ -908,22 +846,6 @@ module Make<LocationSig Location, InputSig<Location> Input> {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `lastRefRedef`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
deprecated predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) {
|
||||
lastRef(def, bb, i) and
|
||||
not variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(BasicBlock bb0, int i0 |
|
||||
lastRef(def, bb0, i0) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
|
||||
/** A static single assignment (SSA) definition. */
|
||||
class Definition extends TDefinition {
|
||||
/** Gets the source variable underlying this SSA definition. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ssa
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.0.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.0.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.0.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/threat-models/change-notes/released/0.0.5.md
Normal file
3
shared/threat-models/change-notes/released/0.0.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.5
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/threat-models/change-notes/released/0.0.6.md
Normal file
3
shared/threat-models/change-notes/released/0.0.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.6
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.0.4
|
||||
lastReleaseVersion: 0.0.6
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/threat-models
|
||||
version: 0.0.4
|
||||
version: 0.0.6
|
||||
library: true
|
||||
groups: shared
|
||||
dataExtensions:
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/tutorial/change-notes/released/0.2.6.md
Normal file
3
shared/tutorial/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/tutorial/change-notes/released/0.2.7.md
Normal file
3
shared/tutorial/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: codeql/tutorial
|
||||
description: Library for the CodeQL detective tutorials, helping new users learn to
|
||||
write CodeQL queries.
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/typetracking/change-notes/released/0.2.6.md
Normal file
3
shared/typetracking/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/typetracking/change-notes/released/0.2.7.md
Normal file
3
shared/typetracking/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -113,6 +113,12 @@ signature module TypeTrackingInput {
|
||||
* themselves.
|
||||
*/
|
||||
predicate hasFeatureBacktrackStoreTarget();
|
||||
|
||||
/**
|
||||
* Holds if a non-standard `flowsTo` predicate is needed, i.e., one that is not
|
||||
* simply `simpleLocalSmallStep*(localSource, dst)`.
|
||||
*/
|
||||
default predicate nonStandardFlowsTo(LocalSourceNode localSource, Node dst) { none() }
|
||||
}
|
||||
|
||||
private import internal.TypeTrackingImpl as Impl
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
/**
|
||||
* Provides the implementation of type tracking steps through flow summaries.
|
||||
* To use this, you must implement the `Input` signature. You can then use the predicates in the `Output`
|
||||
* signature to implement the predicates of the same names inside `TypeTrackerSpecific.qll`.
|
||||
*/
|
||||
|
||||
/** The classes and predicates needed to generate type-tracking steps from summaries. */
|
||||
signature module Input {
|
||||
// Dataflow nodes
|
||||
class Node;
|
||||
|
||||
// Content
|
||||
class Content;
|
||||
|
||||
class ContentFilter;
|
||||
|
||||
// Relating content and filters
|
||||
/**
|
||||
* Gets a content filter to use for a `WithoutContent[content]` step, (data is not allowed to be stored in `content`)
|
||||
* or has no result if
|
||||
* the step should be treated as ordinary flow.
|
||||
*
|
||||
* `WithoutContent` is often used to perform strong updates on individual collection elements, but for
|
||||
* type-tracking this is rarely beneficial and quite expensive. However, `WithoutContent` can be quite useful
|
||||
* for restricting the type of an object, and in these cases we translate it to a filter.
|
||||
*/
|
||||
ContentFilter getFilterFromWithoutContentStep(Content content);
|
||||
|
||||
/**
|
||||
* Gets a content filter to use for a `WithContent[content]` step, (data must be stored in `content`)
|
||||
* or has no result if
|
||||
* the step cannot be handled by type-tracking.
|
||||
*
|
||||
* `WithContent` is often used to perform strong updates on individual collection elements (or rather
|
||||
* to preserve those that didn't get updated). But for type-tracking this is rarely beneficial and quite expensive.
|
||||
* However, `WithContent` can be quite useful for restricting the type of an object, and in these cases we translate it to a filter.
|
||||
*/
|
||||
ContentFilter getFilterFromWithContentStep(Content content);
|
||||
|
||||
// Summaries and their stacks
|
||||
class SummaryComponent;
|
||||
|
||||
class SummaryComponentStack {
|
||||
SummaryComponent head();
|
||||
}
|
||||
|
||||
/** Gets a singleton stack containing `component`. */
|
||||
SummaryComponentStack singleton(SummaryComponent component);
|
||||
|
||||
/**
|
||||
* Gets the stack obtained by pushing `head` onto `tail`.
|
||||
*/
|
||||
SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail);
|
||||
|
||||
/** Gets a singleton stack representing a return. */
|
||||
SummaryComponent return();
|
||||
|
||||
// Relating content to summaries
|
||||
/** Gets a summary component for content `c`. */
|
||||
SummaryComponent content(Content contents);
|
||||
|
||||
/** Gets a summary component where data is not allowed to be stored in `contents`. */
|
||||
SummaryComponent withoutContent(Content contents);
|
||||
|
||||
/** Gets a summary component where data must be stored in `contents`. */
|
||||
SummaryComponent withContent(Content contents);
|
||||
|
||||
// Callables
|
||||
class SummarizedCallable {
|
||||
predicate propagatesFlow(
|
||||
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
);
|
||||
}
|
||||
|
||||
// Relating nodes to summaries
|
||||
/**
|
||||
* Gets a dataflow node respresenting the argument of `call` indicated by `arg`.
|
||||
*
|
||||
* Returns the post-update node of the argument when `isPostUpdate` is true.
|
||||
*/
|
||||
Node argumentOf(Node call, SummaryComponent arg, boolean isPostUpdate);
|
||||
|
||||
/** Gets a dataflow node respresenting the parameter of `callable` indicated by `param`. */
|
||||
Node parameterOf(Node callable, SummaryComponent param);
|
||||
|
||||
/** Gets a dataflow node respresenting the return of `callable` indicated by `return`. */
|
||||
Node returnOf(Node callable, SummaryComponent return);
|
||||
|
||||
// Relating callables to nodes
|
||||
/** Gets a dataflow node respresenting a call to `callable`. */
|
||||
Node callTo(SummarizedCallable callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The predicates provided by a summary type tracker.
|
||||
* These are meant to be used in `TypeTrackerSpecific.qll`
|
||||
* inside the predicates of the same names.
|
||||
*/
|
||||
signature module Output<Input I> {
|
||||
/**
|
||||
* Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph.
|
||||
*/
|
||||
predicate levelStepNoCall(I::Node nodeFrom, I::Node nodeTo);
|
||||
|
||||
/**
|
||||
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
|
||||
*/
|
||||
predicate basicLoadStep(I::Node nodeFrom, I::Node nodeTo, I::Content content);
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
|
||||
*/
|
||||
predicate basicStoreStep(I::Node nodeFrom, I::Node nodeTo, I::Content content);
|
||||
|
||||
/**
|
||||
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
|
||||
*/
|
||||
predicate basicLoadStoreStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::Content loadContent, I::Content storeContent
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here.
|
||||
*/
|
||||
predicate basicWithoutContentStep(I::Node nodeFrom, I::Node nodeTo, I::ContentFilter filter);
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`.
|
||||
*/
|
||||
predicate basicWithContentStep(I::Node nodeFrom, I::Node nodeTo, I::ContentFilter filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the summary type tracker, that is type tracking through flow summaries.
|
||||
*/
|
||||
module SummaryFlow<Input I> implements Output<I> {
|
||||
pragma[nomagic]
|
||||
private predicate isNonLocal(I::SummaryComponent component) {
|
||||
component = I::content(_)
|
||||
or
|
||||
component = I::withContent(_)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasLoadSummary(
|
||||
I::SummarizedCallable callable, I::Content contents, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
) {
|
||||
callable.propagatesFlow(I::push(I::content(contents), input), output, true) and
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasStoreSummary(
|
||||
I::SummarizedCallable callable, I::Content contents, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
) {
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head()) and
|
||||
(
|
||||
callable.propagatesFlow(input, I::push(I::content(contents), output), true)
|
||||
or
|
||||
// Allow the input to start with an arbitrary WithoutContent[X].
|
||||
// Since type-tracking only tracks one content deep, and we're about to store into another content,
|
||||
// we're already preventing the input from being in a content.
|
||||
callable
|
||||
.propagatesFlow(I::push(I::withoutContent(_), input),
|
||||
I::push(I::content(contents), output), true)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasLoadStoreSummary(
|
||||
I::SummarizedCallable callable, I::Content loadContents, I::Content storeContents,
|
||||
I::SummaryComponentStack input, I::SummaryComponentStack output
|
||||
) {
|
||||
callable
|
||||
.propagatesFlow(I::push(I::content(loadContents), input),
|
||||
I::push(I::content(storeContents), output), true) and
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasWithoutContentSummary(
|
||||
I::SummarizedCallable callable, I::ContentFilter filter, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
) {
|
||||
exists(I::Content content |
|
||||
callable.propagatesFlow(I::push(I::withoutContent(content), input), output, true) and
|
||||
filter = I::getFilterFromWithoutContentStep(content) and
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head()) and
|
||||
input != output
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasWithContentSummary(
|
||||
I::SummarizedCallable callable, I::ContentFilter filter, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
) {
|
||||
exists(I::Content content |
|
||||
callable.propagatesFlow(I::push(I::withContent(content), input), output, true) and
|
||||
filter = I::getFilterFromWithContentStep(content) and
|
||||
not isNonLocal(input.head()) and
|
||||
not isNonLocal(output.head()) and
|
||||
input != output
|
||||
)
|
||||
}
|
||||
|
||||
private predicate componentLevelStep(I::SummaryComponent component) {
|
||||
exists(I::Content content |
|
||||
component = I::withoutContent(content) and
|
||||
not exists(I::getFilterFromWithoutContentStep(content))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow `I::Node` corresponding an argument or return value of `call`,
|
||||
* as specified by `component`. `isOutput` indicates whether the node represents
|
||||
* an output node or an input node.
|
||||
*/
|
||||
bindingset[call, component]
|
||||
private I::Node evaluateSummaryComponentLocal(
|
||||
I::Node call, I::SummaryComponent component, boolean isOutput
|
||||
) {
|
||||
result = I::argumentOf(call, component, isOutput)
|
||||
or
|
||||
component = I::return() and
|
||||
result = call and
|
||||
isOutput = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `callable` is relevant for type-tracking and we therefore want `stack` to
|
||||
* be evaluated locally at its call sites.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStack(
|
||||
I::SummarizedCallable callable, I::SummaryComponentStack stack
|
||||
) {
|
||||
exists(I::callTo(callable)) and
|
||||
(
|
||||
callable.propagatesFlow(stack, _, true)
|
||||
or
|
||||
callable.propagatesFlow(_, stack, true)
|
||||
or
|
||||
// include store summaries as they may skip an initial step at the input
|
||||
hasStoreSummary(callable, _, stack, _)
|
||||
)
|
||||
or
|
||||
dependsOnSummaryComponentStackCons(callable, _, stack)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackCons(
|
||||
I::SummarizedCallable callable, I::SummaryComponent head, I::SummaryComponentStack tail
|
||||
) {
|
||||
dependsOnSummaryComponentStack(callable, I::push(head, tail))
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackConsLocal(
|
||||
I::SummarizedCallable callable, I::SummaryComponent head, I::SummaryComponentStack tail
|
||||
) {
|
||||
dependsOnSummaryComponentStackCons(callable, head, tail) and
|
||||
not isNonLocal(head)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate dependsOnSummaryComponentStackLeaf(
|
||||
I::SummarizedCallable callable, I::SummaryComponent leaf
|
||||
) {
|
||||
dependsOnSummaryComponentStack(callable, I::singleton(leaf))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow I::Node corresponding to the local input or output of `call`
|
||||
* identified by `stack`, if possible.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private I::Node evaluateSummaryComponentStackLocal(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack stack, boolean isOutput
|
||||
) {
|
||||
exists(I::SummaryComponent component |
|
||||
dependsOnSummaryComponentStackLeaf(callable, component) and
|
||||
stack = I::singleton(component) and
|
||||
call = I::callTo(callable) and
|
||||
result = evaluateSummaryComponentLocal(call, component, isOutput)
|
||||
)
|
||||
or
|
||||
exists(
|
||||
I::Node prev, I::SummaryComponent head, I::SummaryComponentStack tail, boolean isOutput0
|
||||
|
|
||||
prev = evaluateSummaryComponentStackLocal(callable, call, tail, isOutput0) and
|
||||
dependsOnSummaryComponentStackConsLocal(callable, pragma[only_bind_into](head),
|
||||
pragma[only_bind_out](tail)) and
|
||||
stack = I::push(pragma[only_bind_out](head), pragma[only_bind_out](tail))
|
||||
|
|
||||
// `Parameter[X]` is only allowed in the output of flow summaries (hence `isOutput = true`),
|
||||
// however the target of the parameter (e.g. `Argument[Y].Parameter[X]`) should be fetched
|
||||
// not from a post-update argument node (hence `isOutput0 = false`)
|
||||
result = I::parameterOf(prev, head) and
|
||||
isOutput0 = false and
|
||||
isOutput = true
|
||||
or
|
||||
// `ReturnValue` is only allowed in the input of flow summaries (hence `isOutput = false`),
|
||||
// and the target of the return value (e.g. `Argument[X].ReturnValue`) should be fetched not
|
||||
// from a post-update argument node (hence `isOutput0 = false`)
|
||||
result = I::returnOf(prev, head) and
|
||||
isOutput0 = false and
|
||||
isOutput = false
|
||||
or
|
||||
componentLevelStep(head) and
|
||||
result = prev and
|
||||
isOutput = isOutput0
|
||||
)
|
||||
}
|
||||
|
||||
// Implement Output
|
||||
predicate levelStepNoCall(I::Node nodeFrom, I::Node nodeTo) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
callable.propagatesFlow(input, output, true) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input, false) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output, true)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicLoadStep(I::Node nodeFrom, I::Node nodeTo, I::Content content) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasLoadSummary(callable, content, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input, false) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output, true)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicStoreStep(I::Node nodeFrom, I::Node nodeTo, I::Content content) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasStoreSummary(callable, content, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input, false) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output, true)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicLoadStoreStep(
|
||||
I::Node nodeFrom, I::Node nodeTo, I::Content loadContent, I::Content storeContent
|
||||
) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasLoadStoreSummary(callable, loadContent, storeContent, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input, false) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output, true)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicWithoutContentStep(I::Node nodeFrom, I::Node nodeTo, I::ContentFilter filter) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasWithoutContentSummary(callable, filter, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input, false) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output, true)
|
||||
)
|
||||
}
|
||||
|
||||
predicate basicWithContentStep(I::Node nodeFrom, I::Node nodeTo, I::ContentFilter filter) {
|
||||
exists(
|
||||
I::SummarizedCallable callable, I::Node call, I::SummaryComponentStack input,
|
||||
I::SummaryComponentStack output
|
||||
|
|
||||
hasWithContentSummary(callable, filter, pragma[only_bind_into](input),
|
||||
pragma[only_bind_into](output)) and
|
||||
call = I::callTo(callable) and
|
||||
nodeFrom = evaluateSummaryComponentStackLocal(callable, call, input, false) and
|
||||
nodeTo = evaluateSummaryComponentStackLocal(callable, call, output, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -249,18 +249,15 @@ module TypeTracking<TypeTrackingInput I> {
|
||||
pragma[inline]
|
||||
private predicate isLocalSourceNode(LocalSourceNode n) { any() }
|
||||
|
||||
/**
|
||||
* Holds if there is flow from `localSource` to `dst` using zero or more
|
||||
* `simpleLocalSmallStep`s.
|
||||
*/
|
||||
cached
|
||||
predicate flowsTo(Node localSource, Node dst) {
|
||||
predicate standardFlowsTo(Node localSource, Node dst) {
|
||||
not nonStandardFlowsTo(_, _) and
|
||||
// explicit type check in base case to avoid repeated type tests in recursive case
|
||||
isLocalSourceNode(localSource) and
|
||||
dst = localSource
|
||||
or
|
||||
exists(Node mid |
|
||||
flowsTo(localSource, mid) and
|
||||
standardFlowsTo(localSource, mid) and
|
||||
simpleLocalSmallStep(mid, dst)
|
||||
)
|
||||
}
|
||||
@@ -278,6 +275,16 @@ module TypeTracking<TypeTrackingInput I> {
|
||||
|
||||
import Cached
|
||||
|
||||
/**
|
||||
* Holds if there is flow from `localSource` to `dst` using zero or more
|
||||
* `simpleLocalSmallStep`s.
|
||||
*/
|
||||
predicate flowsTo(LocalSourceNode localSource, Node dst) {
|
||||
nonStandardFlowsTo(localSource, dst)
|
||||
or
|
||||
standardFlowsTo(localSource, dst)
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/typetracking
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/typos/change-notes/released/0.2.6.md
Normal file
3
shared/typos/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/typos/change-notes/released/0.2.7.md
Normal file
3
shared/typos/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/typos
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/util/change-notes/released/0.2.6.md
Normal file
3
shared/util/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/util/change-notes/released/0.2.7.md
Normal file
3
shared/util/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
*
|
||||
* As opposed to `boolean`, this type does not require explicit binding.
|
||||
*/
|
||||
class Boolean extends boolean {
|
||||
final class Boolean extends FinalBoolean {
|
||||
Boolean() { this = [true, false] }
|
||||
|
||||
/** Returns either "true" or "false". */
|
||||
// reimplement to avoid explicit binding
|
||||
string toString() { result = super.toString() }
|
||||
}
|
||||
|
||||
final private class FinalBoolean = boolean;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/util
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies: null
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
shared/yaml/change-notes/released/0.2.6.md
Normal file
3
shared/yaml/change-notes/released/0.2.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.6
|
||||
|
||||
No user-facing changes.
|
||||
3
shared/yaml/change-notes/released/0.2.7.md
Normal file
3
shared/yaml/change-notes/released/0.2.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.2.7
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.2.5
|
||||
lastReleaseVersion: 0.2.7
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/yaml
|
||||
version: 0.2.5
|
||||
version: 0.2.7
|
||||
groups: shared
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
|
||||
Reference in New Issue
Block a user