Skip to content

Conversation

yln
Copy link
Collaborator

@yln yln commented Oct 10, 2025

For debugging and bug-finding workflows on Darwin, support
launching processes with memory tagging for binaries that are
not entitled.

This will cause the process to behave as if the binary was entitled with:

<key>com.apple.security.hardened-process.checked-allocations</key>
<true/>

This has no effect on hardware without MTE support.

@llvmbot
Copy link
Member

llvmbot commented Oct 10, 2025

@llvm/pr-subscribers-lldb

Author: Julian Lettner (yln)

Changes

For debugging and bug-finding workflows, support
launching processes with MTE for binaries that are
not MTE entitled.


Full diff: https://github.com/llvm/llvm-project/pull/162944.diff

6 Files Affected:

  • (modified) lldb/include/lldb/lldb-enumerations.h (+2)
  • (modified) lldb/source/Commands/CommandOptionsProcessLaunch.cpp (+4)
  • (modified) lldb/source/Commands/Options.td (+4)
  • (modified) lldb/source/Host/macosx/objcxx/Host.mm (+27)
  • (modified) lldb/test/API/macosx/mte/Makefile (+6-3)
  • (modified) lldb/test/API/macosx/mte/TestDarwinMTE.py (+14-2)
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index fec9fdef44df9..e21880e8dcfb2 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -130,6 +130,8 @@ FLAGS_ENUM(LaunchFlags){
     eLaunchFlagInheritTCCFromParent =
         (1u << 12), ///< Don't make the inferior responsible for its own TCC
                     ///< permissions but instead inherit them from its parent.
+    eLaunchFlagMemoryTagging =
+        (1u << 13), ///< Launch with memory tagging (MTE).
 };
 
 /// Thread Run Modes.
diff --git a/lldb/source/Commands/CommandOptionsProcessLaunch.cpp b/lldb/source/Commands/CommandOptionsProcessLaunch.cpp
index 21d94d68ceb91..8ae20bd76f456 100644
--- a/lldb/source/Commands/CommandOptionsProcessLaunch.cpp
+++ b/lldb/source/Commands/CommandOptionsProcessLaunch.cpp
@@ -127,6 +127,10 @@ Status CommandOptionsProcessLaunch::SetOptionValue(
     break;
   }
 
+  case 'M':
+    launch_info.GetFlags().Set(eLaunchFlagMemoryTagging);
+    break;
+
   case 'c':
     if (!option_arg.empty())
       launch_info.SetShell(FileSpec(option_arg));
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 595b3d08abec5..a3742aacb63a6 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1173,6 +1173,10 @@ let Command = "process launch" in {
         Arg<"Boolean">,
         Desc<"Set whether to shell expand arguments to the process when "
              "launching.">;
+  def process_launch_memory_tagging
+      : Option<"memory-tagging", "M">,
+        Desc<"Set whether to enable memory tagging (MTE) when launching the "
+             "process.">;
 }
 
 let Command = "process attach" in {
diff --git a/lldb/source/Host/macosx/objcxx/Host.mm b/lldb/source/Host/macosx/objcxx/Host.mm
index 3c1d1179963d2..28f4717bfc87c 100644
--- a/lldb/source/Host/macosx/objcxx/Host.mm
+++ b/lldb/source/Host/macosx/objcxx/Host.mm
@@ -1210,6 +1210,33 @@ static Status LaunchProcessPosixSpawn(const char *exe_path,
     }
   }
 
