Vulnerabilities

V8 Turboshaft Removed a Required Initialization Store (CVE-2026-15132)

CVE-2026-15132 lets V8 Turboshaft remove a required FixedArray initialization store under a specific loop-unrolling and store-elimination sequence, allowing the garbage collector to read an uninitialized slot and compromise renderer memory safety.

A hand-drawn three-slot tagged array whose third initialization store disappears just before the garbage collector arrives, while the later write of 42 is too late to repair what the collector observed.
In this article

Research basisThis review covers Chrome's July 8 advisory, fixed V8 commit a90e80ff481a and parent ff4b5e8651c5, review 8005985, the regress-527385397.js test, and official Turboshaft, garbage-collection, embedding, source, and test documentation.

SourceChrome Security / V8 source and regression test / SOSEC source review

The garbage collector stops in front of a newly published three-slot array. The first two cells contain the familiar the_hole sentinel. The third still resembles raw allocator residue. An instruction that will write 42 into that cell exists, but a major collection stands between the missing first write and that later assignment. CVE-2026-15132 lives inside this very small interval: Turboshaft treated a store that first made a tagged slot legal as an ordinary overwrite, then removed it because another write to the same address appeared later.

1 When the collector arrives, the third slot has no identity

1.1 Allocated memory is not yet a finished object

V8 represents a JavaScript array with more than one heap object. The JSArray carries its map, properties, elements pointer and length; a separate FixedArray commonly holds tagged elements. Allocation reserves memory, but reservation alone does not make each word a valid tagged value. Before the object can be published to a point where garbage collection may trace it, every tagged slot must contain a value that the collector can interpret, normally the hole sentinel during array creation.

Turboshaft models that interval explicitly. Allocate<AnyFixedArray>() returns an Uninitialized wrapper. Header writes use InitializeField(). Element writes are expected to use an initialization-aware store. FinishInitialization() is the type-level handoff that says the object may now flow as an ordinary heap object. This is more than naming: store optimizations use initialization and transition metadata to decide which writes may be combined or removed around allocations and observable heap effects.

The vulnerable lowering violated both halves of that contract. It called FinishInitialization() immediately after initializing the header, before a loop filled the elements. The loop then called StoreNonArrayBufferElement(), whose call chain supplied no initialization flag. The generated graph therefore described ordinary stores to an already finished object even though those stores were the only writes making its backing slots safe for tracing.

V8 does not advertise “partly written” as an ordinary heap state. Raw space may remain uninitialized briefly, but once an address becomes reachable, the marking visitor follows the map and interprets every tagged slot without waiting for the remaining stores. The hole value is therefore not cosmetic padding: it separates “JavaScript has no element here” from “the runtime cannot interpret this word.” The first state may remain public; the second belongs only inside a controlled construction interval.

Uninitialized<T> and StoreOp metadata record that interval together. The wrapper tells C++ callers that construction is unfinished, while the graph property tells optimizations that a store establishes legal object state; neither is an automatic barrier. The vulnerable code closed the type state with FinishInitialization() and let the remaining first writes fall back to ordinary-overwrite metadata. The source still appeared to allocate, initialize the header, fill the holes, and return, but the finish point and the final StoreOp attributes disagreed about when the object became valid.

The public evidence fixes both the defect and its limit. The commit states that a missing annotation, loop unrolling, and other optimizations can eliminate initialization stores and expose uninitialized memory to GC; the diff propagates the marker and moves the finish point; the regression places a deterministic collection between the early and late writes. The sources do not describe a general sandbox escape, stable heap shaping, or exploitation observed in the wild. Architecture, allocator state, pointer compression, and the raw bits affect what follows, but a collector reading a word never made into a tagged value is already a renderer memory-safety defect.

A hand-drawn three-slot array remains behind a construction gate until every tagged cell contains a collector-safe value.
Figure 1. Construction completion is an observable lifetime transition. Before the gate opens, every tagged slot in the three-cell backing store must already be safe for the collector to interpret.

1.2 Initialization semantics disappear across five helper layers

MachineLoweringReducer::ReduceNewArray calculates the backing store, writes the map and length, and then fills each element with the hole value. The affected implementation moved Uninitialized<AnyFixedArray> into FinishInitialization() before creating ScopedVar<WordPtr> index and the WHILE loop, so the fill received an ordinary array. The reducer—the layer that still knew the store belonged to construction—discarded information no lower layer could infer.

