Commit dc49225 for mammothjs
commit dc49225425c2c07de0a6dc3529f2386c82a032b4
Author: Michael Williamson <mike@zwobble.org>
Date: Sat Sep 12 09:29:13 2026 +0100
Avoid excessive backtracking when parsing unterminated strings
diff --git a/NEWS b/NEWS
index 1206dac..77f348b 100644
--- a/NEWS
+++ b/NEWS
@@ -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")]);
});