Post

Replies

Boosts

Views

Activity

Reply to Memory Zeroing Issue After iOS 18 Update
As described, what I do was very simple, so I did not provide much more detail. Apart from mprotect and mlock, no low-level stuff or techniques were used. If you need any other details, you can read the demo code. #import "MallocGuardManager.h" #include <malloc/malloc.h> #import "UIKit/UIKit.h" @interface MallocGuardManager() { uintptr_t *_buffers; int _bufferCount; int _cachePageSize; BOOL _use_mlock; } @property (nonatomic, strong) NSMutableArray *container; @property (nonatomic, strong) NSString *templateSource; @property (nonatomic, strong) dispatch_queue_t queue; @property (nonatomic, assign) BOOL started; @property (nonatomic, assign) BOOL executing; @property (nonatomic, assign) BOOL scheduled; @end @implementation MallocGuardManager + (instancetype)sharedInstance{ static id instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[MallocGuardManager alloc] init]; }); return instance; } - (instancetype)init{ self = [super init]; if (self) { _container = [[NSMutableArray alloc] init]; _cachePageSize = getpagesize(); _executing = NO; _queue = dispatch_queue_create("com.malloc.guard", DISPATCH_QUEUE_SERIAL); _use_mlock = NO; // If YES, NO crash will happen!!!! } return self; } #pragma mark - public - (void)start{ if (self.started) { return; } self.started = YES; self.executing = YES; [self scheduleNext]; } - (void)stop{ self.started = NO; self.executing = NO; } #pragma mark - - (void)scheduleNext{ dispatch_async(self.queue, ^{ if (self.scheduled) {return;} self.scheduled = YES; NSTimeInterval delay = arc4random() % 7 + 3; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), self.queue, ^{ self.scheduled = NO; [self setupGuardPages]; }); }); } - (void)protect{ for (int index = 0; index < _bufferCount; index++) { uintptr_t ptr = *(_buffers + index); uintptr_t *isa = (uintptr_t *)ptr; Log(@"protect:%d, 0x%lx, 0x%lx", index, ptr, *isa); if (mprotect((void*)ptr, _cachePageSize, PROT_READ) == -1) { Log(@"mprotect failed"); } if (_use_mlock && mlock((const void *)ptr, _cachePageSize) == -1) { Log(@"mlock failed"); } Log(@"protected:%d, 0x%lx, 0x%lx", index, ptr, *isa); } } - (void)createPages{ NSInteger total = 0; NSInteger i = 0; for (NSInteger index = 0; index < 20 && total < 20 * 1024 * 1024; index++) { if (!self.executing) { return; } NSInteger pageCout = arc4random() % (800*1024/_cachePageSize); NSInteger strLen = MIN((pageCout*_cachePageSize + arc4random()% _cachePageSize), self.templateSource.length); NSString *string = [self.templateSource substringWithRange:NSMakeRange(0, strLen)]; if ((uintptr_t)string % _cachePageSize == 0 && malloc_size((__bridge void *)string) > _cachePageSize) { total += strLen; [self.container addObject:string]; Log(@"add:%ld, %p, %lu", (long)i, string, (unsigned long)string.length); i++; } } } - (void)unprotect{ for (int index = 0; index < _bufferCount; index++) { uintptr_t ptr = *(_buffers + index); uintptr_t *isa = (uintptr_t *)ptr; uintptr_t *ptr1 = (uintptr_t *)(ptr+8); uintptr_t *ptr2 = (uintptr_t *)(ptr+24); Log(@"unprotect:%d, 0x%lx, 0x%lx, %ld, 0x%lx, 0x%lx", index, ptr, *isa, malloc_size((const void *)ptr), *ptr1, *ptr2); if (*isa == 0) { // It crashed, proving readonly protection working correctly!!!! *isa = 0x10; mprotect((void*)ptr, _cachePageSize, PROT_READ); *isa = 0x20; } if (mprotect((void*)ptr, _cachePageSize, PROT_READ|PROT_WRITE) == -1) { Log(@"mprotect fail"); } Log(@"unprotected:%d, 0x%lx, 0x%lx", index, ptr, *isa); [self.container removeObjectAtIndex:0]; if (_use_mlock && munlock((const void *)ptr, _cachePageSize) == -1) { Log(@"munlock failed"); } } } - (void)setupGuardPages{ @autoreleasepool { [self unprotect]; if (_buffers) { _bufferCount = 0; free(_buffers); _buffers = NULL; } if (self.executing == NO) { self.templateSource = nil; return; } if (self.templateSource.length == 0) { self.templateSource = [@"" stringByPaddingToLength:800*1024 withString:@"5656" startingAtIndex:0]; } [self createPages]; _bufferCount = (int)self.container.count; if (_bufferCount > 0) { _buffers = malloc(_bufferCount * sizeof(uintptr_t)); int index = 0; for (id object in self.container) { uintptr_t ptr = (uintptr_t)object; *(_buffers + index) = ptr; index++; } } } [self protect]; [self scheduleNext]; } @end
Topic: App & System Services SubTopic: Core OS Tags:
Jul ’25
Reply to Memory Zeroing Issue After iOS 18 Update
Thank you for your reply. Certainly we have tried some memory debugging tools, but we didn't find any issues. I also find it extremely hard to believe that there was a problem with the system. I have been trying to figure out what our own issue might be. "Simulated malloc guard detection" is somewhat like our final attempt. Keep in mind that, when you operate at the scale of iOS, a ‘one in a million’ bug happens thousands of times a day O-: We cannot reproduce this issue locally. However, with our daily user traffic reaching billions, approximately 100 reports are generated every day. (This issue has some notable characteristics. Certain models account for a large proportion, especially iPhone 14,5.) I believe that the Apple receive an enormous number of abnormal reports every day. But how to determine whether these abnormalities are not caused by system issues or other common problems related to memory corruption? This is also why I think the feedback is necessary. If you saw some strange crashes, perhaps based on my feedback, you could consider the reasons again. Don't hastily assume that it's not a system issue but rather some common memory problem. How are you getting the first page of the memory for these objects? There’s no supported way to get at the backing buffer for an NSString in the general case. There is no need to find the backing buffer,we just protect memory pointed by string object pointer. Because we found that for a long NSString object we created, the object pointer points to a large block of memory. The memory starts with 8 bytes as the isa field, and then several bytes after that are all the string content. We set the first page of memory containing the isa field as read-only. (I know this is a bit tricky)
Topic: App & System Services SubTopic: Core OS Tags:
Jul ’25
Reply to In statistical objects In MXMetricPayload histogrammedTimeToFirstDrawKey start times not equal applicationExitMetrics exit
We have a relevant problem. We record process launches in a database in App, and we found the total number of processes in one day recorded by us is not equal to that in the corresponding applicationExitMetrics often. I understand that the numbers should not be same always, since we have different standards of counting. However, sometimes the difference is quite significant. For example, we record 10 exits, but applicationExitMetrics only contains 5. I suspect that exitMetrics may omit some launches in some particular conditions. But I can't figure out the exact logic to explain the gap. If some insight regarding the mismatch can be provided, it will be much appreciated.
Dec ’24
Reply to Memory Zeroing Issue After iOS 18 Update
As described, what I do was very simple, so I did not provide much more detail. Apart from mprotect and mlock, no low-level stuff or techniques were used. If you need any other details, you can read the demo code. #import "MallocGuardManager.h" #include <malloc/malloc.h> #import "UIKit/UIKit.h" @interface MallocGuardManager() { uintptr_t *_buffers; int _bufferCount; int _cachePageSize; BOOL _use_mlock; } @property (nonatomic, strong) NSMutableArray *container; @property (nonatomic, strong) NSString *templateSource; @property (nonatomic, strong) dispatch_queue_t queue; @property (nonatomic, assign) BOOL started; @property (nonatomic, assign) BOOL executing; @property (nonatomic, assign) BOOL scheduled; @end @implementation MallocGuardManager + (instancetype)sharedInstance{ static id instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[MallocGuardManager alloc] init]; }); return instance; } - (instancetype)init{ self = [super init]; if (self) { _container = [[NSMutableArray alloc] init]; _cachePageSize = getpagesize(); _executing = NO; _queue = dispatch_queue_create("com.malloc.guard", DISPATCH_QUEUE_SERIAL); _use_mlock = NO; // If YES, NO crash will happen!!!! } return self; } #pragma mark - public - (void)start{ if (self.started) { return; } self.started = YES; self.executing = YES; [self scheduleNext]; } - (void)stop{ self.started = NO; self.executing = NO; } #pragma mark - - (void)scheduleNext{ dispatch_async(self.queue, ^{ if (self.scheduled) {return;} self.scheduled = YES; NSTimeInterval delay = arc4random() % 7 + 3; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), self.queue, ^{ self.scheduled = NO; [self setupGuardPages]; }); }); } - (void)protect{ for (int index = 0; index < _bufferCount; index++) { uintptr_t ptr = *(_buffers + index); uintptr_t *isa = (uintptr_t *)ptr; Log(@"protect:%d, 0x%lx, 0x%lx", index, ptr, *isa); if (mprotect((void*)ptr, _cachePageSize, PROT_READ) == -1) { Log(@"mprotect failed"); } if (_use_mlock && mlock((const void *)ptr, _cachePageSize) == -1) { Log(@"mlock failed"); } Log(@"protected:%d, 0x%lx, 0x%lx", index, ptr, *isa); } } - (void)createPages{ NSInteger total = 0; NSInteger i = 0; for (NSInteger index = 0; index < 20 && total < 20 * 1024 * 1024; index++) { if (!self.executing) { return; } NSInteger pageCout = arc4random() % (800*1024/_cachePageSize); NSInteger strLen = MIN((pageCout*_cachePageSize + arc4random()% _cachePageSize), self.templateSource.length); NSString *string = [self.templateSource substringWithRange:NSMakeRange(0, strLen)]; if ((uintptr_t)string % _cachePageSize == 0 && malloc_size((__bridge void *)string) > _cachePageSize) { total += strLen; [self.container addObject:string]; Log(@"add:%ld, %p, %lu", (long)i, string, (unsigned long)string.length); i++; } } } - (void)unprotect{ for (int index = 0; index < _bufferCount; index++) { uintptr_t ptr = *(_buffers + index); uintptr_t *isa = (uintptr_t *)ptr; uintptr_t *ptr1 = (uintptr_t *)(ptr+8); uintptr_t *ptr2 = (uintptr_t *)(ptr+24); Log(@"unprotect:%d, 0x%lx, 0x%lx, %ld, 0x%lx, 0x%lx", index, ptr, *isa, malloc_size((const void *)ptr), *ptr1, *ptr2); if (*isa == 0) { // It crashed, proving readonly protection working correctly!!!! *isa = 0x10; mprotect((void*)ptr, _cachePageSize, PROT_READ); *isa = 0x20; } if (mprotect((void*)ptr, _cachePageSize, PROT_READ|PROT_WRITE) == -1) { Log(@"mprotect fail"); } Log(@"unprotected:%d, 0x%lx, 0x%lx", index, ptr, *isa); [self.container removeObjectAtIndex:0]; if (_use_mlock && munlock((const void *)ptr, _cachePageSize) == -1) { Log(@"munlock failed"); } } } - (void)setupGuardPages{ @autoreleasepool { [self unprotect]; if (_buffers) { _bufferCount = 0; free(_buffers); _buffers = NULL; } if (self.executing == NO) { self.templateSource = nil; return; } if (self.templateSource.length == 0) { self.templateSource = [@"" stringByPaddingToLength:800*1024 withString:@"5656" startingAtIndex:0]; } [self createPages]; _bufferCount = (int)self.container.count; if (_bufferCount > 0) { _buffers = malloc(_bufferCount * sizeof(uintptr_t)); int index = 0; for (id object in self.container) { uintptr_t ptr = (uintptr_t)object; *(_buffers + index) = ptr; index++; } } } [self protect]; [self scheduleNext]; } @end
Topic: App & System Services SubTopic: Core OS Tags:
Replies
Boosts
Views
Activity
Jul ’25
Reply to Memory Zeroing Issue After iOS 18 Update
if ((uintptr_t)string % _cachePageSize == 0 && malloc_size((__bridge void *)string) > _cachePageSize) { We simply use string objects that meet the above conditions.
Topic: App & System Services SubTopic: Core OS Tags:
Replies
Boosts
Views
Activity
Jul ’25
Reply to Memory Zeroing Issue After iOS 18 Update
Thank you for your reply. Certainly we have tried some memory debugging tools, but we didn't find any issues. I also find it extremely hard to believe that there was a problem with the system. I have been trying to figure out what our own issue might be. "Simulated malloc guard detection" is somewhat like our final attempt. Keep in mind that, when you operate at the scale of iOS, a ‘one in a million’ bug happens thousands of times a day O-: We cannot reproduce this issue locally. However, with our daily user traffic reaching billions, approximately 100 reports are generated every day. (This issue has some notable characteristics. Certain models account for a large proportion, especially iPhone 14,5.) I believe that the Apple receive an enormous number of abnormal reports every day. But how to determine whether these abnormalities are not caused by system issues or other common problems related to memory corruption? This is also why I think the feedback is necessary. If you saw some strange crashes, perhaps based on my feedback, you could consider the reasons again. Don't hastily assume that it's not a system issue but rather some common memory problem. How are you getting the first page of the memory for these objects? There’s no supported way to get at the backing buffer for an NSString in the general case. There is no need to find the backing buffer,we just protect memory pointed by string object pointer. Because we found that for a long NSString object we created, the object pointer points to a large block of memory. The memory starts with 8 bytes as the isa field, and then several bytes after that are all the string content. We set the first page of memory containing the isa field as read-only. (I know this is a bit tricky)
Topic: App & System Services SubTopic: Core OS Tags:
Replies
Boosts
Views
Activity
Jul ’25
Reply to In statistical objects In MXMetricPayload histogrammedTimeToFirstDrawKey start times not equal applicationExitMetrics exit
We have a relevant problem. We record process launches in a database in App, and we found the total number of processes in one day recorded by us is not equal to that in the corresponding applicationExitMetrics often. I understand that the numbers should not be same always, since we have different standards of counting. However, sometimes the difference is quite significant. For example, we record 10 exits, but applicationExitMetrics only contains 5. I suspect that exitMetrics may omit some launches in some particular conditions. But I can't figure out the exact logic to explain the gap. If some insight regarding the mismatch can be provided, it will be much appreciated.
Replies
Boosts
Views
Activity
Dec ’24