StoreNonArrayBufferElement() then passed the access, base, index, and value to the private StoreElement(), which derived alignment, representation, barrier kind, header offset, and scale before calling general Store(). The old interface had no way to say that this write was building an object, so maybe_initializing_or_transitioning remained at its default false value. The final graph node exposed an address and an ordinary overwrite, but not the GC invariant being created.

A later same-address store may dominate an ordinary overwrite when no observer needs the old value. GC is the missing observer here: JavaScript never loads the third slot, but the collector interprets it through the map. The initialization store does more than produce a program value; it turns uninterpretable memory into a legal object field. An assignment after collection cannot perform that transition retroactively.

The root cause is therefore not an overaggressive store-elimination pass, and a write barrier cannot replace the missing property. Elimination followed the semantics it received; barriers maintain reference relationships, while initialization metadata describes object lifetime. The break occurred when fresh-object construction borrowed a shared element-store route without carrying its intent. Restoring that intent leaves legitimate elimination of ordinary overwrites intact.

A downstream branch can verify the same route even when its names and templates differ: locate backing-store allocation, the unfinished-to-ordinary type handoff, the helper that emits element writes, and the final operation that controls initialization-aware optimization. Once those four points connect, the next chapter's three iterations, offset 16, and GC stop looking accidental and become a repeatable semantic break.

A hand-drawn five-stage lowering chain carries an unfinished array through header writes, an early finish point, element helpers, and an ordinary store.
Figure 2. The failure is not a missing machine instruction by itself. The phrase “this write finishes object construction” falls back to false while crossing shared helper layers.

2 A three-iteration unroll brings the narrow window under the lamp

2.1 A major collection stands between the early and late stores at offset 16

The upstream regression deliberately shapes the graph in which the missing marker matters. Two XOR trees express the same rotation in different orders. TurboFan leaves their equality unresolved, so length remains a phi between three and four; Turboshaft later recognizes the rotation and folds the length to three. The constant appears at exactly the stage where a loop of three iterations or fewer is fully unrolled.

Unrolling turns the hole fill into fixed-offset stores. The test publishes the array through glob.x, invokes %MajorGCForCompilerTesting(), and then executes arr[2] = 42. In the target pointer-compressed layout, the third FixedArray element sits at offset 16, while four JSArray header stores occupy 0, 4, 8, and 12 and do not confuse the pair.

Without initialization metadata, the optimizer sees the hole write and 42 as ordinary stores to the same address. Removing the early one leaves a globally reachable array with an uninitialized tag when major GC traverses it; the late assignment cannot repair an observation that has already happened. That is how an eliminated store becomes an uninitialized use.

Three and sixteen belong to the regression configuration, not the vulnerability's permanent constants. Three aligns the current unroll threshold with layout separation; sixteen comes from that pointer-compressed object layout. Other architectures or future revisions may change both, so validation recomputes the address while preserving the relationship: the early and late writes target one element, unrelated header stores do not alias it, and GC remains between them.

glob.x is equally necessary. It makes the backing store reachable from a root and avoids a purely local path that scalar replacement or allocation folding could remove. The forced collection then converts a probabilistic real-world observation window into a deterministic sequence that can be inspected repeatedly.

Final machine code can make the missing hole store look like a normal optimization, while the eventual GC fault may point far from the compiler. Reading the graph, publication point, and collection on one timeline reveals that the early write made the object scannable at that moment. The next section explains why the test uses several conspicuous inputs to keep that timeline intact.

A hand-drawn three-iteration loop becomes three fixed writes, with the third early write erased before garbage collection and a later write placed after it.
Figure 3. Unrolling makes the third store easy to compare with the later assignment. The collection between them turns a seemingly redundant early write into a required lifetime transition.

2.2 0x80000000 preserves the feedback fork that shapes the graph

The test first warms foo() with 0x80000000 and then with ordinary small integers. Because that Int32 cannot use Smi representation, feedback must retain both Smi and HeapNumber paths and a phi. Otherwise an earlier representation simplification can rewrite the shift before Turboshaft's rotation matcher proves the XOR trees equivalent. The input is graph-shaping material, not business data.

