[llvm] 271a088 - [lldb] Load scripts from code signed dSYM bundles (#189444)

via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 3 10:04:53 PDT 2026


Author: Jonas Devlieghere
Date: 2026-04-03T17:04:47Z
New Revision: 271a08889b3e408037db15491b0390e472f003dc

URL: https://github.com/llvm/llvm-project/commit/271a08889b3e408037db15491b0390e472f003dc
DIFF: https://github.com/llvm/llvm-project/commit/271a08889b3e408037db15491b0390e472f003dc.diff

LOG: [lldb] Load scripts from code signed dSYM bundles (#189444)

LLDB automatically discovers, but doesn't automatically load, scripts in
the dSYM bundle. This is to prevent running untrusted code. Users can
choose to import the script manually or toggle a global setting to
override this policy. This isn't a great user experience: the former
quickly becomes tedious and the latter leads to decreased security.

This PR offers a middle ground that allows LLDB to automatically load
scripts from trusted dSYM bundles. Trusted here means that the bundle
was signed with a certificate trusted by the system. This can be a
locally created certificate (but not an ad-hoc certificate) or a
certificate from a trusted vendor.

Added: 
    lldb/test/API/macosx/dsym_codesign/Makefile
    lldb/test/API/macosx/dsym_codesign/TestdSYMCodesign.py
    lldb/test/API/macosx/dsym_codesign/dsym_script.py
    lldb/test/API/macosx/dsym_codesign/main.c

Modified: 
    lldb/include/lldb/Host/macosx/HostInfoMacOSX.h
    lldb/include/lldb/Target/Platform.h
    lldb/include/lldb/Target/Target.h
    lldb/source/Core/ModuleList.cpp
    lldb/source/Host/macosx/objcxx/CMakeLists.txt
    lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm
    lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
    lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h
    lldb/source/Target/Platform.cpp
    lldb/source/Target/Target.cpp
    lldb/source/Target/TargetProperties.td
    llvm/docs/ReleaseNotes.md

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h b/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h
index ed00c44df4bdc..6c2cabbf55c74 100644
--- a/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h
+++ b/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h
@@ -58,6 +58,10 @@ class HostInfoMacOSX : public HostInfoPosix {
   static bool SharedCacheIndexFiles(FileSpec &filepath, UUID &uuid,
                                     lldb::SymbolSharedCacheUse sc_mode);
 
+  /// Check whether a bundle at the given path has a valid code signature that
+  /// chains to a trusted anchor in the system trust store.
+  static bool IsBundleCodeSignTrusted(const FileSpec &bundle_path);
+
 protected:
   static bool ComputeSupportExeDirectory(FileSpec &file_spec);
   static void ComputeHostArchitectureSupport(ArchSpec &arch_32,

diff  --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h
index 6bdaf10ef0713..c94f6e84ff889 100644
--- a/lldb/include/lldb/Target/Platform.h
+++ b/lldb/include/lldb/Target/Platform.h
@@ -296,6 +296,11 @@ class Platform : public PluginInterface {
                                                   FileSpec module_spec,
                                                   const Target &target);
 
+  /// Returns true if the module's symbol file (e.g. a dSYM bundle) is
+  /// code-signed with a trusted signature. Used to decide whether to
+  /// auto-loaded scripts.
+  virtual bool IsSymbolFileTrusted(Module &module);
+
   /// \param[in] module_spec
   ///     The ModuleSpec of a binary to find.
   ///

diff  --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 77b8f04a4b3b8..2e9ee1de3c456 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -56,7 +56,8 @@ enum InlineStrategy {
 enum LoadScriptFromSymFile {
   eLoadScriptFromSymFileTrue,
   eLoadScriptFromSymFileFalse,
-  eLoadScriptFromSymFileWarn
+  eLoadScriptFromSymFileWarn,
+  eLoadScriptFromSymFileTrusted,
 };
 
 enum LoadCWDlldbinitFile {

diff  --git a/lldb/source/Core/ModuleList.cpp b/lldb/source/Core/ModuleList.cpp
index 67ebe58cdb872..34d609bb22d8f 100644
--- a/lldb/source/Core/ModuleList.cpp
+++ b/lldb/source/Core/ModuleList.cpp
@@ -1376,16 +1376,22 @@ bool ModuleList::LoadScriptingResourceInTargetForModule(Module &module,
     debugger.ReportWarning(feedback_stream.GetString().str(), debugger.GetID());
 
   for (const auto &[scripting_fspec, load_style] : file_specs) {
-    if (load_style == eLoadScriptFromSymFileFalse)
-      continue;
-
     if (!FileSystem::Instance().Exists(scripting_fspec))
       continue;
 
-    if (load_style == eLoadScriptFromSymFileWarn) {
-      // clang-format off
+    switch (load_style) {
+    case eLoadScriptFromSymFileFalse:
+      continue;
+    case eLoadScriptFromSymFileTrue:
+      break;
+    case eLoadScriptFromSymFileTrusted:
+      if (!platform_sp->IsSymbolFileTrusted(module))
+        continue;
+      break;
+    case eLoadScriptFromSymFileWarn:
       debugger.ReportWarning(
           llvm::formatv(
+      // clang-format off
 R"('{0}' contains a debug script. To run this script in this debug session:
 
     command script import "{1}"
@@ -1394,10 +1400,10 @@ To run all discovered debug scripts in this session:
 
     settings set target.load-script-from-symbol-file true
 )",
+      // clang-format on
               module.GetFileSpec().GetFileNameStrippingExtension(),
               scripting_fspec.GetPath()),
           debugger.GetID());
-      // clang-format on
 
       return false;
     }

diff  --git a/lldb/source/Host/macosx/objcxx/CMakeLists.txt b/lldb/source/Host/macosx/objcxx/CMakeLists.txt
index 1d7573335b8ec..9304f23c019b4 100644
--- a/lldb/source/Host/macosx/objcxx/CMakeLists.txt
+++ b/lldb/source/Host/macosx/objcxx/CMakeLists.txt
@@ -1,7 +1,8 @@
-
 remove_module_flags()
 include_directories(..)
 
+find_library(SECURITY_FRAMEWORK Security)
+
 add_lldb_library(lldbHostMacOSXObjCXX NO_PLUGIN_DEPENDENCIES
   Host.mm
   HostInfoMacOSX.mm
@@ -16,6 +17,7 @@ add_lldb_library(lldbHostMacOSXObjCXX NO_PLUGIN_DEPENDENCIES
   LINK_LIBS
     lldbUtility
     ${EXTRA_LIBS}
+    ${SECURITY_FRAMEWORK}
   )
 
 target_compile_options(lldbHostMacOSXObjCXX PRIVATE

diff  --git a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm
index fd0c11197fc66..2214678d392b5 100644
--- a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm
+++ b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm
@@ -44,6 +44,7 @@
 #include <AvailabilityMacros.h>
 #include <CoreFoundation/CoreFoundation.h>
 #include <Foundation/Foundation.h>
+#include <Security/Security.h>
 #include <mach-o/dyld.h>
 #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
     MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_0
@@ -1096,3 +1097,30 @@ static dispatch_data_t (*g_dyld_image_segment_data_4HWTrace)(
         uuid, filepath.GetPath());
   return false;
 }
+
+bool HostInfoMacOSX::IsBundleCodeSignTrusted(const FileSpec &bundle_path) {
+  std::string path = bundle_path.GetPath();
+  CFURLRef url = CFURLCreateFromFileSystemRepresentation(
+      kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(path.data()),
+      path.size(), /*isDirectory=*/true);
+  if (!url)
+    return false;
+  auto url_cleanup = llvm::make_scope_exit([&]() { CFRelease(url); });
+
+  SecStaticCodeRef static_code = nullptr;
+  if (SecStaticCodeCreateWithPath(url, kSecCSDefaultFlags, &static_code) !=
+      errSecSuccess)
+    return false;
+  auto code_cleanup = llvm::make_scope_exit([&]() { CFRelease(static_code); });
+
+  // Check that the signature chains to a trusted root CA.
+  SecRequirementRef requirement = nullptr;
+  if (SecRequirementCreateWithString(CFSTR("anchor trusted"),
+                                     kSecCSDefaultFlags,
+                                     &requirement) != errSecSuccess)
+    return false;
+  auto req_cleanup = llvm::make_scope_exit([&]() { CFRelease(requirement); });
+
+  return SecStaticCodeCheckValidity(static_code, kSecCSDefaultFlags,
+                                    requirement) == errSecSuccess;
+}

diff  --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
index c4865c4664651..21be75c5a25da 100644
--- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
+++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
@@ -50,6 +50,7 @@
 #include "llvm/Support/VersionTuple.h"
 
 #if defined(__APPLE__)
+#include "lldb/Host/macosx/HostInfoMacOSX.h"
 #include <TargetConditionals.h>
 #endif
 
@@ -295,6 +296,40 @@ PlatformDarwin::LocateExecutableScriptingResourcesForPlatform(
   return empty;
 }
 
+bool PlatformDarwin::IsSymbolFileTrusted(Module &module) {
+#if defined(__APPLE__)
+  SymbolFile *symfile = module.GetSymbolFile();
+  if (!symfile)
+    return false;
+
+  ObjectFile *objfile = symfile->GetObjectFile();
+  if (!objfile)
+    return false;
+
+  std::string symfile_path = objfile->GetFileSpec().GetPath();
+  llvm::StringRef path_ref(symfile_path);
+
+  // Find the .dSYM bundle root from the symfile path, which is typically
+  // .dSYM/Contents/Resources/DWARF/<name>.
+  auto pos = path_ref.find(".dSYM/");
+  if (pos == llvm::StringRef::npos)
+    return false;
+
+  FileSpec bundle_spec(path_ref.substr(0, pos + 5));
+
+  if (HostInfoMacOSX::IsBundleCodeSignTrusted(bundle_spec)) {
+    LLDB_LOG(GetLog(LLDBLog::Modules),
+             "dSYM bundle '{0}' has valid trusted code signature",
+             bundle_spec.GetPath());
+    return true;
+  }
+
+  return false;
+#else
+  return false;
+#endif
+}
+
 Status PlatformDarwin::ResolveSymbolFile(Target &target,
                                          const ModuleSpec &sym_spec,
                                          FileSpec &sym_file) {

diff  --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h
index 3c98d420dde8c..fd5207e82b6db 100644
--- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h
+++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h
@@ -71,6 +71,8 @@ class PlatformDarwin : public PlatformPOSIX {
   LocateExecutableScriptingResourcesForPlatform(
       Target *target, Module &module_spec, Stream &feedback_stream) override;
 
+  bool IsSymbolFileTrusted(Module &module) override;
+
   Status GetSharedModule(const ModuleSpec &module_spec, Process *process,
                          lldb::ModuleSP &module_sp,
                          llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules,

diff  --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp
index 83f4fdf33d4d2..57c30f2c95eb2 100644
--- a/lldb/source/Target/Platform.cpp
+++ b/lldb/source/Target/Platform.cpp
@@ -157,6 +157,8 @@ Status Platform::GetFileWithUUID(const FileSpec &platform_file,
   return Status();
 }
 
+bool Platform::IsSymbolFileTrusted(Module &module) { return false; }
+
 llvm::SmallDenseMap<FileSpec, LoadScriptFromSymFile>
 Platform::LocateExecutableScriptingResourcesFromSafePaths(
     Stream &feedback_stream, FileSpec module_spec, const Target &target) {

diff  --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 96c1a2400dd78..0168c7d686e37 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4371,6 +4371,12 @@ static constexpr OptionEnumValueElement g_load_script_from_sym_file_values[] = {
         "warn",
         "Warn about debug scripts inside symbol files but do not load them.",
     },
+    {
+        eLoadScriptFromSymFileTrusted,
+        "trusted",
+        "Load debug scripts inside trusted symbol files, and warn about "
+        "scripts from untrusted symbol files.",
+    },
 };
 
 static constexpr OptionEnumValueElement g_load_cwd_lldbinit_values[] = {

diff  --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index 2361314d506ac..f8b51ad8558b9 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -176,10 +176,12 @@ let Definition = "target", Path = "target" in {
   def UseFastStepping: Property<"use-fast-stepping", "Boolean">,
     DefaultTrue,
     Desc<"Use a fast stepping algorithm based on running from branch to branch rather than instruction single-stepping.">;
-  def LoadScriptFromSymbolFile: Property<"load-script-from-symbol-file", "Enum">,
-    DefaultEnumValue<"eLoadScriptFromSymFileWarn">,
-    EnumValues<"OptionEnumValues(g_load_script_from_sym_file_values)">,
-    Desc<"Allow LLDB to load scripting resources embedded in symbol files when available.">;
+  def LoadScriptFromSymbolFile
+      : Property<"load-script-from-symbol-file", "Enum">,
+        DefaultEnumValue<"eLoadScriptFromSymFileTrusted">,
+        EnumValues<"OptionEnumValues(g_load_script_from_sym_file_values)">,
+        Desc<"Allow LLDB to load scripting resources embedded in symbol files "
+             "when available.">;
   def LoadCWDlldbinitFile: Property<"load-cwd-lldbinit", "Enum">,
     DefaultEnumValue<"eLoadCWDlldbinitWarn">,
     EnumValues<"OptionEnumValues(g_load_cwd_lldbinit_values)">,

diff  --git a/lldb/test/API/macosx/dsym_codesign/Makefile b/lldb/test/API/macosx/dsym_codesign/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/macosx/dsym_codesign/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/macosx/dsym_codesign/TestdSYMCodesign.py b/lldb/test/API/macosx/dsym_codesign/TestdSYMCodesign.py
new file mode 100644
index 0000000000000..002343ab8c8ff
--- /dev/null
+++ b/lldb/test/API/macosx/dsym_codesign/TestdSYMCodesign.py
@@ -0,0 +1,78 @@
+import os
+import shutil
+import subprocess
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+def has_lldb_codesign():
+    """Check if the lldb_codesign certificate is available."""
+    try:
+        result = subprocess.run(
+            [
+                "security",
+                "find-certificate",
+                "-c",
+                "lldb_codesign",
+                "/Library/Keychains/System.keychain",
+            ],
+            capture_output=True,
+        )
+        return result.returncode == 0
+    except FileNotFoundError:
+        return False
+
+
+ at skipUnlessDarwin
+class TestdSYMCodesign(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+    SHARED_BUILD_TESTCASE = False
+
+    def build_dsym_with_script(self):
+        self.build(debug_info="dsym")
+        exe = self.getBuildArtifact("a.out")
+        dsym = self.getBuildArtifact("a.out.dSYM")
+        python_dir = os.path.join(dsym, "Contents", "Resources", "Python")
+        os.makedirs(python_dir, exist_ok=True)
+        shutil.copy(
+            os.path.join(self.getSourceDir(), "dsym_script.py"),
+            os.path.join(python_dir, "a.py"),
+        )
+        return exe, dsym
+
+    def test_adhoc_signed_dsym(self):
+        """An ad-hoc signed dSYM should not be loaded because the
+        signature doesn't chain to a trusted root CA."""
+        exe, dsym = self.build_dsym_with_script()
+        subprocess.check_call(["codesign", "-f", "-s", "-", dsym])
+
+        self.runCmd("settings set target.load-script-from-symbol-file trusted")
+        self.createTestTarget(file_path=exe)
+
+        self.expect(
+            "script -- print('SENTINEL')",
+            substrs=["SENTINEL"],
+        )
+        # The script should NOT have been loaded.
+        self.assertFalse(
+            hasattr(lldb, "_dsym_codesign_test_loaded"),
+            "Script should not auto-load from ad-hoc signed dSYM",
+        )
+
+    @unittest.skipUnless(has_lldb_codesign(), "requires lldb_codesign certificate")
+    def test_trusted_signed_dsym_auto_loads(self):
+        """A dSYM signed with the trusted lldb_codesign certificate should
+        auto-load scripts."""
+        exe, dsym = self.build_dsym_with_script()
+        subprocess.check_call(["codesign", "-f", "-s", "lldb_codesign", dsym])
+
+        self.runCmd("settings set target.load-script-from-symbol-file trusted")
+        self.createTestTarget(file_path=exe)
+
+        # The script sets a marker attribute on the lldb module.
+        self.assertTrue(
+            getattr(lldb, "_dsym_codesign_test_loaded", False),
+            "Script should auto-load from trusted signed dSYM",
+        )

diff  --git a/lldb/test/API/macosx/dsym_codesign/dsym_script.py b/lldb/test/API/macosx/dsym_codesign/dsym_script.py
new file mode 100644
index 0000000000000..2bc2887de75ed
--- /dev/null
+++ b/lldb/test/API/macosx/dsym_codesign/dsym_script.py
@@ -0,0 +1,5 @@
+import lldb
+
+
+def __lldb_init_module(debugger, internal_dict):
+    lldb._dsym_codesign_test_loaded = True

diff  --git a/lldb/test/API/macosx/dsym_codesign/main.c b/lldb/test/API/macosx/dsym_codesign/main.c
new file mode 100644
index 0000000000000..78f2de106c92b
--- /dev/null
+++ b/lldb/test/API/macosx/dsym_codesign/main.c
@@ -0,0 +1 @@
+int main(void) { return 0; }

diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 450b65a80c5cd..d0d8b9f4ddabc 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -244,6 +244,8 @@ Changes to LLDB
 * ``SBTarget::GetDataByteSize()``, ``SBTarget::GetCodeByteSize()``, and ``SBSection::GetTargetByteSize()``
   have been deprecated. They always return 1, as before.
 * A new ``webinspector-wasm`` platform was added to list and attach to WebAssebly processes in Safari.
+* The default for `load-script-from-symbol-file` was changed from `warn` to `trusted`. This means that scripts from
+  code signed dSYM bundles are now loaded automatically, while untrusted bundles continue to produce a warning.
 
 ### FreeBSD
 
@@ -256,7 +258,7 @@ Changes to LLDB
 #### Kernel Debugging
 
 * The plugin that analyzes FreeBSD kernel core dump and live core has been renamed from `freebsd-kernel` to
- `freebsd-kernel-core`. Remote kernel debugging is still handled by the `gdb-remote` plugin. 
+ `freebsd-kernel-core`. Remote kernel debugging is still handled by the `gdb-remote` plugin.
 * Support for libfbsdvmcore has been removed. As a result, FreeBSD kernel dump debugging is now only
   available on FreeBSD hosts. Live kernel debugging through the GDB remote protocol is still available
   from any platform.


        


More information about the llvm-commits mailing list