CVE-2023-29017: Improper Control of Dynamically-Managed Code Resources (CWE-913)
value: vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules. Prior to version 3.9.15, vm2 was not properly handling host objects passed to `Error.prepareStackTrace` in case of unhandled async errors. A threat actor could bypass the sandbox protections to gain remote code execution rights on the host running the sandbox. This vulnerability was patched in the release of version 3.9.15 of vm2. There are no known workarounds.
CWE description: The product does not properly restrict reading from or writing to dynamically-managed code resources such as variables, objects, classes, attributes, functions, or executable instructions or statements.
CVSS v3 base score: 9.8
Published: 2023-04-06T20:15Z
Repository: https://github.com/patriksimek/vm2
Language: JavaScript
Files changed: lib/setup-sandbox.js
Commit date: 2023-04-06 20:11:02 +0200
Commit message: Wrap host objects passes through prepareStackTrace

Diff: diff --git a/lib/setup-sandbox.js b/lib/setup-sandbox.js
index f6cbb8e..539ce04 100644
--- a/lib/setup-sandbox.js
+++ b/lib/setup-sandbox.js
@@ -274,17 +274,34 @@ if (typeof OriginalCallSite === 'function') {
 			if (wrapped) {
 				currentPrepareStackTrace = wrapped;
 				return;
 			}
 			const newWrapped = (error, sst) => {
+				const sandboxSst = ensureThis(sst);
 				if (localArrayIsArray(sst)) {
-					for (let i=0; i < sst.length; i++) {
-						const cs = sst[i];
-						if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) {
-							sst[i] = new CallSite(cs);
+					if (sst === sandboxSst) {
+						for (let i=0; i < sst.length; i++) {
+							const cs = sst[i];
+							if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) {
+								sst[i] = new CallSite(cs);
+							}
+						}
+					} else {
+						sst = [];
+						for (let i=0; i < sandboxSst.length; i++) {
+							const cs = sandboxSst[i];
+							localReflectDefineProperty(sst, i, {
+								__proto__: null,
+								value: new CallSite(cs),
+								enumerable: true,
+								configurable: true,
+								writable: true
+							});
 						}
 					}
+				} else {
+					sst = sandboxSst;
 				}
 				return value(error, sst);
 			};
 			localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]);
 			localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]);

Vulnerable code: 					for (let i=0; i < sst.length; i++) {
						const cs = sst[i];
						if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) {
							sst[i] = new CallSite(cs);
Fixed code: 				const sandboxSst = ensureThis(sst);
					if (sst === sandboxSst) {
						for (let i=0; i < sst.length; i++) {
							const cs = sst[i];
							if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) {
								sst[i] = new CallSite(cs);
							}
						}
					} else {
						sst = [];
						for (let i=0; i < sandboxSst.length; i++) {
							const cs = sandboxSst[i];
							localReflectDefineProperty(sst, i, {
								__proto__: null,
								value: new CallSite(cs),
								enumerable: true,
								configurable: true,
								writable: true
							});
				} else {
					sst = sandboxSst;