diff options
Diffstat (limited to 'lldb')
18 files changed, 462 insertions, 120 deletions
diff --git a/lldb/packages/Python/lldbsuite/test/cpu_feature.py b/lldb/packages/Python/lldbsuite/test/cpu_feature.py index b46a5ac..d7668c1 100644 --- a/lldb/packages/Python/lldbsuite/test/cpu_feature.py +++ b/lldb/packages/Python/lldbsuite/test/cpu_feature.py @@ -62,7 +62,7 @@ class CPUFeature: class AArch64: FPMR = CPUFeature("fpmr") GCS = CPUFeature("gcs") - MTE = CPUFeature("mte") + MTE = CPUFeature("mte", "hw.optional.arm.FEAT_MTE4") MTE_STORE_ONLY = CPUFeature("mtestoreonly") PTR_AUTH = CPUFeature("paca", "hw.optional.arm.FEAT_PAuth2") SME = CPUFeature("sme", "hw.optional.arm.FEAT_SME") diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp index 91c93be..9cdb846 100644 --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -2067,6 +2067,43 @@ static bool ParseTrieEntries(DataExtractor &data, lldb::offset_t offset, return true; } +static bool +TryParseV2ObjCMetadataSymbol(const char *&symbol_name, + const char *&symbol_name_non_abi_mangled, + SymbolType &type) { + static constexpr llvm::StringLiteral g_objc_v2_prefix_class("_OBJC_CLASS_$_"); + static constexpr llvm::StringLiteral g_objc_v2_prefix_metaclass( + "_OBJC_METACLASS_$_"); + static constexpr llvm::StringLiteral g_objc_v2_prefix_ivar("_OBJC_IVAR_$_"); + + llvm::StringRef symbol_name_ref(symbol_name); + if (symbol_name_ref.empty()) + return false; + + if (symbol_name_ref.starts_with(g_objc_v2_prefix_class)) { + symbol_name_non_abi_mangled = symbol_name + 1; + symbol_name = symbol_name + g_objc_v2_prefix_class.size(); + type = eSymbolTypeObjCClass; + return true; + } + + if (symbol_name_ref.starts_with(g_objc_v2_prefix_metaclass)) { + symbol_name_non_abi_mangled = symbol_name + 1; + symbol_name = symbol_name + g_objc_v2_prefix_metaclass.size(); + type = eSymbolTypeObjCMetaClass; + return true; + } + + if (symbol_name_ref.starts_with(g_objc_v2_prefix_ivar)) { + symbol_name_non_abi_mangled = symbol_name + 1; + symbol_name = symbol_name + g_objc_v2_prefix_ivar.size(); + type = eSymbolTypeObjCIVar; + return true; + } + + return false; +} + static SymbolType GetSymbolType(const char *&symbol_name, bool &demangled_is_synthesized, const SectionSP &text_section_sp, @@ -2183,9 +2220,6 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); uint32_t i; FileSpecList dylib_files; - llvm::StringRef g_objc_v2_prefix_class("_OBJC_CLASS_$_"); - llvm::StringRef g_objc_v2_prefix_metaclass("_OBJC_METACLASS_$_"); - llvm::StringRef g_objc_v2_prefix_ivar("_OBJC_IVAR_$_"); UUID image_uuid; for (i = 0; i < m_header.ncmds; ++i) { @@ -2805,33 +2839,15 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { is_gsym = true; sym[sym_idx].SetExternal(true); - llvm::StringRef symbol_name_ref(symbol_name); - if (symbol_name_ref.starts_with( - g_objc_v2_prefix_class)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = - symbol_name + g_objc_v2_prefix_class.size(); - type = eSymbolTypeObjCClass; - demangled_is_synthesized = true; - - } else if (symbol_name_ref.starts_with( - g_objc_v2_prefix_metaclass)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = - symbol_name + g_objc_v2_prefix_metaclass.size(); - type = eSymbolTypeObjCMetaClass; - demangled_is_synthesized = true; - } else if (symbol_name_ref.starts_with( - g_objc_v2_prefix_ivar)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = - symbol_name + g_objc_v2_prefix_ivar.size(); - type = eSymbolTypeObjCIVar; + if (TryParseV2ObjCMetadataSymbol( + symbol_name, symbol_name_non_abi_mangled, + type)) { demangled_is_synthesized = true; } else { if (nlist.n_value != 0) symbol_section = section_info.GetSection( nlist.n_sect, nlist.n_value); + type = eSymbolTypeData; } break; @@ -3317,48 +3333,10 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { symbol_sect_name) { type = eSymbolTypeRuntime; - if (symbol_name) { - llvm::StringRef symbol_name_ref(symbol_name); - if (symbol_name_ref.starts_with("_OBJC_")) { - llvm::StringRef - g_objc_v2_prefix_class( - "_OBJC_CLASS_$_"); - llvm::StringRef - g_objc_v2_prefix_metaclass( - "_OBJC_METACLASS_$_"); - llvm::StringRef - g_objc_v2_prefix_ivar("_OBJC_IVAR_$_"); - if (symbol_name_ref.starts_with( - g_objc_v2_prefix_class)) { - symbol_name_non_abi_mangled = - symbol_name + 1; - symbol_name = - symbol_name + - g_objc_v2_prefix_class.size(); - type = eSymbolTypeObjCClass; - demangled_is_synthesized = true; - } else if ( - symbol_name_ref.starts_with( - g_objc_v2_prefix_metaclass)) { - symbol_name_non_abi_mangled = - symbol_name + 1; - symbol_name = - symbol_name + - g_objc_v2_prefix_metaclass.size(); - type = eSymbolTypeObjCMetaClass; - demangled_is_synthesized = true; - } else if (symbol_name_ref.starts_with( - g_objc_v2_prefix_ivar)) { - symbol_name_non_abi_mangled = - symbol_name + 1; - symbol_name = - symbol_name + - g_objc_v2_prefix_ivar.size(); - type = eSymbolTypeObjCIVar; - demangled_is_synthesized = true; - } - } - } + if (TryParseV2ObjCMetadataSymbol( + symbol_name, + symbol_name_non_abi_mangled, type)) + demangled_is_synthesized = true; } else if (symbol_sect_name && ::strstr(symbol_sect_name, "__gcc_except_tab") == @@ -3665,27 +3643,14 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { is_gsym = true; sym[sym_idx].SetExternal(true); - llvm::StringRef symbol_name_ref(symbol_name); - if (symbol_name_ref.starts_with(g_objc_v2_prefix_class)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = symbol_name + g_objc_v2_prefix_class.size(); - type = eSymbolTypeObjCClass; - demangled_is_synthesized = true; - - } else if (symbol_name_ref.starts_with(g_objc_v2_prefix_metaclass)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = symbol_name + g_objc_v2_prefix_metaclass.size(); - type = eSymbolTypeObjCMetaClass; - demangled_is_synthesized = true; - } else if (symbol_name_ref.starts_with(g_objc_v2_prefix_ivar)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = symbol_name + g_objc_v2_prefix_ivar.size(); - type = eSymbolTypeObjCIVar; + if (TryParseV2ObjCMetadataSymbol(symbol_name, + symbol_name_non_abi_mangled, type)) { demangled_is_synthesized = true; } else { if (nlist.n_value != 0) symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); + type = eSymbolTypeData; } } break; @@ -4124,38 +4089,9 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { ::strstr(symbol_sect_name, "__objc") == symbol_sect_name) { type = eSymbolTypeRuntime; - if (symbol_name) { - llvm::StringRef symbol_name_ref(symbol_name); - if (symbol_name_ref.starts_with("_OBJC_")) { - llvm::StringRef g_objc_v2_prefix_class( - "_OBJC_CLASS_$_"); - llvm::StringRef g_objc_v2_prefix_metaclass( - "_OBJC_METACLASS_$_"); - llvm::StringRef g_objc_v2_prefix_ivar( - "_OBJC_IVAR_$_"); - if (symbol_name_ref.starts_with(g_objc_v2_prefix_class)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = - symbol_name + g_objc_v2_prefix_class.size(); - type = eSymbolTypeObjCClass; - demangled_is_synthesized = true; - } else if (symbol_name_ref.starts_with( - g_objc_v2_prefix_metaclass)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = - symbol_name + g_objc_v2_prefix_metaclass.size(); - type = eSymbolTypeObjCMetaClass; - demangled_is_synthesized = true; - } else if (symbol_name_ref.starts_with( - g_objc_v2_prefix_ivar)) { - symbol_name_non_abi_mangled = symbol_name + 1; - symbol_name = - symbol_name + g_objc_v2_prefix_ivar.size(); - type = eSymbolTypeObjCIVar; - demangled_is_synthesized = true; - } - } - } + if (TryParseV2ObjCMetadataSymbol( + symbol_name, symbol_name_non_abi_mangled, type)) + demangled_is_synthesized = true; } else if (symbol_sect_name && ::strstr(symbol_sect_name, "__gcc_except_tab") == symbol_sect_name) { diff --git a/lldb/test/API/macosx/mte/Makefile b/lldb/test/API/macosx/mte/Makefile new file mode 100644 index 0000000..cb20942 --- /dev/null +++ b/lldb/test/API/macosx/mte/Makefile @@ -0,0 +1,12 @@ +C_SOURCES := main.c + +EXE := uaf_mte + +all: uaf_mte sign + +include Makefile.rules + +sign: mte-entitlements.plist uaf_mte +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 new file mode 100644 index 0000000..ef858b1 --- /dev/null +++ b/lldb/test/API/macosx/mte/TestDarwinMTE.py @@ -0,0 +1,110 @@ +"""Test MTE Memory Tagging on Apple platforms""" + +import lldb +import re +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbsuite.test.cpu_feature as cpu_feature + +exe_name = "uaf_mte" # Must match Makefile + + +class TestDarwinMTE(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessFeature(cpu_feature.AArch64.MTE) + def test_tag_fault(self): + self.build() + exe = self.getBuildArtifact(exe_name) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + process = target.LaunchSimple(None, None, None) + self.assertState(process.GetState(), lldb.eStateStopped, PROCESS_STOPPED) + + self.expect( + "thread info", + substrs=[ + "stop reason = EXC_ARM_MTE_TAG_FAULT", + "MTE tag mismatch detected", + ], + ) + + @skipUnlessFeature(cpu_feature.AArch64.MTE) + def test_memory_region(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "// before free", lldb.SBFileSpec("main.c"), exe_name=exe_name + ) + + # (lldb) memory region ptr + # [0x00000001005ec000-0x00000001009ec000) rw- + # memory tagging: enabled + # Modified memory (dirty) page list provided, 2 entries. + # Dirty pages: 0x1005ec000, 0x1005fc000. + self.expect("memory region ptr", substrs=["memory tagging: enabled"]) + + @skipUnlessFeature(cpu_feature.AArch64.MTE) + def test_memory_read_with_tags(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "// before free", lldb.SBFileSpec("main.c"), exe_name=exe_name + ) + + # (lldb) memory read ptr-16 ptr+48 --show-tags + # 0x7d2c00930: 00 00 00 00 00 00 00 00 d0 e3 a5 0a 02 00 00 00 ................ (tag: 0x3) + # 0x7d2c00940: 48 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 Hello........... (tag: 0xb) + # 0x7d2c00950: 57 6f 72 6c 64 00 00 00 00 00 00 00 00 00 00 00 World........... (tag: 0xb) + # 0x7d2c00960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ (tag: 0x9) + self.expect( + "memory read ptr-16 ptr+48 --show-tags", + substrs=[" Hello...........", " World..........."], + patterns=[r"(.*\(tag: 0x[0-9a-f]\)\n){4}"], + ) + + def _parse_pointer_tag(self, output): + return re.search(r"Logical tag: (0x[0-9a-f])", output).group(1) + + def _parse_memory_tags(self, output, expected_tag_count): + tags = re.findall(r"\): (0x[0-9a-f])", output) + self.assertEqual(len(tags), expected_tag_count) + return tags + + @skipUnlessFeature(cpu_feature.AArch64.MTE) + def test_memory_tag_read(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "// before free", lldb.SBFileSpec("main.c"), exe_name=exe_name + ) + + # (lldb) memory tag read ptr-1 ptr+33 + # Logical tag: 0x5 + # Allocation tags: + # [0x100a65a40, 0x100a65a50): 0xf (mismatch) + # [0x100a65a50, 0x100a65a60): 0x5 + # [0x100a65a60, 0x100a65a70): 0x5 + # [0x100a65a70, 0x100a65a80): 0x2 (mismatch) + self.expect( + "memory tag read ptr-1 ptr+33", + substrs=["Logical tag: 0x", "Allocation tags:", "(mismatch)"], + patterns=[r"(\[.*\): 0x[0-9a-f].*\n){4}"], + ) + output = self.res.GetOutput() + self.assertEqual(output.count("(mismatch)"), 2) + ptr_tag = self._parse_pointer_tag(output) + tags = self._parse_memory_tags(output, 4) + self.assertEqual(tags[1], ptr_tag) + self.assertEqual(tags[2], ptr_tag) + self.assertNotEqual(tags[0], ptr_tag) # Memory that comes before/after + self.assertNotEqual(tags[3], ptr_tag) # allocation has different tag. + + # Continue running until MTE fault + self.expect("process continue", substrs=["stop reason = EXC_ARM_MTE_TAG_FAULT"]) + + self.runCmd("memory tag read ptr-1 ptr+33") + output = self.res.GetOutput() + self.assertEqual(output.count("(mismatch)"), 4) + tags = self._parse_memory_tags(output, 4) + self.assertTrue(all(t != ptr_tag for t in tags)) diff --git a/lldb/test/API/macosx/mte/main.c b/lldb/test/API/macosx/mte/main.c new file mode 100644 index 0000000..f9f6b15 --- /dev/null +++ b/lldb/test/API/macosx/mte/main.c @@ -0,0 +1,28 @@ +#include <malloc/malloc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// Produce some names on the trace +const size_t tag_granule = 16; +static uint8_t *my_malloc(void) { return malloc(2 * tag_granule); } +static uint8_t *allocate(void) { return my_malloc(); } + +static void my_free(void *ptr) { free(ptr); } +static void deallocate(void *ptr) { my_free(ptr); } + +static void touch_memory(uint8_t *ptr) { ptr[7] = 1; } // invalid access +static void modify(uint8_t *ptr) { touch_memory(ptr); } + +int main() { + uint8_t *ptr = allocate(); + + strncpy((char *)ptr, "Hello", 16); + strncpy((char *)ptr + 16, "World", 16); + + deallocate(ptr); // before free + + modify(ptr); // use-after-free + + return 0; +} diff --git a/lldb/test/API/macosx/mte/mte-entitlements.plist b/lldb/test/API/macosx/mte/mte-entitlements.plist new file mode 100644 index 0000000..6de5d56 --- /dev/null +++ b/lldb/test/API/macosx/mte/mte-entitlements.plist @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.security.hardened-process</key> + <true/> + <key>com.apple.security.hardened-process.checked-allocations</key> + <true/> +</dict> +</plist> diff --git a/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp b/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp index 81d643d..beb5ae2 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp +++ b/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp @@ -1,4 +1,4 @@ -// REQUIRES: x86 +// REQUIRES: lld, x86 // Test symtab reading // RUN: %build --compiler=clang-cl --arch=64 --nodefaultlib -o %t.exe -- %s diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp index f541134..0cd48d9 100644 --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -1386,6 +1386,16 @@ int DNBProcessMemoryRegionInfo(nub_process_t pid, nub_addr_t addr, return -1; } +nub_bool_t DNBProcessGetMemoryTags(nub_process_t pid, nub_addr_t addr, + nub_size_t size, + std::vector<uint8_t> &tags) { + MachProcessSP procSP; + if (GetProcessSP(pid, procSP)) + return procSP->Task().GetMemoryTags(addr, size, tags); + + return false; +} + std::string DNBProcessGetProfileData(nub_process_t pid, DNBProfileDataScanType scanType) { MachProcessSP procSP; diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h index 10d1f68..1f3d539 100644 --- a/lldb/tools/debugserver/source/DNB.h +++ b/lldb/tools/debugserver/source/DNB.h @@ -105,6 +105,9 @@ nub_bool_t DNBProcessMemoryDeallocate(nub_process_t pid, nub_addr_t addr) DNB_EXPORT; int DNBProcessMemoryRegionInfo(nub_process_t pid, nub_addr_t addr, DNBRegionInfo *region_info) DNB_EXPORT; +nub_bool_t DNBProcessGetMemoryTags(nub_process_t pid, nub_addr_t addr, + nub_size_t size, + std::vector<uint8_t> &tags) DNB_EXPORT; std::string DNBProcessGetProfileData(nub_process_t pid, DNBProfileDataScanType scanType) DNB_EXPORT; diff --git a/lldb/tools/debugserver/source/DNBDefs.h b/lldb/tools/debugserver/source/DNBDefs.h index df8ca80..d98399a 100644 --- a/lldb/tools/debugserver/source/DNBDefs.h +++ b/lldb/tools/debugserver/source/DNBDefs.h @@ -358,10 +358,11 @@ struct DNBExecutableImageInfo { struct DNBRegionInfo { public: DNBRegionInfo() - : addr(0), size(0), permissions(0), dirty_pages(), vm_types() {} + : addr(0), size(0), permissions(0), flags(), dirty_pages(), vm_types() {} nub_addr_t addr; nub_addr_t size; uint32_t permissions; + std::vector<std::string> flags; std::vector<nub_addr_t> dirty_pages; std::vector<std::string> vm_types; }; diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.h b/lldb/tools/debugserver/source/MacOSX/MachTask.h index 2284f6b..c4a20b8 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.h +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.h @@ -56,6 +56,8 @@ public: nub_size_t ReadMemory(nub_addr_t addr, nub_size_t size, void *buf); nub_size_t WriteMemory(nub_addr_t addr, nub_size_t size, const void *buf); int GetMemoryRegionInfo(nub_addr_t addr, DNBRegionInfo *region_info); + nub_bool_t GetMemoryTags(nub_addr_t addr, nub_size_t size, + std::vector<uint8_t> &tags); std::string GetProfileData(DNBProfileDataScanType scanType); nub_addr_t AllocateMemory(nub_size_t size, uint32_t permissions); diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.mm b/lldb/tools/debugserver/source/MacOSX/MachTask.mm index e2395cf..21156fe 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.mm @@ -229,6 +229,23 @@ int MachTask::GetMemoryRegionInfo(nub_addr_t addr, DNBRegionInfo *region_info) { return ret; } +//---------------------------------------------------------------------- +// MachTask::GetMemoryTags +//---------------------------------------------------------------------- +nub_bool_t MachTask::GetMemoryTags(nub_addr_t addr, nub_size_t size, + std::vector<uint8_t> &tags) { + task_t task = TaskPort(); + if (task == TASK_NULL) + return false; + + bool ok = m_vm_memory.GetMemoryTags(task, addr, size, tags); + DNBLogThreadedIf(LOG_MEMORY, "MachTask::GetMemoryTags ( addr = 0x%8.8llx, " + "size = 0x%8.8llx ) => %s ( tag count = %llu)", + (uint64_t)addr, (uint64_t)size, (ok ? "ok" : "err"), + (uint64_t)tags.size()); + return ok; +} + #define TIME_VALUE_TO_TIMEVAL(a, r) \ do { \ (r)->tv_sec = (a)->seconds; \ diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp index f3aa4d7..bb57245 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp @@ -13,6 +13,7 @@ #include "MachVMMemory.h" #include "DNBLog.h" #include "MachVMRegion.h" +#include <cassert> #include <dlfcn.h> #include <mach/mach_vm.h> #include <mach/shared_region.h> @@ -123,6 +124,7 @@ nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address, region_info->addr = vmRegion.StartAddress(); region_info->size = vmRegion.GetByteSize(); region_info->permissions = vmRegion.GetDNBPermissions(); + region_info->flags = vmRegion.GetFlags(); region_info->dirty_pages = get_dirty_pages(task, vmRegion.StartAddress(), vmRegion.GetByteSize()); region_info->vm_types = vmRegion.GetMemoryTypes(); @@ -150,6 +152,63 @@ nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address, return true; } +// API availability: +// mach_vm_update_pointers_with_remote_tags() - 26.0 +// VM_OFFSET_LIST_MAX macro - 26.1 +#ifndef VM_OFFSET_LIST_MAX +#define VM_OFFSET_LIST_MAX 512 +#endif +using mach_vm_offset_list_t = mach_vm_offset_t *; +using mach_vm_update_pointers_with_remote_tags_t = kern_return_t( + mach_port_name_t target, mach_vm_offset_list_t in_pointer_list, + mach_msg_type_number_t in_pointer_listCnt, + mach_vm_offset_list_t out_pointer_list, + mach_msg_type_number_t *out_pointer_listCnt); + +nub_bool_t MachVMMemory::GetMemoryTags(task_t task, nub_addr_t address, + nub_size_t size, + std::vector<uint8_t> &tags) { + static auto mach_vm_update_pointers_with_remote_tags = + (mach_vm_update_pointers_with_remote_tags_t *)dlsym( + RTLD_DEFAULT, "mach_vm_update_pointers_with_remote_tags"); + assert(mach_vm_update_pointers_with_remote_tags); + + // Max batch size supported by mach_vm_update_pointers_with_remote_tags. + constexpr uint32_t max_ptr_count = VM_OFFSET_LIST_MAX; + constexpr uint32_t tag_shift = 56; + constexpr nub_addr_t tag_mask = + ((nub_addr_t)0x0f << tag_shift); // Lower half of top byte. + constexpr uint32_t tag_granule = 16; + + mach_msg_type_number_t ptr_count = + (size / tag_granule) + ((size % tag_granule > 0) ? 1 : 0); + ptr_count = std::min(ptr_count, max_ptr_count); + + auto ptr_arr = std::make_unique<mach_vm_offset_t[]>(ptr_count); + for (size_t i = 0; i < ptr_count; i++) + ptr_arr[i] = (address + i * tag_granule); + + mach_msg_type_number_t ptr_count_out = ptr_count; + m_err = mach_vm_update_pointers_with_remote_tags( + task, ptr_arr.get(), ptr_count, ptr_arr.get(), &ptr_count_out); + + const bool failed = (m_err.Fail() || (ptr_count != ptr_count_out)); + if (failed || DNBLogCheckLogBit(LOG_MEMORY)) + m_err.LogThreaded("::mach_vm_update_pointers_with_remote_tags ( task = " + "0x%4.4x, ptr_count = %d ) => %i ( ptr_count_out = %d)", + task, ptr_count, m_err.Status(), ptr_count_out); + if (failed) + return false; + + tags.reserve(ptr_count); + for (size_t i = 0; i < ptr_count; i++) { + nub_addr_t tag = (ptr_arr[i] & tag_mask) >> tag_shift; + tags.push_back(tag); + } + + return true; +} + static uint64_t GetPhysicalMemory() { // This doesn't change often at all. No need to poll each time. static uint64_t physical_memory = 0; diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h index 05d2c02..8a76160 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h +++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.h @@ -28,6 +28,8 @@ public: nub_size_t PageSize(task_t task); nub_bool_t GetMemoryRegionInfo(task_t task, nub_addr_t address, DNBRegionInfo *region_info); + nub_bool_t GetMemoryTags(task_t task, nub_addr_t address, nub_size_t size, + std::vector<uint8_t> &tags); nub_bool_t GetMemoryProfile(DNBProfileDataScanType scanType, task_t task, struct task_basic_info ti, cpu_type_t cputype, nub_process_t pid, vm_statistics64_data_t &vminfo, diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp index 97908b4..9d0d60f 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp @@ -114,6 +114,11 @@ bool MachVMRegion::RestoreProtections() { return false; } +#ifdef VM_REGION_FLAG_JIT_ENABLED +#define VM_REGION_HAS_FLAGS 1 +#else +#define VM_REGION_HAS_FLAGS 0 +#endif bool MachVMRegion::GetRegionForAddress(nub_addr_t addr) { // Restore any original protections and clear our vars Clear(); @@ -140,6 +145,30 @@ bool MachVMRegion::GetRegionForAddress(nub_addr_t addr) { if (failed) return false; if (log_protections) { +#if VM_REGION_HAS_FLAGS + DNBLogThreaded("info = { prot = %u, " + "max_prot = %u, " + "inheritance = 0x%8.8x, " + "offset = 0x%8.8llx, " + "user_tag = 0x%8.8x, " + "ref_count = %u, " + "shadow_depth = %u, " + "ext_pager = %u, " + "share_mode = %u, " + "is_submap = %d, " + "behavior = %d, " + "object_id = 0x%8.8x, " + "user_wired_count = 0x%4.4x, " + "flags = %d }", + m_data.protection, m_data.max_protection, m_data.inheritance, + (uint64_t)m_data.offset, m_data.user_tag, m_data.ref_count, + m_data.shadow_depth, m_data.external_pager, + m_data.share_mode, m_data.is_submap, m_data.behavior, + m_data.object_id, m_data.user_wired_count, m_data.flags); +#else + // Duplicate log call instead of #if-defing printing of flags to avoid + // compiler warning: 'embedding a directive within macro arguments has + // undefined behavior' DNBLogThreaded("info = { prot = %u, " "max_prot = %u, " "inheritance = 0x%8.8x, " @@ -158,6 +187,7 @@ bool MachVMRegion::GetRegionForAddress(nub_addr_t addr) { m_data.shadow_depth, m_data.external_pager, m_data.share_mode, m_data.is_submap, m_data.behavior, m_data.object_id, m_data.user_wired_count); +#endif } m_curr_protection = m_data.protection; @@ -183,6 +213,22 @@ uint32_t MachVMRegion::GetDNBPermissions() const { return dnb_permissions; } +#ifndef VM_REGION_FLAG_MTE_ENABLED +#define VM_REGION_FLAG_MTE_ENABLED 0x4 +#endif +std::vector<std::string> MachVMRegion::GetFlags() const { + std::vector<std::string> flags; +#if VM_REGION_HAS_FLAGS + if (m_data.flags & VM_REGION_FLAG_JIT_ENABLED) + flags.push_back("jit"); + if (m_data.flags & VM_REGION_FLAG_TPRO_ENABLED) + flags.push_back("tpro"); + if (m_data.flags & VM_REGION_FLAG_MTE_ENABLED) + flags.push_back("mt"); +#endif + return flags; +} + std::vector<std::string> MachVMRegion::GetMemoryTypes() const { std::vector<std::string> types; if (m_data.user_tag == VM_MEMORY_STACK) { diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h index cb77058..ba6e1f3 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h +++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.h @@ -40,9 +40,10 @@ public: vm_prot_t prot); bool RestoreProtections(); bool GetRegionForAddress(nub_addr_t addr); - std::vector<std::string> GetMemoryTypes() const; uint32_t GetDNBPermissions() const; + std::vector<std::string> GetFlags() const; + std::vector<std::string> GetMemoryTypes() const; const DNBError &GetError() { return m_err; } diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index d9fb22c..434e9cf 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -22,6 +22,9 @@ #include <mach/mach_vm.h> #include <mach/task_info.h> #include <memory> +#if __has_include(<os/security_config.h>) +#include <os/security_config.h> +#endif #include <pwd.h> #include <string> #include <sys/stat.h> @@ -502,6 +505,8 @@ void RNBRemote::CreatePacketTable() { memory_region_info, &RNBRemote::HandlePacket_MemoryRegionInfo, NULL, "qMemoryRegionInfo", "Return size and attributes of a memory region that " "contains the given address")); + t.push_back(Packet(get_memory_tags, &RNBRemote::HandlePacket_qMemTags, NULL, + "qMemTags", "Return tags for a region of memory")); t.push_back(Packet(get_profile_data, &RNBRemote::HandlePacket_GetProfileData, NULL, "qGetProfileData", "Return profiling data of the current target.")); @@ -3475,6 +3480,18 @@ static bool GetProcessNameFrom_vAttach(const char *&p, return return_val; } +static bool supports_memory_tagging() { + const char *name = "hw.optional.arm.FEAT_MTE4"; + uint32_t val; + size_t len = sizeof(val); + int ret = ::sysctlbyname(name, &val, &len, nullptr, 0); + if (ret != 0) + return false; + + assert(len == sizeof(val)); + return val; +} + rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) { uint32_t max_packet_size = 128 * 1024; // 128 KiB is a reasonable max packet // size--debugger can always use less @@ -3505,6 +3522,9 @@ rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) { reply << "SupportedWatchpointTypes=x86_64;"; #endif + if (supports_memory_tagging()) + reply << "memory-tagging+;"; + return SendPacket(reply.str().c_str()); } @@ -4251,7 +4271,6 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) { is in unmapped memory Region lookup cannot be performed on this platform or process is not yet launched - This packet isn't implemented Examples of use: qMemoryRegionInfo:3a55140 @@ -4303,6 +4322,16 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) { ostrm << 'x'; ostrm << ';'; + if (!region_info.flags.empty()) { + ostrm << "flags:"; + for (size_t i = 0; i < region_info.flags.size(); i++) { + if (i != 0) + ostrm << " "; // Separator is whitespace + ostrm << region_info.flags[i]; + } + ostrm << ";"; + } + ostrm << "dirty-pages:"; if (region_info.dirty_pages.size() > 0) { bool first = true; @@ -4327,6 +4356,62 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) { return SendPacket(ostrm.str()); } +// qMemTags:<hex address>,<hex length>:<hex type> +rnb_err_t RNBRemote::HandlePacket_qMemTags(const char *p) { + nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket("OK"); + + StdStringExtractor packet(p); + packet.SetFilePos(strlen("qMemTags:")); + + // Address + nub_addr_t addr = + packet.GetHexMaxU64(StdStringExtractor::BigEndian, INVALID_NUB_ADDRESS); + if (addr == INVALID_NUB_ADDRESS) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid/missing address in qMemTags packet"); + // , + if (packet.GetChar() != ',') + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid qMemTags packet format"); + // Length + uint64_t length = packet.GetHexMaxU64(StdStringExtractor::BigEndian, 0); + if (length == 0) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid/missing length in qMemTags packet"); + // : + if (packet.GetChar() != ':') + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid qMemTags packet format"); + // Type + // On the LLDB side this is a `int32_t` serialized as (unsigned) hex, which + // means negative values will show up as large positive values here. Right + // now, we only support MTE (type 1), so we can ignore this complication. + uint32_t type = packet.GetHexMaxU32(StdStringExtractor::BigEndian, 0); + if (type != 1 /* MTE */) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid/missing type in qMemTags packet, " + "only MTE (type 1) is supported"); + // <EOF> + if (packet.GetBytesLeft() != 0) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid qMemTags packet format"); + + std::vector<uint8_t> tags; + bool ok = DNBProcessGetMemoryTags(pid, addr, length, tags); + if (!ok) + return SendErrorPacket("E91"); + + std::ostringstream ostrm; + ostrm << "m"; // Multi part replies + for (uint8_t tag : tags) { + ostrm << RAWHEX8(tag); // 2 hex chars per tag + } + + return SendPacket(ostrm.str()); +} + // qGetProfileData;scan_type:0xYYYYYYY rnb_err_t RNBRemote::HandlePacket_GetProfileData(const char *p) { nub_process_t pid = m_ctx.ProcessID(); @@ -6162,6 +6247,21 @@ GetCPUTypesFromHost(nub_process_t pid) { return {cputype, cpusubtype}; } +static bool ProcessRunningWithMemoryTagging(pid_t pid) { +#if __has_include(<os/security_config.h>) + if (__builtin_available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, + visionOS 26.0, driverkit 25.0, *)) { + os_security_config_t config; + int ret = ::os_security_config_get_for_proc(pid, &config); + if (ret != 0) + return false; + + return (config & OS_SECURITY_CONFIG_MTE); + } +#endif + return false; +} + // Note that all numeric values returned by qProcessInfo are hex encoded, // including the pid and the cpu type. @@ -6338,6 +6438,9 @@ rnb_err_t RNBRemote::HandlePacket_qProcessInfo(const char *p) { rep << "vendor:apple;"; + if (ProcessRunningWithMemoryTagging(pid)) + rep << "mte:enabled;"; + #if defined(__LITTLE_ENDIAN__) rep << "endian:little;"; #elif defined(__BIG_ENDIAN__) diff --git a/lldb/tools/debugserver/source/RNBRemote.h b/lldb/tools/debugserver/source/RNBRemote.h index ad254ae..cf1c978 100644 --- a/lldb/tools/debugserver/source/RNBRemote.h +++ b/lldb/tools/debugserver/source/RNBRemote.h @@ -121,6 +121,7 @@ public: set_list_threads_in_stop_reply, // 'QListThreadsInStopReply:' sync_thread_state, // 'QSyncThreadState:' memory_region_info, // 'qMemoryRegionInfo:' + get_memory_tags, // 'qMemTags:' get_profile_data, // 'qGetProfileData' set_enable_profiling, // 'QSetEnableAsyncProfiling' enable_compression, // 'QEnableCompression:' @@ -237,6 +238,7 @@ public: rnb_err_t HandlePacket_SaveRegisterState(const char *p); rnb_err_t HandlePacket_RestoreRegisterState(const char *p); rnb_err_t HandlePacket_MemoryRegionInfo(const char *p); + rnb_err_t HandlePacket_qMemTags(const char *p); rnb_err_t HandlePacket_GetProfileData(const char *p); rnb_err_t HandlePacket_SetEnableAsyncProfiling(const char *p); rnb_err_t HandlePacket_QEnableCompression(const char *p); |