Description
endConnection() crashes with EXC_BAD_ACCESS (SIGSEGV) when it is called while initConnection() is still in flight. Because OpenIapModule is a plain class with no actor isolation, the async Tasks spawned by initConnection() race with cleanupExistingState() called from endConnection(), causing Swift ARC to attempt to release an object whose memory has already been freed or remapped.
The crash manifests as a pointer authentication failure on ARM64e devices and a standard translation fault on ARM64. Users are sometimes seeing an immediate crash upon opening the app.
Crash stack
0 libswiftCore _swift_release_dealloc + 32
1 libswiftCore bool swift::RefCounts<...>::doDecrementSlow<PerformDeinit>(...) + 152
2 MyApp OpenIapModule.endConnection() + 128
3 MyApp closure #5 in ExpoIapModule.definition() + 1 (ExpoIapModule.swift:57)
4 MyApp <deduplicated_symbol>
5 MyApp closure #1 in ConcurrentFunctionDefinition.call(by:withArguments:appContext:callback:)
...
11 libswift_Concurrency completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1
Exception: EXC_BAD_ACCESS (SIGSEGV) — KERN_INVALID_ADDRESS (possible pointer authentication failure)
ESR: 0x92000004 — Data Abort, byte read, Translation fault
Root cause
endConnection() immediately cancels and nils initTask, then calls cleanupExistingState():
// OpenIapModule.swift
public func endConnection() async throws -> Bool {
initTask?.cancel()
initTask = nil
await cleanupExistingState() // ← races with in-flight initConnection Tasks
return true
}
cleanupExistingState() then accesses updateListenerTask and productManager:
private func cleanupExistingState() async {
updateListenerTask?.cancel()
updateListenerTask = nil
await state.reset()
if let manager = productManager { await manager.removeAll() }
productManager = nil
}
If initConnection()'s Tasks — which create productManager and start updateListenerTask — are still executing concurrently, cleanupExistingState() touches objects that those Tasks are in the process of initializing. Swift ARC then decrements a reference count on an object whose memory is being concurrently freed, producing a segfault.
Additionally, when the Expo module unmounts it fires two concurrent endConnection() calls:
useIAP cleanup effect (useIAP.js:338): endConnection() (JS → AsyncFunction)
ExpoIapModule.OnDestroy → ExpoIapHelper.cleanupStore() (ExpoIapHelper.swift:199): OpenIapModule.shared.endConnection() directly
This makes the race window wider and harder to avoid.
How to reproduce
The scenario that reliably triggers this is:
useIAP mounts → initConnection() starts async Tasks
- Before those Tasks complete, the component unmounts
endConnection() fires and races with the in-flight Tasks
Run on a physical iOS device (StoreKit does not fully initialize on Simulator). The app crashes within ~150ms of launch every time.
Environment
|
|
expo-iap |
3.4.13 |
openiap pod |
as bundled with 3.4.13 |
| iOS |
Reproducible on iOS 18.7.3 (stable) and iOS 26.3.1 (beta) |
| Devices |
iPhone 14 Pro (iPhone15,2), iPhone 15 Pro (iPhone16,1), iPhone 16 Pro (iPhone17,1) |
| Architecture |
ARM-64 (native) |
Description
endConnection()crashes withEXC_BAD_ACCESS (SIGSEGV)when it is called whileinitConnection()is still in flight. BecauseOpenIapModuleis a plainclasswith no actor isolation, the async Tasks spawned byinitConnection()race withcleanupExistingState()called fromendConnection(), causing Swift ARC to attempt to release an object whose memory has already been freed or remapped.The crash manifests as a pointer authentication failure on ARM64e devices and a standard translation fault on ARM64. Users are sometimes seeing an immediate crash upon opening the app.
Crash stack
Exception:
EXC_BAD_ACCESS (SIGSEGV)—KERN_INVALID_ADDRESS(possible pointer authentication failure)ESR:
0x92000004— Data Abort, byte read, Translation faultRoot cause
endConnection()immediately cancels and nilsinitTask, then callscleanupExistingState():cleanupExistingState()then accessesupdateListenerTaskandproductManager:If
initConnection()'s Tasks — which createproductManagerand startupdateListenerTask— are still executing concurrently,cleanupExistingState()touches objects that those Tasks are in the process of initializing. Swift ARC then decrements a reference count on an object whose memory is being concurrently freed, producing a segfault.Additionally, when the Expo module unmounts it fires two concurrent
endConnection()calls:useIAPcleanup effect (useIAP.js:338):endConnection()(JS →AsyncFunction)ExpoIapModule.OnDestroy→ExpoIapHelper.cleanupStore()(ExpoIapHelper.swift:199):OpenIapModule.shared.endConnection()directlyThis makes the race window wider and harder to avoid.
How to reproduce
The scenario that reliably triggers this is:
useIAPmounts →initConnection()starts async TasksendConnection()fires and races with the in-flight TasksRun on a physical iOS device (StoreKit does not fully initialize on Simulator). The app crashes within ~150ms of launch every time.
Environment
expo-iap3.4.13openiappod3.4.13