Skip to content

Commit

Permalink
Merge pull request #1 from Wallapop/feature/WPA-54019-fixes-for-cocoa…
Browse files Browse the repository at this point in the history
…-async-socket

Fix bugs related to a deadlock, and iOS 16 Deprecations.
  • Loading branch information
iMostfa committed Apr 4, 2024
2 parents 5ddba5e + ec18c94 commit a247807
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 15 deletions.
6 changes: 3 additions & 3 deletions Source/GCD/GCDAsyncSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
91 changes: 87 additions & 4 deletions Source/GCD/GCDAsyncSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#import <sys/uio.h>
#import <sys/un.h>
#import <unistd.h>
#import <mach/mach_init.h>
#import <mach/vm_map.h>

#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions Source/GCD/GCDAsyncUdpSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
12 changes: 6 additions & 6 deletions Source/GCD/GCDAsyncUdpSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)");
Expand All @@ -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)");
Expand Down

0 comments on commit a247807

Please sign in to comment.