Skip to content

[WatchConnectivity] unsafeBitCast reply-handler bridges hide a data race on the captured replied flag #86

@leogdion

Description

@leogdion

Bug

WatchConnectivitySession+WCSessionDelegate.swift (the didReceiveMessage and didReceiveMessageData bridges, around lines 116 and 170) builds a once-only reply guard like:

var replied = false
let safeReply: ([String: Any]) -> Void = { response in
  guard !replied else { return }
  replied = true
  replyHandler(response)
}
let handler = unsafeBitCast(safeReply, to: ConnectivityHandler.self)

The unsafeBitCast launders a non-@Sendable closure with a captured mutable var replied into a @Sendable function type. The compiler would reject this capture in a real @Sendable closure — the cast hides a genuine data race: if the delegate's consumer invokes the handler from another thread while the bridge's auto-acknowledge path checks replied, the flag is read/written unsynchronized, and double-reply (undefined behavior on the sender side) is possible. Function-type unsafeBitCast is also unsupported-layout territory.

Impact today is limited — downstream AtLeast doesn't use the sendMessage path — but the bridge is public API.

Fix direction

Replace the cast with a properly @Sendable closure guarding the flag with a lock (OSAllocatedUnfairLock(initialState: false) or NSLock + captured reference box).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcomponent:watchconnectivitySundialKitConnectivity implementation

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions