diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-09-12 09:29:13 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-09-12 09:29:13 +0100 |
| commit | dc49225425c2c07de0a6dc3529f2386c82a032b4 (patch) | |
| tree | 916a12e9c4fbfe7a40fd79ec6ef516b2f3e16d95 | |
| parent | 870603494b464c737b0a2200742370332142d516 (diff) | |
Avoid excessive backtracking when parsing unterminated strings
| -rw-r--r-- | NEWS | 9 | ||||
| -rw-r--r-- | lib/styles/parser/tokeniser.js | 2 | ||||
| -rw-r--r-- | test/styles/parser/tokeniser.tests.js | 20 |
3 files changed, 30 insertions, 1 deletions
@@ -1,3 +1,12 @@ +# 1.12.3 + +* Avoid excessive backtracking when parsing an unterminated string with many + escape sequences. The previous behaviour would allow maliciously crafted + documents to cause a denial of service. + + Note that it is still strongly recommended to process untrusted documents in + a separate thread with a timeout to avoid potential similar issues. + # 1.12.2 * Avoid prototype pollution when reading the styles defined in a document. This diff --git a/lib/styles/parser/tokeniser.js b/lib/styles/parser/tokeniser.js index fa45ff9..ef2a2c3 100644 --- a/lib/styles/parser/tokeniser.js +++ b/lib/styles/parser/tokeniser.js @@ -3,7 +3,7 @@ var RegexTokeniser = lop.RegexTokeniser; exports.tokenise = tokenise; -var stringPrefix = "'((?:\\\\.|[^'])*)"; +var stringPrefix = "'((?:\\\\(?:.|$)|[^'\\\\])*)"; function tokenise(string) { var identifierCharacter = "(?:[a-zA-Z\\-_]|\\\\.)"; diff --git a/test/styles/parser/tokeniser.tests.js b/test/styles/parser/tokeniser.tests.js index 16f3847..04c25e0 100644 --- a/test/styles/parser/tokeniser.tests.js +++ b/test/styles/parser/tokeniser.tests.js @@ -31,10 +31,30 @@ test("strings are tokenised", function() { assertTokens("'Tristan'", [isToken("string", "Tristan")]); }); +test("escaped string terminators in strings are tokenised", function() { + assertTokens("'Tristan\\''", [isToken("string", "Tristan\\'")]); +}); + +test("escape sequences in strings are tokenised", function() { + assertTokens("'Tristan\\\\'", [isToken("string", "Tristan\\\\")]); +}); + test("unterminated strings are tokenised", function() { assertTokens("'Tristan", [isToken("unterminated-string", "Tristan")]); }); +test("unterminated strings ending with escaped string terminator are tokenised", function() { + assertTokens("'Tristan\\'", [isToken("unterminated-string", "Tristan\\'")]); +}); + +test("unterminated strings with unterminated escape are tokenised", function() { + assertTokens("'Tristan\\", [isToken("unterminated-string", "Tristan\\")]); +}); + +test("unterminated strings with many escape sequences do not cause excessive backtracking", function() { + assertTokens("'" + "\\a".repeat(50), [isToken("unterminated-string", "\\a".repeat(50))]); +}); + test("arrows are tokenised", function() { assertTokens("=>", [isToken("arrow")]); }); |