The rest parameter, two warm-up calls, and %OptimizeFunctionOnNextCall(foo) belong to the same plan. If native syntax is disabled, optimization is off, or the function deoptimizes, the script may return normally without reaching the target tier. Acceptance must show that the length folds to three in the intended stage, the loop fully unrolls, and the V8 build and flags match the result.

The causal chain has two halves. Rotation recognition, constant three, and unrolling create comparable fixed stores; glob.x, major GC, and the later 42 make their incorrect elimination observable. Without the first half the graph never forms. Without the second, collection may still see a valid array.

Durable validation consequently has an outer and an inner result. The optimized invocation must complete correctly, and the graph must still show escape before GC, an initialization-aware third store, and preservation of that store across elimination. Changes to unroll limits, layout, representation, or pass order do not make a green test meaningful unless those preconditions remain true.

Three, sixteen, 0x80000000, and forty-two pin the unroll threshold, element address, mixed feedback, and later overwrite. They are not exploit constants. A port may recompute or replace them, but it must preserve the order “initialization store becomes an elimination candidate, object becomes reachable, GC observes it, late overwrite follows.”

Test-only native functions and forced collection are not network indicators. Ordinary pages do not need to copy those strings, and equivalent JavaScript can be minified or generated dynamically. Searching traffic for the upstream fixture mostly finds tests and says nothing about whether the deployed engine contains the correction.

All of these conditions serve one question: did the missing property let object legalization look like an ordinary overwrite? The patch does not chase the four positioning pins; it restores initialization identity across every length, layout, and address.

Two hand-drawn bit-operation paths merge into one rotation while mixed numeric feedback preserves a three-cell array and its unrolled loop.
Figure 4. The regression is not a random pile of native syntax. Numeric feedback, rotation recognition, and the three-iteration cutoff deliberately shape a graph whose lifecycle mistake can be verified.

3 The patch carries the lost identity all the way down

Code and response. The gap sits in V8 fix a90e80ff481a0689ba1a835f3dbcbf9a078076a5, across commit-pinned src/compiler/turboshaft/assembler.h and machine-lowering-reducer-inl.h: MachineLoweringReducer::ReduceNewArray called FinishInitialization() too early, while element helpers failed to carry maybe_initializing_or_transitioning into the final StoreOp. A permanent repair must both delay the finish point and propagate the initialization property. Desktop Chrome should move to the July 8, 2026 fixed line—150.0.7871.115 on Windows/macOS and 150.0.7871.114 on Linux—while downstream and embedded products require vendor-confirmed equivalent ports and termination of old processes. Until an update is available, restricting untrusted-script entry, shortening sessions, or isolating the product only reduces exposure; disabling JavaScript, matching regression strings, or waiting for crashes does not replace the fix.

3.1 The initializing marker now crosses every store helper

The patch makes StoreNonArrayBufferElement(), typed StoreElement(), and the private element writer accept maybe_initializing_or_transitioning and forward it to low-level Store(). Ordinary writes retain default false. InitializeElement() and InitializeNonArrayBufferElement() explicitly pass true, so construction intent survives the helper stack.

The same change keeps uninitialized_array alive through the element loop, calls the initialization-specific helper on every iteration, and invokes FinishInitialization() only after the loop. Propagation and delayed completion are inseparable: one corrects optimizer metadata, while the other corrects the type-level lifetime.

The commit changes the assembler interface, machine-lowering-reducer-inl.h, and regress-527385397.js. They preserve the semantic property, its timing, and the graph that exposes its absence, and should be treated as one intent. A branch with different templates may use a typed enum or dedicated wrapper, but should neither scatter literal true values nor stop after finding a new parameter in a header.

The diff does not alter the hole value, allocation arithmetic, barrier policy, or optimization switches. Ordinary overwrites remain eliminable, loops still unroll, and the array still escapes. Only the first writes retain their real construction meaning, so the regression's recovery can be attributed directly to restored initialization semantics rather than a disabled performance path.

3.2 The finish gate moves behind the last element write

The corrected reducer keeps the unfinished wrapper throughout the fill and lets the backing store become ordinary only after the last tagged slot is legal. A zero-length array completes after an empty loop; short unrolled arrays and longer retained loops use the same semantics. The repair restores a lifetime invariant rather than special-casing the third slot.

The unfinished type also makes future hazards visible. Publishing the object or inserting a potentially collecting operation during the fill now conflicts with APIs expecting an ordinary heap object. Types do not prove the entire GC protocol, but they turn an implicit construction window into interface state.

