[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.
This commit is contained in:
parent
7da3a66c06
commit
271a08889b
@ -58,6 +58,10 @@ public:
|
||||
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,
|
||||
|
||||
@ -296,6 +296,11 @@ public:
|
||||
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.
|
||||
///
|
||||
|
||||
@ -56,7 +56,8 @@ enum InlineStrategy {
|
||||
enum LoadScriptFromSymFile {
|
||||
eLoadScriptFromSymFileTrue,
|
||||
eLoadScriptFromSymFileFalse,
|
||||
eLoadScriptFromSymFileWarn
|
||||
eLoadScriptFromSymFileWarn,
|
||||
eLoadScriptFromSymFileTrusted,
|
||||
};
|
||||
|
||||
enum LoadCWDlldbinitFile {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 @@ bool HostInfoMacOSX::SharedCacheIndexFiles(FileSpec &filepath, UUID &uuid,
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -71,6 +71,8 @@ public:
|
||||
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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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[] = {
|
||||
|
||||
@ -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)">,
|
||||
|
||||
3
lldb/test/API/macosx/dsym_codesign/Makefile
Normal file
3
lldb/test/API/macosx/dsym_codesign/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
||||
78
lldb/test/API/macosx/dsym_codesign/TestdSYMCodesign.py
Normal file
78
lldb/test/API/macosx/dsym_codesign/TestdSYMCodesign.py
Normal file
@ -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
|
||||
|
||||
|
||||
@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",
|
||||
)
|
||||
5
lldb/test/API/macosx/dsym_codesign/dsym_script.py
Normal file
5
lldb/test/API/macosx/dsym_codesign/dsym_script.py
Normal file
@ -0,0 +1,5 @@
|
||||
import lldb
|
||||
|
||||
|
||||
def __lldb_init_module(debugger, internal_dict):
|
||||
lldb._dsym_codesign_test_loaded = True
|
||||
1
lldb/test/API/macosx/dsym_codesign/main.c
Normal file
1
lldb/test/API/macosx/dsym_codesign/main.c
Normal file
@ -0,0 +1 @@
|
||||
int main(void) { return 0; }
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user