Environment: Telegram iOS 12.9.3 (34622), iPhone 17 Pro (iPhone18,1), iOS 26.6.1 (23G83)
Steps to reproduce (100% for me):
- Connect any VPN (NEPacketTunnelProvider-based, tun keeps NWPath satisfied).
- Enable Airplane Mode. The app keeps trying to connect (path still looks
satisfied through the tun interface), opening many parallel TCP connections
to DCs (443/80/5222) that never complete.
- Wait ~4-5 minutes → app crashes in foreground.
Crash: EXC_BREAKPOINT — "BUG IN CLIENT OF LIBPLATFORM: Trying to
recursively lock an os_unfair_lock", faulting queue: GCDAsyncSocket.
Faulting thread:
_os_unfair_lock_recursive_abort
_os_unfair_lock_lock_slow
CFSocketInvalidate ← takes __CFAllSocketsLock
Schedulables::_SchedulablesInvalidateApplierFunction
CFArrayApplyFunction
SocketStream::~SocketStream() ← destructor re-enters
_CFRelease
CFSocketInvalidate ← same lock, recursive → abort
SocketStream::close
_CFStreamClose
MtProtoKitFramework (offsets 35960 / 52876 / 46652)
dispatch_source → GCDAsyncSocket queue
At crash time two more GCDAsyncSocket threads are blocked in
CFSocketInvalidate on the same lock, and another thread is inside
nw_endpoint_handler_path_change — i.e. concurrent CFStream teardown from
multiple queues during a network path change.
Root cause looks like a CF re-entrancy issue, but it is triggered by
MtProtoKit closing many CFStream-based sockets concurrently on path change.
Serializing stream close (or migrating those paths off CFStream/CFSocket)
would avoid it.
Full .ips attached.
ad1944bb-Telegram20260826230339.txt
Environment: Telegram iOS 12.9.3 (34622), iPhone 17 Pro (iPhone18,1), iOS 26.6.1 (23G83)
Steps to reproduce (100% for me):
satisfied through the tun interface), opening many parallel TCP connections
to DCs (443/80/5222) that never complete.
Crash: EXC_BREAKPOINT — "BUG IN CLIENT OF LIBPLATFORM: Trying to
recursively lock an os_unfair_lock", faulting queue: GCDAsyncSocket.
Faulting thread:
_os_unfair_lock_recursive_abort
_os_unfair_lock_lock_slow
CFSocketInvalidate ← takes __CFAllSocketsLock
Schedulables::_SchedulablesInvalidateApplierFunction
CFArrayApplyFunction
SocketStream::~SocketStream() ← destructor re-enters
_CFRelease
CFSocketInvalidate ← same lock, recursive → abort
SocketStream::close
_CFStreamClose
MtProtoKitFramework (offsets 35960 / 52876 / 46652)
dispatch_source → GCDAsyncSocket queue
At crash time two more GCDAsyncSocket threads are blocked in
CFSocketInvalidate on the same lock, and another thread is inside
nw_endpoint_handler_path_change — i.e. concurrent CFStream teardown from
multiple queues during a network path change.
Root cause looks like a CF re-entrancy issue, but it is triggered by
MtProtoKit closing many CFStream-based sockets concurrently on path change.
Serializing stream close (or migrating those paths off CFStream/CFSocket)
would avoid it.
Full .ips attached.
ad1944bb-Telegram20260826230339.txt