Backports should start from two postconditions: every tagged field is legal before the object is traceable, and each store that establishes that state remains initialization-aware through the branch's final memory operation. The type clock at FinishInitialization() and the optimizer clock at the last initializing StoreOp must cross completion only after the final first write.

The evidence lands in two source files. assembler.h accepts and forwards the property, while machine-lowering-reducer-inl.h keeps the array unfinished through the loop. A branch showing only one side still has half a repair.

Parent ff4b5e8651c5b9af8d563a10c515642cd6638dc8 fixes the affected baseline, review 8005985 connects patch history to the landed commit, and position #108296 supplies linear ordering. Product inclusion should still rely on final-hash ancestry or an equivalent port; position, DEPS, and vendor statements supplement that proof according to their strength.

regress-527385397.js landed with the code and preserves why mixed feedback, escape, GC, and the late overwrite must stay together. A branch may rename or reshape the case, but cannot remove those causal roles merely to regain a green result. The requirement is that the early legalizing store survive every observer, not that 42 can never overwrite the hole afterward.

Named initialization APIs leave a durable checkpoint for future element representations, helper refactors, and pass reordering. The design protects the property “first establishes legal state,” so it does not become obsolete when the target moves from a third slot to a fifth.

An unfinished array carries one continuous construction marker through the element-store helpers and crosses the finish gate only after all three slots are filled.
Figure 5. The fix aligns the type lifetime with StoreOp metadata. A downstream branch that ports only one side preserves the original semantic split.

4 The same V8 code wears many product shells

4.1 Chrome Stable provides the first actionable version line

Chrome fixed the V8 uninitialized use in the July 8 desktop stable update. The fixed build is 150.0.7871.115 on Windows and macOS and 150.0.7871.114 on Linux in the same release line. Fleet policy must retain the platform and all four version components rather than copying the Windows patch number to Linux or comparing only major version 150.

Downstream browsers, distributions, and embedders use their own versions and delivery clocks. The CVE range finds candidates; actual repair evidence is V8 revision a90e80ff481a0689ba1a835f3dbcbf9a078076a5, an equivalent port, or an explicit vendor build mapping. A differently numbered product may already contain the fix, while a package labeled Chromium 150 can still precede it.

Public material establishes a renderer memory-safety condition reachable through JavaScript optimization and GC, but no complete exploit chain or in-the-wild use. Optimized JavaScript is ordinary web content, so priority follows code reachability, memory-safety impact, and content exposure rather than public PoC availability. Allocator, architecture, and sandbox details affect later exploitability, not the need to update.

The upstream and product clocks are separate. Review began on June 26, the V8 commit landed on June 29 at position #108296, and the Chrome stable advisory appeared on July 8. Commit ancestry determines the state of a custom snapshot built inside that interval; dates alone cannot. “Built after the patch was discussed” says nothing about whether the final change was merged into the exact source checkout.

Automatic update creates separate installed and running states. Staged rollout, offline devices, and long sessions can leave a fixed package on disk while an old renderer handles content. Inventory should distinguish offline, not downloaded, awaiting relaunch, and running-fixed states, and close only on process version and restart evidence.

Extended Stable, enterprise channels, WebView, mobile products, and specialized derivatives each require platform-specific vendor evidence; desktop numbers must not be extrapolated. An unmapped product remains unknown until its vendor supplies a fixed line or engine revision.

“Contains the affected code” and “patch first” are separate decisions. Code ancestry determines applicability; content source, population, sandboxing, and business role determine order. The next task is therefore to find every process that carries this V8 revision without displaying a Chrome name.

4.2 An outer product version cannot substitute for the embedded engine revision

The first ring is Chrome Stable, Extended Stable, Beta where deployed, Chromium packages, and downstream browsers. Each row records platform, architecture, channel, installed and running builds, update source, and last relaunch. Different 150.x channels cannot be collapsed into one state.

The second ring is Electron, CEF, Qt WebEngine, WebView, and custom desktop shells; the third is services, test tools, plugin hosts, and device interfaces that embed libv8 directly. System Chrome updates do not replace these private engines, and the absence of a browser UI does not exclude them. Extend SBOM entries to Chromium/V8 revisions and the modules actually loaded.

