Crash - Pointer Authentication Failures or invalid memory accesses
2021-03-05
Recently, a colleague came to me for a crash. It is quite tricky. So i took a note here. The exception section is as follow:
1 2 3 4 5 6 7 8 9 10 11 12
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000ec033bb40320 -> 0x000000033bb40320 (possible pointer authentication failure) VM Region Info: 0x33bb40320 is not in any region. Bytes after previous region: 2612265761 Bytes before following region: 53759180000 REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL MALLOC_NANO 283fb8000-2a0000000 [448.3M] rw-/rwx SM=ZER ---> GAP OF 0xd20000000 BYTES commpage (reserved) fc0000000-1000000000 [ 1.0G] ---/--- SM=NUL ...(unallocated)
From this exception type, we can see KERN_INVALID_ADDRESS at 0x0000ec033bb40320 -> 0x000000033bb40320 (possible pointer authentication failure) . You may wonder what it is. Well, according to Apple doc
When pointer authentication fails, the system invalidates the failing pointer by setting a high-order bit. Subsequent use of the pointer results in a segmentation fault. The crash report contains a message that includes the value of the pointer both after and before invalidation:
But there is another much more common crash caused by this kind:
Be aware that other invalid memory accesses, where high-order bits are erroneously set, can also look like pointer authentication failures.
In exception section in the crash report, there are 3 messages matching
The exception type is EXC_BAD_ACCESS (SIGSEGV), it is a segment violation signal from system to kill your process
The subtype is KERN_INVALID_ADDRESS at 0x0000ec033bb40320 -> 0x000000033bb40320 (possible pointer authentication failure). We can see here the failing pointer is 0x0000ec033bb40320, after system invalidates the higher bits of this pointer, it becomes 0x000000033bb40320.
The converted address 0x33bb40320 isn’t in any valid memory region.
Now we know the invalid address is 0x0000ec033bb40320. Then we actually can see it is in x0 register in the Thread State. Thread State in crash report actually records the state of thread’s register when crash happened. See more about thread state in doc. In Objective-C , c or Swift, register 0 holds address for current object when function calling.
So we can switch gears and take a deeper look into the stacktrace in the crashed thread to find out the current instance. At the top of the stacktrace, it is loadApplication function from Instance class. Since the invalid address is in x0, which usually holds the pointer to current object. I suspect something goes wrong with Instance instance.
// This doesn't really do anything. The real work happens in initializeBridge. _reactInstance.reset(new Instance);
__weak RCTCxxBridge *weakSelf = self;
// Prepare executor factory (shared_ptr for copy into block) std::shared_ptr<JSExecutorFactory> executorFactory; if (!self.executorClass) { if ([self.delegate conformsToProtocol:@protocol(RCTCxxBridgeDelegate)]) { id<RCTCxxBridgeDelegate> cxxDelegate = (id<RCTCxxBridgeDelegate>) self.delegate; executorFactory = [cxxDelegate jsExecutorFactoryForBridge:self]; } if (!executorFactory) { executorFactory = std::make_shared<JSCExecutorFactory>(nullptr); } } else { id<RCTJavaScriptExecutor> objcExecutor = [self moduleForClass:self.executorClass]; executorFactory.reset(newRCTObjcExecutorFactory(objcExecutor, ^(NSError *error) { if (error) { [weakSelf handleError:error]; } })); }
// Dispatch the instance initialization as soon as the initial module metadata has // been collected (see initModules) dispatch_group_enter(prepareBridge); [self ensureOnJavaScriptThread:^{ [weakSelf _initializeBridge:executorFactory]; dispatch_group_leave(prepareBridge); }];
// Load the source asynchronously, then store it for later execution. dispatch_group_enter(prepareBridge); __block NSData *sourceCode; [self loadSource:^(NSError *error, RCTSource *source) { if (error) { [weakSelf handleError:error]; }
sourceCode = source.data; dispatch_group_leave(prepareBridge); } onProgress:^(RCTLoadingProgress *progressData) { #if RCT_DEV && __has_include("RCTDevLoadingView.h") // Note: RCTDevLoadingView should have been loaded at this point, so no need to allow lazy loading. RCTDevLoadingView *loadingView = [weakSelf moduleForName:RCTBridgeModuleNameForClass([RCTDevLoadingView class]) lazilyLoadIfNecessary:NO]; [loadingView updateProgress:progressData]; #endif }];
// Wait for both the modules and source code to have finished loading dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ RCTCxxBridge *strongSelf = weakSelf; if (sourceCode && strongSelf.loading) { [strongSelf executeSourceCode:sourceCode sync:NO]; } }); RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); }
This snippet of code comes from React Native 0.61 , what this function basically does is to
Init JSThread and . If you are interested in CFRunloop as well, can take a look at my another article