Skip to content

Commit 7c006d4

Browse files
author
Esben Sparre Andreasen
authored
Merge pull request #222 from xiemaisi/js/identity-replacement
JavaScript: Add new query flagging identity replacements.
2 parents 9c219b9 + 0e63ea1 commit 7c006d4

File tree

11 files changed

+139
-0
lines changed

11 files changed

+139
-0
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
|-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1515
| Enabling Node.js integration for Electron web content renderers (`js/enabling-electron-renderer-node-integration`) | security, frameworks/electron, external/cwe/cwe-094 | Highlights Electron web content renderer preferences with Node.js integration enabled, indicating a violation of [CWE-94](https://cwe.mitre.org/data/definitions/94.html). Results are not shown on LGTM by default. |
1616
| Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on LGTM by default. |
17+
| Replacement of a substring with itself (`js/identity-replacement`) | correctness, security, external/cwe/cwe-116 | Highlights string replacements that replace a string with itself, which usually indicates a mistake. Results shown on LGTM by default. |
1718

1819
## Changes to existing queries
1920

javascript/config/suites/javascript/correctness-core

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
+ semmlecode-javascript-queries/RegExp/BackrefIntoNegativeLookahead.ql: /Correctness/Regular Expressions
3333
+ semmlecode-javascript-queries/RegExp/DuplicateCharacterInCharacterClass.ql: /Correctness/Regular Expressions
3434
+ semmlecode-javascript-queries/RegExp/EmptyCharacterClass.ql: /Correctness/Regular Expressions
35+
+ semmlecode-javascript-queries/RegExp/IdentityReplacement.ql: /Correctness/Regular Expressions
3536
+ semmlecode-javascript-queries/RegExp/UnboundBackref.ql: /Correctness/Regular Expressions
3637
+ semmlecode-javascript-queries/RegExp/UnmatchableCaret.ql: /Correctness/Regular Expressions
3738
+ semmlecode-javascript-queries/RegExp/UnmatchableDollar.ql: /Correctness/Regular Expressions
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Replacing a substring with itself has no effect and usually indicates a mistake, such as
9+
misspelling a backslash escape.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
Examine the string replacement to find and correct any typos.
16+
</p>
17+
</recommendation>
18+
19+
<example>
20+
<p>
21+
The following code snippet attempts to backslash-escape all double quotes in <code>raw</code>
22+
by replacing all instances of <code>"</code> with <code>\"</code>:
23+
</p>
24+
<sample src="examples/IdentityReplacement.js" />
25+
<p>
26+
However, the replacement string <code>'\"'</code> is actually the same as <code>'"'</code>,
27+
with <code>\"</code> interpreted as an identity escape, so the replacement does nothing.
28+
Instead, the replacement string should be <code>'\\"'</code>:
29+
</p>
30+
<sample src="examples/IdentityReplacementGood.js" />
31+
</example>
32+
33+
<references>
34+
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Escape_notation">String escape notation</a>.</li>
35+
</references>
36+
</qhelp>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @name Replacement of a substring with itself
3+
* @description Replacing a substring with itself has no effect and may indicate a mistake.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id js/identity-replacement
7+
* @precision very-high
8+
* @tags correctness
9+
* security
10+
* external/cwe/cwe-116
11+
*/
12+
13+
import javascript
14+
15+
/**
16+
* Holds if `e`, when used as the first argument of `String.prototype.replace`, matches
17+
* `s` and nothing else.
18+
*/
19+
predicate matchesString(Expr e, string s) {
20+
exists (RegExpLiteral rl |
21+
rl = e and
22+
not rl.isIgnoreCase() and
23+
regExpMatchesString(rl.getRoot(), s)
24+
)
25+
or
26+
s = e.getStringValue()
27+
}
28+
29+
/**
30+
* Holds if `t` matches `s` and nothing else.
31+
*/
32+
language[monotonicAggregates]
33+
predicate regExpMatchesString(RegExpTerm t, string s) {
34+
// constants match themselves
35+
s = t.(RegExpConstant).getValue()
36+
or
37+
// assertions match the empty string
38+
(t instanceof RegExpCaret or
39+
t instanceof RegExpDollar or
40+
t instanceof RegExpWordBoundary or
41+
t instanceof RegExpNonWordBoundary or
42+
t instanceof RegExpLookahead or
43+
t instanceof RegExpLookbehind) and
44+
s = ""
45+
or
46+
// groups match their content
47+
regExpMatchesString(t.(RegExpGroup).getAChild(), s)
48+
or
49+
// single-character classes match that character
50+
exists (RegExpCharacterClass recc | recc = t and not recc.isInverted() |
51+
recc.getNumChild() = 1 and
52+
regExpMatchesString(recc.getChild(0), s)
53+
)
54+
or
55+
// sequences match the concatenation of their elements
56+
exists (RegExpSequence seq | seq = t |
57+
s = concat(int i, RegExpTerm child | child = seq.getChild(i) |
58+
any(string subs | regExpMatchesString(child, subs)) order by i
59+
)
60+
)
61+
}
62+
63+
from MethodCallExpr repl, string s, string friendly
64+
where repl.getMethodName() = "replace" and
65+
matchesString(repl.getArgument(0), s) and
66+
repl.getArgument(1).getStringValue() = s and
67+
(if s = "" then friendly = "the empty string" else friendly = "'" + s + "'")
68+
select repl.getArgument(0), "This replaces " + friendly + " with itself."
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var escaped = raw.replace(/"/g, '\"');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var escaped = raw.replace(/"/g, '\\"');
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
| IdentityReplacement.js:1:27:1:30 | /"/g | This replaces '"' with itself. |
2+
| tst.js:1:13:1:16 | "\\\\" | This replaces '\\' with itself. |
3+
| tst.js:2:13:2:18 | /(\\\\)/ | This replaces '\\' with itself. |
4+
| tst.js:3:13:3:17 | /["]/ | This replaces '"' with itself. |
5+
| tst.js:6:13:6:18 | /foo/g | This replaces 'foo' with itself. |
6+
| tst.js:9:13:9:17 | /^\\\\/ | This replaces '\\' with itself. |
7+
| tst.js:10:13:10:17 | /\\\\$/ | This replaces '\\' with itself. |
8+
| tst.js:11:13:11:18 | /\\b\\\\/ | This replaces '\\' with itself. |
9+
| tst.js:12:13:12:18 | /\\B\\\\/ | This replaces '\\' with itself. |
10+
| tst.js:13:13:13:22 | /\\\\(?!\\\\)/ | This replaces '\\' with itself. |
11+
| tst.js:14:13:14:23 | /(?<!\\\\)\\\\/ | This replaces '\\' with itself. |
12+
| tst.js:16:13:16:15 | /^/ | This replaces the empty string with itself. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var escaped = raw.replace(/"/g, '\"');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RegExp/IdentityReplacement.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
var escaped = raw.replace(/"/g, '\\"');

0 commit comments

Comments
 (0)