diff --git a/.cirrus.yml b/.cirrus.yml index 7c4188b1cfa..436c370ca9c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -112,7 +112,7 @@ plugin_qa_nodejs_task: <<: *ONLY_SONARSOURCE_QA gke_container: matrix: - - dockerfile: .cirrus/nodejs-10.Dockerfile + - dockerfile: .cirrus/nodejs-12.Dockerfile - dockerfile: .cirrus/nodejs-12.Dockerfile - dockerfile: .cirrus/nodejs-14.Dockerfile - dockerfile: .cirrus/nodejs-16.Dockerfile @@ -154,7 +154,7 @@ ruling_task: <<: *ONLY_SONARSOURCE_QA gke_container: matrix: - - dockerfile: .cirrus/nodejs-10.Dockerfile + - dockerfile: .cirrus/nodejs-12.Dockerfile - dockerfile: .cirrus/nodejs-14.Dockerfile <<: *CONTAINER_DEFINITION cpu: 4 diff --git a/.cirrus/nodejs-10.Dockerfile b/.cirrus/nodejs-10.Dockerfile deleted file mode 100644 index 54727a34633..00000000000 --- a/.cirrus/nodejs-10.Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM gcr.io/language-team/base:latest - -USER root - -ENV NODE_VERSION v10.23.2 - -RUN wget -U "nodejs" -q -O nodejs.tar.xz https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.xz \ - && tar -xJf "nodejs.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ - && rm nodejs.tar.xz \ - && ln -s /usr/local/bin/node /usr/local/bin/nodejs - -ENV YARN_VERSION 1.22.5 - -RUN curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \ - && mkdir -p /opt \ - && tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \ - && ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \ - && ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \ - && rm yarn-v$YARN_VERSION.tar.gz - - -USER sonarsource diff --git a/eslint-bridge/package.json b/eslint-bridge/package.json index fed730fe28d..e20c29d89c1 100644 --- a/eslint-bridge/package.json +++ b/eslint-bridge/package.json @@ -58,6 +58,7 @@ "functional-red-black-tree": "1.0.1", "regexpp": "3.2.0", "run-node": "2.0.0", + "scslre": "0.1.6", "typescript": "4.3.5", "vue-eslint-parser": "7.6.0" }, @@ -79,6 +80,7 @@ "functional-red-black-tree", "regexpp", "run-node", + "scslre", "vue-eslint-parser", "typescript" ], diff --git a/eslint-bridge/src/rules/main.ts b/eslint-bridge/src/rules/main.ts index 6e00a8951a6..6e8e777a968 100644 --- a/eslint-bridge/src/rules/main.ts +++ b/eslint-bridge/src/rules/main.ts @@ -165,6 +165,7 @@ import { rule as regularExpr } from './regular-expr'; import { rule as sessionRegeneration } from './session-regeneration'; import { rule as shorthandPropertyGrouping } from './shorthand-property-grouping'; import { rule as singleCharacterAlternative } from './single-character-alternation'; +import { rule as slowRegex } from './slow-regex'; import { rule as sockets } from './sockets'; import { rule as sonarBlockScopedVar } from './sonar-block-scoped-var'; import { rule as sonarMaxLines } from './sonar-max-lines'; @@ -350,6 +351,7 @@ ruleModules['regular-expr'] = regularExpr; ruleModules['session-regeneration'] = sessionRegeneration; ruleModules['shorthand-property-grouping'] = shorthandPropertyGrouping; ruleModules['single-character-alternation'] = singleCharacterAlternative; +ruleModules['slow-regex'] = slowRegex; ruleModules['sockets'] = sockets; ruleModules['sonar-block-scoped-var'] = sonarBlockScopedVar; ruleModules['sonar-max-lines'] = sonarMaxLines; diff --git a/eslint-bridge/src/rules/slow-regex.ts b/eslint-bridge/src/rules/slow-regex.ts new file mode 100644 index 00000000000..7fc0974dae2 --- /dev/null +++ b/eslint-bridge/src/rules/slow-regex.ts @@ -0,0 +1,41 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// https://sonarsource.github.io/rspec/#/rspec/S5852 + +import { Rule } from 'eslint'; +import { createRegExpRule } from './regex-rule-template'; +import { RegExpLiteral } from 'regexpp/ast'; +import { analyse } from 'scslre'; + +const message = `Make sure the regex used here, which is vulnerable to super-linear runtime due to backtracking, cannot lead to denial of service.`; + +export const rule: Rule.RuleModule = createRegExpRule(context => { + return { + onRegExpLiteralEnter: (node: RegExpLiteral) => { + const { reports } = analyse(node); + if (reports.length > 0) { + context.report({ + message, + node: context.node, + }); + } + }, + }; +}); diff --git a/eslint-bridge/tests/rules/slow-regex.test.ts b/eslint-bridge/tests/rules/slow-regex.test.ts new file mode 100644 index 00000000000..9dfb2640435 --- /dev/null +++ b/eslint-bridge/tests/rules/slow-regex.test.ts @@ -0,0 +1,124 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { RuleTesterTs } from '../RuleTesterTs'; + +const ruleTesterTs = new RuleTesterTs(); + +import { rule } from 'rules/slow-regex'; + +ruleTesterTs.run('redos', rule, { + valid: [ + { + code: ` + /a|b|c/; + /x*x*/; + /a*(ab)*/; + /x*|x*/; + /a*b*/; + `, + }, + ], + invalid: [ + { + code: ` + /(a+)+$/ + `, + errors: [ + { + message: `Make sure the regex used here, which is vulnerable to super-linear runtime due to backtracking, cannot lead to denial of service.`, + line: 2, + endLine: 2, + column: 9, + endColumn: 17, + }, + ], + }, + { + code: ` + /([^,]*,)*/; // FP? compliant in SonarJava tests + `, + errors: 1, + }, + { + code: ` + new RegExp('x*$'); + `, + errors: 1, + }, + { + // Real vulnerabilities + code: ` + protocol.trim().split(/ *, */); + var matcher = /.+\\@.+\\..+/; + enclosure = /[{[].*\\/.*[}\\]]$/; + f.replace(/\\/+$/, ''); + regex = /^(?:\\r\\n|\\n|\\r)+|(?:\\r\\n|\\n|\\r)+$/g; + text.replace(/\\033\\[(\\d+)*m/g, ''); + /^[\s\u200c]+|[\s\u200c]+$/; + str.replace(/\s*$/, ''); // fixed by next line but it's reported + str.replace(/^\s+|\s+$/g, ''); + const entryPattern1 = /^(.)(.*?)\\t(.*?)\\t(.*?)\\t(.*?)\\u000d\\u000a$/ + const entryPattern2 = /^(.)([^\\t]*)\\t([^\\t]*)\\t([^\\t]*)\\t([^\\t]*)\\r\\n$/ // OK, fix for previous one + const match = /^data:(?.*?),(?.*?)(?:#(?.*))?$/.exec(urlString); + const match = /^data:(?[^,]*?),(?[^#]*?)(?:#(?.*))?$/.exec(urlString); // OK, fix for previous one + `, + errors: [ + { + line: 2, + }, + { + line: 3, + }, + { + line: 4, + }, + { + line: 5, + }, + { + line: 6, + }, + { + line: 7, + }, + { + line: 8, + }, + { + line: 9, + }, + { + line: 10, + }, + { + line: 11, + }, + { + line: 13, + }, + ], + }, + { + // fails on Node 10 + code: `new RegExp('[\\x09\\x0A]*$');`, + errors: 1, + }, + ], +}); diff --git a/eslint-bridge/yarn.lock b/eslint-bridge/yarn.lock index 047f37d47a6..0ee34983d74 100644 --- a/eslint-bridge/yarn.lock +++ b/eslint-bridge/yarn.lock @@ -4382,6 +4382,13 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" +refa@^0.9.0: + version "0.9.1" + resolved "https://repox.jfrog.io/repox/api/npm/npm/refa/-/refa-0.9.1.tgz#12731fce378d235731b1f73182b20083c8a75ca8" + integrity sha1-EnMfzjeNI1cxsfcxgrIAg8inXKg= + dependencies: + regexpp "^3.2.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://repox.jfrog.io/repox/api/npm/npm/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -4414,7 +4421,15 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@3.2.0, regexpp@^3.1.0: +regexp-ast-analysis@^0.2.3: + version "0.2.4" + resolved "https://repox.jfrog.io/repox/api/npm/npm/regexp-ast-analysis/-/regexp-ast-analysis-0.2.4.tgz#a497a7c8bfbba51438693821e4b0e3ed43e20f1b" + integrity sha1-pJenyL+7pRQ4aTgh5LDj7UPiDxs= + dependencies: + refa "^0.9.0" + regexpp "^3.2.0" + +regexpp@3.2.0, regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" resolved "https://repox.jfrog.io/repox/api/npm/npm/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha1-BCWido2PI7rXDKS5BGH6LxIT4bI= @@ -4576,6 +4591,15 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" +scslre@0.1.6: + version "0.1.6" + resolved "https://repox.jfrog.io/repox/api/npm/npm/scslre/-/scslre-0.1.6.tgz#71a2832e4bf3a9254973a04fbed90aec94f75757" + integrity sha1-caKDLkvzqSVJc6BPvtkK7JT3V1c= + dependencies: + refa "^0.9.0" + regexp-ast-analysis "^0.2.3" + regexpp "^3.2.0" + "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://repox.jfrog.io/repox/api/npm/npm/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" diff --git a/its/ruling/src/test/expected/js/amplify/javascript-S5852.json b/its/ruling/src/test/expected/js/amplify/javascript-S5852.json new file mode 100644 index 00000000000..cbe8f924b61 --- /dev/null +++ b/its/ruling/src/test/expected/js/amplify/javascript-S5852.json @@ -0,0 +1,11 @@ +{ +'amplify:lib/amplify.js':[ +520, +], +'amplify:lib/amplify.request.js':[ +114, +], +'amplify:src/request.js':[ +105, +], +} diff --git a/its/ruling/src/test/expected/js/angular.js/javascript-S5852.json b/its/ruling/src/test/expected/js/angular.js/javascript-S5852.json new file mode 100644 index 00000000000..28ad94347e8 --- /dev/null +++ b/its/ruling/src/test/expected/js/angular.js/javascript-S5852.json @@ -0,0 +1,103 @@ +{ +'angular.js:docs/app/src/errors.js':[ +6, +], +'angular.js:lib/grunt/utils.js':[ +103, +104, +105, +106, +174, +189, +], +'angular.js:scripts/compare-master-to-stable.js':[ +129, +], +'angular.js:src/Angular.js':[ +1001, +], +'angular.js:src/auto/injector.js':[ +67, +70, +], +'angular.js:src/jqLite.js':[ +170, +], +'angular.js:src/ng/compile.js':[ +1383, +2060, +], +'angular.js:src/ng/directive/input.js':[ +25, +], +'angular.js:src/ng/directive/ngModelOptions.js':[ +5, +], +'angular.js:src/ng/directive/ngOptions.js':[ +238, +], +'angular.js:src/ng/directive/ngRepeat.js':[ +476, +], +'angular.js:src/ng/filter/filters.js':[ +72, +], +'angular.js:src/ngAnimate/animateCss.js':[ +279, +], +'angular.js:src/ngMessageFormat/messageFormatParser.js':[ +138, +196, +241, +271, +395, +], +'angular.js:src/ngMock/angular-mocks.js':[ +2102, +], +'angular.js:src/ngResource/resource.js':[ +654, +], +'angular.js:src/ngSanitize/filter/linky.js':[ +132, +], +'angular.js:src/routeToRegExp.js':[ +36, +], +'angular.js:test/AngularSpec.js':[ +78, +], +'angular.js:test/e2e/tools/fixture.js':[ +67, +], +'angular.js:test/helpers/privateMocks.js':[ +62, +], +'angular.js:test/ng/anchorScrollSpec.js':[ +31, +], +'angular.js:test/ng/compileSpec.js':[ +11928, +11936, +], +'angular.js:test/ng/directive/ngOptionsSpec.js':[ +2931, +], +'angular.js:test/ng/httpBackendSpec.js':[ +425, +], +'angular.js:test/ng/sceSpecs.js':[ +352, +358, +359, +471, +542, +], +'angular.js:test/ngMock/angular-mocksSpec.js':[ +1804, +2323, +], +'angular.js:test/ngSanitize/sanitizeSpec.js':[ +39, +], +} diff --git a/its/ruling/src/test/expected/js/backbone/javascript-S5852.json b/its/ruling/src/test/expected/js/backbone/javascript-S5852.json new file mode 100644 index 00000000000..84d19320cc5 --- /dev/null +++ b/its/ruling/src/test/expected/js/backbone/javascript-S5852.json @@ -0,0 +1,11 @@ +{ +'backbone:backbone.js':[ +1304, +1646, +1757, +1760, +1763, +1805, +2031, +], +} diff --git a/its/ruling/src/test/expected/js/es5-shim/javascript-S5852.json b/its/ruling/src/test/expected/js/es5-shim/javascript-S5852.json new file mode 100644 index 00000000000..80574ce96a0 --- /dev/null +++ b/its/ruling/src/test/expected/js/es5-shim/javascript-S5852.json @@ -0,0 +1,9 @@ +{ +'es5-shim:es5-shim.js':[ +69, +1969, +], +'es5-shim:tests/spec/s-string.js':[ +31, +], +} diff --git a/its/ruling/src/test/expected/js/file-for-rules/javascript-S5852.json b/its/ruling/src/test/expected/js/file-for-rules/javascript-S5852.json new file mode 100644 index 00000000000..66d2ce59a49 --- /dev/null +++ b/its/ruling/src/test/expected/js/file-for-rules/javascript-S5852.json @@ -0,0 +1,11 @@ +{ +'file-for-rules:S5860.js':[ +15, +], +'file-for-rules:S6328.js':[ +3, +4, +6, +7, +], +} diff --git a/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S5852.json b/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S5852.json new file mode 100644 index 00000000000..71a5e425418 --- /dev/null +++ b/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S5852.json @@ -0,0 +1,79 @@ +{ +'javascript-test-sources:src/ecmascript6/Ghost/core/client/app/utils/date-formatting.js':[ +20, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/client/app/utils/word-count.js':[ +7, +9, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/api/mail.js':[ +20, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/data/meta/excerpt.js':[ +8, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/data/meta/next_url.js':[ +2, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/data/meta/previous_url.js':[ +2, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/errors/index.js':[ +257, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/helpers/get.js':[ +51, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/helpers/navigation.js':[ +45, +46, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/utils/index.js':[ +88, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/test/functional/client/about_test.js':[ +14, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/test/unit/showdown_client_integrated_spec.js':[ +475, +479, +], +'javascript-test-sources:src/ecmascript6/http/test/IInterceptResolution.spec.js':[ +32, +37, +47, +], +'javascript-test-sources:src/ecmascript6/reddit-mobile/buildTasks/js.js':[ +71, +], +'javascript-test-sources:src/ecmascript6/reddit-mobile/gulpfile.js':[ +5, +], +'javascript-test-sources:src/ecmascript6/reddit-mobile/index.js':[ +43, +], +'javascript-test-sources:src/ecmascript6/reddit-mobile/test/index.js':[ +3, +], +'javascript-test-sources:src/ecmascript6/router/src/history.js':[ +4, +7, +19, +41, +], +'javascript-test-sources:src/ecmascript6/router/third_party/brick/brick-1.0.1.byob.js':[ +14, +894, +2886, +], +'javascript-test-sources:src/ecmascript6/snoode/src/endpoints/v1.es6.js':[ +29, +36, +], +'javascript-test-sources:src/ecmascript6/sonar-web/src/main/js/libs/application.js':[ +332, +], +'javascript-test-sources:src/ecmascript6/templating/src/lib/compiler/selector_config.js':[ +5, +], +} diff --git a/its/ruling/src/test/expected/js/jquery/javascript-S5852.json b/its/ruling/src/test/expected/js/jquery/javascript-S5852.json new file mode 100644 index 00000000000..be27631757d --- /dev/null +++ b/its/ruling/src/test/expected/js/jquery/javascript-S5852.json @@ -0,0 +1,44 @@ +{ +'jquery:build/tasks/build.js':[ +24, +38, +95, +198, +220, +], +'jquery:src/ajax.js':[ +16, +18, +], +'jquery:src/manipulation.js':[ +27, +], +'jquery:src/var/pnum.js':[ +1, +], +'jquery:test/data/jquery-1.9.1.js':[ +68, +74, +88, +1605, +3894, +3908, +3914, +3918, +3929, +6025, +7642, +7644, +], +'jquery:test/unit/ajax.js':[ +998, +2457, +2461, +], +'jquery:test/unit/basic.js':[ +45, +], +'jquery:test/unit/manipulation.js':[ +1754, +], +} diff --git a/its/ruling/src/test/expected/js/jshint/javascript-S5852.json b/its/ruling/src/test/expected/js/jshint/javascript-S5852.json new file mode 100644 index 00000000000..bb645d78181 --- /dev/null +++ b/its/ruling/src/test/expected/js/jshint/javascript-S5852.json @@ -0,0 +1,14 @@ +{ +'jshint:src/cli.js':[ +609, +], +'jshint:src/lex.js':[ +472, +], +'jshint:src/platforms/rhino.js':[ +107, +], +'jshint:src/style.js':[ +47, +], +} diff --git a/its/ruling/src/test/expected/js/knockout/javascript-S5852.json b/its/ruling/src/test/expected/js/knockout/javascript-S5852.json new file mode 100644 index 00000000000..e1461745db8 --- /dev/null +++ b/its/ruling/src/test/expected/js/knockout/javascript-S5852.json @@ -0,0 +1,24 @@ +{ +'knockout:Gruntfile.js':[ +60, +], +'knockout:spec/lib/innershiv.js':[ +18, +18, +], +'knockout:src/binding/expressionRewriting.js':[ +7, +43, +], +'knockout:src/templating/templateRewriting.js':[ +3, +4, +], +'knockout:src/utils.js':[ +316, +], +'knockout:src/virtualElements.js':[ +15, +15, +], +} diff --git a/its/ruling/src/test/expected/js/mootools-core/javascript-S5852.json b/its/ruling/src/test/expected/js/mootools-core/javascript-S5852.json new file mode 100644 index 00000000000..96dc2dd64c1 --- /dev/null +++ b/its/ruling/src/test/expected/js/mootools-core/javascript-S5852.json @@ -0,0 +1,33 @@ +{ +'mootools-core:Grunt/plugins/karma/syn/lib/syn.js':[ +575, +576, +1652, +], +'mootools-core:Source/Browser/Browser.js':[ +30, +], +'mootools-core:Source/Element/Element.Style.js':[ +181, +], +'mootools-core:Source/Element/Element.js':[ +747, +], +'mootools-core:Source/Fx/Fx.CSS.js':[ +141, +], +'mootools-core:Source/Slick/Slick.Finder.js':[ +254, +562, +], +'mootools-core:Source/Slick/Slick.Parser.js':[ +22, +], +'mootools-core:Source/Types/String.js':[ +30, +], +'mootools-core:Specs/Element/Element.Style.js':[ +220, +277, +], +} diff --git a/its/ruling/src/test/expected/js/ocanvas/javascript-S5852.json b/its/ruling/src/test/expected/js/ocanvas/javascript-S5852.json new file mode 100644 index 00000000000..6b09d3a10d2 --- /dev/null +++ b/its/ruling/src/test/expected/js/ocanvas/javascript-S5852.json @@ -0,0 +1,20 @@ +{ +'ocanvas:scripts/release.js':[ +66, +73, +], +'ocanvas:src/animation.js':[ +280, +], +'ocanvas:src/mouse.js':[ +212, +], +'ocanvas:src/style.js':[ +134, +325, +330, +437, +672, +724, +], +} diff --git a/its/ruling/src/test/expected/js/p5.js/javascript-S5852.json b/its/ruling/src/test/expected/js/p5.js/javascript-S5852.json new file mode 100644 index 00000000000..cdbfec267b2 --- /dev/null +++ b/its/ruling/src/test/expected/js/p5.js/javascript-S5852.json @@ -0,0 +1,25 @@ +{ +'p5.js:docs/preprocessor.js':[ +37, +], +'p5.js:docs/yuidoc-p5-theme/assets/js/reference.js':[ +78, +79, +2833, +3296, +], +'p5.js:docs/yuidoc-p5-theme/assets/js/render.js':[ +58, +163, +], +'p5.js:src/core/environment.js':[ +737, +], +'p5.js:src/utilities/string_functions.js':[ +296, +], +'p5.js:utils/sample-linter.js':[ +44, +117, +], +} diff --git a/its/ruling/src/test/expected/js/paper.js/javascript-S5852.json b/its/ruling/src/test/expected/js/paper.js/javascript-S5852.json new file mode 100644 index 00000000000..712fdbd9352 --- /dev/null +++ b/its/ruling/src/test/expected/js/paper.js/javascript-S5852.json @@ -0,0 +1,28 @@ +{ +'paper.js:gulp/typescript/typescript-definition-generator.js':[ +278, +], +'paper.js:src/anim/Tween.js':[ +356, +378, +], +'paper.js:src/core/PaperScope.js':[ +90, +], +'paper.js:src/core/PaperScript.js':[ +461, +], +'paper.js:src/path/PathItem.js':[ +164, +], +'paper.js:src/style/Color.js':[ +93, +], +'paper.js:src/svg/SvgImport.js':[ +127, +458, +], +'paper.js:test/helpers.js':[ +549, +], +} diff --git a/its/ruling/src/test/expected/js/prototype/javascript-S5852.json b/its/ruling/src/test/expected/js/prototype/javascript-S5852.json new file mode 100644 index 00000000000..59aa20ecca8 --- /dev/null +++ b/its/ruling/src/test/expected/js/prototype/javascript-S5852.json @@ -0,0 +1,20 @@ +{ +'prototype:src/prototype/ajax/request.js':[ +305, +], +'prototype:src/prototype/lang/function.js':[ +40, +], +'prototype:src/prototype/lang/string.js':[ +246, +283, +489, +626, +], +'prototype:test/unit/static/js/mocha.js':[ +1643, +5225, +5256, +5292, +], +} diff --git a/its/ruling/src/test/expected/js/qunit/javascript-S5852.json b/its/ruling/src/test/expected/js/qunit/javascript-S5852.json new file mode 100644 index 00000000000..4b44a394953 --- /dev/null +++ b/its/ruling/src/test/expected/js/qunit/javascript-S5852.json @@ -0,0 +1,21 @@ +{ +'qunit:Gruntfile.js':[ +120, +], +'qunit:reporter/html.js':[ +99, +790, +], +'qunit:src/cli/find-reporter.js':[ +56, +], +'qunit:src/core/stacktrace.js':[ +5, +], +'qunit:src/equiv.js':[ +63, +], +'qunit:test/reporter-html/reporter-html.js':[ +58, +], +} diff --git a/its/ruling/src/test/expected/js/sizzle/javascript-S5852.json b/its/ruling/src/test/expected/js/sizzle/javascript-S5852.json new file mode 100644 index 00000000000..c7f1d592512 --- /dev/null +++ b/its/ruling/src/test/expected/js/sizzle/javascript-S5852.json @@ -0,0 +1,17 @@ +{ +'sizzle:Gruntfile.js':[ +73, +], +'sizzle:speed/speed.js':[ +31, +31, +], +'sizzle:src/sizzle.js':[ +109, +126, +133, +], +'sizzle:tasks/dist.js':[ +27, +], +} diff --git a/its/ruling/src/test/expected/js/underscore/javascript-S5852.json b/its/ruling/src/test/expected/js/underscore/javascript-S5852.json new file mode 100644 index 00000000000..def20770c3b --- /dev/null +++ b/its/ruling/src/test/expected/js/underscore/javascript-S5852.json @@ -0,0 +1,7 @@ +{ +'underscore:test/utility.js':[ +240, +270, +448, +], +} diff --git a/its/ruling/src/test/expected/ts/TypeScript/typescript-S5852.json b/its/ruling/src/test/expected/ts/TypeScript/typescript-S5852.json new file mode 100644 index 00000000000..37a43ad6a5b --- /dev/null +++ b/its/ruling/src/test/expected/ts/TypeScript/typescript-S5852.json @@ -0,0 +1,25 @@ +{ +'TypeScript:src/compiler/commandLineParser.ts':[ +1399, +1458, +], +'TypeScript:src/compiler/program.ts':[ +298, +], +'TypeScript:src/compiler/utilities.ts':[ +640, +641, +642, +3002, +], +'TypeScript:src/harness/harness.ts':[ +680, +], +'TypeScript:src/harness/rwcRunner.ts':[ +39, +241, +], +'TypeScript:src/server/editorServices.ts':[ +85, +], +} diff --git a/its/ruling/src/test/expected/ts/ag-grid/typescript-S5852.json b/its/ruling/src/test/expected/ts/ag-grid/typescript-S5852.json new file mode 100644 index 00000000000..b84219b4dbb --- /dev/null +++ b/its/ruling/src/test/expected/ts/ag-grid/typescript-S5852.json @@ -0,0 +1,7 @@ +{ +'ag-grid:src/ts/utils.ts':[ +4, +649, +658, +], +} diff --git a/its/ruling/src/test/expected/ts/console/typescript-S5852.json b/its/ruling/src/test/expected/ts/console/typescript-S5852.json new file mode 100644 index 00000000000..b9b2ea0bfdb --- /dev/null +++ b/its/ruling/src/test/expected/ts/console/typescript-S5852.json @@ -0,0 +1,19 @@ +{ +'console:src/utils/functionTest.ts':[ +42, +], +'console:src/utils/utils.ts':[ +106, +110, +], +'console:src/views/SchemaView/SchemaEditor.tsx':[ +340, +344, +], +'console:src/views/models/AuthProviderPopup/AuthProviderSidePanel.tsx':[ +405, +], +'console:src/views/models/DatabrowserView/DatabrowserView.tsx':[ +659, +], +} diff --git a/its/ruling/src/test/expected/ts/desktop/typescript-S5852.json b/its/ruling/src/test/expected/ts/desktop/typescript-S5852.json new file mode 100644 index 00000000000..e20a4bc42bd --- /dev/null +++ b/its/ruling/src/test/expected/ts/desktop/typescript-S5852.json @@ -0,0 +1,20 @@ +{ +'desktop:app/src/lib/dispatcher/git-store.ts':[ +748, +], +'desktop:app/src/lib/git/reflog.ts':[ +9, +9, +], +'desktop:app/src/lib/remote-parsing.ts':[ +25, +26, +27, +], +'desktop:app/src/lib/remove-remote-prefix.ts':[ +10, +], +'desktop:app/src/models/branch.ts':[ +37, +], +} diff --git a/its/ruling/src/test/expected/ts/postgraphql/typescript-S5852.json b/its/ruling/src/test/expected/ts/postgraphql/typescript-S5852.json new file mode 100644 index 00000000000..c0056a5a4ff --- /dev/null +++ b/its/ruling/src/test/expected/ts/postgraphql/typescript-S5852.json @@ -0,0 +1,8 @@ +{ +'postgraphql:src/graphql/utils/formatName.ts':[ +4, +], +'postgraphql:src/postgres/inventory/type/PgRangeType.ts':[ +164, +], +} diff --git a/its/ruling/src/test/expected/ts/searchkit/typescript-S5852.json b/its/ruling/src/test/expected/ts/searchkit/typescript-S5852.json new file mode 100644 index 00000000000..e02603ec3c2 --- /dev/null +++ b/its/ruling/src/test/expected/ts/searchkit/typescript-S5852.json @@ -0,0 +1,5 @@ +{ +'searchkit:src/__test__/core/SearchkitManagerSpec.ts':[ +36, +], +} diff --git a/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java b/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java index f00992c9b26..e042d312f2a 100644 --- a/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java +++ b/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java @@ -273,6 +273,7 @@ public static List> getAllChecks() { SessionRegenerationCheck.class, ShorthandPropertiesNotGroupedCheck.class, SingleCharacterAlternativeCheck.class, + SlowRegexCheck.class, SocketsCheck.class, SonarNoInvalidRegexCheck.class, SonarNoMisleadingCharacterClassCheck.class, @@ -340,4 +341,3 @@ public static List> getAllChecks() { ); } } - diff --git a/javascript-checks/src/main/java/org/sonar/javascript/checks/SlowRegexCheck.java b/javascript-checks/src/main/java/org/sonar/javascript/checks/SlowRegexCheck.java new file mode 100644 index 00000000000..3128fe5471b --- /dev/null +++ b/javascript-checks/src/main/java/org/sonar/javascript/checks/SlowRegexCheck.java @@ -0,0 +1,37 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.javascript.checks; + +import org.sonar.check.Rule; +import org.sonar.plugins.javascript.api.EslintBasedCheck; +import org.sonar.plugins.javascript.api.JavaScriptRule; +import org.sonar.plugins.javascript.api.TypeScriptRule; + +@TypeScriptRule +@JavaScriptRule +@Rule(key = "S5852") +public class SlowRegexCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "slow-regex"; + } + +} diff --git a/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S5852.html b/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S5852.html new file mode 100644 index 00000000000..e8cc74dbe63 --- /dev/null +++ b/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S5852.html @@ -0,0 +1,94 @@ +