+  if (launch_info.GetFlags().Test(eLaunchFlagMemoryTagging)) {
+    typedef int (*posix_spawnattr_set_use_sec_transition_shims_np_t)(
+        posix_spawnattr_t *attr, uint32_t flags);
+    posix_spawnattr_set_use_sec_transition_shims_np_t
+        posix_spawnattr_set_use_sec_transition_shims_np_fn =
+            (posix_spawnattr_set_use_sec_transition_shims_np_t)dlsym(
+                RTLD_DEFAULT,
+                "posix_spawnattr_set_use_sec_transition_shims_np");
+    if (posix_spawnattr_set_use_sec_transition_shims_np_fn) {
+      error =
+          Status(posix_spawnattr_set_use_sec_transition_shims_np_fn(&attr, 0),
+                 eErrorTypePOSIX);
+      if (error.Fail()) {
+        LLDB_LOG(log,
+                 "error: {0}, "
+                 "posix_spawnattr_set_use_sec_transition_shims_np(&attr, 0)",
+                 error);
+        return error;
+      }
+    } else {
+      LLDB_LOG(log,
+               "error: posix_spawnattr_set_use_sec_transition_shims_np not "
+               "available",
+               error);
+    }
+  }
+
   // Don't set the binpref if a shell was provided. After all, that's only
   // going to affect what version of the shell is launched, not what fork of
   // the binary is launched.  We insert "arch --arch <ARCH> as part of the
diff --git a/lldb/test/API/macosx/mte/Makefile b/lldb/test/API/macosx/mte/Makefile
index cb20942805e2a..d614e0f0a3bcd 100644
--- a/lldb/test/API/macosx/mte/Makefile
+++ b/lldb/test/API/macosx/mte/Makefile
@@ -1,12 +1,15 @@
 C_SOURCES := main.c
 
-EXE := uaf_mte
+EXE := uaf
 
-all: uaf_mte sign
+binary-plain:    uaf
+binary-entitled: uaf sign
+
+all: binary-entitled
 
 include Makefile.rules
 
-sign: mte-entitlements.plist uaf_mte
+sign: mte-entitlements.plist uaf
 ifeq ($(OS),Darwin)
 	codesign -s - -f --entitlements $^
 endif
diff --git a/lldb/test/API/macosx/mte/TestDarwinMTE.py b/lldb/test/API/macosx/mte/TestDarwinMTE.py
index ef858b1fc2710..a70b4b4aed26b 100644
--- a/lldb/test/API/macosx/mte/TestDarwinMTE.py
+++ b/lldb/test/API/macosx/mte/TestDarwinMTE.py
@@ -7,12 +7,24 @@
 from lldbsuite.test import lldbutil
 import lldbsuite.test.cpu_feature as cpu_feature
 
-exe_name = "uaf_mte"  # Must match Makefile
+exe_name = "uaf"  # Must match Makefile
 
 
 class TestDarwinMTE(TestBase):
     NO_DEBUG_INFO_TESTCASE = True
 
+    @skipUnlessFeature(cpu_feature.AArch64.MTE)
+    def test_process_launch_memory_tagging(self):
+        self.build(make_targets=["binary-plain"])
+        self.createTestTarget(self.getBuildArtifact(exe_name))
+
+        self.expect("process launch", substrs=["exited with status = 0"])
+
+        self.expect(
+            "process launch --memory-tagging",
+            substrs=["stopped", "stop reason = EXC_ARM_MTE_TAG_FAULT"],
+        )
+
     @skipUnlessFeature(cpu_feature.AArch64.MTE)
     def test_tag_fault(self):
         self.build()
@@ -47,7 +59,7 @@ def test_memory_region(self):
         self.expect("memory region ptr", substrs=["memory tagging: enabled"])
 
     @skipUnlessFeature(cpu_feature.AArch64.MTE)
