Skip to content

Commit 44cf9e8

Browse files
committed
fix theoretically possible ReDoS vulnerabilities
1 parent 17081e2 commit 44cf9e8

12 files changed

+101
-11
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
- Fixed configurability and `ToString` conversion of some accessors
3535
- Added Opera Android 73 compat data mapping
3636
- Added TypeScript definitions to `core-js-builder`
37+
- Added proper error on the excess number of trailing `=` in the `atob` polyfill
38+
- Fixed theoretically possible ReDoS vulnerabilities in `String.prototype.{ trim, trimEnd, trimRight }`, `atob`, and `URL` polyfills in some ancient engines
3739

3840
##### [3.27.2 - 2023.01.19](https://github.com/zloirock/core-js/releases/tag/v3.27.2)
3941
- [`Set` methods proposal](https://github.com/tc39/proposal-set-methods) updates:
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
var userAgent = require('../internals/engine-user-agent');
22

3+
// eslint-disable-next-line redos/no-vulnerable -- safe
34
module.exports = /(?:ipad|iphone|ipod).*applewebkit/i.test(userAgent);

packages/core-js/internals/error-stack-clear.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var $Error = Error;
44
var replace = uncurryThis(''.replace);
55

66
var TEST = (function (arg) { return String($Error(arg).stack); })('zxcasd');
7+
// eslint-disable-next-line redos/no-vulnerable -- safe
78
var V8_OR_CHAKRA_STACK_ENTRY = /\n\s*at [^:]*:[^\n]*/;
89
var IS_V8_OR_CHAKRA_STACK = V8_OR_CHAKRA_STACK_ENTRY.test(TEST);
910

packages/core-js/internals/get-substitution.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var floor = Math.floor;
55
var charAt = uncurryThis(''.charAt);
66
var replace = uncurryThis(''.replace);
77
var stringSlice = uncurryThis(''.slice);
8+
// eslint-disable-next-line redos/no-vulnerable -- safe
89
var SUBSTITUTION_SYMBOLS = /\$([$&'`]|\d{1,2}|<[^>]*>)/g;
910
var SUBSTITUTION_SYMBOLS_NO_NAMED = /\$([$&'`]|\d{1,2})/g;
1011

packages/core-js/internals/string-trim.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ var toString = require('../internals/to-string');
44
var whitespaces = require('../internals/whitespaces');
55

66
var replace = uncurryThis(''.replace);
7-
var whitespace = '[' + whitespaces + ']';
8-
var ltrim = RegExp('^' + whitespace + whitespace + '*');
9-
var rtrim = RegExp(whitespace + whitespace + '*$');
7+
var ltrim = RegExp('^[' + whitespaces + ']+');
8+
var rtrim = RegExp('(^|[^' + whitespaces + '])[' + whitespaces + ']+$');
109

1110
// `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation
1211
var createMethod = function (TYPE) {
1312
return function ($this) {
1413
var string = toString(requireObjectCoercible($this));
1514
if (TYPE & 1) string = replace(string, ltrim, '');
16-
if (TYPE & 2) string = replace(string, rtrim, '');
15+
if (TYPE & 2) string = replace(string, rtrim, '$1');
1716
return string;
1817
};
1918
};

packages/core-js/internals/typed-array-constructor.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ if (DESCRIPTORS) {
121121
});
122122

123123
module.exports = function (TYPE, wrapper, CLAMPED) {
124-
var BYTES = TYPE.match(/\d+$/)[0] / 8;
124+
var BYTES = TYPE.match(/\d+/)[0] / 8;
125125
var CONSTRUCTOR_NAME = TYPE + (CLAMPED ? 'Clamped' : '') + 'Array';
126126
var GETTER = 'get' + TYPE;
127127
var SETTER = 'set' + TYPE;

packages/core-js/modules/web.atob.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var ctoi = require('../internals/base64-map').ctoi;
1111

1212
var disallowed = /[^\d+/a-z]/i;
1313
var whitespaces = /[\t\n\f\r ]+/g;
14-
var finalEq = /[=]+$/;
14+
var finalEq = /[=]{1,2}$/;
1515

1616
var $atob = getBuiltIn('atob');
1717
var fromCharCode = String.fromCharCode;

packages/core-js/modules/web.url.constructor.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ var HEX = /^[\da-f]+$/i;
6161
/* eslint-disable regexp/no-control-character -- safe */
6262
var FORBIDDEN_HOST_CODE_POINT = /[\0\t\n\r #%/:<>?@[\\\]^|]/;
6363
var FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT = /[\0\t\n\r #/:<>?@[\\\]^|]/;
64-
var LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g;
64+
var LEADING_C0_CONTROL_OR_SPACE = /^[\u0000-\u0020]+/;
65+
var TRAILING_C0_CONTROL_OR_SPACE = /(^|[^\u0000-\u0020])[\u0000-\u0020]+$/;
6566
var TAB_AND_NEW_LINE = /[\t\n\r]/g;
6667
/* eslint-enable regexp/no-control-character -- safe */
6768
var EOF;
@@ -357,7 +358,8 @@ URLState.prototype = {
357358
url.query = null;
358359
url.fragment = null;
359360
url.cannotBeABaseURL = false;
360-
input = replace(input, LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE, '');
361+
input = replace(input, LEADING_C0_CONTROL_OR_SPACE, '');
362+
input = replace(input, TRAILING_C0_CONTROL_OR_SPACE, '$1');
361363
}
362364

363365
input = replace(input, TAB_AND_NEW_LINE, '');

tests/eslint/eslint.config.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const pluginJSONC = require('eslint-plugin-jsonc');
1010
const pluginN = require('eslint-plugin-n');
1111
const pluginPromise = require('eslint-plugin-promise');
1212
const pluginQUnit = require('eslint-plugin-qunit');
13+
const pluginReDoS = require('eslint-plugin-redos');
1314
const pluginRegExp = require('eslint-plugin-regexp');
1415
const pluginSonarJS = require('eslint-plugin-sonarjs');
1516
const pluginUnicorn = require('eslint-plugin-unicorn');
@@ -625,8 +626,6 @@ const base = {
625626
'regexp/no-potentially-useless-backreference': ERROR,
626627
// disallow standalone backslashes
627628
'regexp/no-standalone-backslash': ERROR,
628-
// disallow exponential and polynomial backtracking
629-
'regexp/no-super-linear-backtracking': ERROR,
630629
// disallow trivially nested assertions
631630
'regexp/no-trivially-nested-assertion': ERROR,
632631
// disallow nested quantifiers that can be rewritten as one quantifier
@@ -701,6 +700,8 @@ const base = {
701700
'regexp/unicode-escape': ERROR,
702701
// use the `i` flag if it simplifies the pattern
703702
'regexp/use-ignore-case': ERROR,
703+
// ReDoS vulnerability check
704+
'redos/no-vulnerable': [ERROR, { timeout: 1e3 }],
704705

705706
// disallow function declarations in if statement clauses without using blocks
706707
'es/no-function-declarations-in-if-statement-clauses-without-block': ERROR,
@@ -1043,6 +1044,8 @@ const nodeDev = {
10431044
...forbidES2023BuiltIns,
10441045
'es/no-intl-supportedvaluesof': ERROR,
10451046
...forbidES2023IntlBuiltIns,
1047+
// ReDoS vulnerability check
1048+
'redos/no-vulnerable': OFF,
10461049
};
10471050

10481051
const tests = {
@@ -1071,6 +1074,8 @@ const tests = {
10711074
'unicorn/error-message': OFF,
10721075
// functions should not have identical implementations
10731076
'sonarjs/no-identical-functions': OFF,
1077+
// ReDoS vulnerability check
1078+
'redos/no-vulnerable': OFF,
10741079
// allow Annex B methods for testing
10751080
...disable(forbidESAnnexBBuiltIns),
10761081
};
@@ -1270,6 +1275,7 @@ module.exports = [
12701275
node: pluginN,
12711276
promise: pluginPromise,
12721277
qunit: pluginQUnit,
1278+
redos: pluginReDoS,
12731279
regexp: pluginRegExp,
12741280
sonarjs: pluginSonarJS,
12751281
unicorn: pluginUnicorn,

tests/eslint/package-lock.json

+77
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/eslint/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"eslint-plugin-n": "^15.6.1",
1212
"eslint-plugin-promise": "^6.1.1",
1313
"eslint-plugin-qunit": "^7.3.4",
14+
"eslint-plugin-redos": "^4.4.3",
1415
"eslint-plugin-regexp": "^1.12.0",
1516
"eslint-plugin-sonarjs": "~0.18.0",
1617
"eslint-plugin-unicorn": "^45.0.2",

tests/unit-global/es.string.replace.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable regexp/no-super-linear-backtracking, regexp/no-unused-capturing-group -- required for testing */
1+
/* eslint-disable regexp/no-unused-capturing-group -- required for testing */
22
import { GLOBAL, NATIVE, STRICT } from '../helpers/constants';
33
import { patchRegExp$exec } from '../helpers/helpers';
44

0 commit comments

Comments
 (0)