(Xcode 26.2, iPhone 17 Pro)
I can't seem to get hardware tag checks to work in an app launched without the special "Hardware Memory Tagging" diagnostics. In other words, I have been unable to reproduce the crash example at 6:40 in Apple's video "Secure your app with Memory Integrity Enforcement".
When I write a heap overflow or a UAF, it is picked up perfectly provided I enable the "Hardware Memory Tagging" feature under Scheme Diagnostics.
If I instead add the Enhanced Security capability with the memory-tagging related entitlements:
- I'm seeing distinct memory tags being assigned in pointers returned by malloc (without the capability, this is not the case)
- Tag mismatches are not being caught or enforced, regardless of soft mode
The behaviour is the same whether I launch from Xcode without "Hardware Memory Tagging", or if I launch the app by tapping it on launchpad. In case it was related to debug builds, I also tried creating an ad hoc IPA and it didn't make any difference.
I realise there's a wrinkle here that the debugger sets MallocTagAll=1, so possibly it will pick up a wider range of issues. However I would have expected that a straight UAF would be caught. For example, this test code demonstrates that tagging is active but it doesn't crash:
#define PTR_TAG(p) ((unsigned)(((uintptr_t)(p) >> 56) & 0xF))
void *p1 = malloc(32);
void *p2 = malloc(32);
void *p3 = malloc(32);
os_log(OS_LOG_DEFAULT, "p1 = %p (tag: %u)\n", p1, PTR_TAG(p1));
os_log(OS_LOG_DEFAULT, "p2 = %p (tag: %u)\n", p2, PTR_TAG(p2));
os_log(OS_LOG_DEFAULT, "p3 = %p (tag: %u)\n", p3, PTR_TAG(p3));
free(p2);
void *p2_realloc = malloc(32);
os_log(OS_LOG_DEFAULT, "p2 after free+malloc = %p (tag: %u)\n", p2_realloc, PTR_TAG(p2_realloc));
// Is p2_realloc the same address as p2 but different tag?
os_log(OS_LOG_DEFAULT, "Same address? %s\n",
((uintptr_t)p2 & 0x00FFFFFFFFFFFFFF) == ((uintptr_t)p2_realloc & 0x00FFFFFFFFFFFFFF)
? "YES" : "NO");
// Now try to use the OLD pointer p2
os_log(OS_LOG_DEFAULT, "Attempting use-after-free via old pointer p2...\n");
volatile char c = *(volatile char *)p2; // Should this crash?
os_log(OS_LOG_DEFAULT, "Read succeeded! Value: %d\n", c);
Example output:
p1 = 0xf00000b71019660 (tag: 15)
p2 = 0x200000b711958c0 (tag: 2)
p3 = 0x300000b711958e0 (tag: 3)
p2 after free+malloc = 0x700000b71019680 (tag: 7)
Same address? NO
Attempting use-after-free via old pointer p2...
Read succeeded! Value: -55
For reference, these are my entitlements.
[Dict]
[Key] application-identifier
[Value]
[String] …
[Key] com.apple.developer.team-identifier
[Value]
[String] …
[Key] com.apple.security.hardened-process
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.checked-allocations
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.checked-allocations.enable-pure-data
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.dyld-ro
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.enhanced-security-version
[Value]
[Int] 1
[Key] com.apple.security.hardened-process.hardened-heap
[Value]
[Bool] true
[Key] com.apple.security.hardened-process.platform-restrictions
[Value]
[Int] 2
[Key] get-task-allow
[Value]
[Bool] true
What do I need to do to make Memory Integrity Enforcement do something outside the debugger?