crash in __nw_protocol_udp_finalize_output_frames_block_invoke

I have following `NWUDPSession`'s wrapper which handles my network traffic in packet tunnel provider extension:


class UDPSession: NSObject {
    private var lock = NSLock()
    private var session: NWUDPSession?

    var onDataReceived: ([Data]) -> Void = {_ in }
    
    init(withProvider provider: NEProvider?, address: String, port: String) {
        super.init()
        
        let endpoint = NWHostEndpoint(hostname: address, port: port)
        let session = provider?.createUDPSession(to: endpoint, from: nil)
        
        self.session = session
        self.setReadHandler()
    }
    
    deinit {
        cleanup()
    }
    
    func send(_ datagram: Data) -> Void {
        let currentSession = session
        currentSession?.writeDatagram(datagram) { [weak self, weak currentSession] (error) in
            if error != nil {
                self?.restartSession(currentSession)
                return
            }
        }
    }

    private func cleanup() {
        lock.lock()
        defer {
            lock.unlock()
        }

        session?.cancel()
    }

    private func setReadHandler() {
        session?.setReadHandler({ [weak self] (datagrams, error) in
            guard let data = datagrams else {
                return
            }
            self?.onDataReceived(data)

        }, maxDatagrams: 1)
    }

    private func restartSession(_ failedSession: NWUDPSession?) {
        lock.lock()
        defer {
            lock.unlock()
        }

        let currentSession = session
        guard let sessionToUpgrade = failedSession, sessionToUpgrade === currentSession else {
            return
        }

        session = NWUDPSession(upgradeFor: sessionToUpgrade)
        setReadHandler()
    }
}


Using Firebase crashlytics in my network extension I noticed the process crashes quite often. Usually the stack trace looks like this:


EXC_BAD_ACCESS KERN_PROTECTION_FAILURE 0x0000000105563f20


Crashed: com.apple.network.connections
0  libnetwork.dylib               0x1bc436b90 __nw_protocol_udp_finalize_output_frames_block_invoke + 864
1  libnetwork.dylib               0x1bc433b94 nw_protocol_udp_finalize_output_frames + 128
2  libnetwork.dylib               0x1bc4dd17c nw_flow_prepare_output_frames + 7808
3  libnetwork.dylib               0x1bc4daa98 nw_flow_service_writes + 1276
4  libnetwork.dylib               0x1bc4da11c nw_endpoint_handler_service_writes + 100
5  libnetwork.dylib               0x1bc51ceec __nw_connection_batch_block_invoke + 1248
6  libdispatch.dylib              0x1b9cbe610 _dispatch_call_block_and_release + 24
7  libdispatch.dylib              0x1b9cbf184 _dispatch_client_callout + 16
8  libdispatch.dylib              0x1b9c6d0b0 _dispatch_workloop_invoke$VARIANT$mp + 2104
9  libdispatch.dylib              0x1b9c75314 _dispatch_workloop_worker_thread + 588
10 libsystem_pthread.dylib        0x1b9d0eb88 _pthread_wqthread + 276
11 libsystem_pthread.dylib        0x1b9d11760 start_wqthread + 8


com.apple.main-thread
0  libsystem_kernel.dylib         0x1b9dc8634 mach_msg_trap + 8
1  libsystem_kernel.dylib         0x1b9dc7aa0 mach_msg + 72
2  CoreFoundation                 0x1b9f70288 __CFRunLoopServiceMachPort + 216
3  CoreFoundation                 0x1b9f6b3a8 __CFRunLoopRun + 1444
4  CoreFoundation                 0x1b9f6aadc CFRunLoopRunSpecific + 464
5  Foundation                     0x1ba2aa784 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 228
6  Foundation                     0x1ba2e4198 -[NSRunLoop(NSRunLoop) run] + 88
7  libxpc.dylib                   0x1b9bc7178 _xpc_objc_main + 304
8  libxpc.dylib                   0x1b9bc9acc xpc_main + 148
9  Foundation                     0x1ba2e62c8 +[NSXPCListener serviceListener] + 170
10 PlugInKit                      0x1c728ebb0 __PLUGINKIT_CALLING_OUT_TO_CLIENT_SUBSYSTEM_FOR_BEGINUSING__ + 31876
11 PlugInKit                      0x1c728e8bc __PLUGINKIT_CALLING_OUT_TO_CLIENT_SUBSYSTEM_FOR_BEGINUSING__ + 31120
12 PlugInKit                      0x1c728efc0 __PLUGINKIT_CALLING_OUT_TO_CLIENT_SUBSYSTEM_FOR_BEGINUSING__ + 32916
13 Foundation                     0x1ba4c1004 NSExtensionMain + 64
14 libdyld.dylib                  0x1b9df4360 start + 4


Thread
0  libsystem_kernel.dylib         0x1b9deaa7c __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x1b9d0ebd4 _pthread_wqthread + 352
2  libsystem_pthread.dylib        0x1b9d11760 start_wqthread + 8


com.google.firebase.crashlytics.MachExceptionServer
0                    0x102f1f00c FIRCLSProcessRecordAllThreads + 392 (FIRCLSProcess.c:392)
1                    0x102f1f3f0 FIRCLSProcessRecordAllThreads + 423 (FIRCLSProcess.c:423)
2                    0x102f15c40 FIRCLSHandler + 34 (FIRCLSHandler.m:34)
3                    0x102f185e8 FIRCLSMachExceptionServer + 524 (FIRCLSMachException.c:524)
4  libsystem_pthread.dylib        0x1b9d0dd8c _pthread_start + 156
5  libsystem_pthread.dylib        0x1b9d1176c thread_start + 8


Thread
0  libsystem_kernel.dylib         0x1b9deaa7c __workq_kernreturn + 8
1  libsystem_pthread.dylib        0x1b9d0ebd4 _pthread_wqthread + 352
2  libsystem_pthread.dylib        0x1b9d11760 start_wqthread + 8


com.apple.NSURLConnectionLoader
0  libsystem_kernel.dylib         0x1b9dc8634 mach_msg_trap + 8
1  libsystem_kernel.dylib         0x1b9dc7aa0 mach_msg + 72
2  CoreFoundation                 0x1b9f70288 __CFRunLoopServiceMachPort + 216
3  CoreFoundation                 0x1b9f6b3a8 __CFRunLoopRun + 1444
4  CoreFoundation                 0x1b9f6aadc CFRunLoopRunSpecific + 464
5  CFNetwork                      0x1bd2344e8 (Niepełna)
6  Foundation                     0x1ba3db09c __NSThread__start__ + 848
7  libsystem_pthread.dylib        0x1b9d0dd8c _pthread_start + 156
8  libsystem_pthread.dylib        0x1b9d1176c thread_start + 8

I haven't found much on the method that crashes the extension. Could it be Firebase component's fault or is there something I could investigate further?

Replies

What is the state of the NWUDPSession? Is the session ready? If not and the session is stuck in waiting or preparing and you are writing datagrams to it, I could see this as a potential issue. That also might explain why your frames on your crashing thread stop at __nw_protocol_udp_finalize_output_frames_block_invoke.


0  libnetwork.dylib               0x1bc436b90 __nw_protocol_udp_finalize_output_frames_block_invoke + 864 
1  libnetwork.dylib               0x1bc433b94 nw_protocol_udp_finalize_output_frames + 128 
2  libnetwork.dylib               0x1bc4dd17c nw_flow_prepare_output_frames + 7808 
3  libnetwork.dylib               0x1bc4daa98 nw_flow_service_writes + 1276 
4  libnetwork.dylib               0x1bc4da11c nw_endpoint_handler_service_writes + 100 
5  libnetwork.dylib               0x1bc51ceec __nw_connection_batch_block_invoke + 1248


Matt Eaton

DTS Engineering, CoreOS

meaton3 at apple.com

Hi meaton,

thanks for the answer. I've added code checking session's state before writing any datagram to it. Unfortunately, the crash still happens. Could it matter that 100% of affected devices run iOS 13? Is there anything else regarding NWUDPSession that I should check?

Interesting. From here I would check that the NWUDPSession you are writing to is still the existing session in memory. I noticed that when errors come in a session is restarted. Check to make sure there isnat a race condition present here. If you log out the session being used before your write to it I'm thinking you should be able to get an id or connection number that you can reference here.



Matt Eaton

DTS Engineering, CoreOS

meaton3 at apple.com

(posted at the end of the thread)
We're seeing a similar issue in another app. This appears to be happening on iOS 13 and iOS 14 only. Before we send data, we check the state of NWUDPSession (we expect it to be "ready" and viable):

Code Block
func send(_ datagram: Data) -> Void {
queuedDatagrams.append(datagram)
guard let currentSession = session else {
Log.info("cannot send packet; session does not exist")
return
}
guard currentSession.state == .ready, currentSession.isViable else {
Log.info("\(address(of: currentSession)) cannot send packet; state: \(currentSession.state), viable: \(currentSession.isViable)")
return
}
currentSession.writeMultipleDatagrams(queuedDatagrams) { [weak self] (error) in
Log.error(error)
if let error = error {
self?.handleError(error)
}
}
queuedDatagrams = []
}

but even with this check, we're still seeing this crash happen a lot.