[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