mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Merge branch 'main' into js/shared-dataflow-merge-main
This commit is contained in:
@@ -1,3 +1,29 @@
|
||||
## 2.2.0
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* The `js/incomplete-sanitization` query now also checks regular expressions constructed using `new RegExp(..)`. Previously it only checked regular expression literals.
|
||||
* Regular expression-based sanitisers implemented with `new RegExp(..)` are now detected in more cases.
|
||||
* Regular expression related queries now account for unknown flags.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added taint-steps for `String.prototype.toWellFormed`.
|
||||
* Added taint-steps for `Map.groupBy` and `Object.groupBy`.
|
||||
* Added taint-steps for `Array.prototype.findLast`.
|
||||
* Added taint-steps for `Array.prototype.findLastIndex`.
|
||||
|
||||
## 2.1.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added taint-steps for `Array.prototype.with`.
|
||||
* Added taint-steps for `Array.prototype.toSpliced`
|
||||
* Added taint-steps for `Array.prototype.toReversed`.
|
||||
* Added taint-steps for `Array.prototype.toSorted`.
|
||||
* Added support for `String.prototype.matchAll`.
|
||||
* Added taint-steps for `Array.prototype.reverse`
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
Added support for `String.prototype.matchAll`.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added taint-steps for `Array.prototype.reverse`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added taint-steps for `Array.prototype.toReversed`.
|
||||
* Added taint-steps for `Array.prototype.toSorted`.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
Added taint-steps for `Array.prototype.toSpliced`
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
Added taint-steps for `Array.prototype.with`.
|
||||
10
javascript/ql/lib/change-notes/released/2.1.1.md
Normal file
10
javascript/ql/lib/change-notes/released/2.1.1.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## 2.1.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added taint-steps for `Array.prototype.with`.
|
||||
* Added taint-steps for `Array.prototype.toSpliced`
|
||||
* Added taint-steps for `Array.prototype.toReversed`.
|
||||
* Added taint-steps for `Array.prototype.toSorted`.
|
||||
* Added support for `String.prototype.matchAll`.
|
||||
* Added taint-steps for `Array.prototype.reverse`
|
||||
14
javascript/ql/lib/change-notes/released/2.2.0.md
Normal file
14
javascript/ql/lib/change-notes/released/2.2.0.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## 2.2.0
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* The `js/incomplete-sanitization` query now also checks regular expressions constructed using `new RegExp(..)`. Previously it only checked regular expression literals.
|
||||
* Regular expression-based sanitisers implemented with `new RegExp(..)` are now detected in more cases.
|
||||
* Regular expression related queries now account for unknown flags.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added taint-steps for `String.prototype.toWellFormed`.
|
||||
* Added taint-steps for `Map.groupBy` and `Object.groupBy`.
|
||||
* Added taint-steps for `Array.prototype.findLast`.
|
||||
* Added taint-steps for `Array.prototype.findLastIndex`.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 2.1.0
|
||||
lastReleaseVersion: 2.2.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 2.1.1-dev
|
||||
version: 2.2.1-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -384,10 +384,10 @@ private module ArrayLibraries {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call to `Array.prototype.find` or a polyfill implementing the same functionality.
|
||||
* Gets a call to `Array.prototype.find` or `Array.prototype.findLast` or a polyfill implementing the same functionality.
|
||||
*/
|
||||
DataFlow::CallNode arrayFindCall(DataFlow::Node array) {
|
||||
result.(DataFlow::MethodCallNode).getMethodName() = "find" and
|
||||
result.(DataFlow::MethodCallNode).getMethodName() in ["find", "findLast"] and
|
||||
array = result.getReceiver()
|
||||
or
|
||||
result = DataFlow::moduleImport(["array.prototype.find", "array-find"]).getACall() and
|
||||
@@ -483,4 +483,31 @@ private module ArrayLibraries {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a data flow step that tracks the flow of data through callback functions in arrays.
|
||||
*/
|
||||
private class ArrayCallBackDataFlowStep extends PreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["findLast", "find", "findLastIndex"] and
|
||||
prop = arrayLikeElement() and
|
||||
obj = call.getReceiver() and
|
||||
element = call.getCallback(0).getParameter(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This step models the propagation of data from the array to the callback function's parameter.
|
||||
*/
|
||||
private class ArrayCallBackDataTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["findLast", "find", "findLastIndex"] and
|
||||
obj = call.getReceiver() and
|
||||
element = call.getCallback(0).getParameter(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,4 +151,32 @@ private module CollectionDataFlow {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for a call to `groupBy` on an iterable object.
|
||||
*/
|
||||
private class GroupByTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call = DataFlow::globalVarRef(["Map", "Object"]).getAMemberCall("groupBy") and
|
||||
pred = call.getArgument(0) and
|
||||
(succ = call.getCallback(1).getParameter(0) or succ = call)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for handling data flow and taint tracking for the groupBy method on iterable objects.
|
||||
* Ensures propagation of taint and data flow through the groupBy operation.
|
||||
*/
|
||||
private class GroupByDataFlowStep extends PreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call = DataFlow::globalVarRef("Map").getAMemberCall("groupBy") and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call and
|
||||
prop = mapValueUnknownKey()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,12 @@ class StringReplaceCall extends DataFlow::MethodCallNode {
|
||||
*/
|
||||
predicate isGlobal() { this.getRegExp().isGlobal() or this.getMethodName() = "replaceAll" }
|
||||
|
||||
/**
|
||||
* Holds if this is a global replacement, that is, the first argument is a regular expression
|
||||
* with the `g` flag or unknown flags, or this is a call to `.replaceAll()`.
|
||||
*/
|
||||
predicate maybeGlobal() { this.getRegExp().maybeGlobal() or this.getMethodName() = "replaceAll" }
|
||||
|
||||
/**
|
||||
* Holds if this call to `replace` replaces `old` with `new`.
|
||||
*/
|
||||
|
||||
@@ -1690,6 +1690,9 @@ class RegExpCreationNode extends DataFlow::SourceNode {
|
||||
/** Holds if the constructed predicate has the `g` flag. */
|
||||
predicate isGlobal() { RegExp::isGlobal(this.getFlags()) }
|
||||
|
||||
/** Holds if the constructed predicate has the `g` flag or unknown flags. */
|
||||
predicate maybeGlobal() { RegExp::maybeGlobal(this.tryGetFlags()) }
|
||||
|
||||
/** Gets a data flow node referring to this regular expression. */
|
||||
private DataFlow::SourceNode getAReference(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
|
||||
@@ -453,7 +453,8 @@ module TaintTracking {
|
||||
"anchor", "big", "blink", "bold", "concat", "fixed", "fontcolor", "fontsize",
|
||||
"italics", "link", "padEnd", "padStart", "repeat", "replace", "replaceAll", "slice",
|
||||
"small", "strike", "sub", "substr", "substring", "sup", "toLocaleLowerCase",
|
||||
"toLocaleUpperCase", "toLowerCase", "toUpperCase", "trim", "trimLeft", "trimRight"
|
||||
"toLocaleUpperCase", "toLowerCase", "toUpperCase", "trim", "trimLeft", "trimRight",
|
||||
"toWellFormed"
|
||||
]
|
||||
or
|
||||
// sorted, interesting, properties of Object.prototype
|
||||
|
||||
@@ -30,7 +30,7 @@ module Cryptography {
|
||||
class PasswordHashingAlgorithm = CryptoAlgorithms::PasswordHashingAlgorithm;
|
||||
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* A data flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
@@ -40,7 +40,7 @@ module Cryptography {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/** Gets the data-flow node where the cryptographic algorithm used in this operation is configured. */
|
||||
/** Gets the data flow node where the cryptographic algorithm used in this operation is configured. */
|
||||
DataFlow::Node getInitialization() { result = super.getInitialization() }
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
@@ -61,14 +61,14 @@ module Cryptography {
|
||||
/** Provides classes for modeling new applications of a cryptographic algorithms. */
|
||||
module CryptographicOperation {
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* A data flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CryptographicOperation` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the data-flow node where the cryptographic algorithm used in this operation is configured. */
|
||||
/** Gets the data flow node where the cryptographic algorithm used in this operation is configured. */
|
||||
abstract DataFlow::Node getInitialization();
|
||||
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
@@ -118,14 +118,14 @@ module Http {
|
||||
/** Provides classes for modeling HTTP clients. */
|
||||
module Client {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
* A data flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `Http::Client::Request::Range` instead.
|
||||
*/
|
||||
class Request extends DataFlow::Node instanceof Request::Range {
|
||||
/**
|
||||
* Gets a data-flow node that contributes to the URL of the request.
|
||||
* Gets a data flow node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
DataFlow::Node getAUrlPart() { result = super.getAUrlPart() }
|
||||
@@ -150,14 +150,14 @@ module Http {
|
||||
/** Provides a class for modeling new HTTP requests. */
|
||||
module Request {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
* A data flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `Http::Client::Request` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a data-flow node that contributes to the URL of the request.
|
||||
* Gets a data flow node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
abstract DataFlow::Node getAUrlPart();
|
||||
|
||||
@@ -74,7 +74,7 @@ private StringReplaceCall getAStringReplaceMethodCall(StringReplaceCall n) {
|
||||
module HtmlSanitization {
|
||||
private predicate fixedGlobalReplacement(StringReplaceCallSequence chain) {
|
||||
forall(StringReplaceCall member | member = chain.getAMember() |
|
||||
member.isGlobal() and member.getArgument(0) instanceof DataFlow::RegExpLiteralNode
|
||||
member.maybeGlobal() and member.getArgument(0) instanceof DataFlow::RegExpCreationNode
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,12 @@ module CleartextLogging {
|
||||
*/
|
||||
class MaskingReplacer extends Barrier, StringReplaceCall {
|
||||
MaskingReplacer() {
|
||||
this.isGlobal() and
|
||||
this.maybeGlobal() and
|
||||
exists(this.getRawReplacement().getStringValue()) and
|
||||
any(RegExpDot term).getLiteral() = this.getRegExp().asExpr()
|
||||
exists(DataFlow::RegExpCreationNode regexpObj |
|
||||
this.(StringReplaceCall).getRegExp() = regexpObj and
|
||||
regexpObj.getRoot() = any(RegExpDot term).getRootTerm()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ module PrototypePollutingAssignmentConfig implements DataFlow::StateConfigSig {
|
||||
// Replacing with "_" is likely to be exploitable
|
||||
not replace.getRawReplacement().getStringValue() = "_" and
|
||||
(
|
||||
replace.isGlobal()
|
||||
replace.maybeGlobal()
|
||||
or
|
||||
// Non-global replace with a non-empty string can also prevent __proto__ by
|
||||
// inserting a chunk of text that doesn't fit anywhere in __proto__
|
||||
|
||||
@@ -76,7 +76,7 @@ module RegExpInjection {
|
||||
*/
|
||||
class MetacharEscapeSanitizer extends Sanitizer, StringReplaceCall {
|
||||
MetacharEscapeSanitizer() {
|
||||
this.isGlobal() and
|
||||
this.maybeGlobal() and
|
||||
(
|
||||
RegExp::alwaysMatchesMetaCharacter(this.getRegExp().getRoot(), ["{", "[", "+"])
|
||||
or
|
||||
|
||||
@@ -360,10 +360,10 @@ module TaintedPath {
|
||||
this instanceof StringReplaceCall and
|
||||
input = this.getReceiver() and
|
||||
output = this and
|
||||
not exists(RegExpLiteral literal, RegExpTerm term |
|
||||
this.(StringReplaceCall).getRegExp().asExpr() = literal and
|
||||
this.(StringReplaceCall).isGlobal() and
|
||||
literal.getRoot() = term
|
||||
not exists(DataFlow::RegExpCreationNode regexp, RegExpTerm term |
|
||||
this.(StringReplaceCall).getRegExp() = regexp and
|
||||
this.(StringReplaceCall).maybeGlobal() and
|
||||
regexp.getRoot() = term
|
||||
|
|
||||
term.getAMatchedString() = "/" or
|
||||
term.getAMatchedString() = "." or
|
||||
@@ -444,9 +444,9 @@ module TaintedPath {
|
||||
input = this.getReceiver() and
|
||||
output = this and
|
||||
this.isGlobal() and
|
||||
exists(RegExpLiteral literal, RegExpTerm term |
|
||||
this.getRegExp().asExpr() = literal and
|
||||
literal.getRoot() = term and
|
||||
exists(DataFlow::RegExpCreationNode regexp, RegExpTerm term |
|
||||
this.getRegExp() = regexp and
|
||||
regexp.getRoot() = term and
|
||||
not term.getAMatchedString() = "/"
|
||||
|
|
||||
term.getAMatchedString() = "." or
|
||||
|
||||
@@ -266,7 +266,7 @@ module UnsafeShellCommandConstruction {
|
||||
class ReplaceQuotesSanitizer extends Sanitizer, StringReplaceCall {
|
||||
ReplaceQuotesSanitizer() {
|
||||
this.getAReplacedString() = "'" and
|
||||
this.isGlobal() and
|
||||
this.maybeGlobal() and
|
||||
this.getRawReplacement().mayHaveStringValue(["'\\''", ""])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ module Shared {
|
||||
*/
|
||||
class MetacharEscapeSanitizer extends Sanitizer, StringReplaceCall {
|
||||
MetacharEscapeSanitizer() {
|
||||
this.isGlobal() and
|
||||
this.maybeGlobal() and
|
||||
(
|
||||
RegExp::alwaysMatchesMetaCharacter(this.getRegExp().getRoot(), ["<", "'", "\""])
|
||||
or
|
||||
|
||||
187
javascript/ql/lib/utils/test/ConsistencyChecking.qll
Normal file
187
javascript/ql/lib/utils/test/ConsistencyChecking.qll
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* DEPRECATED, but can be imported with a `deprecated import`.
|
||||
*
|
||||
* Will be replaced with standardized inline test expectations in the future.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A configuration for consistency checking.
|
||||
* Used to specify where the alerts are (the positives)
|
||||
* And which files should be included in the consistency-check.
|
||||
*
|
||||
* If no configuration is specified, then the default is that the all sinks from a `DataFlow::Configuration` are alerts, and all files are consistency-checked.
|
||||
*/
|
||||
abstract deprecated class ConsistencyConfiguration extends string {
|
||||
bindingset[this]
|
||||
ConsistencyConfiguration() { any() }
|
||||
|
||||
/**
|
||||
* Gets an alert that should be checked for consistency.
|
||||
* The alert must match with a `NOT OK` comment.
|
||||
*
|
||||
* And likewise a `OK` comment must not have a corresponding alert on the same line.
|
||||
*/
|
||||
DataFlow::Node getAnAlert() { result = getASink() }
|
||||
|
||||
/**
|
||||
* Gets a file to include in the consistency checking.
|
||||
*/
|
||||
File getAFile() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A string that either equals a `ConsistencyConfiguration`, or the empty string if no such configuration exists.
|
||||
*
|
||||
* Is used internally to match a configuration or lack thereof.
|
||||
*/
|
||||
deprecated final private class Conf extends string {
|
||||
Conf() {
|
||||
this instanceof ConsistencyConfiguration
|
||||
or
|
||||
not exists(ConsistencyConfiguration c) and
|
||||
this = ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment that asserts whether a result exists at that line or not.
|
||||
* Can optionally include `[INCONSISTENCY]` to indicate that a consistency issue is expected at the location
|
||||
*/
|
||||
private class AssertionComment extends Comment {
|
||||
boolean shouldHaveAlert;
|
||||
|
||||
AssertionComment() {
|
||||
if this.getText().regexpMatch("\\s*(NOT OK|BAD).*")
|
||||
then shouldHaveAlert = true
|
||||
else (
|
||||
this.getText().regexpMatch("\\s*(OK|GOOD).*") and shouldHaveAlert = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there should be an alert at this location
|
||||
*/
|
||||
predicate shouldHaveAlert() { shouldHaveAlert = true }
|
||||
|
||||
/**
|
||||
* Holds if a consistency issue is expected at this location.
|
||||
*/
|
||||
predicate expectConsistencyError() { this.getText().matches("%[INCONSISTENCY]%") }
|
||||
}
|
||||
|
||||
deprecated private DataFlow::Node getASink() {
|
||||
exists(DataFlow::Configuration cfg | cfg.hasFlow(_, result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the alerts for consistency consistency checking from a configuration `conf`.
|
||||
*/
|
||||
deprecated private DataFlow::Node alerts(Conf conf) {
|
||||
result = conf.(ConsistencyConfiguration).getAnAlert()
|
||||
or
|
||||
not exists(ConsistencyConfiguration r) and
|
||||
result = getASink() and
|
||||
conf = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an alert in `file` at `line` for configuration `conf`.
|
||||
* The `line` can be either the first or the last line of the alert.
|
||||
* And if no expression exists at `line`, then an alert on the next line is used.
|
||||
*/
|
||||
deprecated private DataFlow::Node getAlert(File file, int line, Conf conf) {
|
||||
result = alerts(conf) and
|
||||
result.getFile() = file and
|
||||
(result.hasLocationInfo(_, _, _, line, _) or result.hasLocationInfo(_, line, _, _, _))
|
||||
or
|
||||
// The comment can be right above the result, so an alert also counts for the line above.
|
||||
not exists(Expr e |
|
||||
e.getFile() = file and [e.getLocation().getStartLine(), e.getLocation().getEndLine()] = line
|
||||
) and
|
||||
result = alerts(conf) and
|
||||
result.getFile() = file and
|
||||
result.hasLocationInfo(_, line + 1, _, _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a comment that asserts either the existence or the absence of an alert in `file` at `line`.
|
||||
*/
|
||||
private AssertionComment getComment(File file, int line) {
|
||||
result.getLocation().getEndLine() = line and
|
||||
result.getFile() = file
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a false positive in `file` at `line` for configuration `conf`.
|
||||
*/
|
||||
deprecated private predicate falsePositive(File file, int line, AssertionComment comment, Conf conf) {
|
||||
exists(getAlert(file, line, conf)) and
|
||||
comment = getComment(file, line) and
|
||||
not comment.shouldHaveAlert()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a false negative in `file` at `line` for configuration `conf`.
|
||||
*/
|
||||
deprecated private predicate falseNegative(File file, int line, AssertionComment comment, Conf conf) {
|
||||
not exists(getAlert(file, line, conf)) and
|
||||
comment = getComment(file, line) and
|
||||
comment.shouldHaveAlert()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file that should be included for consistency checking for configuration `conf`.
|
||||
*/
|
||||
deprecated private File getATestFile(string conf) {
|
||||
not exists(any(ConsistencyConfiguration res).getAFile()) and
|
||||
result = any(LineComment comment).getFile() and
|
||||
(conf = "" or conf instanceof ConsistencyConfiguration)
|
||||
or
|
||||
result = conf.(ConsistencyConfiguration).getAFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a description of the configuration that has a sink in `file` at `line` for configuration `conf`.
|
||||
* Or the empty string
|
||||
*/
|
||||
bindingset[file, line]
|
||||
deprecated private string getSinkDescription(File file, int line, Conf conf) {
|
||||
not exists(DataFlow::Configuration c | c.hasFlow(_, getAlert(file, line, conf))) and
|
||||
result = ""
|
||||
or
|
||||
exists(DataFlow::Configuration c | c.hasFlow(_, getAlert(file, line, conf)) |
|
||||
result = " for " + c
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a consistency-issue at `location` with description `msg` for configuration `conf`.
|
||||
* The consistency issue an unexpected false positive/negative.
|
||||
* Or that false positive/negative was expected, and none were found.
|
||||
*/
|
||||
deprecated query predicate consistencyIssue(
|
||||
string location, string msg, string commentText, Conf conf
|
||||
) {
|
||||
exists(File file, int line |
|
||||
file = getATestFile(conf) and location = file.getRelativePath() + ":" + line
|
||||
|
|
||||
exists(AssertionComment comment |
|
||||
comment.getText().trim() = commentText and comment = getComment(file, line)
|
||||
|
|
||||
falsePositive(file, line, comment, conf) and
|
||||
not comment.expectConsistencyError() and
|
||||
msg = "did not expect an alert, but found an alert" + getSinkDescription(file, line, conf)
|
||||
or
|
||||
falseNegative(file, line, comment, conf) and
|
||||
not comment.expectConsistencyError() and
|
||||
msg = "expected an alert, but found none"
|
||||
or
|
||||
not falsePositive(file, line, comment, conf) and
|
||||
not falseNegative(file, line, comment, conf) and
|
||||
comment.expectConsistencyError() and
|
||||
msg = "expected consistency issue, but found no such issue (" + comment.getText().trim() + ")"
|
||||
)
|
||||
)
|
||||
}
|
||||
8
javascript/ql/lib/utils/test/InlineExpectationsTest.qll
Normal file
8
javascript/ql/lib/utils/test/InlineExpectationsTest.qll
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Inline expectation tests for JS.
|
||||
* See `shared/util/codeql/util/test/InlineExpectationsTest.qll`
|
||||
*/
|
||||
|
||||
private import codeql.util.test.InlineExpectationsTest
|
||||
private import internal.InlineExpectationsTestImpl
|
||||
import Make<Impl>
|
||||
21
javascript/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Normal file
21
javascript/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @kind test-postprocess
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import codeql.util.test.InlineExpectationsTest as T
|
||||
private import internal.InlineExpectationsTestImpl
|
||||
import T::TestPostProcessing
|
||||
import T::TestPostProcessing::Make<Impl, Input>
|
||||
|
||||
private module Input implements T::TestPostProcessing::InputSig<Impl> {
|
||||
string getRelativeUrl(Location location) {
|
||||
exists(File f, int startline, int startcolumn, int endline, int endcolumn |
|
||||
location.hasLocationInfo(_, startline, startcolumn, endline, endcolumn) and
|
||||
f = location.getFile()
|
||||
|
|
||||
result =
|
||||
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
|
||||
)
|
||||
}
|
||||
}
|
||||
25
javascript/ql/lib/utils/test/InlineFlowTest.qll
Normal file
25
javascript/ql/lib/utils/test/InlineFlowTest.qll
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Inline flow tests for JavaScript.
|
||||
* See `shared/util/codeql/dataflow/test/InlineFlowTest.qll`
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.Locations
|
||||
private import codeql.dataflow.test.InlineFlowTest
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowArg
|
||||
private import semmle.javascript.frameworks.data.internal.ApiGraphModelsExtensions as ApiGraphModelsExtensions
|
||||
private import internal.InlineExpectationsTestImpl
|
||||
|
||||
private module FlowTestImpl implements InputSig<Location, JSDataFlow> {
|
||||
import testUtilities.InlineFlowTestUtil
|
||||
|
||||
bindingset[src, sink]
|
||||
string getArgString(DataFlow::Node src, DataFlow::Node sink) {
|
||||
(if exists(getSourceArgString(src)) then result = getSourceArgString(src) else result = "") and
|
||||
exists(sink)
|
||||
}
|
||||
|
||||
predicate interpretModelForTest = ApiGraphModelsExtensions::interpretModelForTest/2;
|
||||
}
|
||||
|
||||
import InlineFlowTestMake<Location, JSDataFlow, JSTaintFlow, Impl, FlowTestImpl>
|
||||
21
javascript/ql/lib/utils/test/InlineFlowTestUtil.qll
Normal file
21
javascript/ql/lib/utils/test/InlineFlowTestUtil.qll
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Defines the default source and sink recognition for `InlineFlowTest.qll`.
|
||||
*
|
||||
* We reuse these predicates in some type-tracking tests that don't wish to bring in the
|
||||
* test configuration from `InlineFlowTest`.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
|
||||
predicate defaultSource(DataFlow::Node src) { src.(DataFlow::CallNode).getCalleeName() = "source" }
|
||||
|
||||
predicate defaultSink(DataFlow::Node sink) {
|
||||
exists(DataFlow::CallNode call | call.getCalleeName() = "sink" | sink = call.getAnArgument())
|
||||
}
|
||||
|
||||
bindingset[src]
|
||||
string getSourceArgString(DataFlow::Node src) {
|
||||
src.(DataFlow::CallNode).getAnArgument().getStringValue() = result
|
||||
or
|
||||
src.(DataFlow::ParameterNode).getName() = result
|
||||
}
|
||||
37
javascript/ql/lib/utils/test/InlineSummaries.qll
Normal file
37
javascript/ql/lib/utils/test/InlineSummaries.qll
Normal file
@@ -0,0 +1,37 @@
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.FlowSummary
|
||||
|
||||
class MkSummary extends SummarizedCallable {
|
||||
private CallExpr mkSummary;
|
||||
|
||||
MkSummary() {
|
||||
mkSummary.getCalleeName() = "mkSummary" and
|
||||
this =
|
||||
"mkSummary at " + mkSummary.getFile().getRelativePath() + ":" +
|
||||
mkSummary.getLocation().getStartLine()
|
||||
}
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = mkSummary.flow().(DataFlow::CallNode).getAnInvocation()
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
// mkSummary(input, output)
|
||||
input = mkSummary.getArgument(0).getStringValue() and
|
||||
output = mkSummary.getArgument(1).getStringValue()
|
||||
or
|
||||
// mkSummary([
|
||||
// [input1, output1],
|
||||
// [input2, output2],
|
||||
// ...
|
||||
// ])
|
||||
exists(ArrayExpr pair |
|
||||
pair = mkSummary.getArgument(0).(ArrayExpr).getAnElement() and
|
||||
input = pair.getElement(0).getStringValue() and
|
||||
output = pair.getElement(1).getStringValue()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
19
javascript/ql/lib/utils/test/LegacyDataFlowDiff.qll
Normal file
19
javascript/ql/lib/utils/test/LegacyDataFlowDiff.qll
Normal file
@@ -0,0 +1,19 @@
|
||||
private import javascript
|
||||
|
||||
private signature class LegacyConfigSig {
|
||||
predicate hasFlow(DataFlow::Node source, DataFlow::Node sink);
|
||||
}
|
||||
|
||||
module DataFlowDiff<DataFlow::GlobalFlowSig NewFlow, LegacyConfigSig LegacyConfig> {
|
||||
query predicate legacyDataFlowDifference(
|
||||
DataFlow::Node source, DataFlow::Node sink, string message
|
||||
) {
|
||||
NewFlow::flow(source, sink) and
|
||||
not any(LegacyConfig cfg).hasFlow(source, sink) and
|
||||
message = "only flow with NEW data flow library"
|
||||
or
|
||||
not NewFlow::flow(source, sink) and
|
||||
any(LegacyConfig cfg).hasFlow(source, sink) and
|
||||
message = "only flow with OLD data flow library"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
private import javascript as JS
|
||||
private import codeql.util.test.InlineExpectationsTest
|
||||
|
||||
module Impl implements InlineExpectationsTestSig {
|
||||
private import javascript
|
||||
|
||||
final private class LineCommentFinal = LineComment;
|
||||
|
||||
class ExpectationComment extends LineCommentFinal {
|
||||
string getContents() { result = this.getText() }
|
||||
|
||||
/** Gets this element's location. */
|
||||
Location getLocation() { result = super.getLocation() }
|
||||
}
|
||||
|
||||
class Location = JS::Location;
|
||||
}
|
||||
Reference in New Issue
Block a user