From 501eadf6375cbb1373f49ae0e905d443e58fc435 Mon Sep 17 00:00:00 2001 From: Carl Smedstad Date: Sat, 4 Apr 2026 17:47:48 +0200 Subject: [PATCH 1/2] Fix UB in IntegralTypedArrayAdaptor::toNativeFromDouble() static_cast(double) is undefined behavior in C++ when the value is outside [INT32_MIN, INT32_MAX]. The subsequent range check can be legally optimized away by the compiler, causing values > 2^31 stored to typed arrays to silently become 0x80000000 (INT32_MIN). This breaks pure-JS crypto libraries (@noble/hashes, better-auth, etc.) that rely on typed array correctness for bitwise operations. Always use toInt32() unconditionally, matching the existing ARM (FJCVTZS) code path. toInt32() is ALWAYS_INLINE and implements proper ECMA-262 ToInt32 semantics via bit manipulation. First detected on Arch Linux since version 1.3.11, built with Clang 22, triggered the UB. References: - https://github.com/oven-sh/bun/issues/28607 - https://gitlab.archlinux.org/archlinux/packaging/packages/bun/-/commit/e6f882ca --- Source/JavaScriptCore/runtime/TypedArrayAdaptors.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Source/JavaScriptCore/runtime/TypedArrayAdaptors.h b/Source/JavaScriptCore/runtime/TypedArrayAdaptors.h index b6c443ca1833..4dd85f48d8b1 100644 --- a/Source/JavaScriptCore/runtime/TypedArrayAdaptors.h +++ b/Source/JavaScriptCore/runtime/TypedArrayAdaptors.h @@ -68,14 +68,7 @@ struct IntegralTypedArrayAdaptor { static Type toNativeFromDouble(double value) { -#if HAVE(FJCVTZS_INSTRUCTION) return static_cast(toInt32(value)); -#else - int32_t result = static_cast(value); - if (static_cast(result) != value) - result = toInt32(value); - return static_cast(result); -#endif } static constexpr Type toNativeFromUndefined() From 3f10db7949c164275691d6296e0237303deac999 Mon Sep 17 00:00:00 2001 From: Carl Smedstad Date: Sat, 4 Apr 2026 18:41:25 +0200 Subject: [PATCH 2/2] toInt32: add x86_64 fast path using cvttsd2si Use inline asm to avoid UB from static_cast(double), falling back to toIntImpl when truncation isn't exact. Co-Authored-By: Claude Opus 4.6 (1M context) --- Source/JavaScriptCore/runtime/MathCommon.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/JavaScriptCore/runtime/MathCommon.h b/Source/JavaScriptCore/runtime/MathCommon.h index b049b32a1ef6..10204adb4461 100644 --- a/Source/JavaScriptCore/runtime/MathCommon.h +++ b/Source/JavaScriptCore/runtime/MathCommon.h @@ -182,6 +182,12 @@ ALWAYS_INLINE int32_t toInt32(double number) int32_t result = 0; __asm__ ("fjcvtzs %w0, %d1" : "=r" (result) : "w" (number) : "cc"); return result; +#elif CPU(X86_64) && COMPILER(GCC_COMPATIBLE) + int32_t result; + __asm__ ("cvttsd2si %1, %0" : "=r" (result) : "x" (number)); + if (static_cast(result) != number) + return toIntImpl(number); + return result; #else return toIntImpl(number); #endif