fix(ios): prevent extension crash from late Go callbacks during network flapping#142
fix(ios): prevent extension crash from late Go callbacks during network flapping#142pappz wants to merge 1 commit into
Conversation
…rk flapping The packet tunnel extension crash-loops (EXC_BAD_ACCESS, KERN_INVALID_ADDRESS at 0x28) on wifi<->cellular path flapping: it connects briefly, then the extension is killed and relaunched every ~6-12s, so no IP/device name renders and iOS never activates the VPN (T-808). Root cause (code-derived; not yet confirmed against a symbolicated build-7 crash because no dSYM was available): the Go engine retains NetworkChangeListener and DNSManager for the lifetime of the SDK client they were created with. When the adapter is swapped/torn down (profile switch, or a restart triggered by flapping) the old client can still fire a late onNetworkChanged/applyDns into the now-stale listener, which dereferences a tunnel manager whose owning provider has already been deallocated -> use-after-free. Fix: make the Go-held listeners null-safe and explicitly detachable. - NetworkChangeListener / DNSManager: tunnelManager is now optional; every Go callback runs on a serial queue behind a guard (isValid + non-nil tunnelManager). Late/in-flight callbacks become no-ops instead of crashing. Added invalidate() to detach, serialized on the same queue. - NetBirdAdapter: added invalidateListeners(). Deliberately NOT called from stop()/restart (the same adapter is reused there and must keep delivering callbacks) -- only when the adapter is actually discarded. - PacketTunnelProvider: invalidate the outgoing adapter's listeners before replacing it at the two reconfigure sites (startTunnel, handleAppMessage). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds lifecycle invalidation to ChangesGo callback lifecycle safety
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Problem
On wifi↔cellular path flapping the packet-tunnel extension crash-loops: it connects briefly, then is killed and relaunched every ~6–12 s, so no IP / device name renders and iOS never activates the VPN. Customer-reported, multiple iOS devices, build 7 / 0.2.0, iOS 26.5.
Crash signature from the device
.ipsreports:EXC_BAD_ACCESS/KERN_INVALID_ADDRESS at 0x28,com.apple.main-thread, in the gomobile bridge.Root cause (code-derived)
The Go engine retains
NetworkChangeListenerandDNSManagerfor the lifetime of the SDK client they were created with. When the adapter is swapped/torn down (profile switch, or arestartClient()triggered by flapping), the old client can still fire a lateonNetworkChanged/applyDnsinto the now-stale listener, which dereferences atunnelManagerwhose owning provider has already been deallocated → use-after-free →0x28. The listener/adapter swap had no synchronization against in-flight Go callbacks.Fix
Make the Go-held listeners null-safe and explicitly detachable.
NetworkChangeListener.swift/DNSManager.swift:tunnelManageris now optional; every Go callback (onNetworkChanged,setInterfaceIP,setInterfaceIPv6,applyDns) runs on a serial queue behind aguard isValid, let tunnelManager— late/in-flight callbacks become no-ops instead of crashing. Addedinvalidate()(serialized on the same queue).NetBirdAdapter.swift: addedinvalidateListeners(). Deliberately NOT called fromstop()/restart — the same adapter is reused there and must keep delivering callbacks; only invalidated when the adapter is actually discarded.PacketTunnelProvider.swift: invalidate the outgoing adapter's listeners before replacing it at the two reconfigure sites (startTunnel,handleAppMessage).Risk / things to watch
callbackQueue.sync— fine today (no reentrant call back onto the queue;setTunnelNetworkSettingscompletion is async, so no deadlock), but a future reentrant caller would deadlock. Flagging for reviewer awareness.Testing — NOT yet done (needs macOS/CI; no swiftc on the dev box)
Follow-up suggestion
Add an
actions/upload-artifactstep for$RUNNER_TEMP/NetBird.xcarchive/dSYMsinbuild-upload.ymlso future extension crashes symbolicate without an ASC dSYM hunt.🤖 Generated with Claude Code
Summary by CodeRabbit