CVE-2021-35065: Inefficient Regular Expression Complexity (CWE-1333)
value: The glob-parent package before 6.0.1 for Node.js allows ReDoS (regular expression denial of service) attacks against the enclosure regular expression.
CWE description: The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles.
CVSS v3 base score: 7.5
Published: 2022-12-26T07:15Z
Repository: https://github.com/gulpjs/glob-parent
Language: JavaScript
Files changed: index.js, test/index.test.js
Commit date: 2021-07-19 22:46:33 -0700
Commit message: fix: Resolve ReDoS vulnerability from CVE-2021-35065 (#49)


Diff: diff --git a/index.js b/index.js
index f861468..7e61d6e 100644
--- a/index.js
+++ b/index.js
@@ -4,11 +4,10 @@ var isGlob = require('is-glob');
 var pathPosixDirname = require('path').posix.dirname;
 var isWin32 = require('os').platform() === 'win32';
 
 var slash = '/';
 var backslash = /\\/g;
-var enclosure = /[{[].*\/.*[}\]]$/;
 var globby = /(^|[^\\])([{[]|\([^)]+$)/;
 var escaped = /\\([!*?|[\](){}])/g;
 
 /**
  * @param {string} str
@@ -22,11 +21,11 @@ module.exports = function globParent(str, opts) {
   if (options.flipBackslashes && isWin32 && str.indexOf(slash) < 0) {
     str = str.replace(backslash, slash);
   }
 
   // special case for strings ending in enclosure containing path separator
-  if (enclosure.test(str)) {
+  if (isEnclosure(str)) {
     str += slash;
   }
 
   // preserves full path in case of trailing path separator
   str += 'a';
@@ -37,5 +36,29 @@ module.exports = function globParent(str, opts) {
   } while (isGlob(str) || globby.test(str));
 
   // remove escape chars and return result
   return str.replace(escaped, '$1');
 };
+
+
+function isEnclosure(str) {
+  var lastChar = str.slice(-1)
+
+  var enclosureStart;
+  switch (lastChar) {
+    case '}':
+      enclosureStart = '{';
+      break;
+    case ']':
+      enclosureStart = '[';
+      break;
+    default:
+      return false;
+  }
+
+  var foundIndex = str.indexOf(enclosureStart);
+  if (foundIndex < 0) {
+    return false;
+  }
+
+  return str.slice(foundIndex + 1, -1).includes(slash);
+}
diff --git a/test/index.test.js b/test/index.test.js
index 9cd8eb5..4769a74 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -222,10 +222,28 @@ describe('glob2base test patterns', function () {
     // This will time out if susceptible.
     gp('{' + '/'.repeat(5000));
 
     done();
   });
+
+  it('should finish in reasonable time for \'{\' + \'/\'.repeat(n) [CVE-2021-35065]', function(done) {
+    this.timeout(1000);
+    gp('{' + '/'.repeat(500000));
+    done();
+  });
+
+  it('should finish in reasonable time for \'{\'.repeat(n)', function(done) {
+    this.timeout(1000);
+    gp('{'.repeat(500000));
+    done();
+  });
+
+  it('should finish in reasonable time for \'(\'.repeat(n)', function(done) {
+    this.timeout(1000);
+    gp('('.repeat(500000));
+    done();
+  });
 });
 
 if (isWin32) {
   describe('technically invalid windows globs', function () {
     it('should manage simple globs with backslash path separator', function (done) {

Vulnerable code: var enclosure = /[{[].*\/.*[}\]]$/;
  if (enclosure.test(str)) {
Fixed code:   if (isEnclosure(str)) {


function isEnclosure(str) {
  var lastChar = str.slice(-1)

  var enclosureStart;
  switch (lastChar) {
    case '}':
      enclosureStart = '{';
      break;
    case ']':
      enclosureStart = '[';
      break;
    default:
      return false;
  }

  var foundIndex = str.indexOf(enclosureStart);
  if (foundIndex < 0) {
    return false;
  }

  return str.slice(foundIndex + 1, -1).includes(slash);
}

  it('should finish in reasonable time for \'{\' + \'/\'.repeat(n) [CVE-2021-35065]', function(done) {
    this.timeout(1000);
    gp('{' + '/'.repeat(500000));
    done();
  });

  it('should finish in reasonable time for \'{\'.repeat(n)', function(done) {
    this.timeout(1000);
    gp('{'.repeat(500000));
    done();
  });

  it('should finish in reasonable time for \'(\'.repeat(n)', function(done) {
    this.timeout(1000);
    gp('('.repeat(500000));
    done();
  });