From b1d1256f00254111b96cf6208ce722605a28f89a Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 31 Jul 2026 04:21:13 +0000 Subject: [PATCH 1/2] node:crypto: throw (not return) validation errors from createDiffieHellman Several error paths in constructDiffieHellman() built their errors with createError() and returned them, so crypto.createDiffieHellman(2) evaluated to a TypeError instance rather than throwing one. A constructor returning an object makes that object the result of the new-expression, so the caller saw an Error where a DiffieHellman was expected and no exception was ever raised. Node throws. The bn_g.setWord() failure path also called throwCryptoError() without returning, falling through into DHPointer::New() with a pending exception. Co-authored-by: saklani <50768838+saklani@users.noreply.github.com> --- .../crypto/JSDiffieHellmanConstructor.cpp | 22 ++++++++++----- test/js/node/crypto/node-crypto.test.js | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/jsc/bindings/node/crypto/JSDiffieHellmanConstructor.cpp b/src/jsc/bindings/node/crypto/JSDiffieHellmanConstructor.cpp index b50cd58b2a75..601a2ad376a7 100644 --- a/src/jsc/bindings/node/crypto/JSDiffieHellmanConstructor.cpp +++ b/src/jsc/bindings/node/crypto/JSDiffieHellmanConstructor.cpp @@ -95,7 +95,8 @@ JSC_DEFINE_HOST_FUNCTION(constructDiffieHellman, (JSC::JSGlobalObject * globalOb } if (!generatorValue.isNumber()) { - return JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, "Second argument must be an int32"_s)); + throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "Second argument must be an int32"_s); + return {}; } int32_t generator = 0; @@ -110,7 +111,8 @@ JSC_DEFINE_HOST_FUNCTION(constructDiffieHellman, (JSC::JSGlobalObject * globalOb dh = ncrypto::DHPointer::New(bits, generator); if (!dh) { - return JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid DH parameters"_s)); + throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid DH parameters"_s); + return {}; } } else { @@ -121,12 +123,14 @@ JSC_DEFINE_HOST_FUNCTION(constructDiffieHellman, (JSC::JSGlobalObject * globalOb RETURN_IF_EXCEPTION(scope, {}); if (keyView->byteLength() > INT32_MAX) { - return JSValue::encode(createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, "prime is too big"_s)); + throwError(globalObject, scope, ErrorCode::ERR_OUT_OF_RANGE, "prime is too big"_s); + return {}; } ncrypto::BignumPointer bn_p(reinterpret_cast(keyView->vector()), keyView->byteLength()); if (!bn_p) { - return JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid prime"_s)); + throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid prime"_s); + return {}; } ncrypto::BignumPointer bn_g; @@ -141,18 +145,21 @@ JSC_DEFINE_HOST_FUNCTION(constructDiffieHellman, (JSC::JSGlobalObject * globalOb if (!bn_g.setWord(generator)) { ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__); throwCryptoError(globalObject, scope, ERR_get_error(), "Invalid generator"_s); + return {}; } } else { auto* generatorView = getArrayBufferOrView(globalObject, scope, generatorValue, "generator"_s, genEncodingValue); RETURN_IF_EXCEPTION(scope, {}); if (generatorView->byteLength() > INT32_MAX) { - return JSValue::encode(createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, "generator is too big"_s)); + throwError(globalObject, scope, ErrorCode::ERR_OUT_OF_RANGE, "generator is too big"_s); + return {}; } bn_g = ncrypto::BignumPointer(reinterpret_cast(generatorView->vector()), generatorView->byteLength()); if (!bn_g) { - return JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid generator"_s)); + throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid generator"_s); + return {}; } // A generator too wide for BN_get_word is necessarily >= 2, so only @@ -167,7 +174,8 @@ JSC_DEFINE_HOST_FUNCTION(constructDiffieHellman, (JSC::JSGlobalObject * globalOb dh = ncrypto::DHPointer::New(WTF::move(bn_p), WTF::move(bn_g)); if (!dh) { - return JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid DH parameters"_s)); + throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_VALUE, "Invalid DH parameters"_s); + return {}; } } diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js index 2381da70a7a2..931e1e10edf3 100644 --- a/test/js/node/crypto/node-crypto.test.js +++ b/test/js/node/crypto/node-crypto.test.js @@ -661,6 +661,34 @@ describe("DiffieHellman", () => { expect(() => crypto.createDiffieHellman(p, Buffer.from([0x01]))).toThrow(/bad.generator/i); expect(() => crypto.createDiffieHellman(p, Buffer.from([0x02]))).not.toThrow(); }); + + it("throws (not returns) validation errors from the constructor", () => { + // toThrow() accepts a *returned* Error instance as a throw, so it cannot + // distinguish the two here; capture the control-flow outcome explicitly. + function outcome(fn) { + try { + return { threw: false, value: fn() }; + } catch (e) { + return { threw: true, value: e }; + } + } + + // DHPointer::New rejects a 2-bit modulus; that must surface as a thrown error. + expect(outcome(() => crypto.createDiffieHellman(2))).toEqual({ + threw: true, + value: expect.objectContaining({ code: "ERR_INVALID_ARG_VALUE", message: "Invalid DH parameters" }), + }); + // Numeric sizeOrKey with a non-numeric generator reaches the int32-only guard. + expect(outcome(() => crypto.createDiffieHellman(1024, "abc"))).toEqual({ + threw: true, + value: expect.objectContaining({ code: "ERR_INVALID_ARG_TYPE", message: "Second argument must be an int32" }), + }); + // `new DiffieHellman(...)` must behave identically to the factory. + expect(outcome(() => new crypto.DiffieHellman(2))).toEqual({ + threw: true, + value: expect.objectContaining({ code: "ERR_INVALID_ARG_VALUE" }), + }); + }); }); describe("ECDH", () => { From f250508a3d0d563d192b13eecd2e7ad6e0fef8a1 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 31 Jul 2026 05:15:48 +0000 Subject: [PATCH 2/2] ci: retrigger