mirror of
https://github.com/github/codeql.git
synced 2026-07-20 10:48:17 +02:00
Merge branch 'main' of github.com:github/codeql into labelNaming
This commit is contained in:
@@ -413,10 +413,10 @@ predicate isValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
bindingset[name, argument]
|
||||
predicate isValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
|
||||
name = ["Argument", "Parameter"] and
|
||||
argument.regexpMatch("(N-|-)?\\d+(\\.\\.(N-|-)?\\d+)?")
|
||||
argument.regexpMatch("(N-|-)?\\d+(\\.\\.((N-|-)?\\d+)?)?")
|
||||
or
|
||||
name = "WithArity" and
|
||||
argument.regexpMatch("\\d+(\\.\\.\\d+)?")
|
||||
argument.regexpMatch("\\d+(\\.\\.(\\d+)?)?")
|
||||
or
|
||||
Specific::isExtraValidTokenArgumentInIdentifyingAccessPath(name, argument)
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
// API graphs do not use store/load steps for arrays
|
||||
token.getName() = ["ArrayElement", "Element"] and
|
||||
result = node.getUnknownMember()
|
||||
or
|
||||
token.getName() = "Parameter" and
|
||||
token.getAnArgument() = "this" and
|
||||
result = node.getReceiver()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +133,10 @@ bindingset[token]
|
||||
API::Node getExtraSuccessorFromInvoke(API::InvokeNode node, AccessPathToken token) {
|
||||
token.getName() = "Instance" and
|
||||
result = node.getInstance()
|
||||
or
|
||||
token.getName() = "Argument" and
|
||||
token.getAnArgument() = "this" and
|
||||
result.getARhs() = node.(DataFlow::CallNode).getReceiver()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,69 +14,73 @@ taintFlow
|
||||
| test.js:24:43:24:50 | source() | test.js:24:8:24:51 | testlib ... urce()) |
|
||||
| test.js:31:29:31:36 | source() | test.js:32:10:32:10 | y |
|
||||
| test.js:37:29:37:36 | source() | test.js:38:10:38:10 | y |
|
||||
| test.js:46:18:46:25 | source() | test.js:46:18:46:25 | source() |
|
||||
| test.js:47:22:47:29 | source() | test.js:47:22:47:29 | source() |
|
||||
| test.js:49:24:49:31 | source() | test.js:49:24:49:31 | source() |
|
||||
| test.js:53:27:53:34 | source() | test.js:53:27:53:34 | source() |
|
||||
| test.js:58:31:58:38 | source() | test.js:58:31:58:38 | source() |
|
||||
| test.js:62:34:62:41 | source() | test.js:62:34:62:41 | source() |
|
||||
| test.js:67:31:67:38 | source() | test.js:67:31:67:38 | source() |
|
||||
| test.js:68:34:68:41 | source() | test.js:68:34:68:41 | source() |
|
||||
| test.js:72:36:72:43 | source() | test.js:72:36:72:43 | source() |
|
||||
| test.js:73:39:73:46 | source() | test.js:73:39:73:46 | source() |
|
||||
| test.js:75:28:75:35 | source() | test.js:75:28:75:35 | source() |
|
||||
| test.js:76:31:76:38 | source() | test.js:76:31:76:38 | source() |
|
||||
| test.js:77:34:77:41 | source() | test.js:77:34:77:41 | source() |
|
||||
| test.js:81:28:81:35 | source() | test.js:81:28:81:35 | source() |
|
||||
| test.js:87:17:87:24 | source() | test.js:87:17:87:24 | source() |
|
||||
| test.js:88:17:88:24 | source() | test.js:88:17:88:24 | source() |
|
||||
| test.js:89:17:89:24 | source() | test.js:89:17:89:24 | source() |
|
||||
| test.js:43:29:43:36 | source() | test.js:44:10:44:10 | y |
|
||||
| test.js:47:33:47:40 | source() | test.js:49:10:49:13 | this |
|
||||
| test.js:54:18:54:25 | source() | test.js:54:18:54:25 | source() |
|
||||
| test.js:55:22:55:29 | source() | test.js:55:22:55:29 | source() |
|
||||
| test.js:57:24:57:31 | source() | test.js:57:24:57:31 | source() |
|
||||
| test.js:61:27:61:34 | source() | test.js:61:27:61:34 | source() |
|
||||
| test.js:66:31:66:38 | source() | test.js:66:31:66:38 | source() |
|
||||
| test.js:70:34:70:41 | source() | test.js:70:34:70:41 | source() |
|
||||
| test.js:75:31:75:38 | source() | test.js:75:31:75:38 | source() |
|
||||
| test.js:76:34:76:41 | source() | test.js:76:34:76:41 | source() |
|
||||
| test.js:80:36:80:43 | source() | test.js:80:36:80:43 | source() |
|
||||
| test.js:81:39:81:46 | source() | test.js:81:39:81:46 | source() |
|
||||
| test.js:83:28:83:35 | source() | test.js:83:28:83:35 | source() |
|
||||
| test.js:84:31:84:38 | source() | test.js:84:31:84:38 | source() |
|
||||
| test.js:85:34:85:41 | source() | test.js:85:34:85:41 | source() |
|
||||
| test.js:89:28:89:35 | source() | test.js:89:28:89:35 | source() |
|
||||
| test.js:95:17:95:24 | source() | test.js:95:17:95:24 | source() |
|
||||
| test.js:96:17:96:24 | source() | test.js:96:17:96:24 | source() |
|
||||
| test.js:97:17:97:24 | source() | test.js:97:17:97:24 | source() |
|
||||
| test.js:102:16:102:34 | testlib.getSource() | test.js:103:8:103:13 | source |
|
||||
| test.js:102:16:102:34 | testlib.getSource() | test.js:104:8:104:24 | source.continue() |
|
||||
isSink
|
||||
| test.js:46:18:46:25 | source() | test-sink |
|
||||
| test.js:47:22:47:29 | source() | test-sink |
|
||||
| test.js:49:24:49:31 | source() | test-sink |
|
||||
| test.js:53:27:53:34 | source() | test-sink |
|
||||
| test.js:55:38:55:38 | 4 | test-sink |
|
||||
| test.js:56:38:56:38 | 4 | test-sink |
|
||||
| test.js:57:38:57:38 | 4 | test-sink |
|
||||
| test.js:58:31:58:38 | source() | test-sink |
|
||||
| test.js:60:41:60:41 | 3 | test-sink |
|
||||
| test.js:61:41:61:41 | 3 | test-sink |
|
||||
| test.js:62:34:62:41 | source() | test-sink |
|
||||
| test.js:63:34:63:34 | 3 | test-sink |
|
||||
| test.js:65:38:65:38 | 3 | test-sink |
|
||||
| test.js:65:41:65:41 | 4 | test-sink |
|
||||
| test.js:66:38:66:38 | 3 | test-sink |
|
||||
| test.js:66:41:66:41 | 4 | test-sink |
|
||||
| test.js:67:31:67:38 | source() | test-sink |
|
||||
| test.js:67:41:67:41 | 4 | test-sink |
|
||||
| test.js:68:31:68:31 | 3 | test-sink |
|
||||
| test.js:68:34:68:41 | source() | test-sink |
|
||||
| test.js:70:43:70:43 | 3 | test-sink |
|
||||
| test.js:70:46:70:46 | 4 | test-sink |
|
||||
| test.js:71:43:71:43 | 3 | test-sink |
|
||||
| test.js:71:46:71:46 | 4 | test-sink |
|
||||
| test.js:72:36:72:43 | source() | test-sink |
|
||||
| test.js:72:46:72:46 | 4 | test-sink |
|
||||
| test.js:73:36:73:36 | 3 | test-sink |
|
||||
| test.js:73:39:73:46 | source() | test-sink |
|
||||
| test.js:75:28:75:35 | source() | test-sink |
|
||||
| test.js:75:38:75:38 | 2 | test-sink |
|
||||
| test.js:75:41:75:41 | 3 | test-sink |
|
||||
| test.js:76:28:76:28 | 1 | test-sink |
|
||||
| test.js:76:31:76:38 | source() | test-sink |
|
||||
| test.js:76:41:76:41 | 3 | test-sink |
|
||||
| test.js:77:28:77:28 | 1 | test-sink |
|
||||
| test.js:77:31:77:31 | 2 | test-sink |
|
||||
| test.js:77:34:77:41 | source() | test-sink |
|
||||
| test.js:78:28:78:28 | 1 | test-sink |
|
||||
| test.js:78:31:78:31 | 2 | test-sink |
|
||||
| test.js:78:34:78:34 | 3 | test-sink |
|
||||
| test.js:81:28:81:35 | source() | test-sink |
|
||||
| test.js:82:28:82:28 | 1 | test-sink |
|
||||
| test.js:87:17:87:24 | source() | test-sink |
|
||||
| test.js:88:17:88:24 | source() | test-sink |
|
||||
| test.js:89:17:89:24 | source() | test-sink |
|
||||
| test.js:54:18:54:25 | source() | test-sink |
|
||||
| test.js:55:22:55:29 | source() | test-sink |
|
||||
| test.js:57:24:57:31 | source() | test-sink |
|
||||
| test.js:61:27:61:34 | source() | test-sink |
|
||||
| test.js:63:38:63:38 | 4 | test-sink |
|
||||
| test.js:64:38:64:38 | 4 | test-sink |
|
||||
| test.js:65:38:65:38 | 4 | test-sink |
|
||||
| test.js:66:31:66:38 | source() | test-sink |
|
||||
| test.js:68:41:68:41 | 3 | test-sink |
|
||||
| test.js:69:41:69:41 | 3 | test-sink |
|
||||
| test.js:70:34:70:41 | source() | test-sink |
|
||||
| test.js:71:34:71:34 | 3 | test-sink |
|
||||
| test.js:73:38:73:38 | 3 | test-sink |
|
||||
| test.js:73:41:73:41 | 4 | test-sink |
|
||||
| test.js:74:38:74:38 | 3 | test-sink |
|
||||
| test.js:74:41:74:41 | 4 | test-sink |
|
||||
| test.js:75:31:75:38 | source() | test-sink |
|
||||
| test.js:75:41:75:41 | 4 | test-sink |
|
||||
| test.js:76:31:76:31 | 3 | test-sink |
|
||||
| test.js:76:34:76:41 | source() | test-sink |
|
||||
| test.js:78:43:78:43 | 3 | test-sink |
|
||||
| test.js:78:46:78:46 | 4 | test-sink |
|
||||
| test.js:79:43:79:43 | 3 | test-sink |
|
||||
| test.js:79:46:79:46 | 4 | test-sink |
|
||||
| test.js:80:36:80:43 | source() | test-sink |
|
||||
| test.js:80:46:80:46 | 4 | test-sink |
|
||||
| test.js:81:36:81:36 | 3 | test-sink |
|
||||
| test.js:81:39:81:46 | source() | test-sink |
|
||||
| test.js:83:28:83:35 | source() | test-sink |
|
||||
| test.js:83:38:83:38 | 2 | test-sink |
|
||||
| test.js:83:41:83:41 | 3 | test-sink |
|
||||
| test.js:84:28:84:28 | 1 | test-sink |
|
||||
| test.js:84:31:84:38 | source() | test-sink |
|
||||
| test.js:84:41:84:41 | 3 | test-sink |
|
||||
| test.js:85:28:85:28 | 1 | test-sink |
|
||||
| test.js:85:31:85:31 | 2 | test-sink |
|
||||
| test.js:85:34:85:41 | source() | test-sink |
|
||||
| test.js:86:28:86:28 | 1 | test-sink |
|
||||
| test.js:86:31:86:31 | 2 | test-sink |
|
||||
| test.js:86:34:86:34 | 3 | test-sink |
|
||||
| test.js:89:28:89:35 | source() | test-sink |
|
||||
| test.js:90:28:90:28 | 1 | test-sink |
|
||||
| test.js:95:17:95:24 | source() | test-sink |
|
||||
| test.js:96:17:96:24 | source() | test-sink |
|
||||
| test.js:97:17:97:24 | source() | test-sink |
|
||||
syntaxErrors
|
||||
| Member[foo |
|
||||
| Member[foo] .Member[bar] |
|
||||
|
||||
@@ -40,6 +40,14 @@ function testPreserveTaint() {
|
||||
testlib.taintIntoCallback(source(), undefined, undefined, y => {
|
||||
sink(y); // OK - only callback 1-2 receive taint
|
||||
});
|
||||
testlib.taintIntoCallback(source(), function(y) {
|
||||
sink(y); // NOT OK
|
||||
sink(this); // OK - receiver is not tainted
|
||||
});
|
||||
testlib.taintIntoCallbackThis(source(), function(y) {
|
||||
sink(y); // OK - only receiver is tainted
|
||||
sink(this); // NOT OK
|
||||
});
|
||||
}
|
||||
|
||||
function testSinks() {
|
||||
@@ -89,3 +97,10 @@ function testSinks() {
|
||||
testlib.sink3(source()); // NOT OK
|
||||
testlib.sink4(source()); // OK
|
||||
}
|
||||
|
||||
function testFlowThroughReceiver() {
|
||||
let source = testlib.getSource();
|
||||
sink(source); // NOT OK
|
||||
sink(source.continue()); // NOT OK
|
||||
sink(source.blah()); // OK
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ class Steps extends ModelInput::SummaryModelCsv {
|
||||
[
|
||||
"testlib;;Member[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
"testlib;;Member[taintIntoCallback];Argument[0];Argument[1..2].Parameter[0];taint",
|
||||
"testlib;;Member[taintIntoCallbackThis];Argument[0];Argument[1..2].Parameter[this];taint",
|
||||
"testlib;;Member[preserveArgZeroAndTwo];Argument[0,2];ReturnValue;taint",
|
||||
"testlib;;Member[preserveAllButFirstArgument];Argument[1..];ReturnValue;taint",
|
||||
"testlib;;Member[preserveAllIfCall].Call;Argument[0..];ReturnValue;taint"
|
||||
"testlib;;Member[preserveAllIfCall].Call;Argument[0..];ReturnValue;taint",
|
||||
"testlib;;Member[getSource].ReturnValue.Member[continue];Argument[this];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -35,11 +37,17 @@ class Sinks extends ModelInput::SinkModelCsv {
|
||||
}
|
||||
}
|
||||
|
||||
class Sources extends ModelInput::SourceModelCsv {
|
||||
override predicate row(string row) { row = "testlib;;Member[getSource].ReturnValue;test-source" }
|
||||
}
|
||||
|
||||
class BasicTaintTracking extends TaintTracking::Configuration {
|
||||
BasicTaintTracking() { this = "BasicTaintTracking" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.(DataFlow::CallNode).getCalleeName() = "source"
|
||||
or
|
||||
source = ModelOutput::getASourceNode("test-source").getAnImmediateUse()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
|
||||
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |
|
||||
| Invalid argument '0-1' in token 'Argument[0-1]' in access path: Method[foo].Argument[0-1] |
|
||||
| Invalid argument '0..' in token 'Argument[0..]' in access path: Argument[0..].Member[password] |
|
||||
| Invalid argument '0..' in token 'Argument[0..]' in access path: Argument[0..].Member[username] |
|
||||
| Invalid argument '0..' in token 'Argument[0..]' in access path: Member[executeSql].Argument[0..].Parameter[1] |
|
||||
| Invalid argument '0..' in token 'Argument[0..]' in access path: Member[run].Argument[0..].Parameter[1] |
|
||||
| Invalid argument '*' in token 'Argument[*]' in access path: Method[foo].Argument[*] |
|
||||
| Invalid token 'Argument' is missing its arguments, in access path: Method[foo].Argument |
|
||||
| Invalid token 'Member' is missing its arguments, in access path: Method[foo].Member |
|
||||
|
||||
@@ -552,7 +552,7 @@ class RegExpWordBoundary extends RegExpSpecialChar {
|
||||
|
||||
/**
|
||||
* A character class escape in a regular expression.
|
||||
* That is, an escaped charachter that denotes multiple characters.
|
||||
* That is, an escaped character that denotes multiple characters.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
|
||||
@@ -188,7 +188,7 @@ abstract class RegexString extends Expr {
|
||||
)
|
||||
}
|
||||
|
||||
/** Hold is a character set starts between `start` and `end`. */
|
||||
/** Holds if a character set starts between `start` and `end`. */
|
||||
predicate char_set_start(int start, int end) {
|
||||
this.char_set_start(start) = true and
|
||||
(
|
||||
@@ -316,8 +316,10 @@ abstract class RegexString extends Expr {
|
||||
result = this.(Bytes).getS()
|
||||
}
|
||||
|
||||
/** Gets the `i`th character of this regex */
|
||||
string getChar(int i) { result = this.getText().charAt(i) }
|
||||
|
||||
/** Gets the `i`th character of this regex, unless it is part of a character escape sequence. */
|
||||
string nonEscapedCharAt(int i) {
|
||||
result = this.getText().charAt(i) and
|
||||
not exists(int x, int y | this.escapedCharacter(x, y) and i in [x .. y - 1])
|
||||
@@ -329,6 +331,9 @@ abstract class RegexString extends Expr {
|
||||
|
||||
private predicate isGroupStart(int i) { this.nonEscapedCharAt(i) = "(" and not this.inCharSet(i) }
|
||||
|
||||
/**
|
||||
* Holds if the `i`th character could not be parsed.
|
||||
*/
|
||||
predicate failedToParse(int i) {
|
||||
exists(this.getChar(i)) and
|
||||
not exists(int start, int end |
|
||||
@@ -417,6 +422,9 @@ abstract class RegexString extends Expr {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a simple or escaped character is found between `start` and `end`.
|
||||
*/
|
||||
predicate character(int start, int end) {
|
||||
(
|
||||
this.simpleCharacter(start, end) and
|
||||
@@ -428,12 +436,18 @@ abstract class RegexString extends Expr {
|
||||
not exists(int x, int y | this.backreference(x, y) and x <= start and y >= end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a normal character is found between `start` and `end`.
|
||||
*/
|
||||
predicate normalCharacter(int start, int end) {
|
||||
end = start + 1 and
|
||||
this.character(start, end) and
|
||||
not this.specialCharacter(start, end, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a special character is found between `start` and `end`.
|
||||
*/
|
||||
predicate specialCharacter(int start, int end, string char) {
|
||||
not this.inCharSet(start) and
|
||||
this.character(start, end) and
|
||||
@@ -492,7 +506,7 @@ abstract class RegexString extends Expr {
|
||||
this.specialCharacter(start, end, _)
|
||||
}
|
||||
|
||||
/** Whether the text in the range start,end is a group */
|
||||
/** Whether the text in the range `start,end` is a group */
|
||||
predicate group(int start, int end) {
|
||||
this.groupContents(start, end, _, _)
|
||||
or
|
||||
@@ -611,6 +625,7 @@ abstract class RegexString extends Expr {
|
||||
this.simple_group_start(start, end)
|
||||
}
|
||||
|
||||
/** Matches the start of a non-capturing group, e.g. `(?:` */
|
||||
private predicate non_capturing_group_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
@@ -618,12 +633,18 @@ abstract class RegexString extends Expr {
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
/** Matches the start of a simple group, e.g. `(a+)`. */
|
||||
private predicate simple_group_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) != "?" and
|
||||
end = start + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the start of a named group, such as:
|
||||
* - `(?<name>\w+)`
|
||||
* - `(?'name'\w+)`
|
||||
*/
|
||||
private predicate named_group_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
@@ -675,6 +696,7 @@ abstract class RegexString extends Expr {
|
||||
)
|
||||
}
|
||||
|
||||
/** Matches the start of a positive lookahead assertion, i.e. `(?=`. */
|
||||
private predicate lookahead_assertion_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
@@ -682,6 +704,7 @@ abstract class RegexString extends Expr {
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
/** Matches the start of a negative lookahead assertion, i.e. `(?!`. */
|
||||
private predicate negative_lookahead_assertion_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
@@ -689,6 +712,7 @@ abstract class RegexString extends Expr {
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
/** Matches the start of a positive lookbehind assertion, i.e. `(?<=`. */
|
||||
private predicate lookbehind_assertion_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
@@ -697,6 +721,7 @@ abstract class RegexString extends Expr {
|
||||
end = start + 4
|
||||
}
|
||||
|
||||
/** Matches the start of a negative lookbehind assertion, i.e. `(?<!`. */
|
||||
private predicate negative_lookbehind_assertion_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
@@ -705,6 +730,7 @@ abstract class RegexString extends Expr {
|
||||
end = start + 4
|
||||
}
|
||||
|
||||
/** Matches the start of a comment group, i.e. `(?#`. */
|
||||
private predicate comment_group_start(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
@@ -712,6 +738,7 @@ abstract class RegexString extends Expr {
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
/** Matches the contents of a group. */
|
||||
predicate groupContents(int start, int end, int in_start, int in_end) {
|
||||
this.group_start(start, in_start) and
|
||||
end = in_end + 1 and
|
||||
@@ -719,12 +746,14 @@ abstract class RegexString extends Expr {
|
||||
this.isGroupEnd(in_end)
|
||||
}
|
||||
|
||||
/** Matches a named backreference, e.g. `\k<foo>`. */
|
||||
private predicate named_backreference(int start, int end, string name) {
|
||||
this.named_backreference_start(start, start + 4) and
|
||||
end = min(int i | i > start + 4 and this.getChar(i) = ")") + 1 and
|
||||
name = this.getText().substring(start + 4, end - 2)
|
||||
}
|
||||
|
||||
/** Matches a numbered backreference, e.g. `\1`. */
|
||||
private predicate numbered_backreference(int start, int end, int value) {
|
||||
this.escapingChar(start) and
|
||||
// starting with 0 makes it an octal escape
|
||||
@@ -749,7 +778,7 @@ abstract class RegexString extends Expr {
|
||||
)
|
||||
}
|
||||
|
||||
/** Whether the text in the range start,end is a back reference */
|
||||
/** Whether the text in the range `start,end` is a back reference */
|
||||
predicate backreference(int start, int end) {
|
||||
this.numbered_backreference(start, end, _)
|
||||
or
|
||||
|
||||
@@ -1134,7 +1134,7 @@ class Import extends TImport, ModuleMember, ModuleRef {
|
||||
*/
|
||||
string getImportString() {
|
||||
exists(string selec |
|
||||
not exists(getQualifiedName(_)) and selec = ""
|
||||
not exists(getSelectionName(_)) and selec = ""
|
||||
or
|
||||
selec =
|
||||
"::" + strictconcat(int i, string q | q = this.getSelectionName(i) | q, "::" order by i)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import codeql.ruby.security.performance.RegExpTreeView
|
||||
import codeql.ruby.Regexp
|
||||
|
||||
query predicate nonUniqueChild(RegExpParent parent, int i, RegExpTerm child) {
|
||||
child = parent.getChild(i) and
|
||||
|
||||
4
ruby/ql/lib/change-notes/2022-03-21-regex.md
Normal file
4
ruby/ql/lib/change-notes/2022-03-21-regex.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `ParseRegExp` and `RegExpTreeView` modules are now "internal" modules. Users should use `codeql.ruby.Regexp` instead.
|
||||
@@ -473,36 +473,6 @@ module API {
|
||||
/** Gets a data flow node that flows to the RHS of a def-node. */
|
||||
private DataFlow::LocalSourceNode defCand() { result = defCand(TypeBackTracker::end()) }
|
||||
|
||||
private Label::ApiLabel getLabelFromArgumentPosition(DataFlowDispatch::ArgumentPosition pos) {
|
||||
exists(int n |
|
||||
pos.isPositional(n) and
|
||||
result = Label::parameter(n)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = Label::keywordParameter(name)
|
||||
)
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = Label::blockParameter()
|
||||
}
|
||||
|
||||
private Label::ApiLabel getLabelFromParameterPosition(DataFlowDispatch::ParameterPosition pos) {
|
||||
exists(int n |
|
||||
pos.isPositional(n) and
|
||||
result = Label::parameter(n)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = Label::keywordParameter(name)
|
||||
)
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = Label::blockParameter()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there should be a `lbl`-edge from the given call to an argument.
|
||||
*/
|
||||
@@ -512,7 +482,7 @@ module API {
|
||||
) {
|
||||
exists(DataFlowDispatch::ArgumentPosition argPos |
|
||||
argument.sourceArgumentOf(call.asExpr(), argPos) and
|
||||
lbl = getLabelFromArgumentPosition(argPos)
|
||||
lbl = Label::getLabelFromArgumentPosition(argPos)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -525,7 +495,7 @@ module API {
|
||||
) {
|
||||
exists(DataFlowDispatch::ParameterPosition paramPos |
|
||||
paramNode.isSourceParameterOf(callable.asExpr().getExpr(), paramPos) and
|
||||
lbl = getLabelFromParameterPosition(paramPos)
|
||||
lbl = Label::getLabelFromParameterPosition(paramPos)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -803,5 +773,37 @@ module API {
|
||||
|
||||
/** Gets the label for the edge from the root node to a custom entry point of the given name. */
|
||||
LabelEntryPoint entryPoint(API::EntryPoint name) { result.getName() = name }
|
||||
|
||||
/** Gets the API graph label corresponding to the given argument position. */
|
||||
Label::ApiLabel getLabelFromArgumentPosition(DataFlowDispatch::ArgumentPosition pos) {
|
||||
exists(int n |
|
||||
pos.isPositional(n) and
|
||||
result = Label::parameter(n)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = Label::keywordParameter(name)
|
||||
)
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = Label::blockParameter()
|
||||
}
|
||||
|
||||
/** Gets the API graph label corresponding to the given parameter position. */
|
||||
Label::ApiLabel getLabelFromParameterPosition(DataFlowDispatch::ParameterPosition pos) {
|
||||
exists(int n |
|
||||
pos.isPositional(n) and
|
||||
result = Label::parameter(n)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = Label::keywordParameter(name)
|
||||
)
|
||||
or
|
||||
pos.isBlock() and
|
||||
result = Label::blockParameter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
143
ruby/ql/lib/codeql/ruby/Regexp.qll
Normal file
143
ruby/ql/lib/codeql/ruby/Regexp.qll
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Provides classes for working with regular expressions.
|
||||
*
|
||||
* Regular expression literals are represented as an abstract syntax tree of regular expression
|
||||
* terms.
|
||||
*/
|
||||
|
||||
import regexp.RegExpTreeView // re-export
|
||||
private import regexp.internal.ParseRegExp
|
||||
private import codeql.ruby.ast.Literal as AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.internal.tainttrackingforlibraries.TaintTrackingImpl
|
||||
|
||||
/**
|
||||
* Provides utility predicates related to regular expressions.
|
||||
*/
|
||||
module RegExpPatterns {
|
||||
/**
|
||||
* Gets a pattern that matches common top-level domain names in lower case.
|
||||
*/
|
||||
string getACommonTld() {
|
||||
// according to ranking by http://google.com/search?q=site:.<<TLD>>
|
||||
result = "(?:com|org|edu|gov|uk|net|io)(?![a-z0-9])"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node whose value may flow to a position where it is interpreted
|
||||
* as a part of a regular expression.
|
||||
*/
|
||||
abstract class RegExpPatternSource extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a node where the pattern of this node is parsed as a part of
|
||||
* a regular expression.
|
||||
*/
|
||||
abstract DataFlow::Node getAParse();
|
||||
|
||||
/**
|
||||
* Gets the root term of the regular expression parsed from this pattern.
|
||||
*/
|
||||
abstract RegExpTerm getRegExpTerm();
|
||||
}
|
||||
|
||||
/**
|
||||
* A regular expression literal, viewed as the pattern source for itself.
|
||||
*/
|
||||
private class RegExpLiteralPatternSource extends RegExpPatternSource {
|
||||
private AST::RegExpLiteral astNode;
|
||||
|
||||
RegExpLiteralPatternSource() { astNode = this.asExpr().getExpr() }
|
||||
|
||||
override DataFlow::Node getAParse() { result = this }
|
||||
|
||||
override RegExpTerm getRegExpTerm() { result = astNode.getParsed() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node whose string value may flow to a position where it is interpreted
|
||||
* as a part of a regular expression.
|
||||
*/
|
||||
private class StringRegExpPatternSource extends RegExpPatternSource {
|
||||
private DataFlow::Node parse;
|
||||
|
||||
StringRegExpPatternSource() { this = regExpSource(parse) }
|
||||
|
||||
override DataFlow::Node getAParse() { result = parse }
|
||||
|
||||
override RegExpTerm getRegExpTerm() { result.getRegExp() = this.asExpr().getExpr() }
|
||||
}
|
||||
|
||||
private class RegExpLiteralRegExp extends RegExp, AST::RegExpLiteral {
|
||||
override predicate isDotAll() { this.hasMultilineFlag() }
|
||||
|
||||
override predicate isIgnoreCase() { this.hasCaseInsensitiveFlag() }
|
||||
|
||||
override string getFlags() { result = this.getFlagString() }
|
||||
}
|
||||
|
||||
private class ParsedStringRegExp extends RegExp {
|
||||
private DataFlow::Node parse;
|
||||
|
||||
ParsedStringRegExp() { this = regExpSource(parse).asExpr().getExpr() }
|
||||
|
||||
DataFlow::Node getAParse() { result = parse }
|
||||
|
||||
override predicate isDotAll() { none() }
|
||||
|
||||
override predicate isIgnoreCase() { none() }
|
||||
|
||||
override string getFlags() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `source` may be interpreted as a regular expression.
|
||||
*/
|
||||
private predicate isInterpretedAsRegExp(DataFlow::Node source) {
|
||||
// The first argument to an invocation of `Regexp.new` or `Regexp.compile`.
|
||||
source = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]).getArgument(0)
|
||||
or
|
||||
// The argument of a call that coerces the argument to a regular expression.
|
||||
exists(DataFlow::CallNode mce |
|
||||
mce.getMethodName() = ["match", "match?"] and
|
||||
source = mce.getArgument(0) and
|
||||
// exclude https://ruby-doc.org/core-2.4.0/Regexp.html#method-i-match
|
||||
not mce.getReceiver().asExpr().getExpr() instanceof AST::RegExpLiteral
|
||||
)
|
||||
}
|
||||
|
||||
private class RegExpConfiguration extends Configuration {
|
||||
RegExpConfiguration() { this = "RegExpConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr() =
|
||||
any(ExprCfgNode e |
|
||||
e.getConstantValue().isString(_) and
|
||||
not e instanceof ExprNodes::VariableReadAccessCfgNode and
|
||||
not e instanceof ExprNodes::ConstantReadAccessCfgNode
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { isInterpretedAsRegExp(sink) }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// stop flow if `node` is receiver of
|
||||
// https://ruby-doc.org/core-2.4.0/String.html#method-i-match
|
||||
exists(DataFlow::CallNode mce |
|
||||
mce.getMethodName() = ["match", "match?"] and
|
||||
node = mce.getReceiver() and
|
||||
mce.getArgument(0).asExpr().getExpr() instanceof AST::RegExpLiteral
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node whose value may flow (inter-procedurally) to `re`, where it is interpreted
|
||||
* as a part of a regular expression.
|
||||
*/
|
||||
cached
|
||||
DataFlow::Node regExpSource(DataFlow::Node re) {
|
||||
exists(RegExpConfiguration c | c.hasFlow(result, re))
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.security.performance.RegExpTreeView as RETV
|
||||
private import codeql.ruby.Regexp as RE
|
||||
private import internal.AST
|
||||
private import internal.Constant
|
||||
private import internal.Literal
|
||||
@@ -393,7 +393,7 @@ class RegExpLiteral extends StringlikeLiteral instanceof RegExpLiteralImpl {
|
||||
final predicate hasFreeSpacingFlag() { this.getFlagString().charAt(_) = "x" }
|
||||
|
||||
/** Returns the root node of the parse tree of this regular expression. */
|
||||
final RETV::RegExpTerm getParsed() { result = RETV::getParsedRegExp(this) }
|
||||
final RE::RegExpTerm getParsed() { result = RE::getParsedRegExp(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -257,6 +257,8 @@ private module Cached {
|
||||
name = any(KeywordParameter kp).getName()
|
||||
or
|
||||
exists(any(Call c).getKeywordArgument(name))
|
||||
or
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
|
||||
}
|
||||
|
||||
cached
|
||||
@@ -270,7 +272,11 @@ private module Cached {
|
||||
or
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedArgumentPosition(_, pos)
|
||||
} or
|
||||
TKeywordParameterPosition(string name) { name = any(KeywordParameter kp).getName() }
|
||||
TKeywordParameterPosition(string name) {
|
||||
name = any(KeywordParameter kp).getName()
|
||||
or
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
|
||||
}
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
@@ -55,17 +55,10 @@ predicate summaryElement(DataFlowCallable c, string input, string output, string
|
||||
/**
|
||||
* Gets the summary component for specification component `c`, if any.
|
||||
*
|
||||
* This covers all the Ruby-specific components of a flow summary, and
|
||||
* is currently restricted to `"BlockArgument"`.
|
||||
* This covers all the Ruby-specific components of a flow summary.
|
||||
*/
|
||||
bindingset[c]
|
||||
SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
c = "Receiver" and
|
||||
result = FlowSummary::SummaryComponent::receiver()
|
||||
or
|
||||
c = "BlockArgument" and
|
||||
result = FlowSummary::SummaryComponent::block()
|
||||
or
|
||||
c = "Argument[_]" and
|
||||
result = FlowSummary::SummaryComponent::argument(any(ParameterPosition pos | pos.isPositional(_)))
|
||||
or
|
||||
@@ -83,16 +76,41 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a summary component in the format used for flow summaries. */
|
||||
string getComponentSpecificCsv(SummaryComponent sc) {
|
||||
sc = TArgumentSummaryComponent(any(ParameterPosition pos | pos.isBlock())) and
|
||||
result = "BlockArgument"
|
||||
}
|
||||
string getComponentSpecificCsv(SummaryComponent sc) { none() }
|
||||
|
||||
/** Gets the textual representation of a parameter position in the format used for flow summaries. */
|
||||
string getParameterPositionCsv(ParameterPosition pos) { result = pos.toString() }
|
||||
string getParameterPositionCsv(ParameterPosition pos) {
|
||||
pos.isSelf() and result = "self"
|
||||
or
|
||||
pos.isBlock() and result = "block"
|
||||
or
|
||||
exists(int i |
|
||||
pos.isPositional(i) and
|
||||
result = i.toString()
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = name + ":"
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of an argument position in the format used for flow summaries. */
|
||||
string getArgumentPositionCsv(ArgumentPosition pos) { result = pos.toString() }
|
||||
string getArgumentPositionCsv(ArgumentPosition pos) {
|
||||
pos.isSelf() and result = "self"
|
||||
or
|
||||
pos.isBlock() and result = "block"
|
||||
or
|
||||
exists(int i |
|
||||
pos.isPositional(i) and
|
||||
result = i.toString()
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
pos.isKeyword(name) and
|
||||
result = name + ":"
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if input specification component `c` needs a reference. */
|
||||
predicate inputNeedsReferenceSpecific(string c) { none() }
|
||||
@@ -176,6 +194,16 @@ module ParsePositions {
|
||||
isArgBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
}
|
||||
|
||||
predicate isParsedKeywordParameterPosition(string c, string paramName) {
|
||||
isParamBody(c) and
|
||||
c = paramName + ":"
|
||||
}
|
||||
|
||||
predicate isParsedKeywordArgumentPosition(string c, string paramName) {
|
||||
isArgBody(c) and
|
||||
c = paramName + ":"
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */
|
||||
@@ -184,6 +212,17 @@ ArgumentPosition parseParamBody(string s) {
|
||||
ParsePositions::isParsedParameterPosition(s, i) and
|
||||
result.isPositional(i)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
ParsePositions::isParsedKeywordParameterPosition(s, name) and
|
||||
result.isKeyword(name)
|
||||
)
|
||||
or
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
}
|
||||
|
||||
/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */
|
||||
@@ -192,4 +231,15 @@ ParameterPosition parseArgBody(string s) {
|
||||
ParsePositions::isParsedArgumentPosition(s, i) and
|
||||
result.isPositional(i)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
ParsePositions::isParsedKeywordArgumentPosition(s, name) and
|
||||
result.isKeyword(name)
|
||||
)
|
||||
or
|
||||
s = "self" and
|
||||
result.isSelf()
|
||||
or
|
||||
s = "block" and
|
||||
result.isBlock()
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ private class Summaries extends ModelInput::SummaryModelCsv {
|
||||
row =
|
||||
[
|
||||
"activestorage;;Member[ActiveStorage].Member[Filename].Method[new];Argument[0];ReturnValue;taint",
|
||||
"activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized];Receiver;ReturnValue;taint",
|
||||
"activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized];Argument[self];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,11 @@ private class SplatSummary extends SummarizedCallable {
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
(
|
||||
// *1 = [1]
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
output = "ReturnValue.ArrayElement[0]"
|
||||
or
|
||||
// *[1] = [1]
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
output = "ReturnValue"
|
||||
) and
|
||||
preservesValue = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ module String {
|
||||
* Taint-preserving (but not value-preserving) flow from the receiver to the return value.
|
||||
*/
|
||||
private predicate taintIdentityFlow(string input, string output, boolean preservesValue) {
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = false
|
||||
}
|
||||
@@ -58,7 +58,7 @@ module String {
|
||||
FormatSummary() { this = "%" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = ["Receiver", "Argument[0]", "Argument[0].ArrayElement"] and
|
||||
input = ["Argument[self]", "Argument[0]", "Argument[0].ArrayElement"] and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = false
|
||||
}
|
||||
@@ -94,7 +94,7 @@ module String {
|
||||
CapitalizeSummary() { this = ["capitalize", "capitalize!"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
preservesValue = false and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
@@ -125,9 +125,9 @@ module String {
|
||||
taintIdentityFlow(input, output, preservesValue)
|
||||
or
|
||||
this = ["chomp!", "chop!"] and
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
preservesValue = false and
|
||||
output = "Receiver"
|
||||
output = "Argument[self]"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +152,8 @@ module String {
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = ["Receiver", "Argument[_]"] and
|
||||
output = ["ReturnValue", "Receiver"] and
|
||||
input = ["Argument[self]", "Argument[_]"] and
|
||||
output = ["ReturnValue", "Argument[self]"] and
|
||||
preservesValue = false
|
||||
}
|
||||
}
|
||||
@@ -212,8 +212,8 @@ module String {
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = false and
|
||||
input = "Receiver" and
|
||||
output = ["BlockArgument.Parameter[0]", "ReturnValue"]
|
||||
input = "Argument[self]" and
|
||||
output = ["Argument[block].Parameter[0]", "ReturnValue"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ module String {
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = false and
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
output = "ReturnValue.ArrayElement[?]"
|
||||
}
|
||||
}
|
||||
@@ -278,7 +278,7 @@ module String {
|
||||
// block return -> return value
|
||||
preservesValue = false and
|
||||
output = "ReturnValue" and
|
||||
input = ["Receiver", "Argument[1]", "BlockArgument.ReturnValue"]
|
||||
input = ["Argument[self]", "Argument[1]", "Argument[block].ReturnValue"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ module String {
|
||||
PartitionSummary() { this = ["partition", "rpartition"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
output = "ReturnValue.ArrayElement[" + [0, 1, 2] + "]" and
|
||||
preservesValue = false
|
||||
}
|
||||
@@ -353,10 +353,10 @@ module String {
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = ["ReturnValue", "Receiver"] and
|
||||
output = ["ReturnValue", "Argument[self]"] and
|
||||
preservesValue = false
|
||||
}
|
||||
// TODO: we should also clear any existing content in Receiver
|
||||
// TODO: we should also clear any existing content in Argument[self]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,7 +386,7 @@ module String {
|
||||
ScanBlockSummary() { this = "scan_with_block" and exists(mc.getBlock()) }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
preservesValue = false and
|
||||
output =
|
||||
[
|
||||
@@ -394,7 +394,7 @@ module String {
|
||||
"ReturnValue",
|
||||
// scan(pattern) {|match, ...| block } -> str
|
||||
// Parameter[_] doesn't seem to work
|
||||
"BlockArgument.Parameter[" + [0 .. 10] + "]"
|
||||
"Argument[block].Parameter[" + [0 .. 10] + "]"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -404,7 +404,7 @@ module String {
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// scan(pattern) -> array
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
output = "ReturnValue.ArrayElement[?]" and
|
||||
preservesValue = false
|
||||
}
|
||||
@@ -430,12 +430,12 @@ module String {
|
||||
or
|
||||
preservesValue = false and
|
||||
(
|
||||
input = "Receiver" and
|
||||
output = "BlockArgument.Parameter[0]"
|
||||
input = "Argument[self]" and
|
||||
output = "Argument[block].Parameter[0]"
|
||||
or
|
||||
input = "Argument[0]" and output = "ReturnValue"
|
||||
or
|
||||
input = "BlockArgument.ReturnValue" and
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue"
|
||||
)
|
||||
}
|
||||
@@ -471,7 +471,7 @@ module String {
|
||||
ShellSplitSummary() { this = "shellsplit" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Receiver" and
|
||||
input = "Argument[self]" and
|
||||
output = "ReturnValue.ArrayElement[?]" and
|
||||
preservesValue = false
|
||||
}
|
||||
@@ -551,11 +551,11 @@ module String {
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
taintIdentityFlow(input, output, preservesValue)
|
||||
or
|
||||
input = ["Receiver", "Argument[0]"] and
|
||||
output = "BlockArgument.Parameter[0]" and
|
||||
input = ["Argument[self]", "Argument[0]"] and
|
||||
output = "Argument[block].Parameter[0]" and
|
||||
preservesValue = false
|
||||
or
|
||||
input = "BlockArgument.ReturnValue" and
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue.ArrayElement[?]" and
|
||||
preservesValue = false
|
||||
}
|
||||
@@ -571,11 +571,11 @@ module String {
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Receiver" and
|
||||
output = "BlockArgument.Parameter[0]" and
|
||||
input = "Argument[self]" and
|
||||
output = "Argument[block].Parameter[0]" and
|
||||
preservesValue = false
|
||||
or
|
||||
input = "BlockArgument.ReturnValue" and
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue.ArrayElement[?]" and
|
||||
preservesValue = false
|
||||
}
|
||||
|
||||
@@ -413,10 +413,10 @@ predicate isValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
bindingset[name, argument]
|
||||
predicate isValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
|
||||
name = ["Argument", "Parameter"] and
|
||||
argument.regexpMatch("(N-|-)?\\d+(\\.\\.(N-|-)?\\d+)?")
|
||||
argument.regexpMatch("(N-|-)?\\d+(\\.\\.((N-|-)?\\d+)?)?")
|
||||
or
|
||||
name = "WithArity" and
|
||||
argument.regexpMatch("\\d+(\\.\\.\\d+)?")
|
||||
argument.regexpMatch("\\d+(\\.\\.(\\d+)?)?")
|
||||
or
|
||||
Specific::isExtraValidTokenArgumentInIdentifyingAccessPath(name, argument)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ class Unit = DataFlowPrivate::Unit;
|
||||
import codeql.ruby.ApiGraphs
|
||||
import codeql.ruby.dataflow.internal.AccessPathSyntax as AccessPathSyntax
|
||||
private import AccessPathSyntax
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
|
||||
|
||||
/**
|
||||
* Holds if models describing `package` may be relevant for the analysis of this database.
|
||||
@@ -107,8 +109,10 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
token.getName() = "Instance" and
|
||||
result = node.getInstance()
|
||||
or
|
||||
token.getName() = "BlockArgument" and
|
||||
result = node.getBlock()
|
||||
token.getName() = "Parameter" and
|
||||
result =
|
||||
node.getASuccessor(API::Label::getLabelFromArgumentPosition(FlowSummaryImplSpecific::parseParamBody(token
|
||||
.getAnArgument())))
|
||||
// Note: The "ArrayElement" token is not implemented yet, as it ultimately requires type-tracking and
|
||||
// API graphs to be aware of the steps involving ArrayElement contributed by the standard library model.
|
||||
// Type-tracking cannot summarize function calls on its own, so it doesn't benefit from synthesized callables.
|
||||
@@ -118,7 +122,12 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
* Gets a Ruby-specific API graph successor of `node` reachable by resolving `token`.
|
||||
*/
|
||||
bindingset[token]
|
||||
API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathToken token) { none() }
|
||||
API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathToken token) {
|
||||
token.getName() = "Argument" and
|
||||
result =
|
||||
node.getASuccessor(API::Label::getLabelFromParameterPosition(FlowSummaryImplSpecific::parseArgBody(token
|
||||
.getAnArgument())))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invoke` matches the Ruby-specific call site filter in `token`.
|
||||
@@ -146,7 +155,7 @@ InvokeNode getAnInvocationOf(API::Node node) { result = node }
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Member", "Method", "Instance", "WithBlock", "WithoutBlock", "BlockArgument"]
|
||||
name = ["Member", "Method", "Instance", "WithBlock", "WithoutBlock"]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +163,7 @@ predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
* in an identifying access path.
|
||||
*/
|
||||
predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
name = ["Instance", "WithBlock", "WithoutBlock", "BlockArgument"]
|
||||
name = ["Instance", "WithBlock", "WithoutBlock"]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,4 +174,11 @@ bindingset[name, argument]
|
||||
predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
|
||||
name = ["Member", "Method"] and
|
||||
exists(argument)
|
||||
or
|
||||
name = ["Argument", "Parameter"] and
|
||||
(
|
||||
argument = ["self", "block"]
|
||||
or
|
||||
argument.regexpMatch("\\w+:") // keyword argument
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
private import AST
|
||||
private import codeql.ruby.security.performance.RegExpTreeView as RETV
|
||||
private import codeql.ruby.Regexp as RE
|
||||
private import codeql.ruby.ast.internal.Synthesis
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ private predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode ch
|
||||
|
||||
newtype TPrintNode =
|
||||
TPrintRegularAstNode(AstNode n) { shouldPrintNode(n) } or
|
||||
TPrintRegExpNode(RETV::RegExpTerm term) {
|
||||
TPrintRegExpNode(RE::RegExpTerm term) {
|
||||
exists(RegExpLiteral literal |
|
||||
shouldPrintNode(literal) and
|
||||
term.getRootTerm() = literal.getParsed()
|
||||
@@ -107,7 +107,7 @@ class PrintRegularAstNode extends PrintAstNode, TPrintRegularAstNode {
|
||||
or
|
||||
// If this AST node is a regexp literal, add the parsed regexp tree as a
|
||||
// child.
|
||||
exists(RETV::RegExpTerm t | t = astNode.(RegExpLiteral).getParsed() |
|
||||
exists(RE::RegExpTerm t | t = astNode.(RegExpLiteral).getParsed() |
|
||||
result = TPrintRegExpNode(t) and edgeName = "getParsed"
|
||||
)
|
||||
}
|
||||
@@ -134,7 +134,7 @@ class PrintRegularAstNode extends PrintAstNode, TPrintRegularAstNode {
|
||||
|
||||
/** A parsed regexp node in the output tree. */
|
||||
class PrintRegExpNode extends PrintAstNode, TPrintRegExpNode {
|
||||
RETV::RegExpTerm regexNode;
|
||||
RE::RegExpTerm regexNode;
|
||||
|
||||
PrintRegExpNode() { this = TPrintRegExpNode(regexNode) }
|
||||
|
||||
@@ -147,7 +147,7 @@ class PrintRegExpNode extends PrintAstNode, TPrintRegExpNode {
|
||||
exists(int i | result = TPrintRegExpNode(regexNode.getChild(i)) and edgeName = i.toString())
|
||||
}
|
||||
|
||||
override int getOrder() { exists(RETV::RegExpTerm p | p.getChild(result) = regexNode) }
|
||||
override int getOrder() { exists(RE::RegExpTerm p | p.getChild(result) = regexNode) }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
1030
ruby/ql/lib/codeql/ruby/regexp/RegExpTreeView.qll
Normal file
1030
ruby/ql/lib/codeql/ruby/regexp/RegExpTreeView.qll
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,6 @@
|
||||
|
||||
private import codeql.ruby.ast.Literal as AST
|
||||
private import codeql.Locations
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.internal.tainttrackingforlibraries.TaintTrackingImpl
|
||||
|
||||
/**
|
||||
* A `StringlikeLiteral` containing a regular expression term, that is, either
|
||||
@@ -116,6 +112,7 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a character set starts between `start` and `end`. */
|
||||
predicate charSetStart(int start, int end) {
|
||||
this.charSetStart(start) = true and
|
||||
(
|
||||
@@ -145,14 +142,21 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
predicate charSetToken(int charsetStart, int index, int tokenStart, int tokenEnd) {
|
||||
/**
|
||||
* Holds if the character set starting at `charsetStart` contains either
|
||||
* a character or a `-` found between `start` and `end`.
|
||||
*/
|
||||
private predicate charSetToken(int charsetStart, int index, int tokenStart, int tokenEnd) {
|
||||
tokenStart =
|
||||
rank[index](int start, int end | this.charSetToken(charsetStart, start, end) | start) and
|
||||
this.charSetToken(charsetStart, tokenStart, tokenEnd)
|
||||
}
|
||||
|
||||
/** Either a char or a - */
|
||||
predicate charSetToken(int charsetStart, int start, int end) {
|
||||
/**
|
||||
* Holds if the character set starting at `charsetStart` contains either
|
||||
* a character or a `-` found between `start` and `end`.
|
||||
*/
|
||||
private predicate charSetToken(int charsetStart, int start, int end) {
|
||||
this.charSetStart(charsetStart, start) and
|
||||
(
|
||||
this.escapedCharacter(start, end)
|
||||
@@ -174,6 +178,10 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character set starting at `charsetStart` contains either
|
||||
* a character or a range found between `start` and `end`.
|
||||
*/
|
||||
predicate charSetChild(int charsetStart, int start, int end) {
|
||||
this.charSetToken(charsetStart, start, end) and
|
||||
not exists(int rangeStart, int rangeEnd |
|
||||
@@ -185,6 +193,11 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
this.charRange(charsetStart, start, _, _, end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character set starting at `charset_start` contains a character range
|
||||
* with lower bound found between `start` and `lower_end`
|
||||
* and upper bound found between `upper_start` and `end`.
|
||||
*/
|
||||
predicate charRange(int charsetStart, int start, int lowerEnd, int upperStart, int end) {
|
||||
exists(int index |
|
||||
this.charRangeEnd(charsetStart, index) = true and
|
||||
@@ -193,6 +206,13 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `charRange`.
|
||||
* We can determine where character ranges end by a left to right sweep.
|
||||
*
|
||||
* To avoid negative recursion we return a boolean. See `escaping`,
|
||||
* the helper for `escapingChar`, for a clean use of this pattern.
|
||||
*/
|
||||
private boolean charRangeEnd(int charsetStart, int index) {
|
||||
this.charSetToken(charsetStart, index, _, _) and
|
||||
(
|
||||
@@ -216,8 +236,15 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the character at `pos` is a "\" that is actually escaping what comes after. */
|
||||
predicate escapingChar(int pos) { this.escaping(pos) = true }
|
||||
|
||||
/**
|
||||
* Helper predicate for `escapingChar`.
|
||||
* In order to avoid negative recusrion, we return a boolean.
|
||||
* This way, we can refer to `escaping(pos - 1).booleanNot()`
|
||||
* rather than to a negated version of `escaping(pos)`.
|
||||
*/
|
||||
private boolean escaping(int pos) {
|
||||
pos = -1 and result = false
|
||||
or
|
||||
@@ -229,8 +256,10 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
/** Gets the text of this regex */
|
||||
string getText() { result = this.getConstantValue().getString() }
|
||||
|
||||
/** Gets the `i`th character of this regex */
|
||||
string getChar(int i) { result = this.getText().charAt(i) }
|
||||
|
||||
/** Gets the `i`th character of this regex, unless it is part of a character escape sequence. */
|
||||
string nonEscapedCharAt(int i) {
|
||||
result = this.getText().charAt(i) and
|
||||
not exists(int x, int y | this.escapedCharacter(x, y) and i in [x .. y - 1])
|
||||
@@ -242,6 +271,9 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
|
||||
private predicate isGroupStart(int i) { this.nonEscapedCharAt(i) = "(" and not this.inCharSet(i) }
|
||||
|
||||
/**
|
||||
* Holds if the `i`th character could not be parsed.
|
||||
*/
|
||||
predicate failedToParse(int i) {
|
||||
exists(this.getChar(i)) and
|
||||
not exists(int start, int end |
|
||||
@@ -331,6 +363,11 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
this.getChar(start + 3) = "^"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an escaped character is found between `start` and `end`.
|
||||
* Escaped characters include hex values, octal values and named escapes,
|
||||
* but excludes backreferences.
|
||||
*/
|
||||
predicate escapedCharacter(int start, int end) {
|
||||
this.escapingChar(start) and
|
||||
not this.numberedBackreference(start, _, _) and
|
||||
@@ -350,17 +387,25 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character at `index` is inside a character set.
|
||||
*/
|
||||
predicate inCharSet(int index) {
|
||||
exists(int x, int y | this.charSet(x, y) and index in [x + 1 .. y - 2])
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character at `index` is inside a posix bracket.
|
||||
*/
|
||||
predicate inPosixBracket(int index) {
|
||||
exists(int x, int y |
|
||||
this.posixStyleNamedCharacterProperty(x, y, _) and index in [x + 1 .. y - 2]
|
||||
)
|
||||
}
|
||||
|
||||
/** 'Simple' characters are any that don't alter the parsing of the regex. */
|
||||
/**
|
||||
* 'simple' characters are any that don't alter the parsing of the regex.
|
||||
*/
|
||||
private predicate simpleCharacter(int start, int end) {
|
||||
end = start + 1 and
|
||||
not this.charSet(start, _) and
|
||||
@@ -391,6 +436,9 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a simple or escaped character is found between `start` and `end`.
|
||||
*/
|
||||
predicate character(int start, int end) {
|
||||
(
|
||||
this.simpleCharacter(start, end) and
|
||||
@@ -406,12 +454,18 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
not exists(int x, int y | this.multiples(x, y, _, _) and x <= start and y >= end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a normal character is found between `start` and `end`.
|
||||
*/
|
||||
predicate normalCharacter(int start, int end) {
|
||||
end = start + 1 and
|
||||
this.character(start, end) and
|
||||
not this.specialCharacter(start, end, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a special character is found between `start` and `end`.
|
||||
*/
|
||||
predicate specialCharacter(int start, int end, string char) {
|
||||
this.character(start, end) and
|
||||
not this.inCharSet(start) and
|
||||
@@ -505,6 +559,7 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
this.positiveLookbehindAssertionGroup(start, end)
|
||||
}
|
||||
|
||||
/** Holds if an empty group is found between `start` and `end`. */
|
||||
predicate emptyGroup(int start, int end) {
|
||||
exists(int endm1 | end = endm1 + 1 |
|
||||
this.groupStart(start, endm1) and
|
||||
@@ -538,24 +593,28 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a negative lookahead is found between `start` and `end` */
|
||||
predicate negativeLookaheadAssertionGroup(int start, int end) {
|
||||
exists(int inStart | this.negativeLookaheadAssertionStart(start, inStart) |
|
||||
this.groupContents(start, end, inStart, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a negative lookbehind is found between `start` and `end` */
|
||||
predicate negativeLookbehindAssertionGroup(int start, int end) {
|
||||
exists(int inStart | this.negativeLookbehindAssertionStart(start, inStart) |
|
||||
this.groupContents(start, end, inStart, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a positive lookahead is found between `start` and `end` */
|
||||
predicate positiveLookaheadAssertionGroup(int start, int end) {
|
||||
exists(int inStart | this.lookaheadAssertionStart(start, inStart) |
|
||||
this.groupContents(start, end, inStart, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a positive lookbehind is found between `start` and `end` */
|
||||
predicate positiveLookbehindAssertionGroup(int start, int end) {
|
||||
exists(int inStart | this.lookbehindAssertionStart(start, inStart) |
|
||||
this.groupContents(start, end, inStart, _)
|
||||
@@ -661,6 +720,7 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
/** Matches the contents of a group. */
|
||||
predicate groupContents(int start, int end, int inStart, int inEnd) {
|
||||
this.groupStart(start, inStart) and
|
||||
end = inEnd + 1 and
|
||||
@@ -747,6 +807,11 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a repetition quantifier is found between `start` and `end`,
|
||||
* with the given lower and upper bounds. If a bound is omitted, the corresponding
|
||||
* string is empty.
|
||||
*/
|
||||
predicate multiples(int start, int end, string lower, string upper) {
|
||||
exists(string text, string match, string inner |
|
||||
text = this.getText() and
|
||||
@@ -774,6 +839,13 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
this.qualifiedPart(start, _, end, maybeEmpty, mayRepeatForever)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a qualified part is found between `start` and `part_end` and the qualifier is
|
||||
* found between `part_end` and `end`.
|
||||
*
|
||||
* `maybe_empty` is true if the part is optional.
|
||||
* `may_repeat_forever` is true if the part may be repeated unboundedly.
|
||||
*/
|
||||
predicate qualifiedPart(
|
||||
int start, int partEnd, int end, boolean maybeEmpty, boolean mayRepeatForever
|
||||
) {
|
||||
@@ -781,6 +853,7 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
this.qualifier(partEnd, end, maybeEmpty, mayRepeatForever)
|
||||
}
|
||||
|
||||
/** Holds if the range `start`, `end` contains a character, a quantifier, a character set or a group. */
|
||||
predicate item(int start, int end) {
|
||||
this.qualifiedItem(start, end, _, _)
|
||||
or
|
||||
@@ -960,75 +1033,3 @@ abstract class RegExp extends AST::StringlikeLiteral {
|
||||
this.lastPart(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
private class RegExpLiteralRegExp extends RegExp, AST::RegExpLiteral {
|
||||
override predicate isDotAll() { this.hasMultilineFlag() }
|
||||
|
||||
override predicate isIgnoreCase() { this.hasCaseInsensitiveFlag() }
|
||||
|
||||
override string getFlags() { result = this.getFlagString() }
|
||||
}
|
||||
|
||||
private class ParsedStringRegExp extends RegExp {
|
||||
private DataFlow::Node parse;
|
||||
|
||||
ParsedStringRegExp() { this = regExpSource(parse).asExpr().getExpr() }
|
||||
|
||||
DataFlow::Node getAParse() { result = parse }
|
||||
|
||||
override predicate isDotAll() { none() }
|
||||
|
||||
override predicate isIgnoreCase() { none() }
|
||||
|
||||
override string getFlags() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `source` may be interpreted as a regular expression.
|
||||
*/
|
||||
private predicate isInterpretedAsRegExp(DataFlow::Node source) {
|
||||
// The first argument to an invocation of `Regexp.new` or `Regexp.compile`.
|
||||
source = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]).getArgument(0)
|
||||
or
|
||||
// The argument of a call that coerces the argument to a regular expression.
|
||||
exists(DataFlow::CallNode mce |
|
||||
mce.getMethodName() = ["match", "match?"] and
|
||||
source = mce.getArgument(0) and
|
||||
// exclude https://ruby-doc.org/core-2.4.0/Regexp.html#method-i-match
|
||||
not mce.getReceiver().asExpr().getExpr() instanceof AST::RegExpLiteral
|
||||
)
|
||||
}
|
||||
|
||||
private class RegExpConfiguration extends Configuration {
|
||||
RegExpConfiguration() { this = "RegExpConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr() =
|
||||
any(ExprCfgNode e |
|
||||
e.getConstantValue().isString(_) and
|
||||
not e instanceof ExprNodes::VariableReadAccessCfgNode and
|
||||
not e instanceof ExprNodes::ConstantReadAccessCfgNode
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { isInterpretedAsRegExp(sink) }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// stop flow if `node` is receiver of
|
||||
// https://ruby-doc.org/core-2.4.0/String.html#method-i-match
|
||||
exists(DataFlow::CallNode mce |
|
||||
mce.getMethodName() = ["match", "match?"] and
|
||||
node = mce.getReceiver() and
|
||||
mce.getArgument(0).asExpr().getExpr() instanceof AST::RegExpLiteral
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node whose value may flow (inter-procedurally) to `re`, where it is interpreted
|
||||
* as a part of a regular expression.
|
||||
*/
|
||||
cached
|
||||
DataFlow::Node regExpSource(DataFlow::Node re) {
|
||||
exists(RegExpConfiguration c | c.hasFlow(result, re))
|
||||
}
|
||||
@@ -8,8 +8,7 @@ private import codeql.ruby.AST as AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.security.performance.ParseRegExp as RegExp
|
||||
private import codeql.ruby.security.performance.RegExpTreeView
|
||||
private import codeql.ruby.Regexp
|
||||
private import codeql.ruby.security.performance.SuperlinearBackTracking
|
||||
|
||||
module PolynomialReDoS {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
private import codeql.ruby.ast.Literal as AST
|
||||
private import ParseRegExp
|
||||
private import codeql.NumberUtils
|
||||
/**
|
||||
* This module should provide a class hierarchy corresponding to a parse tree of regular expressions.
|
||||
*/
|
||||
|
||||
import codeql.ruby.Regexp
|
||||
import codeql.Locations
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ast.Literal as AST
|
||||
|
||||
/**
|
||||
* Holds if `term` is an ecape class representing e.g. `\d`.
|
||||
@@ -59,776 +61,3 @@ module RegExpFlags {
|
||||
root.getLiteral().isDotAll()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides utility predicates related to regular expressions.
|
||||
*/
|
||||
module RegExpPatterns {
|
||||
/**
|
||||
* Gets a pattern that matches common top-level domain names in lower case.
|
||||
*/
|
||||
string getACommonTld() {
|
||||
// according to ranking by http://google.com/search?q=site:.<<TLD>>
|
||||
result = "(?:com|org|edu|gov|uk|net|io)(?![a-z0-9])"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An element containing a regular expression term, that is, either
|
||||
* a string literal (parsed as a regular expression)
|
||||
* or another regular expression term.
|
||||
*/
|
||||
class RegExpParent extends TRegExpParent {
|
||||
string toString() { result = "RegExpParent" }
|
||||
|
||||
RegExpTerm getChild(int i) { none() }
|
||||
|
||||
final RegExpTerm getAChild() { result = this.getChild(_) }
|
||||
|
||||
int getNumChild() { result = count(this.getAChild()) }
|
||||
|
||||
/**
|
||||
* Gets the name of a primary CodeQL class to which this regular
|
||||
* expression term belongs.
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "RegExpParent" }
|
||||
|
||||
/**
|
||||
* Gets a comma-separated list of the names of the primary CodeQL classes to
|
||||
* which this regular expression term belongs.
|
||||
*/
|
||||
final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
|
||||
}
|
||||
|
||||
class RegExpLiteral extends TRegExpLiteral, RegExpParent {
|
||||
RegExp re;
|
||||
|
||||
RegExpLiteral() { this = TRegExpLiteral(re) }
|
||||
|
||||
override RegExpTerm getChild(int i) { i = 0 and result.getRegExp() = re and result.isRootTerm() }
|
||||
|
||||
predicate isDotAll() { re.isDotAll() }
|
||||
|
||||
predicate isIgnoreCase() { re.isIgnoreCase() }
|
||||
|
||||
string getFlags() { result = re.getFlags() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpLiteral" }
|
||||
}
|
||||
|
||||
class RegExpTerm extends RegExpParent {
|
||||
RegExp re;
|
||||
int start;
|
||||
int end;
|
||||
|
||||
RegExpTerm() {
|
||||
this = TRegExpAlt(re, start, end)
|
||||
or
|
||||
this = TRegExpBackRef(re, start, end)
|
||||
or
|
||||
this = TRegExpCharacterClass(re, start, end)
|
||||
or
|
||||
this = TRegExpCharacterRange(re, start, end)
|
||||
or
|
||||
this = TRegExpNormalChar(re, start, end)
|
||||
or
|
||||
this = TRegExpGroup(re, start, end)
|
||||
or
|
||||
this = TRegExpQuantifier(re, start, end)
|
||||
or
|
||||
this = TRegExpSequence(re, start, end) and
|
||||
exists(seqChild(re, start, end, 1)) // if a sequence does not have more than one element, it should be treated as that element instead.
|
||||
or
|
||||
this = TRegExpSpecialChar(re, start, end)
|
||||
or
|
||||
this = TRegExpNamedCharacterProperty(re, start, end)
|
||||
}
|
||||
|
||||
RegExpTerm getRootTerm() {
|
||||
this.isRootTerm() and result = this
|
||||
or
|
||||
result = this.getParent().(RegExpTerm).getRootTerm()
|
||||
}
|
||||
|
||||
predicate isUsedAsRegExp() { any() }
|
||||
|
||||
predicate isRootTerm() { start = 0 and end = re.getText().length() }
|
||||
|
||||
override RegExpTerm getChild(int i) {
|
||||
result = this.(RegExpAlt).getChild(i)
|
||||
or
|
||||
result = this.(RegExpBackRef).getChild(i)
|
||||
or
|
||||
result = this.(RegExpCharacterClass).getChild(i)
|
||||
or
|
||||
result = this.(RegExpCharacterRange).getChild(i)
|
||||
or
|
||||
result = this.(RegExpNormalChar).getChild(i)
|
||||
or
|
||||
result = this.(RegExpGroup).getChild(i)
|
||||
or
|
||||
result = this.(RegExpQuantifier).getChild(i)
|
||||
or
|
||||
result = this.(RegExpSequence).getChild(i)
|
||||
or
|
||||
result = this.(RegExpSpecialChar).getChild(i)
|
||||
or
|
||||
result = this.(RegExpNamedCharacterProperty).getChild(i)
|
||||
}
|
||||
|
||||
RegExpParent getParent() { result.getAChild() = this }
|
||||
|
||||
RegExp getRegExp() { result = re }
|
||||
|
||||
int getStart() { result = start }
|
||||
|
||||
int getEnd() { result = end }
|
||||
|
||||
override string toString() { result = re.getText().substring(start, end) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpTerm" }
|
||||
|
||||
Location getLocation() { result = re.getLocation() }
|
||||
|
||||
pragma[noinline]
|
||||
private predicate componentHasLocationInfo(
|
||||
int i, string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
re.getComponent(i)
|
||||
.getLocation()
|
||||
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
exists(int re_start, int re_end |
|
||||
this.componentHasLocationInfo(0, filepath, startline, re_start, _, _) and
|
||||
this.componentHasLocationInfo(re.getNumberOfComponents() - 1, filepath, _, _, endline, re_end) and
|
||||
startcolumn = re_start + start and
|
||||
endcolumn = re_start + end - 1
|
||||
)
|
||||
}
|
||||
|
||||
File getFile() { result = this.getLocation().getFile() }
|
||||
|
||||
string getRawValue() { result = this.toString() }
|
||||
|
||||
RegExpLiteral getLiteral() { result = TRegExpLiteral(re) }
|
||||
|
||||
/** Gets the regular expression term that is matched (textually) before this one, if any. */
|
||||
RegExpTerm getPredecessor() {
|
||||
exists(RegExpTerm parent | parent = this.getParent() |
|
||||
result = parent.(RegExpSequence).previousElement(this)
|
||||
or
|
||||
not exists(parent.(RegExpSequence).previousElement(this)) and
|
||||
not parent instanceof RegExpSubPattern and
|
||||
result = parent.getPredecessor()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the regular expression term that is matched (textually) after this one, if any. */
|
||||
RegExpTerm getSuccessor() {
|
||||
exists(RegExpTerm parent | parent = this.getParent() |
|
||||
result = parent.(RegExpSequence).nextElement(this)
|
||||
or
|
||||
not exists(parent.(RegExpSequence).nextElement(this)) and
|
||||
not parent instanceof RegExpSubPattern and
|
||||
result = parent.getSuccessor()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
newtype TRegExpParent =
|
||||
TRegExpLiteral(RegExp re) or
|
||||
TRegExpQuantifier(RegExp re, int start, int end) { re.qualifiedItem(start, end, _, _) } or
|
||||
TRegExpSequence(RegExp re, int start, int end) { re.sequence(start, end) } or
|
||||
TRegExpAlt(RegExp re, int start, int end) { re.alternation(start, end) } or
|
||||
TRegExpCharacterClass(RegExp re, int start, int end) { re.charSet(start, end) } or
|
||||
TRegExpCharacterRange(RegExp re, int start, int end) { re.charRange(_, start, _, _, end) } or
|
||||
TRegExpGroup(RegExp re, int start, int end) { re.group(start, end) } or
|
||||
TRegExpSpecialChar(RegExp re, int start, int end) { re.specialCharacter(start, end, _) } or
|
||||
TRegExpNormalChar(RegExp re, int start, int end) {
|
||||
re.normalCharacterSequence(start, end)
|
||||
or
|
||||
re.escapedCharacter(start, end) and
|
||||
not re.specialCharacter(start, end, _)
|
||||
} or
|
||||
TRegExpBackRef(RegExp re, int start, int end) { re.backreference(start, end) } or
|
||||
TRegExpNamedCharacterProperty(RegExp re, int start, int end) {
|
||||
re.namedCharacterProperty(start, end, _)
|
||||
}
|
||||
|
||||
class RegExpQuantifier extends RegExpTerm, TRegExpQuantifier {
|
||||
int part_end;
|
||||
boolean may_repeat_forever;
|
||||
|
||||
RegExpQuantifier() {
|
||||
this = TRegExpQuantifier(re, start, end) and
|
||||
re.qualifiedPart(start, part_end, end, _, may_repeat_forever)
|
||||
}
|
||||
|
||||
override RegExpTerm getChild(int i) {
|
||||
i = 0 and
|
||||
result.getRegExp() = re and
|
||||
result.getStart() = start and
|
||||
result.getEnd() = part_end
|
||||
}
|
||||
|
||||
predicate mayRepeatForever() { may_repeat_forever = true }
|
||||
|
||||
string getQualifier() { result = re.getText().substring(part_end, end) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpQuantifier" }
|
||||
}
|
||||
|
||||
class InfiniteRepetitionQuantifier extends RegExpQuantifier {
|
||||
InfiniteRepetitionQuantifier() { this.mayRepeatForever() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "InfiniteRepetitionQuantifier" }
|
||||
}
|
||||
|
||||
class RegExpStar extends InfiniteRepetitionQuantifier {
|
||||
RegExpStar() { this.getQualifier().charAt(0) = "*" }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpStar" }
|
||||
}
|
||||
|
||||
class RegExpPlus extends InfiniteRepetitionQuantifier {
|
||||
RegExpPlus() { this.getQualifier().charAt(0) = "+" }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpPlus" }
|
||||
}
|
||||
|
||||
class RegExpOpt extends RegExpQuantifier {
|
||||
RegExpOpt() { this.getQualifier().charAt(0) = "?" }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpOpt" }
|
||||
}
|
||||
|
||||
class RegExpRange extends RegExpQuantifier {
|
||||
string upper;
|
||||
string lower;
|
||||
|
||||
RegExpRange() { re.multiples(part_end, end, lower, upper) }
|
||||
|
||||
string getUpper() { result = upper }
|
||||
|
||||
string getLower() { result = lower }
|
||||
|
||||
/**
|
||||
* Gets the upper bound of the range, if any.
|
||||
*
|
||||
* If there is no upper bound, any number of repetitions is allowed.
|
||||
* For a term of the form `r{lo}`, both the lower and the upper bound
|
||||
* are `lo`.
|
||||
*/
|
||||
int getUpperBound() { result = this.getUpper().toInt() }
|
||||
|
||||
/** Gets the lower bound of the range. */
|
||||
int getLowerBound() { result = this.getLower().toInt() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpRange" }
|
||||
}
|
||||
|
||||
class RegExpSequence extends RegExpTerm, TRegExpSequence {
|
||||
RegExpSequence() {
|
||||
this = TRegExpSequence(re, start, end) and
|
||||
exists(seqChild(re, start, end, 1)) // if a sequence does not have more than one element, it should be treated as that element instead.
|
||||
}
|
||||
|
||||
override RegExpTerm getChild(int i) { result = seqChild(re, start, end, i) }
|
||||
|
||||
/** Gets the element preceding `element` in this sequence. */
|
||||
RegExpTerm previousElement(RegExpTerm element) { element = this.nextElement(result) }
|
||||
|
||||
/** Gets the element following `element` in this sequence. */
|
||||
RegExpTerm nextElement(RegExpTerm element) {
|
||||
exists(int i |
|
||||
element = this.getChild(i) and
|
||||
result = this.getChild(i + 1)
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpSequence" }
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private int seqChildEnd(RegExp re, int start, int end, int i) {
|
||||
result = seqChild(re, start, end, i).getEnd()
|
||||
}
|
||||
|
||||
// moved out so we can use it in the charpred
|
||||
private RegExpTerm seqChild(RegExp re, int start, int end, int i) {
|
||||
re.sequence(start, end) and
|
||||
(
|
||||
i = 0 and
|
||||
result.getRegExp() = re and
|
||||
result.getStart() = start and
|
||||
exists(int itemEnd |
|
||||
re.item(start, itemEnd) and
|
||||
result.getEnd() = itemEnd
|
||||
)
|
||||
or
|
||||
i > 0 and
|
||||
result.getRegExp() = re and
|
||||
exists(int itemStart | itemStart = seqChildEnd(re, start, end, i - 1) |
|
||||
result.getStart() = itemStart and
|
||||
re.item(itemStart, result.getEnd())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class RegExpAlt extends RegExpTerm, TRegExpAlt {
|
||||
RegExpAlt() { this = TRegExpAlt(re, start, end) }
|
||||
|
||||
override RegExpTerm getChild(int i) {
|
||||
i = 0 and
|
||||
result.getRegExp() = re and
|
||||
result.getStart() = start and
|
||||
exists(int part_end |
|
||||
re.alternationOption(start, end, start, part_end) and
|
||||
result.getEnd() = part_end
|
||||
)
|
||||
or
|
||||
i > 0 and
|
||||
result.getRegExp() = re and
|
||||
exists(int part_start |
|
||||
part_start = this.getChild(i - 1).getEnd() + 1 // allow for the |
|
||||
|
|
||||
result.getStart() = part_start and
|
||||
re.alternationOption(start, end, part_start, result.getEnd())
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpAlt" }
|
||||
}
|
||||
|
||||
class RegExpCharEscape = RegExpEscape;
|
||||
|
||||
class RegExpEscape extends RegExpNormalChar {
|
||||
RegExpEscape() { re.escapedCharacter(start, end) }
|
||||
|
||||
/**
|
||||
* Gets the name of the escaped; for example, `w` for `\w`.
|
||||
* TODO: Handle named escapes.
|
||||
*/
|
||||
override string getValue() {
|
||||
this.isIdentityEscape() and result = this.getUnescaped()
|
||||
or
|
||||
this.getUnescaped() = "n" and result = "\n"
|
||||
or
|
||||
this.getUnescaped() = "r" and result = "\r"
|
||||
or
|
||||
this.getUnescaped() = "t" and result = "\t"
|
||||
or
|
||||
this.isUnicode() and
|
||||
result = this.getUnicode()
|
||||
}
|
||||
|
||||
predicate isIdentityEscape() {
|
||||
not this.getUnescaped() in ["n", "r", "t"] and not this.isUnicode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text for this escape. That is e.g. "\w".
|
||||
*/
|
||||
private string getText() { result = re.getText().substring(start, end) }
|
||||
|
||||
/**
|
||||
* Holds if this is a unicode escape.
|
||||
*/
|
||||
private predicate isUnicode() { this.getText().prefix(2) = ["\\u", "\\U"] }
|
||||
|
||||
/**
|
||||
* Gets the unicode char for this escape.
|
||||
* E.g. for `\u0061` this returns "a".
|
||||
*/
|
||||
private string getUnicode() {
|
||||
this.isUnicode() and
|
||||
result = parseHexInt(this.getText().suffix(2)).toUnicode()
|
||||
}
|
||||
|
||||
string getUnescaped() { result = this.getText().suffix(1) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpEscape" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A word boundary, that is, a regular expression term of the form `\b`.
|
||||
*/
|
||||
class RegExpWordBoundary extends RegExpSpecialChar {
|
||||
RegExpWordBoundary() { this.getChar() = "\\b" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A character class escape in a regular expression.
|
||||
* That is, an escaped character that denotes multiple characters.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* \w
|
||||
* \S
|
||||
* ```
|
||||
*/
|
||||
class RegExpCharacterClassEscape extends RegExpEscape {
|
||||
RegExpCharacterClassEscape() { this.getValue() in ["d", "D", "s", "S", "w", "W", "h", "H"] }
|
||||
|
||||
/** Gets the name of the character class; for example, `w` for `\w`. */
|
||||
// override string getValue() { result = value }
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpCharacterClassEscape" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A character class.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```rb
|
||||
* /[a-fA-F0-9]/
|
||||
* /[^abc]/
|
||||
* ```
|
||||
*/
|
||||
class RegExpCharacterClass extends RegExpTerm, TRegExpCharacterClass {
|
||||
RegExpCharacterClass() { this = TRegExpCharacterClass(re, start, end) }
|
||||
|
||||
predicate isInverted() { re.getChar(start + 1) = "^" }
|
||||
|
||||
predicate isUniversalClass() {
|
||||
// [^]
|
||||
this.isInverted() and not exists(this.getAChild())
|
||||
or
|
||||
// [\w\W] and similar
|
||||
not this.isInverted() and
|
||||
exists(string cce1, string cce2 |
|
||||
cce1 = this.getAChild().(RegExpCharacterClassEscape).getValue() and
|
||||
cce2 = this.getAChild().(RegExpCharacterClassEscape).getValue()
|
||||
|
|
||||
cce1 != cce2 and cce1.toLowerCase() = cce2.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
override RegExpTerm getChild(int i) {
|
||||
i = 0 and
|
||||
result.getRegExp() = re and
|
||||
exists(int itemStart, int itemEnd |
|
||||
result.getStart() = itemStart and
|
||||
re.charSetStart(start, itemStart) and
|
||||
re.charSetChild(start, itemStart, itemEnd) and
|
||||
result.getEnd() = itemEnd
|
||||
)
|
||||
or
|
||||
i > 0 and
|
||||
result.getRegExp() = re and
|
||||
exists(int itemStart | itemStart = this.getChild(i - 1).getEnd() |
|
||||
result.getStart() = itemStart and
|
||||
re.charSetChild(start, itemStart, result.getEnd())
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpCharacterClass" }
|
||||
}
|
||||
|
||||
class RegExpCharacterRange extends RegExpTerm, TRegExpCharacterRange {
|
||||
int lower_end;
|
||||
int upper_start;
|
||||
|
||||
RegExpCharacterRange() {
|
||||
this = TRegExpCharacterRange(re, start, end) and
|
||||
re.charRange(_, start, lower_end, upper_start, end)
|
||||
}
|
||||
|
||||
predicate isRange(string lo, string hi) {
|
||||
lo = re.getText().substring(start, lower_end) and
|
||||
hi = re.getText().substring(upper_start, end)
|
||||
}
|
||||
|
||||
override RegExpTerm getChild(int i) {
|
||||
i = 0 and
|
||||
result.getRegExp() = re and
|
||||
result.getStart() = start and
|
||||
result.getEnd() = lower_end
|
||||
or
|
||||
i = 1 and
|
||||
result.getRegExp() = re and
|
||||
result.getStart() = upper_start and
|
||||
result.getEnd() = end
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpCharacterRange" }
|
||||
}
|
||||
|
||||
class RegExpNormalChar extends RegExpTerm, TRegExpNormalChar {
|
||||
RegExpNormalChar() { this = TRegExpNormalChar(re, start, end) }
|
||||
|
||||
predicate isCharacter() { any() }
|
||||
|
||||
string getValue() { result = re.getText().substring(start, end) }
|
||||
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpNormalChar" }
|
||||
}
|
||||
|
||||
class RegExpConstant extends RegExpTerm {
|
||||
string value;
|
||||
|
||||
RegExpConstant() {
|
||||
this = TRegExpNormalChar(re, start, end) and
|
||||
not this instanceof RegExpCharacterClassEscape and
|
||||
// exclude chars in qualifiers
|
||||
// TODO: push this into regex library
|
||||
not exists(int qstart, int qend | re.qualifiedPart(_, qstart, qend, _, _) |
|
||||
qstart <= start and end <= qend
|
||||
) and
|
||||
value = this.(RegExpNormalChar).getValue()
|
||||
or
|
||||
this = TRegExpSpecialChar(re, start, end) and
|
||||
re.inCharSet(start) and
|
||||
value = this.(RegExpSpecialChar).getChar()
|
||||
}
|
||||
|
||||
predicate isCharacter() { any() }
|
||||
|
||||
string getValue() { result = value }
|
||||
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpConstant" }
|
||||
}
|
||||
|
||||
class RegExpGroup extends RegExpTerm, TRegExpGroup {
|
||||
RegExpGroup() { this = TRegExpGroup(re, start, end) }
|
||||
|
||||
/**
|
||||
* Gets the index of this capture group within the enclosing regular
|
||||
* expression literal.
|
||||
*
|
||||
* For example, in the regular expression `/((a?).)(?:b)/`, the
|
||||
* group `((a?).)` has index 1, the group `(a?)` nested inside it
|
||||
* has index 2, and the group `(?:b)` has no index, since it is
|
||||
* not a capture group.
|
||||
*/
|
||||
int getNumber() { result = re.getGroupNumber(start, end) }
|
||||
|
||||
/** Holds if this is a capture group. */
|
||||
predicate isCapture() { exists(this.getNumber()) }
|
||||
|
||||
/** Holds if this is a named capture group. */
|
||||
predicate isNamed() { exists(this.getName()) }
|
||||
|
||||
/** Gets the name of this capture group, if any. */
|
||||
string getName() { result = re.getGroupName(start, end) }
|
||||
|
||||
predicate isCharacter() { any() }
|
||||
|
||||
string getValue() { result = re.getText().substring(start, end) }
|
||||
|
||||
override RegExpTerm getChild(int i) {
|
||||
result.getRegExp() = re and
|
||||
i = 0 and
|
||||
re.groupContents(start, end, result.getStart(), result.getEnd())
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpGroup" }
|
||||
}
|
||||
|
||||
class RegExpSpecialChar extends RegExpTerm, TRegExpSpecialChar {
|
||||
string char;
|
||||
|
||||
RegExpSpecialChar() {
|
||||
this = TRegExpSpecialChar(re, start, end) and
|
||||
re.specialCharacter(start, end, char)
|
||||
}
|
||||
|
||||
predicate isCharacter() { any() }
|
||||
|
||||
string getChar() { result = char }
|
||||
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpSpecialChar" }
|
||||
}
|
||||
|
||||
class RegExpDot extends RegExpSpecialChar {
|
||||
RegExpDot() { this.getChar() = "." }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpDot" }
|
||||
}
|
||||
|
||||
class RegExpDollar extends RegExpSpecialChar {
|
||||
RegExpDollar() { this.getChar() = ["$", "\\Z", "\\z"] }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpDollar" }
|
||||
}
|
||||
|
||||
class RegExpCaret extends RegExpSpecialChar {
|
||||
RegExpCaret() { this.getChar() = ["^", "\\A"] }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpCaret" }
|
||||
}
|
||||
|
||||
class RegExpZeroWidthMatch extends RegExpGroup {
|
||||
RegExpZeroWidthMatch() { re.zeroWidthMatch(start, end) }
|
||||
|
||||
override predicate isCharacter() { any() }
|
||||
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpZeroWidthMatch" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A zero-width lookahead or lookbehind assertion.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* (?=\w)
|
||||
* (?!\n)
|
||||
* (?<=\.)
|
||||
* (?<!\\)
|
||||
* ```
|
||||
*/
|
||||
class RegExpSubPattern extends RegExpZeroWidthMatch {
|
||||
RegExpSubPattern() { not re.emptyGroup(start, end) }
|
||||
|
||||
/** Gets the lookahead term. */
|
||||
RegExpTerm getOperand() {
|
||||
exists(int in_start, int in_end | re.groupContents(start, end, in_start, in_end) |
|
||||
result.getRegExp() = re and
|
||||
result.getStart() = in_start and
|
||||
result.getEnd() = in_end
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class RegExpLookahead extends RegExpSubPattern { }
|
||||
|
||||
class RegExpPositiveLookahead extends RegExpLookahead {
|
||||
RegExpPositiveLookahead() { re.positiveLookaheadAssertionGroup(start, end) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpPositiveLookahead" }
|
||||
}
|
||||
|
||||
class RegExpNegativeLookahead extends RegExpLookahead {
|
||||
RegExpNegativeLookahead() { re.negativeLookaheadAssertionGroup(start, end) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpNegativeLookahead" }
|
||||
}
|
||||
|
||||
abstract class RegExpLookbehind extends RegExpSubPattern { }
|
||||
|
||||
class RegExpPositiveLookbehind extends RegExpLookbehind {
|
||||
RegExpPositiveLookbehind() { re.positiveLookbehindAssertionGroup(start, end) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpPositiveLookbehind" }
|
||||
}
|
||||
|
||||
class RegExpNegativeLookbehind extends RegExpLookbehind {
|
||||
RegExpNegativeLookbehind() { re.negativeLookbehindAssertionGroup(start, end) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpNegativeLookbehind" }
|
||||
}
|
||||
|
||||
class RegExpBackRef extends RegExpTerm, TRegExpBackRef {
|
||||
RegExpBackRef() { this = TRegExpBackRef(re, start, end) }
|
||||
|
||||
/**
|
||||
* Gets the number of the capture group this back reference refers to, if any.
|
||||
*/
|
||||
int getNumber() { result = re.getBackRefNumber(start, end) }
|
||||
|
||||
/**
|
||||
* Gets the name of the capture group this back reference refers to, if any.
|
||||
*/
|
||||
string getName() { result = re.getBackRefName(start, end) }
|
||||
|
||||
/** Gets the capture group this back reference refers to. */
|
||||
RegExpGroup getGroup() {
|
||||
result.getLiteral() = this.getLiteral() and
|
||||
(
|
||||
result.getNumber() = this.getNumber() or
|
||||
result.getName() = this.getName()
|
||||
)
|
||||
}
|
||||
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpBackRef" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A named character property. For example, the POSIX bracket expression
|
||||
* `[[:digit:]]`.
|
||||
*/
|
||||
class RegExpNamedCharacterProperty extends RegExpTerm, TRegExpNamedCharacterProperty {
|
||||
RegExpNamedCharacterProperty() { this = TRegExpNamedCharacterProperty(re, start, end) }
|
||||
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpNamedCharacterProperty" }
|
||||
|
||||
/**
|
||||
* Gets the property name. For example, in `\p{Space}`, the result is
|
||||
* `"Space"`.
|
||||
*/
|
||||
string getName() { result = re.getCharacterPropertyName(start, end) }
|
||||
|
||||
/**
|
||||
* Holds if the property is inverted. For example, it holds for `\p{^Digit}`,
|
||||
* which matches non-digits.
|
||||
*/
|
||||
predicate isInverted() { re.namedCharacterPropertyIsInverted(start, end) }
|
||||
}
|
||||
|
||||
RegExpTerm getParsedRegExp(AST::RegExpLiteral re) {
|
||||
result.getRegExp() = re and result.isRootTerm()
|
||||
}
|
||||
|
||||
/**
|
||||
* A node whose value may flow to a position where it is interpreted
|
||||
* as a part of a regular expression.
|
||||
*/
|
||||
abstract class RegExpPatternSource extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a node where the pattern of this node is parsed as a part of
|
||||
* a regular expression.
|
||||
*/
|
||||
abstract DataFlow::Node getAParse();
|
||||
|
||||
/**
|
||||
* Gets the root term of the regular expression parsed from this pattern.
|
||||
*/
|
||||
abstract RegExpTerm getRegExpTerm();
|
||||
}
|
||||
|
||||
/**
|
||||
* A regular expression literal, viewed as the pattern source for itself.
|
||||
*/
|
||||
private class RegExpLiteralPatternSource extends RegExpPatternSource {
|
||||
private AST::RegExpLiteral astNode;
|
||||
|
||||
RegExpLiteralPatternSource() { astNode = this.asExpr().getExpr() }
|
||||
|
||||
override DataFlow::Node getAParse() { result = this }
|
||||
|
||||
override RegExpTerm getRegExpTerm() { result = astNode.getParsed() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node whose string value may flow to a position where it is interpreted
|
||||
* as a part of a regular expression.
|
||||
*/
|
||||
private class StringRegExpPatternSource extends RegExpPatternSource {
|
||||
private DataFlow::Node parse;
|
||||
|
||||
StringRegExpPatternSource() { this = regExpSource(parse) }
|
||||
|
||||
override DataFlow::Node getAParse() { result = parse }
|
||||
|
||||
override RegExpTerm getRegExpTerm() { result.getRegExp() = this.asExpr().getExpr() }
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
import codeql.ruby.security.performance.RegExpTreeView
|
||||
import codeql.ruby.Regexp
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import codeql.ruby.security.performance.ExponentialBackTracking
|
||||
import codeql.ruby.security.performance.ReDoSUtil
|
||||
import codeql.ruby.security.performance.RegExpTreeView
|
||||
import codeql.ruby.Regexp
|
||||
|
||||
from RegExpTerm t, string pump, State s, string prefixMsg
|
||||
where hasReDoSResult(t, pump, s, prefixMsg)
|
||||
|
||||
@@ -1,23 +1,42 @@
|
||||
failures
|
||||
edges
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:2:6:2:12 | tainted |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:4:24:4:30 | tainted : |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:16:36:16:42 | tainted : |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:20:25:20:31 | tainted : |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:26:31:26:37 | tainted : |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:30:24:30:30 | tainted : |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:31:27:31:33 | tainted : |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:34:16:34:22 | tainted |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:35:16:35:22 | tainted |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:36:21:36:27 | tainted |
|
||||
| summaries.rb:1:11:1:26 | call to identity : | summaries.rb:37:36:37:42 | tainted |
|
||||
| summaries.rb:1:20:1:26 | "taint" : | summaries.rb:1:11:1:26 | call to identity : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:2:6:2:12 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:2:6:2:12 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:4:24:4:30 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:4:24:4:30 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:16:36:16:42 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:16:36:16:42 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:20:25:20:31 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:26:31:26:37 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:30:24:30:30 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:31:27:31:33 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:34:16:34:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:34:16:34:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:35:16:35:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:35:16:35:22 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:36:21:36:27 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:36:21:36:27 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:37:36:37:42 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:37:36:37:42 | tainted |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:51:24:51:30 | tainted : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:54:23:54:29 | tainted : |
|
||||
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
|
||||
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
|
||||
| summaries.rb:4:12:7:3 | call to apply_block : | summaries.rb:9:6:9:13 | tainted2 |
|
||||
| summaries.rb:4:12:7:3 | call to apply_block : | summaries.rb:9:6:9:13 | tainted2 |
|
||||
| summaries.rb:4:24:4:30 | tainted : | summaries.rb:4:12:7:3 | call to apply_block : |
|
||||
| summaries.rb:4:24:4:30 | tainted : | summaries.rb:4:12:7:3 | call to apply_block : |
|
||||
| summaries.rb:4:24:4:30 | tainted : | summaries.rb:4:36:4:36 | x : |
|
||||
| summaries.rb:4:24:4:30 | tainted : | summaries.rb:4:36:4:36 | x : |
|
||||
| summaries.rb:4:36:4:36 | x : | summaries.rb:5:8:5:8 | x |
|
||||
| summaries.rb:4:36:4:36 | x : | summaries.rb:5:8:5:8 | x |
|
||||
| summaries.rb:11:17:11:17 | x : | summaries.rb:12:8:12:8 | x |
|
||||
| summaries.rb:11:17:11:17 | x : | summaries.rb:12:8:12:8 | x |
|
||||
| summaries.rb:16:12:16:43 | call to apply_lambda : | summaries.rb:18:6:18:13 | tainted3 |
|
||||
| summaries.rb:16:12:16:43 | call to apply_lambda : | summaries.rb:18:6:18:13 | tainted3 |
|
||||
| summaries.rb:16:36:16:42 | tainted : | summaries.rb:11:17:11:17 | x : |
|
||||
| summaries.rb:16:36:16:42 | tainted : | summaries.rb:11:17:11:17 | x : |
|
||||
| summaries.rb:16:36:16:42 | tainted : | summaries.rb:16:12:16:43 | call to apply_lambda : |
|
||||
| summaries.rb:16:36:16:42 | tainted : | summaries.rb:16:12:16:43 | call to apply_lambda : |
|
||||
| summaries.rb:20:12:20:32 | call to firstArg : | summaries.rb:21:6:21:13 | tainted4 |
|
||||
| summaries.rb:20:25:20:31 | tainted : | summaries.rb:20:12:20:32 | call to firstArg : |
|
||||
@@ -25,26 +44,44 @@ edges
|
||||
| summaries.rb:26:31:26:37 | tainted : | summaries.rb:26:12:26:38 | call to secondArg : |
|
||||
| summaries.rb:30:24:30:30 | tainted : | summaries.rb:30:6:30:42 | call to onlyWithBlock |
|
||||
| summaries.rb:31:27:31:33 | tainted : | summaries.rb:31:6:31:34 | call to onlyWithoutBlock |
|
||||
| summaries.rb:40:7:40:13 | "taint" : | summaries.rb:41:24:41:24 | t : |
|
||||
| summaries.rb:40:7:40:13 | "taint" : | summaries.rb:42:24:42:24 | t : |
|
||||
| summaries.rb:40:7:40:13 | "taint" : | summaries.rb:44:8:44:8 | t : |
|
||||
| summaries.rb:40:7:40:17 | call to source : | summaries.rb:41:24:41:24 | t : |
|
||||
| summaries.rb:40:7:40:17 | call to source : | summaries.rb:42:24:42:24 | t : |
|
||||
| summaries.rb:40:7:40:17 | call to source : | summaries.rb:44:8:44:8 | t : |
|
||||
| summaries.rb:41:24:41:24 | t : | summaries.rb:41:8:41:25 | call to matchedByName |
|
||||
| summaries.rb:42:24:42:24 | t : | summaries.rb:42:8:42:25 | call to matchedByName |
|
||||
| summaries.rb:44:8:44:8 | t : | summaries.rb:44:8:44:27 | call to matchedByNameRcv |
|
||||
| summaries.rb:48:24:48:30 | "taint" : | summaries.rb:48:8:48:31 | call to preserveTaint |
|
||||
| summaries.rb:48:24:48:41 | call to source : | summaries.rb:48:8:48:42 | call to preserveTaint |
|
||||
| summaries.rb:51:24:51:30 | tainted : | summaries.rb:51:6:51:31 | call to namedArg |
|
||||
| summaries.rb:54:23:54:29 | tainted : | summaries.rb:54:40:54:40 | x : |
|
||||
| summaries.rb:54:40:54:40 | x : | summaries.rb:55:8:55:8 | x |
|
||||
| summaries.rb:62:24:62:53 | call to source : | summaries.rb:62:8:62:54 | call to preserveTaint |
|
||||
| summaries.rb:65:26:65:56 | call to source : | summaries.rb:65:8:65:57 | call to preserveTaint |
|
||||
nodes
|
||||
| summaries.rb:1:11:1:26 | call to identity : | semmle.label | call to identity : |
|
||||
| summaries.rb:1:20:1:26 | "taint" : | semmle.label | "taint" : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
|
||||
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
|
||||
| summaries.rb:1:20:1:36 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:1:20:1:36 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:2:6:2:12 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:2:6:2:12 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:4:12:7:3 | call to apply_block : | semmle.label | call to apply_block : |
|
||||
| summaries.rb:4:12:7:3 | call to apply_block : | semmle.label | call to apply_block : |
|
||||
| summaries.rb:4:24:4:30 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:4:24:4:30 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:4:36:4:36 | x : | semmle.label | x : |
|
||||
| summaries.rb:4:36:4:36 | x : | semmle.label | x : |
|
||||
| summaries.rb:5:8:5:8 | x | semmle.label | x |
|
||||
| summaries.rb:5:8:5:8 | x | semmle.label | x |
|
||||
| summaries.rb:9:6:9:13 | tainted2 | semmle.label | tainted2 |
|
||||
| summaries.rb:9:6:9:13 | tainted2 | semmle.label | tainted2 |
|
||||
| summaries.rb:11:17:11:17 | x : | semmle.label | x : |
|
||||
| summaries.rb:11:17:11:17 | x : | semmle.label | x : |
|
||||
| summaries.rb:12:8:12:8 | x | semmle.label | x |
|
||||
| summaries.rb:12:8:12:8 | x | semmle.label | x |
|
||||
| summaries.rb:16:12:16:43 | call to apply_lambda : | semmle.label | call to apply_lambda : |
|
||||
| summaries.rb:16:12:16:43 | call to apply_lambda : | semmle.label | call to apply_lambda : |
|
||||
| summaries.rb:16:36:16:42 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:16:36:16:42 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:18:6:18:13 | tainted3 | semmle.label | tainted3 |
|
||||
| summaries.rb:18:6:18:13 | tainted3 | semmle.label | tainted3 |
|
||||
| summaries.rb:20:12:20:32 | call to firstArg : | semmle.label | call to firstArg : |
|
||||
| summaries.rb:20:25:20:31 | tainted : | semmle.label | tainted : |
|
||||
@@ -57,39 +94,65 @@ nodes
|
||||
| summaries.rb:31:6:31:34 | call to onlyWithoutBlock | semmle.label | call to onlyWithoutBlock |
|
||||
| summaries.rb:31:27:31:33 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:34:16:34:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:34:16:34:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:35:16:35:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:35:16:35:22 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:36:21:36:27 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:36:21:36:27 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:37:36:37:42 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:40:7:40:13 | "taint" : | semmle.label | "taint" : |
|
||||
| summaries.rb:37:36:37:42 | tainted | semmle.label | tainted |
|
||||
| summaries.rb:40:7:40:17 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:41:8:41:25 | call to matchedByName | semmle.label | call to matchedByName |
|
||||
| summaries.rb:41:24:41:24 | t : | semmle.label | t : |
|
||||
| summaries.rb:42:8:42:25 | call to matchedByName | semmle.label | call to matchedByName |
|
||||
| summaries.rb:42:24:42:24 | t : | semmle.label | t : |
|
||||
| summaries.rb:44:8:44:8 | t : | semmle.label | t : |
|
||||
| summaries.rb:44:8:44:27 | call to matchedByNameRcv | semmle.label | call to matchedByNameRcv |
|
||||
| summaries.rb:48:8:48:31 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:48:24:48:30 | "taint" : | semmle.label | "taint" : |
|
||||
| summaries.rb:48:8:48:42 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:48:24:48:41 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:51:6:51:31 | call to namedArg | semmle.label | call to namedArg |
|
||||
| summaries.rb:51:24:51:30 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:54:23:54:29 | tainted : | semmle.label | tainted : |
|
||||
| summaries.rb:54:40:54:40 | x : | semmle.label | x : |
|
||||
| summaries.rb:55:8:55:8 | x | semmle.label | x |
|
||||
| summaries.rb:62:8:62:54 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:62:24:62:53 | call to source : | semmle.label | call to source : |
|
||||
| summaries.rb:65:8:65:57 | call to preserveTaint | semmle.label | call to preserveTaint |
|
||||
| summaries.rb:65:26:65:56 | call to source : | semmle.label | call to source : |
|
||||
subpaths
|
||||
invalidSpecComponent
|
||||
invalidOutputSpecComponent
|
||||
#select
|
||||
| summaries.rb:2:6:2:12 | tainted | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:2:6:2:12 | tainted | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:5:8:5:8 | x | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:5:8:5:8 | x | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:9:6:9:13 | tainted2 | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:9:6:9:13 | tainted2 | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:12:8:12:8 | x | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:12:8:12:8 | x | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:18:6:18:13 | tainted3 | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:18:6:18:13 | tainted3 | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:21:6:21:13 | tainted4 | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:21:6:21:13 | tainted4 | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:27:6:27:13 | tainted5 | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:27:6:27:13 | tainted5 | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:30:6:30:42 | call to onlyWithBlock | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:30:6:30:42 | call to onlyWithBlock | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:31:6:31:34 | call to onlyWithoutBlock | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:31:6:31:34 | call to onlyWithoutBlock | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:34:16:34:22 | tainted | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:34:16:34:22 | tainted | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:35:16:35:22 | tainted | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:35:16:35:22 | tainted | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:36:21:36:27 | tainted | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:36:21:36:27 | tainted | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:37:36:37:42 | tainted | summaries.rb:1:20:1:26 | "taint" : | summaries.rb:37:36:37:42 | tainted | $@ | summaries.rb:1:20:1:26 | "taint" : | "taint" : |
|
||||
| summaries.rb:41:8:41:25 | call to matchedByName | summaries.rb:40:7:40:13 | "taint" : | summaries.rb:41:8:41:25 | call to matchedByName | $@ | summaries.rb:40:7:40:13 | "taint" : | "taint" : |
|
||||
| summaries.rb:42:8:42:25 | call to matchedByName | summaries.rb:40:7:40:13 | "taint" : | summaries.rb:42:8:42:25 | call to matchedByName | $@ | summaries.rb:40:7:40:13 | "taint" : | "taint" : |
|
||||
| summaries.rb:44:8:44:27 | call to matchedByNameRcv | summaries.rb:40:7:40:13 | "taint" : | summaries.rb:44:8:44:27 | call to matchedByNameRcv | $@ | summaries.rb:40:7:40:13 | "taint" : | "taint" : |
|
||||
| summaries.rb:48:8:48:31 | call to preserveTaint | summaries.rb:48:24:48:30 | "taint" : | summaries.rb:48:8:48:31 | call to preserveTaint | $@ | summaries.rb:48:24:48:30 | "taint" : | "taint" : |
|
||||
| summaries.rb:2:6:2:12 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:2:6:2:12 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:2:6:2:12 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:2:6:2:12 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:5:8:5:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:5:8:5:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:5:8:5:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:5:8:5:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:9:6:9:13 | tainted2 | summaries.rb:1:20:1:36 | call to source : | summaries.rb:9:6:9:13 | tainted2 | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:9:6:9:13 | tainted2 | summaries.rb:1:20:1:36 | call to source : | summaries.rb:9:6:9:13 | tainted2 | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:12:8:12:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:12:8:12:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:12:8:12:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:12:8:12:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:18:6:18:13 | tainted3 | summaries.rb:1:20:1:36 | call to source : | summaries.rb:18:6:18:13 | tainted3 | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:18:6:18:13 | tainted3 | summaries.rb:1:20:1:36 | call to source : | summaries.rb:18:6:18:13 | tainted3 | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:21:6:21:13 | tainted4 | summaries.rb:1:20:1:36 | call to source : | summaries.rb:21:6:21:13 | tainted4 | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:27:6:27:13 | tainted5 | summaries.rb:1:20:1:36 | call to source : | summaries.rb:27:6:27:13 | tainted5 | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:30:6:30:42 | call to onlyWithBlock | summaries.rb:1:20:1:36 | call to source : | summaries.rb:30:6:30:42 | call to onlyWithBlock | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:31:6:31:34 | call to onlyWithoutBlock | summaries.rb:1:20:1:36 | call to source : | summaries.rb:31:6:31:34 | call to onlyWithoutBlock | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:34:16:34:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:34:16:34:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:34:16:34:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:34:16:34:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:35:16:35:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:35:16:35:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:35:16:35:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:35:16:35:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:36:21:36:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:36:21:36:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:36:21:36:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:36:21:36:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:37:36:37:42 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:37:36:37:42 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:37:36:37:42 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:37:36:37:42 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:41:8:41:25 | call to matchedByName | summaries.rb:40:7:40:17 | call to source : | summaries.rb:41:8:41:25 | call to matchedByName | $@ | summaries.rb:40:7:40:17 | call to source : | call to source : |
|
||||
| summaries.rb:42:8:42:25 | call to matchedByName | summaries.rb:40:7:40:17 | call to source : | summaries.rb:42:8:42:25 | call to matchedByName | $@ | summaries.rb:40:7:40:17 | call to source : | call to source : |
|
||||
| summaries.rb:44:8:44:27 | call to matchedByNameRcv | summaries.rb:40:7:40:17 | call to source : | summaries.rb:44:8:44:27 | call to matchedByNameRcv | $@ | summaries.rb:40:7:40:17 | call to source : | call to source : |
|
||||
| summaries.rb:48:8:48:42 | call to preserveTaint | summaries.rb:48:24:48:41 | call to source : | summaries.rb:48:8:48:42 | call to preserveTaint | $@ | summaries.rb:48:24:48:41 | call to source : | call to source : |
|
||||
| summaries.rb:51:6:51:31 | call to namedArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:51:6:51:31 | call to namedArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:55:8:55:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:55:8:55:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:62:8:62:54 | call to preserveTaint | summaries.rb:62:24:62:53 | call to source : | summaries.rb:62:8:62:54 | call to preserveTaint | $@ | summaries.rb:62:24:62:53 | call to source : | call to source : |
|
||||
| summaries.rb:65:8:65:57 | call to preserveTaint | summaries.rb:65:26:65:56 | call to source : | summaries.rb:65:8:65:57 | call to preserveTaint | $@ | summaries.rb:65:26:65:56 | call to source : | call to source : |
|
||||
warning
|
||||
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
|
||||
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.dataflow.FlowSummary
|
||||
import DataFlow::PathGraph
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.dataflow.internal.FlowSummaryImpl
|
||||
import codeql.ruby.dataflow.internal.AccessPathSyntax
|
||||
import codeql.ruby.frameworks.data.ModelsAsData
|
||||
import TestUtilities.InlineFlowTest
|
||||
import DataFlow::PathGraph
|
||||
|
||||
query predicate invalidSpecComponent(SummarizedCallable sc, string s, string c) {
|
||||
(sc.propagatesFlowExt(s, _, _) or sc.propagatesFlowExt(_, s, _)) and
|
||||
@@ -42,10 +43,10 @@ private class SummarizedCallableApplyBlock extends SummarizedCallable {
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "BlockArgument.Parameter[0]" and
|
||||
output = "Argument[block].Parameter[0]" and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "BlockArgument.ReturnValue" and
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
@@ -75,9 +76,14 @@ private class StepsFromModel extends ModelInput::SummaryModelCsv {
|
||||
";;Member[Foo].Method[secondArg];Argument[1];ReturnValue;taint",
|
||||
";;Member[Foo].Method[onlyWithoutBlock].WithoutBlock;Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[onlyWithBlock].WithBlock;Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[blockArg].BlockArgument.Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[blockArg].Argument[block].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[namedArg];Argument[foo:];ReturnValue;taint",
|
||||
";;Member[Foo].Method[intoNamedCallback];Argument[0];Argument[foo:].Parameter[0];taint",
|
||||
";;Member[Foo].Method[intoNamedParameter];Argument[0];Argument[0].Parameter[foo:];taint",
|
||||
";;Member[Foo].Method[startInNamedCallback].Argument[foo:].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[startInNamedParameter].Argument[0].Parameter[foo:].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
";any;Method[matchedByName];Argument[0];ReturnValue;taint",
|
||||
";any;Method[matchedByNameRcv];Receiver;ReturnValue;taint",
|
||||
";any;Method[matchedByNameRcv];Argument[self];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -112,23 +118,22 @@ private class SinkFromModel extends ModelInput::SinkModelCsv {
|
||||
override predicate row(string row) { row = "test;FooOrBar;Method[method].Argument[0];test-sink" }
|
||||
}
|
||||
|
||||
class Conf extends TaintTracking::Configuration {
|
||||
Conf() { this = "FlowSummaries" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src) {
|
||||
src.asExpr().getExpr().(StringLiteral).getConstantValue().isString("taint")
|
||||
}
|
||||
|
||||
class CustomValueSink extends DefaultValueFlowConf {
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodCall mc |
|
||||
mc.getMethodName() = "sink" and
|
||||
mc.getAnArgument() = sink.asExpr().getExpr()
|
||||
)
|
||||
super.isSink(sink)
|
||||
or
|
||||
sink = ModelOutput::getASinkNode("test-sink").getARhs()
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, Conf conf
|
||||
class CustomTaintSink extends DefaultTaintFlowConf {
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
super.isSink(sink)
|
||||
or
|
||||
sink = ModelOutput::getASinkNode("test-sink").getARhs()
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Configuration conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "$@", source, source.toString()
|
||||
|
||||
@@ -1,49 +1,66 @@
|
||||
tainted = identity "taint"
|
||||
sink tainted
|
||||
tainted = identity source("tainted")
|
||||
sink tainted # $ hasValueFlow=tainted
|
||||
|
||||
tainted2 = apply_block tainted do |x|
|
||||
sink x
|
||||
sink x # $ hasValueFlow=tainted
|
||||
x
|
||||
end
|
||||
|
||||
sink tainted2
|
||||
sink tainted2 # $ hasValueFlow=tainted
|
||||
|
||||
my_lambda = -> (x) {
|
||||
sink x
|
||||
sink x # $ hasValueFlow=tainted
|
||||
x
|
||||
}
|
||||
|
||||
tainted3 = apply_lambda(my_lambda, tainted)
|
||||
|
||||
sink(tainted3)
|
||||
sink(tainted3) # $ hasValueFlow=tainted
|
||||
|
||||
tainted4 = Foo.firstArg(tainted)
|
||||
sink(tainted4)
|
||||
sink(tainted4) # $ hasTaintFlow=tainted
|
||||
|
||||
notTainted = Foo.firstArg(nil, tainted))
|
||||
sink(notTainted)
|
||||
|
||||
tainted5 = Foo.secondArg(nil, tainted)
|
||||
sink(tainted5)
|
||||
sink(tainted5) # $ hasTaintFlow=tainted
|
||||
|
||||
sink(Foo.onlyWithBlock(tainted))
|
||||
sink(Foo.onlyWithBlock(tainted) do |x| end)
|
||||
sink(Foo.onlyWithoutBlock(tainted))
|
||||
sink(Foo.onlyWithBlock(tainted) do |x| end) # $ hasTaintFlow=tainted
|
||||
sink(Foo.onlyWithoutBlock(tainted)) # $ hasTaintFlow=tainted
|
||||
sink(Foo.onlyWithoutBlock(tainted) do |x| end)
|
||||
|
||||
Foo.new.method(tainted)
|
||||
Bar.new.method(tainted)
|
||||
Bar.new.next.method(tainted)
|
||||
Bar.new.next.next.next.next.method(tainted)
|
||||
Foo.new.method(tainted) # $ hasValueFlow=tainted
|
||||
Bar.new.method(tainted) # $ hasValueFlow=tainted
|
||||
Bar.new.next.method(tainted) # $ hasValueFlow=tainted
|
||||
Bar.new.next.next.next.next.method(tainted) # $ hasValueFlow=tainted
|
||||
|
||||
def userDefinedFunction(x, y)
|
||||
t = "taint"
|
||||
sink(x.matchedByName(t))
|
||||
sink(y.matchedByName(t))
|
||||
t = source("t")
|
||||
sink(x.matchedByName(t)) # $ hasTaintFlow=t
|
||||
sink(y.matchedByName(t)) # $ hasTaintFlow=t
|
||||
sink(x.unmatchedName(t))
|
||||
sink(t.matchedByNameRcv())
|
||||
sink(t.matchedByNameRcv()) # $ hasTaintFlow=t
|
||||
end
|
||||
|
||||
Foo.blockArg do |x|
|
||||
sink(x.preserveTaint("taint"))
|
||||
sink(x.preserveTaint(source("blockArg"))) # $ hasTaintFlow=blockArg
|
||||
end
|
||||
|
||||
sink(Foo.namedArg(foo: tainted)) # $ hasTaintFlow=tainted
|
||||
sink(Foo.namedArg(tainted))
|
||||
|
||||
Foo.intoNamedCallback(tainted, foo: ->(x) {
|
||||
sink(x) # $ hasTaintFlow=tainted
|
||||
})
|
||||
Foo.intoNamedParameter(tainted, ->(foo:) {
|
||||
sink(foo) # $ MISSING: hasTaintFlow=tainted
|
||||
})
|
||||
|
||||
Foo.startInNamedCallback(foo: ->(x) {
|
||||
sink(x.preserveTaint(source("startInNamedCallback"))) # $ hasTaintFlow=startInNamedCallback
|
||||
})
|
||||
Foo.startInNamedParameter(->(foo:) {
|
||||
sink(foo.preserveTaint(source("startInNamedParameter"))) # $ hasTaintFlow=startInNamedParameter
|
||||
})
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
*/
|
||||
|
||||
import codeql.Locations
|
||||
import codeql.ruby.security.performance.RegExpTreeView as RETV
|
||||
import codeql.ruby.Regexp as RE
|
||||
|
||||
query predicate nodes(RETV::RegExpTerm n, string attr, string val) {
|
||||
query predicate nodes(RE::RegExpTerm n, string attr, string val) {
|
||||
attr = "semmle.label" and
|
||||
val = "[" + concat(n.getAPrimaryQlClass(), ", ") + "] " + n.toString()
|
||||
or
|
||||
@@ -13,7 +13,7 @@ query predicate nodes(RETV::RegExpTerm n, string attr, string val) {
|
||||
val =
|
||||
any(int i |
|
||||
n =
|
||||
rank[i](RETV::RegExpTerm t, string fp, int sl, int sc, int el, int ec |
|
||||
rank[i](RE::RegExpTerm t, string fp, int sl, int sc, int el, int ec |
|
||||
t.hasLocationInfo(fp, sl, sc, el, ec)
|
||||
|
|
||||
t order by fp, sl, sc, el, ec, t.toString()
|
||||
@@ -21,7 +21,7 @@ query predicate nodes(RETV::RegExpTerm n, string attr, string val) {
|
||||
).toString()
|
||||
}
|
||||
|
||||
query predicate edges(RETV::RegExpTerm pred, RETV::RegExpTerm succ, string attr, string val) {
|
||||
query predicate edges(RE::RegExpTerm pred, RE::RegExpTerm succ, string attr, string val) {
|
||||
attr in ["semmle.label", "semmle.order"] and
|
||||
val = any(int i | succ = pred.getChild(i)).toString()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import codeql.ruby.security.performance.RegExpTreeView
|
||||
import codeql.ruby.Regexp
|
||||
|
||||
query predicate groupName(RegExpGroup g, string name) { name = g.getName() }
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
| tst.rb:137:11:137:17 | (\\w\|G)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'G'. |
|
||||
| tst.rb:143:11:143:18 | (\\d\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. |
|
||||
| tst.rb:146:11:146:17 | (\\d\|5)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '5'. |
|
||||
| tst.rb:155:11:155:20 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'f'. |
|
||||
| tst.rb:149:11:149:20 | (\\s\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. |
|
||||
| tst.rb:152:11:152:24 | (\\s\|[\\v]\|\\\\v)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000b'. |
|
||||
| tst.rb:155:11:155:20 | (\\f\|[\\f])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\u000c'. |
|
||||
| tst.rb:158:11:158:18 | (\\W\|\\D)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ' '. |
|
||||
| tst.rb:161:11:161:18 | (\\S\|\\w)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. |
|
||||
| tst.rb:164:11:164:20 | (\\S\|[\\w])* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. |
|
||||
|
||||
Reference in New Issue
Block a user