Source builds and equivalent ports need two proofs: uninitialized_array survives the element loop and maybe_initializing_or_transitioning=true reaches the final StoreOp; delivery records name the product build, release date, and restart requirement that carry those properties. When only binaries are available, combine symbols, source manifests, reproducible-build metadata, and vendor statements.

When provenance cannot be resolved, preserve an explicit unknown state. Use version and build date to prioritize investigation, then ask the application owner or supplier for the engine revision. Do not silently convert unknown to safe because a product lacks a Chrome logo. Temporary limits on automatically opening external content can reduce exposure, but the state closes only when a fixed revision is proven or the product is removed.

One application can carry separate engines in its installer, updater, main UI, and plugin host; a container can combine system Chromium, an application download, and a CI cache. Bind inventory to the module each process loaded, and for containers to image digest, cache key, and creation time, so stale directories or rollback cannot silently restore the old library.

Developer systems with Stable, Canary, Playwright or Puppeteer downloads, and Electron dependencies are part of the same estate. Update locks and download configuration as well as current caches, or the next clean installation will recreate the vulnerable engine.

Each evidence item retains engine revision, product build, artifact digest, source URL, and collection time. Vendor pages, about values, and caches change; only a dated structured record can reconstruct the original window and support later scope changes.

One upstream V8 revision enters browsers, desktop shells, test runtimes, and direct embedders on different clocks while a magnifier checks the engine inside each package.
Figure 6. Mapping must run from the upstream V8 revision to the binary that is actually executing. “Based on Chromium 150” is a clue, not a fix certificate.

5 Scattered crash evidence can still be brought back together

5.1 Regroup incidents by GC phase, optimization history, and build

An investigation usually begins with an ordinary renderer termination. Fix the full browser build, V8 revision, system and architecture, pointer-compression state, command-line flags, page or application origin, minidump, and V8 code events before reading the top frame. Without a build, the event cannot be placed around the patch; without origin, repeated failures across devices are hard to connect.

The final fault may occur during marking, compaction, traversal, or much later element access and need not name MachineLoweringReducer. MemorySanitizer, AddressSanitizer, and debug assertions also stop at different points—uninitialized flow, derived access, or object validation. Cluster by engine build, optimized-function history, and GC phase rather than splitting events solely by final address or error class.

A high-value record binds page or application revision, script digest, crash time, browser artifact, symbol source, optimization and deoptimization logs, and dump under existing data-minimization rules. A URL loses changing bundle content; a dump alone may not recover the script or tier.

After confirming an affected build, use the public regression in an isolated matching environment to check symbols, collector path, and fixed-versus-vulnerable behavior. Preserve the valuable artifacts and then update production; an isolated binary, symbols, and captured inputs support continued analysis, while any newly suspected impact remains a hypothesis until evidence establishes it.

Ordinary JavaScript can arrive from the network, local content, or application generation, so there is no dependable traffic signature. Engine provenance and running version prevent exposure; crash detection reconnects evidence and finds rollout gaps. Neither can substitute for the other.

5.2 Quiet telemetry only says that no evidence was captured

Many fleets do not upload renderer dumps, and automatic recovery may retain only an exit code. “No matching crashes in thirty days” first describes sensor coverage, not absence of triggering. Report how many processes submit dumps, which architectures have symbols, whether optimization events survive, and whether embedders use the same pipeline.

Operational metrics should follow facts that can close: affected engines, fixed-package downloads, process relaunches, unknown embedded revisions, and crashes on old builds. They expose “package installed, process old” and “outer version changed, inner engine unknown” more reliably than any attempt to count every JavaScript shape.

Stratify anomalous clusters by build, architecture, origin, and proximity to GC, and separate four moments: function optimization, array publication, GC's first interpretation of the slot, and final failure. Repetition on one affected build near similar scripts and collection phases raises priority but does not alone prove exploitation; lack of repetition does not delay remediation.

Version evidence covers the fleet, while crash evidence raises particular events. Quiet assets share the same wrong semantics, and historical artifacts remain analyzable after runtime upgrades. Telemetry is an echo, not a lock: deployment should not wait for a local indicator.

Crash dumps, garbage-collection phases, optimization graphs, and engine build records converge on one suspicious array slot.
Figure 7. The final fault can be far from the removed initialization store. Engine provenance and GC or optimization context reconnect evidence that instruction-address clustering would split apart.

