Message ID | 20230829113513.276561-2-archana.polampalli@windriver.com |
---|---|
State | New |
Headers | show |
Series | [meta-oe,mickledore,1/2] nodejs: upgrade 18.16.1 -> 18.17.1 | expand |
On 8/29/23 7:35 AM, Polampalli, Archana via lists.openembedded.org wrote: > Versions of the package semver before 7.5.2 are vulnerable to Regular Expression > Denial of Service (ReDoS) via the function new Range, when untrusted user data is > provided as a range. Doesn't this apply to master too as it is @ version 18.17.1? - Armin > > References: > https://nvd.nist.gov/vuln/detail/CVE-2022-25883 > > Upstream patches: > https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441 > > Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com> > --- > .../nodejs/nodejs/CVE-2022-25883.patch | 260 ++++++++++++++++++ > .../recipes-devtools/nodejs/nodejs_18.17.1.bb | 1 + > 2 files changed, 261 insertions(+) > create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch > > diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch > new file mode 100644 > index 000000000..1c9daf714 > --- /dev/null > +++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch > @@ -0,0 +1,260 @@ > +From 717534ee353682f3bcf33e60a8af4292626d4441 Mon Sep 17 00:00:00 2001 > +From: Luke Karrys <luke@lukekarrys.com> > +Date: Thu, 15 Jun 2023 12:21:14 -0700 > +Subject: [PATCH] fix: better handling of whitespace (#564) > + > +CVE: CVE-2022-25883 > + > +Upstream-Status: Backport [https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441] > +--- > + classes/comparator.js | 3 +- > + classes/range.js | 64 ++++++++++++++++------------ > + classes/semver.js | 2 +- > + functions/coerce.js | 2 +- > + internal/re.js | 11 +++++ > + package.json | 2 +- > + 6 files changed, 53 insertions(+), 31 deletions(-) > + > +diff --git a/classes/comparator.js b/classes/comparator.js > +index 2146c88..3d39c0e 100644 > +--- a/classes/comparator.js > ++++ b/classes/comparator.js > +@@ -16,6 +16,7 @@ class Comparator { > + } > + } > + > ++ comp = comp.trim().split(/\s+/).join(' ') > + debug('comparator', comp, options) > + this.options = options > + this.loose = !!options.loose > +@@ -133,7 +134,7 @@ class Comparator { > + module.exports = Comparator > + > + const parseOptions = require('../internal/parse-options') > +-const { re, t } = require('../internal/re') > ++const { safeRe: re, t } = require('../internal/re') > + const cmp = require('../functions/cmp') > + const debug = require('../internal/debug') > + const SemVer = require('./semver') > +diff --git a/classes/range.js b/classes/range.js > +index d9e866d..53c2540 100644 > +--- a/classes/range.js > ++++ b/classes/range.js > +@@ -26,19 +26,26 @@ class Range { > + this.loose = !!options.loose > + this.includePrerelease = !!options.includePrerelease > + > +- // First, split based on boolean or || > ++ // First reduce all whitespace as much as possible so we do not have to rely > ++ // on potentially slow regexes like \s*. This is then stored and used for > ++ // future error messages as well. > + this.raw = range > +- this.set = range > ++ .trim() > ++ .split(/\s+/) > ++ .join(' ') > ++ > ++ // First, split on || > ++ this.set = this.raw > + .split('||') > + // map the range to a 2d array of comparators > +- .map(r => this.parseRange(r.trim())) > ++ .map(r => this.parseRange(r)) > + // throw out any comparator lists that are empty > + // this generally means that it was not a valid range, which is allowed > + // in loose mode, but will still throw if the WHOLE range is invalid. > + .filter(c => c.length) > + > + if (!this.set.length) { > +- throw new TypeError(`Invalid SemVer Range: ${range}`) > ++ throw new TypeError(`Invalid SemVer Range: ${this.raw}`) > + } > + > + // if we have any that are not the null set, throw out null sets. > +@@ -64,9 +71,7 @@ class Range { > + > + format () { > + this.range = this.set > +- .map((comps) => { > +- return comps.join(' ').trim() > +- }) > ++ .map((comps) => comps.join(' ').trim()) > + .join('||') > + .trim() > + return this.range > +@@ -77,8 +82,6 @@ class Range { > + } > + > + parseRange (range) { > +- range = range.trim() > +- > + // memoize range parsing for performance. > + // this is a very hot path, and fully deterministic. > + const memoOpts = > +@@ -105,9 +108,6 @@ class Range { > + // `^ 1.2.3` => `^1.2.3` > + range = range.replace(re[t.CARETTRIM], caretTrimReplace) > + > +- // normalize spaces > +- range = range.split(/\s+/).join(' ') > +- > + // At this point, the range is completely trimmed and > + // ready to be split into comparators. > + > +@@ -203,7 +203,7 @@ const Comparator = require('./comparator') > + const debug = require('../internal/debug') > + const SemVer = require('./semver') > + const { > +- re, > ++ safeRe: re, > + t, > + comparatorTrimReplace, > + tildeTrimReplace, > +@@ -257,10 +257,13 @@ const isX = id => !id || id.toLowerCase() === 'x' || id === '*' > + // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 > + // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 > + // ~0.0.1 --> >=0.0.1 <0.1.0-0 > +-const replaceTildes = (comp, options) => > +- comp.trim().split(/\s+/).map((c) => { > +- return replaceTilde(c, options) > +- }).join(' ') > ++const replaceTildes = (comp, options) => { > ++ return comp > ++ .trim() > ++ .split(/\s+/) > ++ .map((c) => replaceTilde(c, options)) > ++ .join(' ') > ++} > + > + const replaceTilde = (comp, options) => { > + const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE] > +@@ -298,10 +301,13 @@ const replaceTilde = (comp, options) => { > + // ^1.2.0 --> >=1.2.0 <2.0.0-0 > + // ^0.0.1 --> >=0.0.1 <0.0.2-0 > + // ^0.1.0 --> >=0.1.0 <0.2.0-0 > +-const replaceCarets = (comp, options) => > +- comp.trim().split(/\s+/).map((c) => { > +- return replaceCaret(c, options) > +- }).join(' ') > ++const replaceCarets = (comp, options) => { > ++ return comp > ++ .trim() > ++ .split(/\s+/) > ++ .map((c) => replaceCaret(c, options)) > ++ .join(' ') > ++} > + > + const replaceCaret = (comp, options) => { > + debug('caret', comp, options) > +@@ -358,9 +364,10 @@ const replaceCaret = (comp, options) => { > + > + const replaceXRanges = (comp, options) => { > + debug('replaceXRanges', comp, options) > +- return comp.split(/\s+/).map((c) => { > +- return replaceXRange(c, options) > +- }).join(' ') > ++ return comp > ++ .split(/\s+/) > ++ .map((c) => replaceXRange(c, options)) > ++ .join(' ') > + } > + > + const replaceXRange = (comp, options) => { > +@@ -443,12 +450,15 @@ const replaceXRange = (comp, options) => { > + const replaceStars = (comp, options) => { > + debug('replaceStars', comp, options) > + // Looseness is ignored here. star is always as loose as it gets! > +- return comp.trim().replace(re[t.STAR], '') > ++ return comp > ++ .trim() > ++ .replace(re[t.STAR], '') > + } > + > + const replaceGTE0 = (comp, options) => { > + debug('replaceGTE0', comp, options) > +- return comp.trim() > ++ return comp > ++ .trim() > + .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '') > + } > + > +@@ -486,7 +496,7 @@ const hyphenReplace = incPr => ($0, > + to = `<=${to}` > + } > + > +- return (`${from} ${to}`).trim() > ++ return `${from} ${to}`.trim() > + } > + > + const testSet = (set, version, options) => { > +diff --git a/classes/semver.js b/classes/semver.js > +index 99dbe82..e1208fe 100644 > +--- a/classes/semver.js > ++++ b/classes/semver.js > +@@ -1,6 +1,6 @@ > + const debug = require('../internal/debug') > + const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants') > +-const { re, t } = require('../internal/re') > ++const { safeRe: re, t } = require('../internal/re') > + > + const parseOptions = require('../internal/parse-options') > + const { compareIdentifiers } = require('../internal/identifiers') > +diff --git a/functions/coerce.js b/functions/coerce.js > +index 2e01452..febbff9 100644 > +--- a/functions/coerce.js > ++++ b/functions/coerce.js > +@@ -1,6 +1,6 @@ > + const SemVer = require('../classes/semver') > + const parse = require('./parse') > +-const { re, t } = require('../internal/re') > ++const { safeRe: re, t } = require('../internal/re') > + > + const coerce = (version, options) => { > + if (version instanceof SemVer) { > +diff --git a/internal/re.js b/internal/re.js > +index ed88398..f73ef1a 100644 > +--- a/internal/re.js > ++++ b/internal/re.js > +@@ -4,16 +4,27 @@ exports = module.exports = {} > + > + // The actual regexps go on exports.re > + const re = exports.re = [] > ++const safeRe = exports.safeRe = [] > + const src = exports.src = [] > + const t = exports.t = {} > + let R = 0 > + > + const createToken = (name, value, isGlobal) => { > ++ // Replace all greedy whitespace to prevent regex dos issues. These regex are > ++ // used internally via the safeRe object since all inputs in this library get > ++ // normalized first to trim and collapse all extra whitespace. The original > ++ // regexes are exported for userland consumption and lower level usage. A > ++ // future breaking change could export the safer regex only with a note that > ++ // all input should have extra whitespace removed. > ++ const safe = value > ++ .split('\\s*').join('\\s{0,1}') > ++ .split('\\s+').join('\\s') > + const index = R++ > + debug(name, index, value) > + t[name] = index > + src[index] = value > + re[index] = new RegExp(value, isGlobal ? 'g' : undefined) > ++ safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) > + } > + > + // The following Regular Expressions can be used for tokenizing, > +diff --git a/package.json b/package.json > +index 204e008..7207703 100644 > +--- a/package.json > ++++ b/package.json > +@@ -37,7 +37,7 @@ > + "range.bnf" > + ], > + "tap": { > +- "check-coverage": true, > ++ "timeout": 30, > + "coverage-map": "map.js", > + "nyc-arg": [ > + "--exclude", > +-- > +2.40.0 > diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb b/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb > index 402cf5671..359ccab54 100644 > --- a/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb > +++ b/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb > @@ -28,6 +28,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ > file://0001-liftoff-Correct-function-signatures.patch \ > file://0001-mips-Use-32bit-cast-for-operand-on-mips32.patch \ > file://run-ptest \ > + file://CVE-2022-25883.patch;patchdir=deps/npm/node_modules/semver \ > " > > SRC_URI:append:class-target = " \ > > -=-=-=-=-=-=-=-=-=-=-=- > Links: You receive all messages sent to this group. > View/Reply Online (#104640): https://lists.openembedded.org/g/openembedded-devel/message/104640 > Mute This Topic: https://lists.openembedded.org/mt/101029114/3616698 > Group Owner: openembedded-devel+owner@lists.openembedded.org > Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [akuster808@gmail.com] > -=-=-=-=-=-=-=-=-=-=-=- >
Hi Armin, CVE-2022-25883 is fixed in 20.x , I will send upgrade to latest 20.x release for master very soon, Regards, Archana
diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch new file mode 100644 index 000000000..1c9daf714 --- /dev/null +++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch @@ -0,0 +1,260 @@ +From 717534ee353682f3bcf33e60a8af4292626d4441 Mon Sep 17 00:00:00 2001 +From: Luke Karrys <luke@lukekarrys.com> +Date: Thu, 15 Jun 2023 12:21:14 -0700 +Subject: [PATCH] fix: better handling of whitespace (#564) + +CVE: CVE-2022-25883 + +Upstream-Status: Backport [https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441] +--- + classes/comparator.js | 3 +- + classes/range.js | 64 ++++++++++++++++------------ + classes/semver.js | 2 +- + functions/coerce.js | 2 +- + internal/re.js | 11 +++++ + package.json | 2 +- + 6 files changed, 53 insertions(+), 31 deletions(-) + +diff --git a/classes/comparator.js b/classes/comparator.js +index 2146c88..3d39c0e 100644 +--- a/classes/comparator.js ++++ b/classes/comparator.js +@@ -16,6 +16,7 @@ class Comparator { + } + } + ++ comp = comp.trim().split(/\s+/).join(' ') + debug('comparator', comp, options) + this.options = options + this.loose = !!options.loose +@@ -133,7 +134,7 @@ class Comparator { + module.exports = Comparator + + const parseOptions = require('../internal/parse-options') +-const { re, t } = require('../internal/re') ++const { safeRe: re, t } = require('../internal/re') + const cmp = require('../functions/cmp') + const debug = require('../internal/debug') + const SemVer = require('./semver') +diff --git a/classes/range.js b/classes/range.js +index d9e866d..53c2540 100644 +--- a/classes/range.js ++++ b/classes/range.js +@@ -26,19 +26,26 @@ class Range { + this.loose = !!options.loose + this.includePrerelease = !!options.includePrerelease + +- // First, split based on boolean or || ++ // First reduce all whitespace as much as possible so we do not have to rely ++ // on potentially slow regexes like \s*. This is then stored and used for ++ // future error messages as well. + this.raw = range +- this.set = range ++ .trim() ++ .split(/\s+/) ++ .join(' ') ++ ++ // First, split on || ++ this.set = this.raw + .split('||') + // map the range to a 2d array of comparators +- .map(r => this.parseRange(r.trim())) ++ .map(r => this.parseRange(r)) + // throw out any comparator lists that are empty + // this generally means that it was not a valid range, which is allowed + // in loose mode, but will still throw if the WHOLE range is invalid. + .filter(c => c.length) + + if (!this.set.length) { +- throw new TypeError(`Invalid SemVer Range: ${range}`) ++ throw new TypeError(`Invalid SemVer Range: ${this.raw}`) + } + + // if we have any that are not the null set, throw out null sets. +@@ -64,9 +71,7 @@ class Range { + + format () { + this.range = this.set +- .map((comps) => { +- return comps.join(' ').trim() +- }) ++ .map((comps) => comps.join(' ').trim()) + .join('||') + .trim() + return this.range +@@ -77,8 +82,6 @@ class Range { + } + + parseRange (range) { +- range = range.trim() +- + // memoize range parsing for performance. + // this is a very hot path, and fully deterministic. + const memoOpts = +@@ -105,9 +108,6 @@ class Range { + // `^ 1.2.3` => `^1.2.3` + range = range.replace(re[t.CARETTRIM], caretTrimReplace) + +- // normalize spaces +- range = range.split(/\s+/).join(' ') +- + // At this point, the range is completely trimmed and + // ready to be split into comparators. + +@@ -203,7 +203,7 @@ const Comparator = require('./comparator') + const debug = require('../internal/debug') + const SemVer = require('./semver') + const { +- re, ++ safeRe: re, + t, + comparatorTrimReplace, + tildeTrimReplace, +@@ -257,10 +257,13 @@ const isX = id => !id || id.toLowerCase() === 'x' || id === '*' + // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 + // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 + // ~0.0.1 --> >=0.0.1 <0.1.0-0 +-const replaceTildes = (comp, options) => +- comp.trim().split(/\s+/).map((c) => { +- return replaceTilde(c, options) +- }).join(' ') ++const replaceTildes = (comp, options) => { ++ return comp ++ .trim() ++ .split(/\s+/) ++ .map((c) => replaceTilde(c, options)) ++ .join(' ') ++} + + const replaceTilde = (comp, options) => { + const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE] +@@ -298,10 +301,13 @@ const replaceTilde = (comp, options) => { + // ^1.2.0 --> >=1.2.0 <2.0.0-0 + // ^0.0.1 --> >=0.0.1 <0.0.2-0 + // ^0.1.0 --> >=0.1.0 <0.2.0-0 +-const replaceCarets = (comp, options) => +- comp.trim().split(/\s+/).map((c) => { +- return replaceCaret(c, options) +- }).join(' ') ++const replaceCarets = (comp, options) => { ++ return comp ++ .trim() ++ .split(/\s+/) ++ .map((c) => replaceCaret(c, options)) ++ .join(' ') ++} + + const replaceCaret = (comp, options) => { + debug('caret', comp, options) +@@ -358,9 +364,10 @@ const replaceCaret = (comp, options) => { + + const replaceXRanges = (comp, options) => { + debug('replaceXRanges', comp, options) +- return comp.split(/\s+/).map((c) => { +- return replaceXRange(c, options) +- }).join(' ') ++ return comp ++ .split(/\s+/) ++ .map((c) => replaceXRange(c, options)) ++ .join(' ') + } + + const replaceXRange = (comp, options) => { +@@ -443,12 +450,15 @@ const replaceXRange = (comp, options) => { + const replaceStars = (comp, options) => { + debug('replaceStars', comp, options) + // Looseness is ignored here. star is always as loose as it gets! +- return comp.trim().replace(re[t.STAR], '') ++ return comp ++ .trim() ++ .replace(re[t.STAR], '') + } + + const replaceGTE0 = (comp, options) => { + debug('replaceGTE0', comp, options) +- return comp.trim() ++ return comp ++ .trim() + .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '') + } + +@@ -486,7 +496,7 @@ const hyphenReplace = incPr => ($0, + to = `<=${to}` + } + +- return (`${from} ${to}`).trim() ++ return `${from} ${to}`.trim() + } + + const testSet = (set, version, options) => { +diff --git a/classes/semver.js b/classes/semver.js +index 99dbe82..e1208fe 100644 +--- a/classes/semver.js ++++ b/classes/semver.js +@@ -1,6 +1,6 @@ + const debug = require('../internal/debug') + const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants') +-const { re, t } = require('../internal/re') ++const { safeRe: re, t } = require('../internal/re') + + const parseOptions = require('../internal/parse-options') + const { compareIdentifiers } = require('../internal/identifiers') +diff --git a/functions/coerce.js b/functions/coerce.js +index 2e01452..febbff9 100644 +--- a/functions/coerce.js ++++ b/functions/coerce.js +@@ -1,6 +1,6 @@ + const SemVer = require('../classes/semver') + const parse = require('./parse') +-const { re, t } = require('../internal/re') ++const { safeRe: re, t } = require('../internal/re') + + const coerce = (version, options) => { + if (version instanceof SemVer) { +diff --git a/internal/re.js b/internal/re.js +index ed88398..f73ef1a 100644 +--- a/internal/re.js ++++ b/internal/re.js +@@ -4,16 +4,27 @@ exports = module.exports = {} + + // The actual regexps go on exports.re + const re = exports.re = [] ++const safeRe = exports.safeRe = [] + const src = exports.src = [] + const t = exports.t = {} + let R = 0 + + const createToken = (name, value, isGlobal) => { ++ // Replace all greedy whitespace to prevent regex dos issues. These regex are ++ // used internally via the safeRe object since all inputs in this library get ++ // normalized first to trim and collapse all extra whitespace. The original ++ // regexes are exported for userland consumption and lower level usage. A ++ // future breaking change could export the safer regex only with a note that ++ // all input should have extra whitespace removed. ++ const safe = value ++ .split('\\s*').join('\\s{0,1}') ++ .split('\\s+').join('\\s') + const index = R++ + debug(name, index, value) + t[name] = index + src[index] = value + re[index] = new RegExp(value, isGlobal ? 'g' : undefined) ++ safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) + } + + // The following Regular Expressions can be used for tokenizing, +diff --git a/package.json b/package.json +index 204e008..7207703 100644 +--- a/package.json ++++ b/package.json +@@ -37,7 +37,7 @@ + "range.bnf" + ], + "tap": { +- "check-coverage": true, ++ "timeout": 30, + "coverage-map": "map.js", + "nyc-arg": [ + "--exclude", +-- +2.40.0 diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb b/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb index 402cf5671..359ccab54 100644 --- a/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb +++ b/meta-oe/recipes-devtools/nodejs/nodejs_18.17.1.bb @@ -28,6 +28,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ file://0001-liftoff-Correct-function-signatures.patch \ file://0001-mips-Use-32bit-cast-for-operand-on-mips32.patch \ file://run-ptest \ + file://CVE-2022-25883.patch;patchdir=deps/npm/node_modules/semver \ " SRC_URI:append:class-target = " \
Versions of the package semver before 7.5.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range. References: https://nvd.nist.gov/vuln/detail/CVE-2022-25883 Upstream patches: https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441 Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com> --- .../nodejs/nodejs/CVE-2022-25883.patch | 260 ++++++++++++++++++ .../recipes-devtools/nodejs/nodejs_18.17.1.bb | 1 + 2 files changed, 261 insertions(+) create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2022-25883.patch