37310f2c57 Reject INT32_MIN in Swift BackForwardList
Triage note: Hardens an integer overflow on an attacker-controlled IPC delta used to index the back-forward list.
Contents
The bug at a glance
A web-process-controlled Int32 delta reaches a UIProcess back/forward index computation. In Swift, signed overflow (and negating INT32_MIN) traps rather than silently wrapping, so the realistic worst case here is a deterministic UIProcess crash / denial of service driven from a compromised or misbehaving WebContent process, not silent memory corruption. It is the Swift port of an already-fixed C++ hardening (314581@main), so it is defense-in-depth on an attacker-reachable integer boundary; medium is appropriate.
The BackForwardItemAtIndexForWebContent IPC lets the web process pass an arbitrary Int32 delta that the UIProcess adds to currentIndex to index the back/forward list. INT32_MIN is the pathological input: its negation overflows and currentIndex + delta can overflow the signed range, so the range check must be done without negating and with explicit overflow reporting.
Root cause
WebBackForwardList (the Swift reimplementation of WKBackForwardList) exposes an IPC entry point backForwardItemAtIndexForWebContent that the WebContent process calls to resolve a history item at a signed offset from the current entry. The delta is Int32 and fully attacker-controlled by any process on that IPC connection.
Internally the request flows through itemAtDeltaFromCurrentIndex(delta:allowSkipping:). Before the patch the bounds check was written as if currentIndex + delta < 0 { return nil }. Two problems: the addition itself is a signed operation that can overflow, and (per the C++ counterpart’s description) the surrounding logic negates the delta in signed arithmetic to walk the list — and -INT32_MIN is not representable. In Swift, both a signed overflow on + and the overflow on negation are trapping operations, so a crafted delta turns a history query into a UIProcess abort.
The patch closes this on two levels. First, in the IPC handler it rejects the single unrepresentable value up front: using messageCheckCompletion with the predicate delta != Int32.min, returning an empty RefPtrFrameState completion and bailing when the check fails, so INT32_MIN never reaches the arithmetic. Second, it rewrites the bound check inside itemAtDeltaFromCurrentIndex to let (targetIndex, overflow) = currentIndex.addingReportingOverflow(delta); if overflow || targetIndex < 0 { return nil } — computing the target index without ever negating delta and treating any overflow as an out-of-range rejection rather than a trap.
The patch also had to extend IPC plumbing: backForwardItemAtIndexForWebContent now takes both a connection: IPC.Connection (used via WebKit.WebProcessProxy.fromConnection to run messageCheckCompletion / terminate a misbehaving process) and a completion handler. This is the first Swift IPC handler combining both, which required fixing HandleMessage.h so handleMessageSynchronous forwards ValidationType::makeConnectionArgument(connection) (with SUPPRESS_UNCOUNTED_ARG) into callMemberFunction for the expectsConnectionArgument case.
Key code
Overflow-safe range check plus the INT32_MIN rejection in the IPC handler (WebBackForwardList.swift).
let (targetIndex, overflow) = currentIndex.addingReportingOverflow(delta)
if overflow || targetIndex < 0 {
return nil
}
// ... in backForwardItemAtIndexForWebContent:
let process = WebKit.WebProcessProxy.fromConnection(connection)
if messageCheckCompletion(
process: process,
completionHandler: { completionHandler.pointee(consuming: WebKit.RefPtrFrameState()) },
delta != Int32.min
) {
return
}
Patch walkthrough
Source/WebKit/UIProcess/WebBackForwardList.swift— Replaces the overflow-prone if currentIndex + delta < 0 test in itemAtDeltaFromCurrentIndex with addingReportingOverflow, rejecting on either overflow or a negative target. Adds a connection: IPC.Connection parameter to backForwardItemAtIndexForWebContent and an up-front messageCheckCompletion that rejects delta == Int32.min (completing with an empty RefPtrFrameState) before any arithmetic runs.Source/WebKit/Platform/IPC/HandleMessage.h— Fixes handleMessageSynchronous so a synchronous Swift handler that expects both a connection argument and a completion handler is dispatched correctly: it now passes ValidationType::makeConnectionArgument(connection) into callMemberFunction (guarded by SUPPRESS_UNCOUNTED_ARG) and includes <wtf/Compiler.h>. Without this, the new connection+completion handler shape could not be wired up.
Background
Back/forward list IPC — The session history lives in the UIProcess; WebContent asks for items relative to the current position through IPC such as BackForwardItemAtIndexForWebContent(delta). Because delta crosses a trust boundary from a potentially compromised renderer, any arithmetic on it in the UIProcess must be robust against adversarial extremes.
Swift trapping arithmetic — Unlike C++, Swift’s default +, -, and unary negation trap on overflow, converting undefined behavior into a deterministic runtime crash. This makes silent memory corruption unlikely but turns an unhandled overflow into a reliable denial-of-service; overflow-reporting operators like addingReportingOverflow are the safe idiom.
INT32_MIN asymmetry — Two’s-complement Int32 ranges from -2147483648 to 2147483647, so -INT32_MIN (2147483648) is not representable. Any code that negates a signed delta to walk history in the opposite direction crashes precisely on this value, which is why the handler special-cases delta != Int32.min.
messageCheckCompletion / WebProcessProxy.fromConnection — WebKit’s message-check pattern validates attacker-supplied IPC arguments and, on failure, completes the reply safely and can terminate the offending process (resolved from the connection). Porting it to Swift required the handler to receive the IPC.Connection, which is what forced the HandleMessage.h dispatch fix.
Cross-language IPC hardening parity — This change is explicitly the Swift counterpart of 314581@main (a6cd3ca) for the C++ WebBackForwardList. As WebKit migrates UIProcess components to Swift, security fixes previously applied to C++ must be re-applied to the Swift reimplementations or the boundary regresses.
Vulnerability window
- C++ fix — 314581@main (a6cd3ca) rejected INT32_MIN in the C++ BackForwardItemAtIndexForWebContent handler and reworked itemAtDeltaFromCurrentIndex to range-check without negating the delta.
- Swift reimplementation gap — The Swift WebBackForwardList retained the naive currentIndex + delta < 0 check and lacked the INT32_MIN rejection, reopening the trapping-overflow window on the migrated code path.
- Reported — Tracked as webkit.org/b/317198 (rdar://179805504), assigned to Adrian Taylor, reviewed by Geoffrey Garen.
- Plumbing prerequisite — Implementing the fix surfaced that no Swift IPC handler had yet taken both a connection and a completion handler, requiring a fix to handleMessageSynchronous in HandleMessage.h.
- Fix landed — Commit ports both the INT32_MIN rejection and the addingReportingOverflow range check to Swift; canonical 315459@main.
Triggering
No test is included in the patch. Trigger: from a compromised or fuzzing WebContent process, send the BackForwardItemAtIndexForWebContent IPC with delta = INT32_MIN (-2147483648) and any frameID. On a pre-patch Swift UIProcess the delta reaches itemAtDeltaFromCurrentIndex, where negation/addition of INT32_MIN triggers a Swift arithmetic trap, aborting the UIProcess (browser-wide crash / DoS). Confirmed impact is crash-only.
Exploitation
- Precondition — Attacker controls a WebContent process (e.g. via a prior renderer compromise) able to issue history IPC to the UIProcess over its connection.
- Trigger — Invoke BackForwardItemAtIndexForWebContent with delta = INT32_MIN so the value flows into the UIProcess back/forward index arithmetic.
- Impact — The signed overflow / negation of INT32_MIN traps in Swift, aborting the UIProcess. Because the UIProcess hosts the whole browser session, this is a full-browser denial of service rather than a per-tab crash.
- No memory-corruption path (honest) — Swift’s trapping semantics mean this does not yield a controllable OOB index or write; the security value of the fix is eliminating the attacker-triggerable UIProcess abort and matching the C++ hardening, not stopping a corruption primitive.
Detection & hunting
For defenders and SOC / detection engineers:
- UIProcess Swift arithmetic-overflow traps — Crash reports showing a Swift fatal error (Swift.arithmeticOverflow / negate) on the WebBackForwardList / itemAtDeltaFromCurrentIndex path, especially with a history IPC in the backtrace, indicate an attempt or bug on this boundary.
- Extremal delta values in history IPC — Monitor BackForwardItemAtIndexForWebContent for delta values at or near Int32 extremes (INT32_MIN, INT32_MAX); legitimate navigation deltas are small, so extremes suggest fuzzing or exploitation.
- messageCheck rejections — After the fix, a spike in the new delta != Int32.min message-check failures (and any resulting process termination) flags a process sending malformed history requests.
Audit directions
- Swift IPC handlers ported from C++ — Systematically diff Swift UIProcess reimplementations against their C++ originals to find security hardening (bounds checks, message checks, INT32_MIN/overflow guards) that was not carried over.
- Signed arithmetic on IPC-supplied integers — Audit UIProcess code that adds, subtracts, or negates attacker-supplied signed deltas/indices; require addingReportingOverflow-style checks and explicit rejection of unrepresentable extremes rather than naive comparisons.
- New connection+completion IPC handlers — Now that handleMessageSynchronous supports Swift handlers taking both a connection and a completion handler, review other handlers adopting this shape to ensure makeConnectionArgument wiring and process-termination-on-failure behave as intended.
- allowSkipping / negation paths — Review the itemAtDeltaFromCurrentIndex allowSkipping branch and any other place that negates delta to walk history for residual trapping-negation hazards on INT32_MIN.