Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-8389 | Kitploit
Tools/GitHubGitHub/crixpwn/cve-2026-8389
Vulnerability AnalysisExploitationWeb Application ExploitationCTFLearning & EducationBinary Exploitation
GitHubcrixpwn/cve-2026-8389

CVE-2026-8389

View Repository
4072 months agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-8389

This vulnerability was intended to be used at Pwn2Own 2026 Berlin, but was patched in version 150.0.3.

SpiderMonkey BaselineJIT pcOffset bitfield truncation on the eager off-thread baseline-compile path, leading to a wrong-but-in-bounds bytecode pc during exception unwinding and a subsequent type confusion.

Summary

RetAddrEntry stores the bytecode offset pcOffset_ as a 28-bit bitfield. The BaselineMaxScriptLength = 0x0fffffff constant defined in the same header is sized to exactly match that bitfield range.

root@kitploit:~
// js/src/jit/BaselineJIT.h:73
static constexpr uint32_t BaselineMaxScriptLength = 0x0fffffffu;

// js/src/jit/BaselineJIT.h:100-105
class RetAddrEntry {
  // Offset from the start of the JIT code where call instruction is.
  uint32_t returnOffset_;

  // The offset of this bytecode op within the JSScript.
  uint32_t pcOffset_ : 28;
root@kitploit:~
// js/src/jit/BaselineJIT.h:141-156 (RetAddrEntry constructor)
RetAddrEntry(uint32_t pcOffset, Kind kind, CodeOffset retOffset)
    : returnOffset_(uint32_t(retOffset.offset())),
      pcOffset_(pcOffset),
      kind_(uint32_t(kind)) {
  MOZ_ASSERT(returnOffset_ == retOffset.offset(),
             "retOffset must fit in returnOffset_");

  // The pc offset must fit in at least 28 bits, since we shave off 4 for
  // the Kind enum.
  MOZ_ASSERT(pcOffset_ == pcOffset);
  static_assert(BaselineMaxScriptLength <= (1u << 28) - 1);
  MOZ_ASSERT(pcOffset <= BaselineMaxScriptLength);

  MOZ_ASSERT(kind < Kind::Invalid);
  MOZ_ASSERT(this->kind() == kind, "kind must fit in kind_ bit field");
}

Before the fix, this upper bound was enforced in release builds only inside CanEnterBaselineJIT (js/src/jit/BaselineJIT.cpp), which is the warmup / OSR main-thread entry path. The eager off-thread baseline-compile path did not pass through that check, so a script whose bytecode exceeds 256 MB could clear baseline compilation while its pcOffset was silently truncated by the 28-bit store (pcOffset & 0x0FFFFFFF). The two informative MOZ_ASSERTs above (pcOffset_ == pcOffset and pcOffset <= BaselineMaxScriptLength) are no-ops in release builds, so the truncation went undetected.

Affected path

The eager off-thread baseline-compile path:

root@kitploit:~
CompilationStencil::instantiateStencils
  -> MaybeDoEagerBaselineCompilations        (js/src/frontend/Stencil.cpp:2720)
    -> DispatchOffThreadBaselineBatchEager    (js/src/jit/BaselineJIT.cpp:386)
      -> BaselineCompileTask::runTask         (js/src/jit/BaselineCompileTask.cpp:69)
        -> BaselineCompile

Pre-patch, MaybeDoEagerBaselineCompilations gated only on script->baselineDisabled() and jit::CanBaselineInterpretScript(script). Neither validates script length, so an over-long script reached baseline compilation through this path.

root@kitploit:~
// js/src/frontend/Stencil.cpp, MaybeDoEagerBaselineCompilations (pre-patch)
    if (script->baselineDisabled()) {
      continue;
    }

    if (!jit::CanBaselineInterpretScript(script)) {
      continue;
    }

By contrast, the main-thread path enforced the bound (this block was later moved into the shared CanBaselineCompileScript):

root@kitploit:~
// js/src/jit/BaselineJIT.cpp, CanEnterBaselineJIT (pre-patch)
  if (script->length() > BaselineMaxScriptLength) {
    script->disableBaselineCompile();
    return Method_CantCompile;
  }

Consequence

The truncated pcOffset is converted back to a bytecode pointer in JSJitFrameIter::baselineScriptAndPc, via RetAddrEntry::pc -> JSScript::offsetToPC.

root@kitploit:~
// js/src/jit/JSJitFrameIter.cpp:155-160
  // address.
  uint8_t* retAddr = resumePCinCurrentFrame();
  const RetAddrEntry& entry =
      script->baselineScript()->retAddrEntryFromReturnAddress(retAddr);
  *pcRes = entry.pc(script);
}
root@kitploit:~
// js/src/jit/BaselineJIT.h:162-164 (RetAddrEntry::pc)
jsbytecode* pc(JSScript* script) const {
  return script->offsetToPC(pcOffset_);
}

That pc is handed to the exception handler HandleExceptionBaseline. Because the truncated offset is smaller than the real script length, offsetToPC returns an in-bounds but incorrect pc, so the failure is silent rather than an obvious out-of-range crash.

root@kitploit:~
// js/src/jit/JitFrames.cpp:584-591
static void HandleExceptionBaseline(JSContext* cx, JSJitFrameIter& frame,
                                    CommonFrameLayout* prevFrame,
                                    ResumeFromException* rfe) {
  MOZ_ASSERT(frame.isBaselineJS());
  MOZ_ASSERT(prevFrame);

  jsbytecode* pc;
  frame.baselineScriptAndPc(nullptr, &pc);

That wrong pc drives incorrect try-note matching during exception unwinding (HandleExceptionBaseline keys on script->trynotes()), leading to an incorrect stack-slot read. A slot that is not a JSObject* can then be treated as one and dispatched through its vtable, producing a type confusion that is the basis for further exploitation.

Download Tool