Hardware Memory Tag (MIE) enforcement outside of debugger

(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?

Hardware Memory Tag (MIE) enforcement outside of debugger
 
 
Q