Most of the regular expression engines use backtracking to try all possible execution paths of the regular expression when evaluating an input, in +some cases it can cause performance issues, called catastrophic backtracking situations. In the worst case, the complexity of the +regular expression is exponential in the size of the input, this means that a small carefully-crafted input (like 20 chars) can trigger catastrophic +backtracking and cause a denial of service of the application. Super-linear regex complexity can lead to the same impact too with, in this case, a +large carefully-crafted input (thousands chars).

+

This rule determines the runtime complexity of a regular expression and informs you if it is not linear.

+

Ask Yourself Whether

+
    +
  • The input is user-controlled.
  • +
  • The input size is not restricted to a small number of characters.
  • +
  • There is no timeout in place to limit the regex evaluation time.
  • +
+

There is a risk if you answered yes to any of those questions.

+

Recommended Secure Coding Practices

+

To avoid catastrophic backtracking situations, make sure that none of the following conditions apply to your regular expression.

+

In all of the following cases, catastrophic backtracking can only happen if the problematic part of the regex is followed by a pattern that can +fail, causing the backtracking to actually happen.

+
    +
  • If you have a repetition r* or r*?, such that the regex r could produce different possible matches (of + possibly different lengths) on the same input, the worst case matching time can be exponential. This can be the case if r contains + optional parts, alternations or additional repetitions (but not if the repetition is written in such a way that there’s only one way to match it). +
  • +
  • If you have multiple repetitions that can match the same contents and are consecutive or are only separated by an optional separator or a + separator that can be matched by both of the repetitions, the worst case matching time can be polynomial (O(n^c) where c is the number of + problematic repetitions). For example a*b* is not a problem because a* and b* match different things and + a*_a* is not a problem because the repetitions are separated by a '_' and can’t match that '_'. However, + a*a* and .*_.* have quadratic runtime.
  • +
  • If the regex is not anchored to the beginning of the string, quadratic runtime is especially hard to avoid because whenever a match fails, the + regex engine will try again starting at the next index. This means that any unbounded repetition, if it’s followed by a pattern that can fail, can + cause quadratic runtime on some inputs. For example str.split(/\s*,/) will run in quadratic time on strings that consist entirely of + spaces (or at least contain large sequences of spaces, not followed by a comma).
  • +