6 Before a regression passes, prove that it crossed the dangerous graph

6.1 Normal JavaScript completion is only the outermost result

Run test/mjsunit/turboshaft/regress-527385397.js with the V8 harness and required native syntax on every target architecture. Normal completion is only the outer result; logs or graph dumps must also show foo entering the target Turboshaft path, rotation folding in the intended stage, and the three-iteration loop unrolling. Interpretation or deoptimization can otherwise manufacture a false green.

Provenance connects the upstream fix, parent, review, mainline position, and downstream port. The build uses the product's actual GN arguments, pointer compression, architecture, and toolchain and retains compiler version, V8 revision, command, and artifact digest. Test built-ins remain confined to the harness rather than production flags.

The three hole writes must carry the initializing or transitioning property, and the third pre-GC store must remain in the optimized graph; ordinary array overwrites should still receive legitimate store elimination. A differently structured downstream helper stack must preserve the same result: every tagged field becomes legal before traceability, and the writes establishing that property remain initialization-aware.

Delivery joins the tested source, final signed package, rollout batch, and post-restart process into one revision chain. Testing A and shipping B—or installing B while process A survives—means source acceptance never reached the product.

If a legacy target cannot run the upstream harness, execute the regression in a same-source test build and connect the production library through artifact provenance. Name an owner and future date for missing architecture coverage. Source, graph, mjsunit, artifact digest, and process sample should cross-check one another rather than derive from one “tests passed” log.

6.2 Length, architecture, GC, and optimization form the acceptance matrix

Lengths 0, 1, 2, 3, 4, and a clearly longer value cover the empty loop, short unrolls, original window, strategy change, and retained loop. Every case verifies legal tagged slots before publication or collection.

Architecture coverage follows shipped x64, arm64, and other targets. Offset 16 is not portable; each target recomputes the element address from tagged width, pointer compression, and layout, then proves it does not alias header initialization and that the early and late writes remain on opposite sides of GC.

Major GC is the established upstream observer. Young-generation collection, incremental marking, and stress compaction are downstream extensions and do not automatically enlarge the public claim. Turning off unrolling, store elimination, or representation choices can locate causality, but the production build must pass with normal optimization rather than hiding the defect by disabling Turboshaft.

The closure package retains inclusion or port proof, both lifecycle properties, upstream regression and extension matrix, final digest, running-process version, and named omissions. If optimization later changes, these records still explain why every slot was believed legal before collection.

Long-lived branches can add two lightweight guards: prevent unfinished objects from entering ordinary element-store APIs, and assert in a graph test that the initialization-aware pre-GC StoreOp survives. They do not replace end-to-end coverage, but they reveal the exact layer broken by a helper rename, template refactor, or pass reorder.

7 The fix must reach the last renderer still running

7.1 Close the version window while preserving high-value evidence

  1. Set the fixed-build floor. Use Chrome 150.0.7871.115 for Windows and macOS, the corresponding fixed Linux build from the stable advisory, and vendor-confirmed equivalents for downstream products.
  2. Enumerate embedded engines. Include desktop clients, launchers, collaboration tools, IDEs, kiosk shells, test runners and appliances that package Chromium or V8.
  3. Deploy and relaunch. Verify the running process version after update, with special handling for persistent sessions and auto-restarting service wrappers.
  4. Validate source backports. Check delayed FinishInitialization(), initialization-aware element stores and the upstream regression across supported architectures.
  5. Review anomalous renderer crashes. Preserve high-value crash context from vulnerable builds and prioritize GC or optimized-array faults for triage.
  6. Keep revision evidence. Bind vendor package, Chromium position, V8 commit and rollout result to the asset record.

First block installation and rollback below the platform-specific security floor, then connect the Chrome advisory to the V8 source correction. Windows and macOS can use 150.0.7871.115; Linux packages follow their own notices, changelogs, or Chromium/V8 mappings rather than a borrowed Windows number.

Scope follows the processes that execute untrusted or semi-trusted JavaScript, not the default-browser list. Electron, CEF, Qt WebEngine, automation browsers, kiosks, and direct V8 embedders each need a fixed version, vendor evidence, and restart path. External-content reach, population, session length, and process isolation determine order.

Each rollout batch records package and process state before and after maintenance, including executable path, process start, and loaded version. Background updates, surviving tabs, and tray applications can keep the old engine alive; use a full system restart when necessary and verify that every old renderer has left.

