CVE-2024-27088: Insufficient Information (NVD-CWE-noinfo)
value: es5-ext contains ECMAScript 5 extensions. Passing functions with very long names or complex default argument names into `function#copy` or `function#toStringTokens` may cause the script to stall. The vulnerability is patched in v0.10.63.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-02-26T17:15Z
Repository: https://github.com/medikoo/es5-ext
Language: JavaScript
Files changed: function/#/to-string-tokens.js, package.json, test/function/#/to-string-tokens.js
Commit date: 2024-02-21 16:33:49 +0100
Commit message: fix: Support ES2015+ function definitions in `function#toStringTokens()`

Additionally revert from problematic regex based implementation. Addresses #021

Diff: diff --git a/function/#/to-string-tokens.js b/function/#/to-string-tokens.js
index 4ce026a5..b35f51d4 100644
--- a/function/#/to-string-tokens.js
+++ b/function/#/to-string-tokens.js
@@ -1,17 +1,52 @@
 "use strict";
 
-var validFunction = require("../valid-function");
+var isValue       = require("../../object/is-value")
+  , esniff        = require("esniff")
+  , validFunction = require("../valid-function");
 
-var re1 = /^\s*function[\0-')-\uffff]*\(([\0-(*-\uffff]*)\)\s*\{([\0-\uffff]*)\}\s*$/
-  , re2 = /^\s*\(?([\0-'*-\uffff]*)\)?\s*=>\s*(\{?[\0-\uffff]*\}?)\s*$/;
+var classRe = /^\s*class[\s{/}]/;
 
 module.exports = function () {
-	var str = String(validFunction(this)), data = str.match(re1);
-	if (!data) {
-		data = str.match(re2);
-		if (!data) throw new Error("Unrecognized string format");
-		data[1] = data[1].trim();
-		if (data[2][0] === "{") data[2] = data[2].trim().slice(1, -1);
-	}
-	return { args: data[1], body: data[2] };
+	var str = String(validFunction(this));
+	if (classRe.test(str)) throw new Error("Class methods are not supported");
+
+	var argsStartIndex
+	  , argsEndIndex
+	  , bodyStartIndex
+	  , bodyEndReverseIndex = -1
+	  , shouldTrimArgs = false;
+
+	esniff(str, function (emitter, accessor) {
+		emitter.once("trigger:(", function () { argsStartIndex = accessor.index + 1; });
+		emitter.once("trigger:=", function () {
+			if (isValue(argsStartIndex)) return;
+			argsStartIndex = 0;
+			argsEndIndex = accessor.index;
+			shouldTrimArgs = true;
+			if (!accessor.skipCodePart("=>")) {
+				throw new Error("Unexpected function string: " + str);
+			}
+			accessor.skipWhitespace();
+			if (!accessor.skipCodePart("{")) bodyEndReverseIndex = Infinity;
+			bodyStartIndex = accessor.index;
+		});
+		emitter.on("trigger:)", function () {
+			if (accessor.scopeDepth) return;
+			argsEndIndex = accessor.index;
+			accessor.skipCodePart(")");
+			accessor.skipWhitespace();
+			if (accessor.skipCodePart("=>")) {
+				accessor.skipWhitespace();
+				if (!accessor.skipCodePart("{")) bodyEndReverseIndex = Infinity;
+			} else if (!accessor.skipCodePart("{")) {
+				throw new Error("Unexpected function string: " + str);
+			}
+			bodyStartIndex = accessor.index;
+			accessor.stop();
+		});
+	});
+
+	var argsString = str.slice(argsStartIndex, argsEndIndex);
+	if (shouldTrimArgs) argsString = argsString.trim();
+	return { args: argsString, body: str.slice(bodyStartIndex, bodyEndReverseIndex) };
 };
diff --git a/package.json b/package.json
index b91b917a..aa75130d 100644
--- a/package.json
+++ b/package.json
@@ -23,10 +23,11 @@
 	],
 	"repository": "medikoo/es5-ext",
 	"dependencies": {
 		"es6-iterator": "^2.0.3",
 		"es6-symbol": "^3.1.3",
+		"esniff": "^2.0.1",
 		"next-tick": "^1.1.0"
 	},
 	"devDependencies": {
 		"eslint": "^8.13.0",
 		"eslint-config-medikoo": "^4.1.2",
diff --git a/test/function/#/to-string-tokens.js b/test/function/#/to-string-tokens.js
index 79bb83b3..c7160dfb 100644
--- a/test/function/#/to-string-tokens.js
+++ b/test/function/#/to-string-tokens.js
@@ -8,19 +8,24 @@ module.exports = function (t, a) {
 		body: " return this[a] + this[b]; "
 	});
 	a.deep(t.call(function () {}), { args: "", body: "" });
 	// eslint-disable-next-line no-unused-vars
 	a.deep(t.call(function (raz) {}), { args: "raz", body: "" });
-	a.deep(t.call(function () { Object(); }), { args: "", body: " Object(); " });
+	a.deep(t.call(function () { return {}; }), { args: "", body: " return {}; " });
 
 	try {
-		eval("(() => {})");
+		eval("((foo = bar) => {})");
 	} catch (e) {
 		// Non ES2015 env
 		return;
 	}
 
 	a.deep(t.call(eval("(() => {})")), { args: "", body: "" });
 	a.deep(t.call(eval("((elo) => foo)")), { args: "elo", body: "foo" });
 	a.deep(t.call(eval("(elo => foo)")), { args: "elo", body: "foo" });
 	a.deep(t.call(eval("((elo, bar) => foo())")), { args: "elo, bar", body: "foo()" });
+	a.deep(t.call(eval("foo=>bar")), { args: "foo", body: "bar" });
+	a.deep(t.call(eval("(foo = { marko: (elo) => { someFunct(); } }) => { body(); }")), {
+		args: "foo = { marko: (elo) => { someFunct(); } }",
+		body: " body(); "
+	});
 };

Vulnerable code: var validFunction = require("../valid-function");
var re1 = /^\s*function[\0-')-\uffff]*\(([\0-(*-\uffff]*)\)\s*\{([\0-\uffff]*)\}\s*$/
  , re2 = /^\s*\(?([\0-'*-\uffff]*)\)?\s*=>\s*(\{?[\0-\uffff]*\}?)\s*$/;
	var str = String(validFunction(this)), data = str.match(re1);
	if (!data) {
		data = str.match(re2);
		if (!data) throw new Error("Unrecognized string format");
		data[1] = data[1].trim();
		if (data[2][0] === "{") data[2] = data[2].trim().slice(1, -1);
	}
	return { args: data[1], body: data[2] };
	a.deep(t.call(function () { Object(); }), { args: "", body: " Object(); " });
		eval("(() => {})");
Fixed code: var isValue       = require("../../object/is-value")
  , esniff        = require("esniff")
  , validFunction = require("../valid-function");
var classRe = /^\s*class[\s{/}]/;
	var str = String(validFunction(this));
	if (classRe.test(str)) throw new Error("Class methods are not supported");

	var argsStartIndex
	  , argsEndIndex
	  , bodyStartIndex
	  , bodyEndReverseIndex = -1
	  , shouldTrimArgs = false;

	esniff(str, function (emitter, accessor) {
		emitter.once("trigger:(", function () { argsStartIndex = accessor.index + 1; });
		emitter.once("trigger:=", function () {
			if (isValue(argsStartIndex)) return;
			argsStartIndex = 0;
			argsEndIndex = accessor.index;
			shouldTrimArgs = true;
			if (!accessor.skipCodePart("=>")) {
				throw new Error("Unexpected function string: " + str);
			}
			accessor.skipWhitespace();
			if (!accessor.skipCodePart("{")) bodyEndReverseIndex = Infinity;
			bodyStartIndex = accessor.index;
		});
		emitter.on("trigger:)", function () {
			if (accessor.scopeDepth) return;
			argsEndIndex = accessor.index;
			accessor.skipCodePart(")");
			accessor.skipWhitespace();
			if (accessor.skipCodePart("=>")) {
				accessor.skipWhitespace();
				if (!accessor.skipCodePart("{")) bodyEndReverseIndex = Infinity;
			} else if (!accessor.skipCodePart("{")) {
				throw new Error("Unexpected function string: " + str);
			}
			bodyStartIndex = accessor.index;
			accessor.stop();
		});
	});

	var argsString = str.slice(argsStartIndex, argsEndIndex);
	if (shouldTrimArgs) argsString = argsString.trim();
	return { args: argsString, body: str.slice(bodyStartIndex, bodyEndReverseIndex) };
		"esniff": "^2.0.1",
	a.deep(t.call(function () { return {}; }), { args: "", body: " return {}; " });
		eval("((foo = bar) => {})");
	a.deep(t.call(eval("foo=>bar")), { args: "foo", body: "bar" });
	a.deep(t.call(eval("(foo = { marko: (elo) => { someFunct(); } }) => { body(); }")), {
		args: "foo = { marko: (elo) => { someFunct(); } }",
		body: " body(); "
	});