+

In order to rewrite your regular expression without these patterns, consider the following strategies:

+
    +
  • If applicable, define a maximum number of expected repetitions using the bounded quantifiers, like {1,5} instead of + + for instance.
  • +
  • Refactor nested quantifiers to limit the number of way the inner group can be matched by the outer quantifier, for instance this nested + quantifier situation (ba+)+ doesn’t cause performance issues, indeed, the inner group can be matched only if there exists exactly one + b char per repetition of the group.
  • +
  • Optimize regular expressions by emulating possessive quantifiers and atomic grouping.
  • +
  • Use negated character classes instead of . to exclude separators where applicable. For example the quadratic regex + .*_.* can be made linear by changing it to [^_]*_.*
  • +
+

Sometimes it’s not possible to rewrite the regex to be linear while still matching what you want it to match. Especially when the regex is not +anchored to the beginning of the string, for which it is quite hard to avoid quadratic runtimes. In those cases consider the following approaches:

+
    +
  • Solve the problem without regular expressions
  • +
  • Use an alternative non-backtracking regex implementations such as Google’s RE2 or node-re2.
  • +
  • Use multiple passes. This could mean pre- and/or post-processing the string manually before/after applying the regular expression to it or + using multiple regular expressions. One example of this would be to replace str.split(/\s*,\s*/) with str.split(",") and + then trimming the spaces from the strings as a second step.
  • +
  • It is often possible to make the regex infallible by making all the parts that could fail optional, which will prevent backtracking. Of course + this means that you’ll accept more strings than intended, but this can be handled by using capturing groups to check whether the optional parts were + matched or not and then ignoring the match if they weren’t. For example the regex x*y could be replaced with x*(y)? and + then the call to str.match(regex) could be replaced with matched = str.match(regex) and matched[1] !== + undefined.
  • +