When immediate update is impossible, shorter sessions, restricted risky content, and isolation only reduce exposure and need an owner, expiry, and replacement build. Preserve a suspicious dump, exact build, script digest, and timeline, then update normally; isolated copies support later analysis without keeping production vulnerable.

Vendors should name the first fixed product build, release date, Chromium/V8 revision, direct merge or equivalent port, and full-exit requirement. Internal branches must carry assembler metadata, the reducer finish point, and the regression together and document branch-specific substitutions. “Use latest” and a test-only cherry-pick are not closure evidence.

Prioritize high-exposure products, but retain offline laptops, lab images, conference systems, and automation nodes for later batches. Rollback must remain above the security floor; compatibility failures move to a later fixed build, a compatibility patch on the corrected branch, or temporary feature withdrawal, never the vulnerable engine.

7.2 Restart, build proof, and regression results belong to the same asset record

The asset record connects source revision, build artifact, deployment batch, and running process. Official Chrome can use the stable advisory, full version, and managed-device report; internal Chromium adds V8 revision, DEPS, GN configuration, digest, and signature; closed-source embedders mark the claims that depend on supplier evidence.

Regression output binds to the final artifact through source commit, build ID, architecture, critical configuration, command, and result, with another engine-identity check after signing or repackaging. Upstream mjsunit proves the public graph; a product scenario proves real linkage and configuration. A smoke test cannot replace the compiler regression.

Verify runtime twice: once immediately after deployment and again after a normal session to find old renderer, GPU, or utility processes. Disk state is insufficient while an old renderer can still receive content.

The board uses confirmed affected, fixed package available, deployed awaiting restart, runtime verified, revision unresolved, and removed states, plus explicit paths for update failure, long-offline devices, refused relaunch, compatibility blocks, and silent suppliers. Unknown never becomes safe by timeout.

Months later, the record should still show why the product was affected, which revision fixed both lifecycle properties, which package carried it, when it deployed, which processes exited, which architectures passed, and which gaps remained. That is the evidence chain that turns one compiler incident into a reusable release check.

An asset inventory, fixed engine, process restart, three-cell regression, and evidence ledger close into one hand-drawn response loop.
Figure 8. Remediation ends when the running process, engine revision, targeted regression, and asset evidence all agree—not when a package merely reaches disk.

8 The lamp crosses the third slot again, and this time it is not blank

8.1 What the public evidence proves—and what remains unknown

The public record establishes one continuous root-cause chain. The V8 commit explains that a missing initialization annotation, combined with loop unrolling and other optimizations, can eliminate the initialization stores and leave garbage collection observing uninitialized memory. The diff carries that property through the element-store helpers and moves FinishInitialization() behind the fill. The regression then puts length simplification, three-way unrolling, global publication, major collection, and the later overwrite on one execution path. Together, those sources establish the defect without borrowing an unverified exploit narrative.

The Chrome Stable advisory brings that source chain into the product, classifying CVE-2026-15132 as a high-severity uninitialized use in V8 and naming the fixed release line. Affected engines therefore need to leave service quickly. The public material does not provide a complete sandbox escape, persistence mechanism, or stable exploitation method, and it does not report in-the-wild use. That means the available public evidence does not establish those outcomes; it does not prove that exploitation never occurred.

The upstream regression cannot quantify exploit reliability either. It uses a test-only built-in to force major collection at the precise point needed to expose the compiler error; ordinary web content would have to reach an equivalent schedule through normal allocation and collection pressure. The interpretation of the raw word also depends on heap state, tagging, pointer compression, architecture, and build configuration. Those variables add engineering work to stable exploitation, but they do not diminish the established memory-safety risk.

The operational answer is consequently direct: move Chrome and every embedded Chromium or V8 product to a supplier-confirmed fixed build, then fully restart the affected processes. Version, engine-revision, and runtime evidence is safer and more repeatable than an untrusted proof of concept, and it covers assets that have not crashed. If a device has already behaved abnormally, preserve the relevant material before and after the upgrade so later evidence can refine the assessment.

8.2 Construction state must survive until the last first write

Return to the opening scene and the collector has done nothing wrong. It follows a published JSArray to its backing store, reads the map's promise that three tagged elements live there, and interprets each machine word accordingly. The lie occurred earlier: the graph declared construction complete while the third cell still awaited the only write that could make it a legal tag. GC was simply the first observer to collect that unpaid debt.

