diff --git a/Source/GCD/GCDAsyncSocket.h b/Source/GCD/GCDAsyncSocket.h index c339f8ab..1f94e30d 100644 --- a/Source/GCD/GCDAsyncSocket.h +++ b/Source/GCD/GCDAsyncSocket.h @@ -988,9 +988,9 @@ typedef NS_ERROR_ENUM(GCDAsyncSocketErrorDomain, GCDAsyncSocketError) { * Configures the socket to allow it to operate when the iOS application has been backgrounded. * In other words, this method creates a read & write stream, and invokes: * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * + * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); + * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); + * * Returns YES if successful, NO otherwise. * * Note: Apple does not officially support backgrounding server sockets. diff --git a/Source/GCD/GCDAsyncSocket.m b/Source/GCD/GCDAsyncSocket.m index 1769f7a4..5c40ea94 100755 --- a/Source/GCD/GCDAsyncSocket.m +++ b/Source/GCD/GCDAsyncSocket.m @@ -28,6 +28,8 @@ #import #import #import +#import +#import #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). @@ -130,6 +132,70 @@ NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; #endif +// patch CFSocket at runtime to work around a crash + +// first, create structs declaring our assumptions of where the needed internal data structures are located +// this workaround will fail if Apple updates CFSocket to change these structure shapes +// that _probably_ won't happen because CFSocket is deprecated in favor of the new Network framework + +struct __CFSocket { + int64_t offset[27]; + CFSocketContext _context; /* immutable */ +}; + +typedef struct { + int64_t offset[33]; + struct __CFSocket *_socket; +} __CFSocketStreamContext; + +struct __CFStream { + int64_t offset[5]; + __CFSocketStreamContext *info; +}; + +/// safely copies memory from the stack, telling you if it was successful +static inline vm_size_t copySafely(const void* restrict const src, void* restrict const dst, const vm_size_t byteCount) +{ + vm_size_t bytesCopied = 0; + kern_return_t result = vm_read_overwrite(mach_task_self(), + (vm_address_t)src, + byteCount, + (vm_address_t)dst, + &bytesCopied); + if (result != KERN_SUCCESS) return 0; + return bytesCopied; +} + +/// a 10KB space on the stack for the following function to use. can be safely static because we never read from it +static char g_memoryTestBuffer[10240]; +/// test if some stack memory is safely readable, to see if unsafe casts will segfault +static inline bool isMemoryReadable(const void* const memory, const size_t byteCount) +{ + const int testBufferSize = sizeof(g_memoryTestBuffer); + vm_size_t bytesRemaining = byteCount; + + while (bytesRemaining > 0) { + vm_size_t bytesToCopy = bytesRemaining > testBufferSize ? testBufferSize : bytesRemaining; + if (copySafely(memory, g_memoryTestBuffer, bytesToCopy) != bytesToCopy) { + break; + } + bytesRemaining -= bytesToCopy; + } + return bytesRemaining == 0; +} + +/// a static serial queue so that socket context release calls get different threads +static dispatch_queue_t socket_context_release_queue = nil; +void (*origin_context_release)(const void *info); +void new_context_release(const void *info) { + if (socket_context_release_queue == nil) { + socket_context_release_queue = dispatch_queue_create("socketContextReleaseQueue", 0x0); + } + dispatch_async(socket_context_release_queue, ^{ + origin_context_release(info); + }); +} + enum GCDAsyncSocketFlags { kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) @@ -2992,8 +3058,8 @@ - (void)didConnect:(int)aStateIndex // // Note: // There may be configuration options that must be set by the delegate before opening the streams. - // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. - // + // The primary example is the kCFStreamNetworkServiceTypeBackground flag, which only works on an unopened stream. + // // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. // This gives the delegate time to properly configure the streams if needed. @@ -3234,6 +3300,23 @@ - (void)closeWithError:(NSError *)error } if (writeStream) { + // replace writeStream's context release function to avoid recursive lock crash + if (@available(iOS 16.0, *)) { + struct __CFStream *cfstream = (struct __CFStream *)writeStream; + if (isMemoryReadable(cfstream, sizeof(*cfstream)) + && isMemoryReadable(cfstream->info, sizeof(*(cfstream->info))) + && isMemoryReadable(cfstream->info->_socket, sizeof(*(cfstream->info->_socket))) + && isMemoryReadable(&(cfstream->info->_socket->_context), sizeof(cfstream->info->_socket->_context)) + && isMemoryReadable(cfstream->info->_socket->_context.release, sizeof(*(cfstream->info->_socket->_context.release)))) { + if (cfstream->info != NULL && cfstream->info->_socket != NULL) { + if ((uintptr_t)cfstream->info->_socket->_context.release == (uintptr_t)CFRelease) { + origin_context_release = cfstream->info->_socket->_context.release; + cfstream->info->_socket->_context.release = new_context_release; + } + } + } + } + CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); CFWriteStreamClose(writeStream); CFRelease(writeStream); @@ -8200,8 +8283,8 @@ - (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); + r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); #pragma clang diagnostic pop if (!r1 || !r2) diff --git a/Source/GCD/GCDAsyncUdpSocket.h b/Source/GCD/GCDAsyncUdpSocket.h index af327e08..a9d7b9df 100644 --- a/Source/GCD/GCDAsyncUdpSocket.h +++ b/Source/GCD/GCDAsyncUdpSocket.h @@ -997,8 +997,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Configures the socket to allow it to operate when the iOS application has been backgrounded. * In other words, this method creates a read & write stream, and invokes: * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); + * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); * * Returns YES if successful, NO otherwise. * diff --git a/Source/GCD/GCDAsyncUdpSocket.m b/Source/GCD/GCDAsyncUdpSocket.m index af0cbf22..73260bf8 100755 --- a/Source/GCD/GCDAsyncUdpSocket.m +++ b/Source/GCD/GCDAsyncUdpSocket.m @@ -5469,9 +5469,9 @@ - (BOOL)enableBackgroundingOnSockets // // if (readStream4 && writeStream4) // { -// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// +// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); +// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); +// // if (!r1 || !r2) // { // LogError(@"Error setting voip type (IPv4)"); @@ -5481,9 +5481,9 @@ - (BOOL)enableBackgroundingOnSockets // // if (readStream6 && writeStream6) // { -// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// +// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); +// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeBackground); +// // if (!r1 || !r2) // { // LogError(@"Error setting voip type (IPv6)");