+

Sensitive Code Example

+

The regex evaluation will never end:

+
+/(a+)+$/.test(
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+
+"aaaaaaaaaaaaaaa!"
+); // Sensitive
+
+

Compliant Solution

+

Possessive quantifiers do not keep backtracking positions, thus can be used, if possible, to avoid performance issues. Unfortunately, they are not +supported in JavaScript, but one can still mimick them using lookahead assertions and backreferences:

+
+/((?=(a+))\2)+$/.test(
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+
+"aaaaaaaaaaaaaaa!"
+); // Compliant
+
+

See

+ + diff --git a/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S5852.json b/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S5852.json new file mode 100644 index 00000000000..f59548c4b55 --- /dev/null +++ b/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S5852.json @@ -0,0 +1,31 @@ +{ + "title": "Using slow regular expressions is security-sensitive", + "type": "SECURITY_HOTSPOT", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "cwe", + "owasp-a1", + "regex" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-5852", + "sqKey": "S5852", + "scope": "All", + "compatibleLanguages": [ + "JAVASCRIPT", + "TYPESCRIPT" + ], + "securityStandards": { + "CWE": [ + 400, + 1333 + ], + "OWASP": [ + "A1" + ] + } +} diff --git a/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json b/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json index 52232baca1a..c5303a8a763 100644 --- a/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json +++ b/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json @@ -173,6 +173,7 @@ "S5842", "S5843", "S5850", + "S5852", "S5856", "S5860", "S5868",