Merge commit '737dd9d4c1' into jb1/lib/dataflowstack

This commit is contained in:
Josh Brown
2024-02-08 08:18:04 -08:00
2596 changed files with 351218 additions and 96833 deletions

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.1.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.1.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.1.5
lastReleaseVersion: 0.1.7

View File

@@ -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
View 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",
],
)

View 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
View 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));
}
}

View File

@@ -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.

View 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/.

View File

@@ -0,0 +1,3 @@
## 0.1.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.1.5
lastReleaseVersion: 0.1.7

View File

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

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/dataflow
version: 0.1.5
version: 0.1.7
groups: shared
library: true
dependencies:

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.2.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

@@ -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",

View File

@@ -1,5 +1,5 @@
name: codeql/mad
version: 0.2.5
version: 0.2.7
groups: shared
library: true
dependencies: null

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.0.5
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.0.6
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.0.4
lastReleaseVersion: 0.0.6

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/rangeanalysis
version: 0.0.4
version: 0.0.6
groups: shared
library: true
dependencies:

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.2.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

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

View File

@@ -64,9 +64,6 @@ module Make<RegexTreeViewSig TreeImpl> {
}
}
/** DEPRECATED: Use `EmptyPositiveSubPattern` instead. */
deprecated class EmptyPositiveSubPatttern = EmptyPositiveSubPattern;
final private class FinalRegExpTerm = RegExpTerm;
/**

View File

@@ -1,5 +1,5 @@
name: codeql/regex
version: 0.2.5
version: 0.2.7
groups: shared
library: true
dependencies:

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,5 @@
## 0.2.7
### Minor Analysis Improvements
* Deleted the deprecated `adjacentDefNoUncertainReads`, `lastRefRedefNoUncertainReads`, and `lastRefNoUncertainReads` predicates.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/ssa
version: 0.2.5
version: 0.2.7
groups: shared
library: true
dependencies:

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.0.5
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.0.6
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.0.4
lastReleaseVersion: 0.0.6

View File

@@ -1,5 +1,5 @@
name: codeql/threat-models
version: 0.0.4
version: 0.0.6
library: true
groups: shared
dataExtensions:

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.2.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

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

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.2.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/typetracking
version: 0.2.5
version: 0.2.7
groups: shared
library: true
dependencies:

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.2.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

@@ -1,5 +1,5 @@
name: codeql/typos
version: 0.2.5
version: 0.2.7
groups: shared
library: true
warnOnImplicitThis: true

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.2.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/util
version: 0.2.5
version: 0.2.7
groups: shared
library: true
dependencies: null

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.2.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.2.7
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.5
lastReleaseVersion: 0.2.7

View File

@@ -1,5 +1,5 @@
name: codeql/yaml
version: 0.2.5
version: 0.2.7
groups: shared
library: true
warnOnImplicitThis: true