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?

Answered by DTS Engineer in 873095022

So, I have three bits of good news:

  • My boss got me an iPhone 17 so that I can test this properly. Thanks boss!
  • It was a public holiday in the US yesterday, which allowed me to catch up a bit, so I had some time to play with MIE today.
  • I figured out the sequence to get soft mode crash reports (-:

Here’s what I did:

  1. Using Xcode 26.2 on macOS 26.2 [1], I created a new project from the iOS > App template, choosing Objective-C as the language.

  2. In the view controller, I added this code:

    - (IBAction)testAction:(id)sender {
        char * buf = malloc(32);
        buf[16] += 1;
        free(buf);
        buf[16] += 1;
    }
    
  3. I added a Test button and wired it up to that action.

  4. In Signing & Capabilities, I added the Enhanced Security capability and checked all the boxes (-:

  5. In the scheme editor, I enabled Diagnostics > Hardware Memory Tagging.

  6. I selected an iPhone 17 running iOS 26.2 as my run destination and chose Product > Run.

  7. On the device, I tapped the Test button. It trapped, with Xcode highlighting the second increment. So far, so good.

  8. Back in Xcode, I chose Product > Stop.

  9. On the device again, I launched the app from the Home Screen.

  10. And tapped the Test button.

  11. The app kept running, because soft mode is enabled. So, back in Xcode, I went to the Devices and Simulators window, selected my device on the left, and clicked Opened Recent Logs.

  12. After waiting a few seconds, a crash report showed up. My app’s name is Test811668, so the crash report was Test811668-2026-01-20-160643.ips.

  13. I checked the timestamp to make sure it aligns with when I tapped the Test button in step 10.

  14. I selected the crash report and pressed the Space bar to Quick Look it. This is what I saw:

    Process:             Test811668 [1142]
    …
    
    Exception Type:    EXC_GUARD
    Exception Subtype: GUARD_TYPE_VIRT_MEMORY
    Exception Message: offset=0x0700000c1d5124f0, flavor=0x000000c8 (GUARD_EXC_MTE_SYNC_FAULT)
    Exception Codes:   0xa00000c800100106, 0x0700000c1d5124f0
    Exception Note:    SIMULATED (this is NOT a crash)
    
    Thread 0 Crashed:
    0   Test811668 … -[ViewController testAction:] + 112
    1   UIKitCore  … -[UIApplication sendAction:to:from:forEvent:] + 99
    2   UIKitCore  … -[UIControl sendAction:to:forEvent:] + 111
    3   UIKitCore  … -[UIControl _sendActionsForEvents:withEvent:] + 375
    4   UIKitCore  … -[UIButton _sendActionsForEvents:withEvent:] + 203
    …
    
  15. At the bottom of the file selection window, I selected Test811668 from the project popup.

  16. I opened the .ips file and Xcode displayed it’s fabulous crash report browser [2].

Please repeat these steps to confirm that you can reproduce my findings.


My understanding is that hard mode is not yet support for third-party apps.

I confirmed that.

There isn’t any deep meaning to this, it’s just a question of schedules not quite lining up. I expect that hard mode will return at some point, although I can’t offer a concrete timeline. I can say that it’s still not available in the iOS 26.3 beta that we’re currently seeding.

The weird part is that I can’t find that documented anywhere.

I managed to track down a mention of this in the Xcode 26.1.1 Release Notes (r. 160806045).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] To be clear, this is all public stuff. That is, I’m using the public release of Xcode, macOS, and iOS, running on ‘as bought in the Apple Store’ Mac and iPhone hardware. There’s no Apple magic here.

[2] Well, I was expecting it to be fabulous but for some reason Xcode can’t find my symbols. I think I know what’s going on there, but I don’t have time right now to dig deeper. The key point is that I am getting soft mode simulated crash reports.

For the record, I've made no further progress triggering an MTE crash using only the entitlements.

I tried a more extreme pointer manipulation and it still proceeds just fine:

    // Now guarantee a tag mismatch by modifying p2's tag
    unsigned old_tag = PTR_TAG(p2);
    unsigned new_tag = (old_tag + 1) % 16;
    uintptr_t addr_only = (uintptr_t)p2 & 0x00FFFFFFFFFFFFFF;
    void *p2_bad_tag = (void *)(addr_only | ((uintptr_t)new_tag << 56));
    
    os_log(OS_LOG_DEFAULT, "Original tag: %u, Modified tag: %u\n", PTR_TAG(p2), PTR_TAG(p2_bad_tag));
    os_log(OS_LOG_DEFAULT, "Attempting read with guaranteed bad tag...\n");
    
    volatile char c2 = *(volatile char *)p2_bad_tag;  // Should this crash?
    os_log(OS_LOG_DEFAULT, "Read succeeded!  Value: %d\n", c2);

yields

Original tag: 14, Modified tag: 15
Attempting read with guaranteed bad tag...
Read succeeded!  Value: 31

So, when running outside of the debug, MIE can run in one of two modes:

  • In soft mode, it generates simulated crash reports for violations.
  • In hard mode, it crashes the process.

My understanding is that hard mode is not yet support for third-party apps. The weird part is that I can’t find that documented anywhere. I’m gonna research why that’s not the case and get back to you.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks Quinn, much appreciated. I'll add that I've also tried Soft Mode extensively and I've never seen it do anything. No log messages, no crashes in the debugger, no crash reports in the on-device logs for MTE. I'm open to suggestions!

Occasionally I can get a crash in _zxm_xzone_malloc_free_outlined in an unrelated stack but this appears to be regular old corruption rather than MTE kicking in.

So, I have three bits of good news:

  • My boss got me an iPhone 17 so that I can test this properly. Thanks boss!
  • It was a public holiday in the US yesterday, which allowed me to catch up a bit, so I had some time to play with MIE today.
  • I figured out the sequence to get soft mode crash reports (-:

Here’s what I did:

  1. Using Xcode 26.2 on macOS 26.2 [1], I created a new project from the iOS > App template, choosing Objective-C as the language.

  2. In the view controller, I added this code:

    - (IBAction)testAction:(id)sender {
        char * buf = malloc(32);
        buf[16] += 1;
        free(buf);
        buf[16] += 1;
    }
    
  3. I added a Test button and wired it up to that action.

  4. In Signing & Capabilities, I added the Enhanced Security capability and checked all the boxes (-:

  5. In the scheme editor, I enabled Diagnostics > Hardware Memory Tagging.

  6. I selected an iPhone 17 running iOS 26.2 as my run destination and chose Product > Run.

  7. On the device, I tapped the Test button. It trapped, with Xcode highlighting the second increment. So far, so good.

  8. Back in Xcode, I chose Product > Stop.

  9. On the device again, I launched the app from the Home Screen.

  10. And tapped the Test button.

  11. The app kept running, because soft mode is enabled. So, back in Xcode, I went to the Devices and Simulators window, selected my device on the left, and clicked Opened Recent Logs.

  12. After waiting a few seconds, a crash report showed up. My app’s name is Test811668, so the crash report was Test811668-2026-01-20-160643.ips.

  13. I checked the timestamp to make sure it aligns with when I tapped the Test button in step 10.

  14. I selected the crash report and pressed the Space bar to Quick Look it. This is what I saw:

    Process:             Test811668 [1142]
    …
    
    Exception Type:    EXC_GUARD
    Exception Subtype: GUARD_TYPE_VIRT_MEMORY
    Exception Message: offset=0x0700000c1d5124f0, flavor=0x000000c8 (GUARD_EXC_MTE_SYNC_FAULT)
    Exception Codes:   0xa00000c800100106, 0x0700000c1d5124f0
    Exception Note:    SIMULATED (this is NOT a crash)
    
    Thread 0 Crashed:
    0   Test811668 … -[ViewController testAction:] + 112
    1   UIKitCore  … -[UIApplication sendAction:to:from:forEvent:] + 99
    2   UIKitCore  … -[UIControl sendAction:to:forEvent:] + 111
    3   UIKitCore  … -[UIControl _sendActionsForEvents:withEvent:] + 375
    4   UIKitCore  … -[UIButton _sendActionsForEvents:withEvent:] + 203
    …
    
  15. At the bottom of the file selection window, I selected Test811668 from the project popup.

  16. I opened the .ips file and Xcode displayed it’s fabulous crash report browser [2].

Please repeat these steps to confirm that you can reproduce my findings.


My understanding is that hard mode is not yet support for third-party apps.

I confirmed that.

There isn’t any deep meaning to this, it’s just a question of schedules not quite lining up. I expect that hard mode will return at some point, although I can’t offer a concrete timeline. I can say that it’s still not available in the iOS 26.3 beta that we’re currently seeding.

The weird part is that I can’t find that documented anywhere.

I managed to track down a mention of this in the Xcode 26.1.1 Release Notes (r. 160806045).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] To be clear, this is all public stuff. That is, I’m using the public release of Xcode, macOS, and iOS, running on ‘as bought in the Apple Store’ Mac and iPhone hardware. There’s no Apple magic here.

[2] Well, I was expecting it to be fabulous but for some reason Xcode can’t find my symbols. I think I know what’s going on there, but I don’t have time right now to dig deeper. The key point is that I am getting soft mode simulated crash reports.

Hardware Memory Tag (MIE) enforcement outside of debugger
 
 
Q