-    def test_memory_read_with_tags(self):
+    def test_memory_read_show_tags(self):
         self.build()
         lldbutil.run_to_source_breakpoint(
             self, "// before free", lldb.SBFileSpec("main.c"), exe_name=exe_name

@llvm llvm deleted a comment from github-actions bot Oct 11, 2025
@DavidSpickett
Copy link
Collaborator

DavidSpickett commented Oct 13, 2025

Please make it clear in the PR description what operating systems this applies to and exactly what the entitlement actually enables. Meaning, what can a process allocate and from where and is it tagged, with and without the entitlement.

On Linux:

  • Any process can request PROT_MTE when calling mmap (I think Darwin does this too)
  • The closest to an entitlement is a glibc tuneable glibc.mem.tagging which enables the memory tagged heap.

So our equivalent for Linux would be some set env GLIBC_TUNABLES=. Which I wouldn't tie into this option because it's library specific.

target.env-vars -- A list of user provided environment variables to be passed to the executable's environment, and their values.

@yln yln changed the title [lldb] Add process launch --memory-tagging option [lldb][Darwin] Add process launch --memory-tagging option Oct 13, 2025
yln and others added 2 commits October 13, 2025 11:07
For debugging and bug-finding workflows on Darwin,
support launching processes with memory tagging
for binaries that are not entitled.

This will cause the process to behave as if the
binary was entitled with:
```
<key>com.apple.security.hardened-process.checked-allocations</key>
<true/>
```

This has no effect on hardware without MTE
support.

Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
Directly set `memory_tagging` in launch info flags
eliminating the need for a dedicated member in
`CommandOptionsProcessLaunch`.
@yln yln force-pushed the users/yln/lldb-process-launch-memory-tagging branch from 145b6cb to 18dca98 Compare October 13, 2025 18:08
@yln
Copy link
Collaborator Author

yln commented Oct 13, 2025

@DavidSpickett, thanks for your review! :)

Please make it clear in the PR description what operating systems this applies to

Update PR title & description, commit messages, comments, and docs.

and exactly what the entitlement actually enables. Meaning, what can a process allocate and from where and is it tagged, with and without the entitlement.

There is 2 separate ways to enable memory tagging in a process:

  • Entitlements: they are codesigned together with the binary (tamper proof). There is no way to run a "memory tagging"-entitled binary without memory tagging on hardware that supports it.
  • Spawn attributes allow us to run any binary--including those that don't carry the entitlement--with memory tagging. Most helpful for bug-finding before committing to enabling (and shipping) with memory tagging enabled.

On Linux:

  • Any process can request PROT_MTE when calling mmap (I think Darwin does this too)

On Darwin, only processes that are started with memory tagging enabled will be able to request tagged memory.

  • The closest to an entitlement is a glibc tuneable glibc.mem.tagging which enables the memory tagged heap.

If a process has memory tagging enabled, OS components like the system allocator will automatically start tagging memory.

Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Darwin experts can give the approval.

Just wondering what part of all the API calls is the key here.

Comment on lines +1213 to +1221
if (launch_info.GetFlags().Test(eLaunchFlagMemoryTagging)) {
// The following function configures the spawn attributes to launch the
// process with memory tagging explicitly enabled. We look it up
// dynamically since it is only available on newer OS. Does nothing on
// hardware which does not support MTE.
//
// int posix_spawnattr_set_use_sec_transition_shims_np(
// posix_spawnattr_t *attr, uint32_t flags);
//
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but I still don't see the key line below.

Generally even in code I'm not familiar with, I could at least point to a boolean or API call that does the key work. Here these names don't suggest "memory tagging" to me.

Is the 0 value what turns on memory tagging? Is "sec_transition_shims_np" something like "when spawning do so with secure mode shims enabled, which causes the process to be memory tagged even though it wouldn't enable it itself"?

I'm just amazed there are so many words here and some of them mean memory tagging but I've no idea what :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call of this API (through the function pointer) is the line that does the work.

posix_spawnattr_set_use_sec_transition_shims_np_fn(&attr, 0)

It means "launch with memory tagging enabled".

  • The naming of this function is unfortunate
  • We call through function pointer that is looked up via dlsym() because the function is not available on older OS
  • The flags=0 value allows for future, more specific semantics.
  • Everything else is just error handling

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment in the code along the lines of:
// This is the call that actually changes the setting.

Push this directly, no need for a PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in: c4d7c42

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@yln yln merged commit ced01f3 into main Oct 14, 2025
10 checks passed
@yln yln deleted the users/yln/lldb-process-launch-memory-tagging branch October 14, 2025 20:01
akadutta pushed a commit to akadutta/llvm-project that referenced this pull request Oct 14, 2025
)

For debugging and bug-finding workflows on Darwin, support
launching processes with memory tagging for binaries that are
not entitled.

This will cause the process to behave as if the binary was entitled
with:
```
<key>com.apple.security.hardened-process.checked-allocations</key>
<true/>
```

This has no effect on hardware without MTE support.

---------

Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants