Skip to content

Commit 8ba79c4

Browse files
committed
[FIX] caja-html-sanitizer: avoid catastrophic backtracking in lexCss
The caja-html-sanitizer uses a complex regular expression to tokenize CSS styles before parsing and sanitizing them. That regular expression contains a sub-expression that allows alternative interpretations of arbitrarily long input sequences and therefore might cause extensive backtracking (see googlearchive/caja#2037 ). To prevent this, the corresponding sub-expression has been rewritten to simulate an atomic group (or possessive quantifier) as described in https://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead. Change-Id: Ia9e8e038d8b4cf5a7cf2684a14877fc08bf1be80 CR-Id: 002075125800001440612020 BCP: 002075129500001766652020
1 parent 1266afe commit 8ba79c4

File tree

2 files changed

+81
-3
lines changed

2 files changed

+81
-3
lines changed

src/sap.ui.core/src/sap/ui/thirdparty/caja-html-sanitizer.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,11 @@ var decodeCss;
964964
// FUNCTION ::= ident '('
965965
// Diff: We exclude url explicitly.
966966
// TODO: should we be tolerant of "fn ("?
967-
var FUNCTION = '(?!url[(])' + IDENT + '[(]';
967+
// ##### BEGIN: MODIFIED BY SAP
968+
// Avoid risk of 'catastrophic backtracking' when unicode escapes are used
969+
// var FUNCTION = '(?!url[(])' + IDENT + '[(]';
970+
var FUNCTION = '(?!url[(])(?=(' + IDENT + '))\\1[(]';
971+
// ##### END: MODIFIED BY SAP
968972
// INCLUDES ::= "~="
969973
var INCLUDES = '~=';
970974
// DASHMATCH ::= "|="

src/sap.ui.core/test/sap/ui/core/qunit/base/security/sanitizeHTML.qunit.js

+76-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
/*global QUnit */
2-
sap.ui.define(["sap/base/security/sanitizeHTML"], function(sanitizeHTML) {
2+
sap.ui.define([
3+
"sap/base/security/sanitizeHTML",
4+
"sap/ui/thirdparty/caja-html-sanitizer"
5+
], function(sanitizeHTML) {
36
"use strict";
47

5-
QUnit.module("Sanitize check");
8+
QUnit.module("Sanitizer Functionality");
69

710
QUnit.test("valid HTML5", function(assert) {
811

@@ -84,4 +87,75 @@ sap.ui.define(["sap/base/security/sanitizeHTML"], function(sanitizeHTML) {
8487

8588
});
8689

90+
QUnit.module("Sanitizer Performance", {
91+
before: function(assert) {
92+
// add a custom assertion "lower than"
93+
QUnit.assert.lt = function(actual, expected, message) {
94+
this.pushResult({
95+
result: actual < expected,
96+
actual: actual,
97+
expected: "< " + expected,
98+
message: message
99+
});
100+
};
101+
},
102+
after: function(assert) {
103+
delete QUnit.assert.lt;
104+
}
105+
});
106+
107+
QUnit.test("lexCss", function(assert) {
108+
109+
assert.equal(typeof window.lexCss, "function", "[precondition] there should be a global function 'lexCss'");
110+
111+
[
112+
{
113+
input: "width: 100%",
114+
tokens: ["width", ":", " ", "100%"]
115+
},
116+
{
117+
input: "background-image: url(foobar.png);",
118+
tokens: ["background-image", ":", " ", "url(\"foobar.png\")", ";"]
119+
},
120+
{
121+
input: "background-image: url('foobar.png');",
122+
tokens: ["background-image", ":", " ", "url(\"foobar.png\")", ";"]
123+
},
124+
{
125+
input: "background-image: url(\"foobar.png\");",
126+
tokens: ["background-image", ":", " ", "url(\"foobar.png\")", ";"]
127+
},
128+
{
129+
input: "width: calc(100px+20em);",
130+
tokens: ["width", ":", " ", "calc(", "100px", "+20em", ")", ";"]
131+
},
132+
{
133+
input: "font: 10pt normal 'Helvetic Neue',sans-serif;",
134+
tokens: ["font", ":", " ", "10pt", " ", "normal", " ", "\"Helvetic Neue\"", ",", "sans-serif", ";"]
135+
},
136+
{
137+
input: "font-size:10.0pt; font-family:\\5320\\7265\\6669\\2020\\2020\\2020\\2020\\2020\\2020\\2020\\2020\\2020\\2020; color:black",
138+
tokens: [
139+
"font-size", ":", "10.0pt", ";", " ",
140+
"font-family", ":", "\u5320\u7265\u6669\u2020\u2020\u2020\u2020\u2020\u2020\u2020\u2020\u2020\u2020", ";", " ",
141+
"color", ":", "black"
142+
]
143+
}
144+
].forEach(function(oData) {
145+
var N = 4,
146+
t0, t1, tokens, i;
147+
148+
// act
149+
t0 = Date.now();
150+
for (i = 0; i < N; i++) {
151+
tokens = window.lexCss(oData.input);
152+
}
153+
t1 = Date.now();
154+
155+
// assert
156+
assert.deepEqual(tokens, oData.tokens, "tokenizing \"" + oData.input + "\" should return the expected tokens");
157+
assert.lt((t1 - t0) / N, 100, "avg. call time should be less than 100ms");
158+
});
159+
});
160+
87161
});

0 commit comments

Comments
 (0)