The optimizer did not randomly become “too aggressive.” Removing an earlier same-address store is correct when a later write is guaranteed and no observer needs the old value. The graph simply failed to say that GC was also an observer and that this early write first made the slot legal. The patch leaves unrolling and store elimination enabled, but carries the Uninitialized state through element filling and finishes construction only after the final cell is valid, restoring the premise those optimizations require.

That yields a durable invariant for both code and tests: when can GC first find the object, which slots must already be legal then, and does the metadata for every first write reach the final memory operation? The three-iteration loop, mixed feedback, global publication, and major collection turn that invariant into a repeatable graph-level observation. Dedicated initialization APIs, parameters, and the regression keep a future helper refactor from relying on “everyone knows this is initialization.” The same review must consider the full observer set, because debuggers, deoptimization, exception paths, and concurrent runtime components may also depend on a continuously valid layout.

The semantic chain has a deployment mirror. The fix must cross the V8 commit, Chromium integration, product packaging, device installation, and process relaunch before it reaches the renderer whose collector actually runs on the endpoint. If the source marker stops before StoreOp, the third cell loses its identity; if the fixed revision stops before the running process, the device continues executing the old logic. Only when the last first write, the product revision, and the live process sit on one timeline does the collector's lamp find an object that remained valid from allocation through publication.

Research record

9Evidence, objects, and sources

The material below preserves the identifiers and references used in this report.

9.1Research objects

Products, actors, techniques, affected objects, and control points discussed in the report.

CVECVE-2026-15132

V8 Turboshaft uninitialized use fixed in Chrome 150 stable

Upstream fixa90e80ff481a0689ba1a835f3dbcbf9a078076a5

Initialization-aware element-store propagation

Key functionMachineLoweringReducer::ReduceNewArray

Backing-store construction and hole fill

Regressionregress-527385397.js

Three-iteration unroll, store elimination and GC coverage

9.2Event chronology

  1. V8 patch authored

    The initialization annotation and regression were prepared for review.

  2. Fix landed in V8 main

    Commit a90e80ff481a reached V8 main at position 108296.

  3. Chrome stable advisory published

    Chrome associated the issue with CVE-2026-15132 and the 150.0.7871.115 security line.

  4. SOSEC completed source review

    The helper propagation, graph preconditions and regression inputs were reproduced from local source objects.

9.3Sources and material

  1. Chrome stable security update, July 8, 2026https://chromereleases.googleblog.com/2026/07/stable-channel-update-for-desktop_01162222768.html
  2. CVE-2026-15132 recordhttps://www.cve.org/CVERecord?id=CVE-2026-15132
  3. V8 fix a90e80ff481a and affected fileshttps://chromium.googlesource.com/v8/v8/+/a90e80ff481a0689ba1a835f3dbcbf9a078076a5
  4. V8 code review 8005985https://chromium-review.googlesource.com/c/v8/v8/+/8005985
  5. Upstream Turboshaft regression testhttps://chromium.googlesource.com/v8/v8/+/a90e80ff481a0689ba1a835f3dbcbf9a078076a5/test/mjsunit/turboshaft/regress-527385397.js
  6. Turboshaft assembler interface at the fixed commithttps://chromium.googlesource.com/v8/v8/+/a90e80ff481a0689ba1a835f3dbcbf9a078076a5/src/compiler/turboshaft/assembler.h
  7. Machine-lowering reducer at the fixed commithttps://chromium.googlesource.com/v8/v8/+/a90e80ff481a0689ba1a835f3dbcbf9a078076a5/src/compiler/turboshaft/machine-lowering-reducer-inl.h
  8. Pre-fix parent commit ff4b5e8651c5https://chromium.googlesource.com/v8/v8/+/ff4b5e8651c5b9af8d563a10c515642cd6638dc8
  9. V8's official Turboshaft architecture explanationhttps://v8.dev/blog/leaving-the-sea-of-nodes
  10. V8's official garbage-collection scheduling articlehttps://v8.dev/blog/free-garbage-collection
  11. Official guide to embedding V8https://v8.dev/docs/embed
  12. Official V8 source checkout guidehttps://v8.dev/docs/source-code
  13. Official V8 test-running guidehttps://v8.dev/docs/test