5e2667f6b1 [JSC] Report Baseline Int32 Overflow in DFG
Triage note: Feeds observed overflow into DFG flags so speculation accounts for it; overflow-profiling soundness fix affecting JIT correctness.
Contents
The bug at a glance
This is a soundness fix in JSC’s DFG bytecode parser: baseline-observed Int32 overflow was not being propagated into DFG node flags, so DFG speculation reasoned from incomplete profiling. Mis-modeled overflow in an optimizing JIT can lead to incorrect range/type assumptions and, in the worst case, memory-safety issues, but the patch is a one-line flag propagation with no PoC and no stated exploit, so medium severity reflects a real correctness/soundness defect of uncertain exploitability.
In ByteCodeParser::makeSafe, DFG already translated several baseline ArithProfile observations into node flags (double result, non-numeric, BigInt32). It was missing one: didObserveInt32Overflow was not mapped to NodeMayOverflowInt32InBaseline. The fix adds exactly that merge so DFG’s overflow speculation accounts for overflow the baseline tier actually saw.
Root cause
When the DFG compiles an arithmetic bytecode operation, ByteCodeParser::makeSafe consults the baseline ArithProfile (the observed object) that the lower JIT tiers recorded for that operation, and translates observations into node flags that guide DFG speculation and overflow handling. Before the patch makeSafe merged NodeMayHaveDoubleResult (from didObserveDouble-style state), NodeMayHaveNonNumericResult (didObserveNonNumeric), and NodeMayHaveBigInt32Result (didObserveBigInt32), and consulted exit-profile sites for BigInt32Overflow, but it never consulted observed.didObserveInt32Overflow().
NodeMayOverflowInt32InBaseline is the flag that tells DFG the operation was observed to overflow the Int32 range in the baseline tier. DFG uses the family of overflow flags to decide whether an arithmetic node can be compiled with unchecked Int32 arithmetic, whether it must carry an overflow check that triggers OSR exit, and how to model the node’s result range/type in later phases (e.g. integer range optimization, constant folding, and result-type propagation). Omitting the baseline-overflow signal means DFG could conclude an operation stays within Int32 when the baseline had already witnessed it overflowing, i.e. it reasoned from strictly incomplete profiling data.
The consequence of missing this flag is that DFG’s model of overflow can be less conservative than reality. Depending on how downstream phases consume the overflow flags, this manifests as at least correctness divergence (wrong numeric results / unexpected OSR-exit churn); and in optimizing JITs, an over-optimistic ‘cannot overflow’ conclusion feeding a range or type assumption is the classic seed for a speculation-soundness memory-safety bug if a later consumer elides a bounds/overflow guard on that basis. The patch is deliberately minimal, if (observed.didObserveInt32Overflow()) node->mergeFlags(NodeMayOverflowInt32InBaseline);, restoring the missing edge from baseline profiling to DFG flags so speculation is at least as conservative as what baseline observed. The absence of a layout test or PoC in the commit, and the terse ‘We were not reporting … Let’s just do it’, indicate this was found by inspection/audit rather than via a demonstrated crash.
Key code
Propagating baseline-observed Int32 overflow into the DFG node flags (makeSafe)
if (observed.didObserveNonNumeric())
node->mergeFlags(NodeMayHaveNonNumericResult);
if (observed.didObserveInt32Overflow())
node->mergeFlags(NodeMayOverflowInt32InBaseline);
if (observed.didObserveBigInt32())
node->mergeFlags(NodeMayHaveBigInt32Result);
Patch walkthrough
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp— In ByteCodeParser::makeSafe, addsif (observed.didObserveInt32Overflow()) node->mergeFlags(NodeMayOverflowInt32InBaseline);, placed alongside the existing merges for NodeMayHaveDoubleResult, NodeMayHaveNonNumericResult, and NodeMayHaveBigInt32Result. This propagates the baseline tier’s observed Int32 overflow into the DFG node’s flags so overflow speculation is not blind to it.
Background
DFG makeSafe / ArithProfile — makeSafe post-processes a freshly created DFG arithmetic node by folding in observations recorded by lower tiers into an ArithProfile (the observed value). These observations (double results, non-numeric operands, BigInt, and overflow) tell DFG what runtime behavior actually occurred so it can speculate accurately.
NodeMayOverflowInt32InBaseline — A DFG node flag meaning the operation was observed to overflow Int32 in the baseline tier. It contributes to how DFG decides between checked and unchecked Int32 arithmetic and how it models the result’s range/type; without it set, DFG can wrongly assume the operation stays in range.
Overflow speculation and OSR exit — DFG compiles arithmetic optimistically and inserts overflow checks that OSR-exit to a lower tier when reality violates the assumption. Correct overflow flags are what make these checks present when needed; a missing flag can lead to an assumption that never gets a guard, or to profiling-driven exit thrash.
Speculation-soundness as a security property — In optimizing JITs, incorrect type/range assumptions are a well-known path to memory-safety bugs: if a downstream phase elides a bounds or overflow check because it ‘proved’ a value cannot overflow, an actually-overflowing value can corrupt indices or lengths. Keeping profiling propagation complete is a soundness prerequisite.
Baseline vs DFG tiers — JSC tiers up from LLInt/Baseline to DFG/FTL. Baseline collects runtime profiles; DFG consumes them. Any profile bit the baseline records but DFG fails to read is a blind spot in the optimizer’s model of the program.
Vulnerability window
- Baseline observation — The baseline tier executes an arithmetic op that overflows Int32 and records didObserveInt32Overflow in its ArithProfile.
- Tier-up — The function tiers up to DFG and makeSafe reads the ArithProfile to set node flags.
- Missed signal — Pre-patch, makeSafe never mapped didObserveInt32Overflow to NodeMayOverflowInt32InBaseline, so DFG speculated without the observed-overflow fact.
- Divergence — DFG’s overflow/range/type model may be less conservative than baseline reality, risking incorrect optimization decisions.
- Fix — makeSafe now merges NodeMayOverflowInt32InBaseline when overflow was observed, restoring complete profiling propagation.
Triggering
No test or PoC was added. Conceptually the bug is triggered by an arithmetic operation whose operands overflow Int32 during baseline execution (e.g. repeatedly executing an add/mul with large integer operands until it overflows) so the baseline ArithProfile records the overflow, followed by tiering the containing function up to DFG. Pre-patch DFG would then optimize as if no baseline overflow had been observed. The commit provides no demonstration that this produces a memory-safety violation, so any exploit claim is inferred.
Exploitation
- Profiling setup — Attacker JS drives a hot arithmetic op with operands that overflow Int32 so baseline records the overflow, then forces DFG tier-up.
- Optimizer mismodel — Pre-patch DFG lacks the NodeMayOverflowInt32InBaseline flag and may derive an over-optimistic range/type or overflow assumption for the node.
- Escalation (inferred / crash-only at most) — Whether the mismodel becomes exploitable depends on a downstream phase eliding a guard based on the wrong assumption; the patch gives no evidence of a concrete corruption primitive, so treat this as a soundness/correctness fix whose worst-case is a speculative memory-safety issue, not a demonstrated one.
Detection & hunting
For defenders and SOC / detection engineers:
- DFG overflow-assumption divergence — In JIT differential/stress testing, compare DFG results and OSR-exit behavior against baseline for integer-overflowing arithmetic; discrepancies flag missing or wrong overflow flag propagation.
- Unexpected OSR-exit or recompile churn — Elevated overflow-related OSR exits on functions with integer arithmetic can indicate DFG assumptions that contradict baseline observations.
- Audit of makeSafe flag coverage — Static review that every ArithProfile observation predicate has a corresponding mergeFlags in makeSafe; a missing pairing (as here) is the detectable defect.
Audit directions
- makeSafe / makeDivSafe completeness — Verify every ArithProfile observe-predicate (didObserve*) is consumed by makeSafe and any sibling helpers, and that no other baseline-observed bit is dropped on the way to DFG flags.
- Consumers of NodeMayOverflowInt32InBaseline — Trace all DFG/FTL phases that read the Int32 overflow flags (range analysis, integer range optimization, arithmetic strength reduction) to confirm they behave correctly now that the flag can be set where it previously was not.
- Exit-profile vs observed-profile parity — Compare how overflow is sourced from exit-profile hasExitSite versus the observed ArithProfile (as is done for BigInt32Overflow) to ensure Int32 overflow is handled consistently across both.
- Other tiers’ profile propagation — Check that FTL and inlining paths inherit the corrected overflow flags and that inline-stack profiling merges preserve baseline-observed overflow.