https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/123720
>From 951a38910e49f3e93dc6c9a084e955ab01ad4c49 Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Fri, 9 Aug 2024 11:56:29 +0100 Subject: [PATCH 1/3] [lldb][AArch64] Add Guarded Control Stack registers The Guarded Control Stack extension implements a shadow stack and the Linux kernel provides access to 3 registers for it via ptrace. struct user_gcs { __u64 features_enabled; __u64 features_locked; __u64 gcspr_el0; }; This commit adds support for reading those from a live process. The first 2 are pseudo registers based on the real control register and the 3rd is a real register. This is the stack pointer for the guarded stack. I have added a "gcs_" prefix to the "features" registers so that they have a clear name when shown individually. Also this means they will tab complete from "gcs", and be next to gcspr_el0 in any sorted lists of registers. Guarded Control Stack Registers: gcs_features_enabled = 0x0000000000000000 gcs_features_locked = 0x0000000000000000 gcspr_el0 = 0x0000000000000000 Testing is more of the usual, where possible I'm writing a register then doing something in the program to confirm the value was actually sent to ptrace. --- .../NativeRegisterContextLinux_arm64.cpp | 86 +++++++++++ .../Linux/NativeRegisterContextLinux_arm64.h | 16 +++ .../Utility/RegisterContextPOSIX_arm64.cpp | 4 + .../Utility/RegisterContextPOSIX_arm64.h | 1 + .../Utility/RegisterInfoPOSIX_arm64.cpp | 39 ++++- .../Process/Utility/RegisterInfoPOSIX_arm64.h | 7 + .../linux/aarch64/gcs/TestAArch64LinuxGCS.py | 134 ++++++++++++++++++ lldb/test/API/linux/aarch64/gcs/main.c | 23 ++- 8 files changed, 305 insertions(+), 5 deletions(-) diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp index 6056f3001fed6e..efd3385c46e92f 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -64,8 +64,14 @@ #define NT_ARM_FPMR 0x40e /* Floating point mode register */ #endif +#ifndef NT_ARM_GCS +#define NT_ARM_GCS 0x410 /* Guarded Control Stack control registers */ +#endif + #define HWCAP_PACA (1 << 30) +#define HWCAP_GCS (1UL << 32) + #define HWCAP2_MTE (1 << 18) #define HWCAP2_FPMR (1UL << 48) @@ -150,6 +156,8 @@ NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux( opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskMTE); if (*auxv_at_hwcap2 & HWCAP2_FPMR) opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskFPMR); + if (*auxv_at_hwcap & HWCAP_GCS) + opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskGCS); } opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS); @@ -193,6 +201,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64( ::memset(&m_pac_mask, 0, sizeof(m_pac_mask)); ::memset(&m_tls_regs, 0, sizeof(m_tls_regs)); ::memset(&m_sme_pseudo_regs, 0, sizeof(m_sme_pseudo_regs)); + ::memset(&m_gcs_regs, 0, sizeof(m_gcs_regs)); std::fill(m_zt_reg.begin(), m_zt_reg.end(), 0); m_mte_ctrl_reg = 0; @@ -213,6 +222,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64( m_tls_is_valid = false; m_zt_buffer_is_valid = false; m_fpmr_is_valid = false; + m_gcs_is_valid = false; // SME adds the tpidr2 register m_tls_size = GetRegisterInfo().IsSSVEPresent() ? sizeof(m_tls_regs) @@ -433,6 +443,14 @@ NativeRegisterContextLinux_arm64::ReadRegister(const RegisterInfo *reg_info, offset = reg_info->byte_offset - GetRegisterInfo().GetFPMROffset(); assert(offset < GetFPMRBufferSize()); src = (uint8_t *)GetFPMRBuffer() + offset; + } else if (IsGCS(reg)) { + error = ReadGCS(); + if (error.Fail()) + return error; + + offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset(); + assert(offset < GetGCSBufferSize()); + src = (uint8_t *)GetGCSBuffer() + offset; } else return Status::FromErrorString( "failed - register wasn't recognized to be a GPR or an FPR, " @@ -657,6 +675,17 @@ Status NativeRegisterContextLinux_arm64::WriteRegister( ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); return WriteFPMR(); + } else if (IsGCS(reg)) { + error = ReadGCS(); + if (error.Fail()) + return error; + + offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset(); + assert(offset < GetGCSBufferSize()); + dst = (uint8_t *)GetGCSBuffer() + offset; + ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); + + return WriteGCS(); } return Status::FromErrorString("Failed to write register value"); @@ -672,6 +701,7 @@ enum RegisterSetType : uint32_t { SME, // ZA only, because SVCR and SVG are pseudo registers. SME2, // ZT only. FPMR, + GCS, // Guarded Control Stack registers. }; static uint8_t *AddRegisterSetType(uint8_t *dst, @@ -759,6 +789,13 @@ NativeRegisterContextLinux_arm64::CacheAllRegisters(uint32_t &cached_size) { return error; } + if (GetRegisterInfo().IsGCSPresent()) { + cached_size += sizeof(RegisterSetType) + GetGCSBufferSize(); + error = ReadGCS(); + if (error.Fail()) + return error; + } + // tpidr is always present but tpidr2 depends on SME. cached_size += sizeof(RegisterSetType) + GetTLSBufferSize(); error = ReadTLS(); @@ -867,6 +904,11 @@ Status NativeRegisterContextLinux_arm64::ReadAllRegisterValues( GetFPMRBufferSize()); } + if (GetRegisterInfo().IsGCSPresent()) { + dst = AddSavedRegisters(dst, RegisterSetType::GCS, GetGCSBuffer(), + GetGCSBufferSize()); + } + dst = AddSavedRegisters(dst, RegisterSetType::TLS, GetTLSBuffer(), GetTLSBufferSize()); @@ -1020,6 +1062,11 @@ Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues( GetFPMRBuffer(), &src, GetFPMRBufferSize(), m_fpmr_is_valid, std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this)); break; + case RegisterSetType::GCS: + error = RestoreRegisters( + GetGCSBuffer(), &src, GetGCSBufferSize(), m_gcs_is_valid, + std::bind(&NativeRegisterContextLinux_arm64::WriteGCS, this)); + break; } if (error.Fail()) @@ -1067,6 +1114,10 @@ bool NativeRegisterContextLinux_arm64::IsFPMR(unsigned reg) const { return GetRegisterInfo().IsFPMRReg(reg); } +bool NativeRegisterContextLinux_arm64::IsGCS(unsigned reg) const { + return GetRegisterInfo().IsGCSReg(reg); +} + llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() { if (!m_refresh_hwdebug_info) { return llvm::Error::success(); @@ -1215,6 +1266,7 @@ void NativeRegisterContextLinux_arm64::InvalidateAllRegisters() { m_tls_is_valid = false; m_zt_buffer_is_valid = false; m_fpmr_is_valid = false; + m_gcs_is_valid = false; // Update SVE and ZA registers in case there is change in configuration. ConfigureRegisterContext(); @@ -1400,6 +1452,40 @@ Status NativeRegisterContextLinux_arm64::WriteTLS() { return WriteRegisterSet(&ioVec, GetTLSBufferSize(), NT_ARM_TLS); } +Status NativeRegisterContextLinux_arm64::ReadGCS() { + Status error; + + if (m_gcs_is_valid) + return error; + + struct iovec ioVec; + ioVec.iov_base = GetGCSBuffer(); + ioVec.iov_len = GetGCSBufferSize(); + + error = ReadRegisterSet(&ioVec, GetGCSBufferSize(), NT_ARM_GCS); + + if (error.Success()) + m_gcs_is_valid = true; + + return error; +} + +Status NativeRegisterContextLinux_arm64::WriteGCS() { + Status error; + + error = ReadGCS(); + if (error.Fail()) + return error; + + struct iovec ioVec; + ioVec.iov_base = GetGCSBuffer(); + ioVec.iov_len = GetGCSBufferSize(); + + m_gcs_is_valid = false; + + return WriteRegisterSet(&ioVec, GetGCSBufferSize(), NT_ARM_GCS); +} + Status NativeRegisterContextLinux_arm64::ReadZAHeader() { Status error; diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h index 16190b5492582b..7ed0da85034969 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -92,6 +92,7 @@ class NativeRegisterContextLinux_arm64 bool m_pac_mask_is_valid; bool m_tls_is_valid; size_t m_tls_size; + bool m_gcs_is_valid; struct user_pt_regs m_gpr_arm64; // 64-bit general purpose registers. @@ -136,6 +137,12 @@ class NativeRegisterContextLinux_arm64 uint64_t m_fpmr_reg; + struct gcs_regs { + uint64_t features_enabled; + uint64_t features_locked; + uint64_t gcspr_e0; + } m_gcs_regs; + bool IsGPR(unsigned reg) const; bool IsFPR(unsigned reg) const; @@ -166,6 +173,10 @@ class NativeRegisterContextLinux_arm64 Status WriteZA(); + Status ReadGCS(); + + Status WriteGCS(); + // No WriteZAHeader because writing only the header will disable ZA. // Instead use WriteZA and ensure you have the correct ZA buffer size set // beforehand if you wish to disable it. @@ -187,6 +198,7 @@ class NativeRegisterContextLinux_arm64 bool IsMTE(unsigned reg) const; bool IsTLS(unsigned reg) const; bool IsFPMR(unsigned reg) const; + bool IsGCS(unsigned reg) const; uint64_t GetSVERegVG() { return m_sve_header.vl / 8; } @@ -212,6 +224,8 @@ class NativeRegisterContextLinux_arm64 void *GetFPMRBuffer() { return &m_fpmr_reg; } + void *GetGCSBuffer() { return &m_gcs_regs; } + size_t GetSVEHeaderSize() { return sizeof(m_sve_header); } size_t GetPACMaskSize() { return sizeof(m_pac_mask); } @@ -234,6 +248,8 @@ class NativeRegisterContextLinux_arm64 size_t GetFPMRBufferSize() { return sizeof(m_fpmr_reg); } + size_t GetGCSBufferSize() { return sizeof(m_gcs_regs); } + llvm::Error ReadHardwareDebugInfo() override; llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override; diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp index 575e9c8c81cbf5..0233837f99d097 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp @@ -63,6 +63,10 @@ bool RegisterContextPOSIX_arm64::IsFPMR(unsigned reg) const { return m_register_info_up->IsFPMRReg(reg); } +bool RegisterContextPOSIX_arm64::IsGCS(unsigned reg) const { + return m_register_info_up->IsGCSReg(reg); +} + RegisterContextPOSIX_arm64::RegisterContextPOSIX_arm64( lldb_private::Thread &thread, std::unique_ptr<RegisterInfoPOSIX_arm64> register_info) diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h index 35ad56c98a7aed..de46c628d836d8 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h +++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h @@ -59,6 +59,7 @@ class RegisterContextPOSIX_arm64 : public lldb_private::RegisterContext { bool IsSME(unsigned reg) const; bool IsMTE(unsigned reg) const; bool IsFPMR(unsigned reg) const; + bool IsGCS(unsigned reg) const; bool IsSVEZ(unsigned reg) const { return m_register_info_up->IsSVEZReg(reg); } bool IsSVEP(unsigned reg) const { return m_register_info_up->IsSVEPReg(reg); } diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp index f51a93e1b2dcbd..c004c0f3c3cf52 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp @@ -97,6 +97,10 @@ static lldb_private::RegisterInfo g_register_infos_sme2[] = { static lldb_private::RegisterInfo g_register_infos_fpmr[] = { DEFINE_EXTENSION_REG(fpmr)}; +static lldb_private::RegisterInfo g_register_infos_gcs[] = { + DEFINE_EXTENSION_REG(gcs_features_enabled), + DEFINE_EXTENSION_REG(gcs_features_locked), DEFINE_EXTENSION_REG(gcspr_el0)}; + // Number of register sets provided by this context. enum { k_num_gpr_registers = gpr_w28 - gpr_x0 + 1, @@ -109,6 +113,7 @@ enum { // only for SME1 registers. k_num_sme_register = 3, k_num_fpmr_register = 1, + k_num_gcs_register = 3, k_num_register_sets_default = 2, k_num_register_sets = 3 }; @@ -221,6 +226,9 @@ static const lldb_private::RegisterSet g_reg_set_sme_arm64 = { static const lldb_private::RegisterSet g_reg_set_fpmr_arm64 = { "Floating Point Mode Register", "fpmr", k_num_fpmr_register, nullptr}; +static const lldb_private::RegisterSet g_reg_set_gcs_arm64 = { + "Guarded Control Stack Registers", "gcs", k_num_gcs_register, nullptr}; + RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64( const lldb_private::ArchSpec &target_arch, lldb_private::Flags opt_regsets) : lldb_private::RegisterInfoAndSetInterface(target_arch), @@ -273,6 +281,9 @@ RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64( if (m_opt_regsets.AllSet(eRegsetMaskFPMR)) AddRegSetFPMR(); + if (m_opt_regsets.AllSet(eRegsetMaskGCS)) + AddRegSetGCS(); + m_register_info_count = m_dynamic_reg_infos.size(); m_register_info_p = m_dynamic_reg_infos.data(); m_register_set_p = m_dynamic_reg_sets.data(); @@ -434,6 +445,24 @@ void RegisterInfoPOSIX_arm64::AddRegSetFPMR() { m_dynamic_reg_sets.back().registers = m_fpmr_regnum_collection.data(); } +void RegisterInfoPOSIX_arm64::AddRegSetGCS() { + uint32_t gcs_regnum = m_dynamic_reg_infos.size(); + for (uint32_t i = 0; i < k_num_gcs_register; i++) { + m_gcs_regnum_collection.push_back(gcs_regnum + i); + m_dynamic_reg_infos.push_back(g_register_infos_gcs[i]); + m_dynamic_reg_infos[gcs_regnum + i].byte_offset = + m_dynamic_reg_infos[gcs_regnum + i - 1].byte_offset + + m_dynamic_reg_infos[gcs_regnum + i - 1].byte_size; + m_dynamic_reg_infos[gcs_regnum + i].kinds[lldb::eRegisterKindLLDB] = + gcs_regnum + i; + } + + m_per_regset_regnum_range[m_register_set_count] = + std::make_pair(gcs_regnum, m_dynamic_reg_infos.size()); + m_dynamic_reg_sets.push_back(g_reg_set_gcs_arm64); + m_dynamic_reg_sets.back().registers = m_gcs_regnum_collection.data(); +} + uint32_t RegisterInfoPOSIX_arm64::ConfigureVectorLengthSVE(uint32_t sve_vq) { // sve_vq contains SVE Quad vector length in context of AArch64 SVE. // SVE register infos if enabled cannot be disabled by selecting sve_vq = 0. @@ -561,6 +590,10 @@ bool RegisterInfoPOSIX_arm64::IsFPMRReg(unsigned reg) const { return llvm::is_contained(m_fpmr_regnum_collection, reg); } +bool RegisterInfoPOSIX_arm64::IsGCSReg(unsigned reg) const { + return llvm::is_contained(m_gcs_regnum_collection, reg); +} + uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEZ0() const { return sve_z0; } uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEFFR() const { return sve_ffr; } @@ -593,4 +626,8 @@ uint32_t RegisterInfoPOSIX_arm64::GetSMEOffset() const { uint32_t RegisterInfoPOSIX_arm64::GetFPMROffset() const { return m_register_info_p[m_fpmr_regnum_collection[0]].byte_offset; -} \ No newline at end of file +} + +uint32_t RegisterInfoPOSIX_arm64::GetGCSOffset() const { + return m_register_info_p[m_gcs_regnum_collection[0]].byte_offset; +} diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h index 16a951ef0935f0..d2ddf7d86d8c39 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h @@ -33,6 +33,7 @@ class RegisterInfoPOSIX_arm64 eRegsetMaskZA = 32, eRegsetMaskZT = 64, eRegsetMaskFPMR = 128, + eRegsetMaskGCS = 256, eRegsetMaskDynamic = ~1, }; @@ -113,6 +114,8 @@ class RegisterInfoPOSIX_arm64 void AddRegSetFPMR(); + void AddRegSetGCS(); + uint32_t ConfigureVectorLengthSVE(uint32_t sve_vq); void ConfigureVectorLengthZA(uint32_t za_vq); @@ -132,6 +135,7 @@ class RegisterInfoPOSIX_arm64 bool IsMTEPresent() const { return m_opt_regsets.AnySet(eRegsetMaskMTE); } bool IsTLSPresent() const { return m_opt_regsets.AnySet(eRegsetMaskTLS); } bool IsFPMRPresent() const { return m_opt_regsets.AnySet(eRegsetMaskFPMR); } + bool IsGCSPresent() const { return m_opt_regsets.AnySet(eRegsetMaskGCS); } bool IsSVEReg(unsigned reg) const; bool IsSVEZReg(unsigned reg) const; @@ -144,6 +148,7 @@ class RegisterInfoPOSIX_arm64 bool IsSMERegZA(unsigned reg) const; bool IsSMERegZT(unsigned reg) const; bool IsFPMRReg(unsigned reg) const; + bool IsGCSReg(unsigned reg) const; uint32_t GetRegNumSVEZ0() const; uint32_t GetRegNumSVEFFR() const; @@ -156,6 +161,7 @@ class RegisterInfoPOSIX_arm64 uint32_t GetTLSOffset() const; uint32_t GetSMEOffset() const; uint32_t GetFPMROffset() const; + uint32_t GetGCSOffset() const; private: typedef std::map<uint32_t, std::vector<lldb_private::RegisterInfo>> @@ -188,6 +194,7 @@ class RegisterInfoPOSIX_arm64 std::vector<uint32_t> m_tls_regnum_collection; std::vector<uint32_t> m_sme_regnum_collection; std::vector<uint32_t> m_fpmr_regnum_collection; + std::vector<uint32_t> m_gcs_regnum_collection; }; #endif diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py index 0928ff8e14e000..e08d4821bafc4f 100644 --- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py +++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py @@ -83,3 +83,137 @@ def test_gcs_fault(self): "stop reason = signal SIGSEGV: control protection fault", ], ) + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + def test_gcs_registers(self): + if not self.isAArch64GCS(): + self.skipTest("Target must support GCS.") + + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + self.runCmd("b test_func") + self.runCmd("b test_func2") + self.runCmd("run", RUN_SUCCEEDED) + + if self.process().GetState() == lldb.eStateExited: + self.fail("Test program failed to run.") + + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + + self.expect("register read --all", substrs=["Guarded Control Stack Registers:"]) + + def check_gcs_registers( + expected_gcs_features_enabled=None, + expected_gcs_features_locked=None, + expected_gcspr_el0=None, + ): + thread = self.dbg.GetSelectedTarget().process.GetThreadAtIndex(0) + registerSets = thread.GetFrameAtIndex(0).GetRegisters() + gcs_registers = registerSets.GetFirstValueByName( + r"Guarded Control Stack Registers" + ) + + gcs_features_enabled = gcs_registers.GetChildMemberWithName( + "gcs_features_enabled" + ).GetValueAsUnsigned() + if expected_gcs_features_enabled is not None: + self.assertEqual(expected_gcs_features_enabled, gcs_features_enabled) + + gcs_features_locked = gcs_registers.GetChildMemberWithName( + "gcs_features_locked" + ).GetValueAsUnsigned() + if expected_gcs_features_locked is not None: + self.assertEqual(expected_gcs_features_locked, gcs_features_locked) + + gcspr_el0 = gcs_registers.GetChildMemberWithName( + "gcspr_el0" + ).GetValueAsUnsigned() + if expected_gcspr_el0 is not None: + self.assertEqual(expected_gcspr_el0, gcspr_el0) + + return gcs_features_enabled, gcs_features_locked, gcspr_el0 + + enabled, locked, spr_el0 = check_gcs_registers() + + # Features enabled should have at least the enable bit set, it could have + # others depending on what the C library did. + self.assertTrue(enabled & 1, "Expected GCS enable bit to be set.") + + # Features locked we cannot predict, we will just assert that it remains + # the same as we continue. + + # spr_el0 will point to some memory region that is a shadow stack region. + self.expect(f"memory region {spr_el0}", substrs=["shadow stack: yes"]) + + # Continue into test_func2, where the GCS pointer should have been + # decremented, and the other registers remain the same. + self.runCmd("continue") + + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + + _, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8) + + # Modify the control stack pointer to cause a fault. + spr_el0 += 8 + self.runCmd(f"register write gcspr_el0 {spr_el0}") + self.expect( + "register read gcspr_el0", substrs=[f"gcspr_el0 = 0x{spr_el0:016x}"] + ) + + # If we wrote it back correctly, we will now fault but don't pass this + # signal to the application. + self.runCmd("process handle SIGSEGV --pass false") + self.runCmd("continue") + + self.expect( + "thread list", + "Expected stopped by SIGSEGV.", + substrs=[ + "stopped", + "stop reason = signal SIGSEGV: control protection fault", + ], + ) + + # Any combination of lock bits could be set. Flip then restore one of them. + STACK_PUSH = 2 + stack_push = bool((locked >> STACK_PUSH) & 1) + new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH) + self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}") + self.expect( + f"register read gcs_features_locked", + substrs=[f"gcs_features_locked = 0x{new_locked:016x}"], + ) + + # We could prove the write made it to hardware by trying to prctl to change + # the feature here, but we cannot know if the libc locked it or not. + # Given that we know the other registers in the set write correctly, we + # can assume this one does. + + self.runCmd(f"register write gcs_features_locked 0x{locked:x}") + + # Now to prove we can write gcs_features_enabled, disable GCS and continue + # past the fault. + enabled &= ~1 + self.runCmd(f"register write gcs_features_enabled {enabled}") + self.expect( + "register read gcs_features_enabled", + substrs=[f"gcs_features_enabled = 0x{enabled:016x}"], + ) + + self.runCmd("continue") + self.expect( + "process status", + substrs=[ + "exited with status = 0", + ], + ) diff --git a/lldb/test/API/linux/aarch64/gcs/main.c b/lldb/test/API/linux/aarch64/gcs/main.c index 32a9b07c207436..09354639af376f 100644 --- a/lldb/test/API/linux/aarch64/gcs/main.c +++ b/lldb/test/API/linux/aarch64/gcs/main.c @@ -2,8 +2,8 @@ #include <sys/auxv.h> #include <sys/prctl.h> -#ifndef HWCAP2_GCS -#define HWCAP2_GCS (1UL << 63) +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) #endif #define PR_GET_SHADOW_STACK_STATUS 74 @@ -49,8 +49,14 @@ void gcs_signal() { "ret\n"); } +// These functions are used to observe gcspr_el0 changing as we enter them, and +// the fault we cause by changing its value. +void test_func2() { volatile int i = 99; } + +void test_func() { test_func2(); } + int main() { - if (!(getauxval(AT_HWCAP2) & HWCAP2_GCS)) + if (!(getauxval(AT_HWCAP) & HWCAP_GCS)) return 1; unsigned long mode = get_gcs_status(); @@ -63,7 +69,16 @@ int main() { } // By now we should have one memory region where the GCS is stored. - gcs_signal(); // Set break point at this line. + + // For register read/write tests. + test_func(); + + // If this was a register test, we would have disabled GCS during the + // test_func call. We cannot re-enable it from ptrace so skip this part in + // this case. + mode = get_gcs_status(); + if ((mode & 1) == 1) + gcs_signal(); // Set break point at this line. return 0; } >From e6faeef5a26158598e0e365d0044c7728edc17b9 Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Fri, 24 Jan 2025 09:13:21 +0000 Subject: [PATCH 2/3] better document test case --- .../linux/aarch64/gcs/TestAArch64LinuxGCS.py | 68 ++++++++++++------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py index e08d4821bafc4f..780624a45ec50b 100644 --- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py +++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py @@ -108,6 +108,8 @@ def test_gcs_registers(self): self.expect("register read --all", substrs=["Guarded Control Stack Registers:"]) + # This helper reads all the GCS registers and optionally compares them + # against a previous state, then returns the current register values. def check_gcs_registers( expected_gcs_features_enabled=None, expected_gcs_features_locked=None, @@ -142,7 +144,8 @@ def check_gcs_registers( enabled, locked, spr_el0 = check_gcs_registers() # Features enabled should have at least the enable bit set, it could have - # others depending on what the C library did. + # others depending on what the C library did, but we can't rely on always + # having them. self.assertTrue(enabled & 1, "Expected GCS enable bit to be set.") # Features locked we cannot predict, we will just assert that it remains @@ -163,15 +166,44 @@ def check_gcs_registers( _, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8) - # Modify the control stack pointer to cause a fault. + # Any combination of GCS feature lock bits might have been set by the C + # library, and could be set to 0 or 1. To check that we can modify them, + # invert one of those bits then write it back to the lock register. + # The stack pushing feature is bit 2 of that register. + STACK_PUSH = 2 + # Get the original value of the stack push lock bit. + stack_push = bool((locked >> STACK_PUSH) & 1) + # Invert the value and put it back into the set of lock bits. + new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH) + # Write the new lock bits, which are the same as before, only with stack + # push locked (if it was previously unlocked), or unlocked (if it was + # previously locked). + self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}") + # We should be able to read back this new set of lock bits. + self.expect( + f"register read gcs_features_locked", + substrs=[f"gcs_features_locked = 0x{new_locked:016x}"], + ) + + # We could prove the write made it to hardware by trying to prctl() to + # enable or disable the stack push feature here, but because the libc + # may or may not have locked it, it's tricky to coordinate this. Given + # that we know the other registers can be written and their values are + # seen by the process, we can assume this is too. + + # Restore the original lock bits, as the libc may rely on being able + # to use certain features during program execution. + self.runCmd(f"register write gcs_features_locked 0x{locked:x}") + + # Modify the guarded control stack pointer to cause a fault. spr_el0 += 8 self.runCmd(f"register write gcspr_el0 {spr_el0}") self.expect( "register read gcspr_el0", substrs=[f"gcspr_el0 = 0x{spr_el0:016x}"] ) - # If we wrote it back correctly, we will now fault but don't pass this - # signal to the application. + # If we wrote it back correctly, we will now fault. Don't pass this signal + # to the application, as we will continue past it later. self.runCmd("process handle SIGSEGV --pass false") self.runCmd("continue") @@ -184,32 +216,18 @@ def check_gcs_registers( ], ) - # Any combination of lock bits could be set. Flip then restore one of them. - STACK_PUSH = 2 - stack_push = bool((locked >> STACK_PUSH) & 1) - new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH) - self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}") - self.expect( - f"register read gcs_features_locked", - substrs=[f"gcs_features_locked = 0x{new_locked:016x}"], - ) - - # We could prove the write made it to hardware by trying to prctl to change - # the feature here, but we cannot know if the libc locked it or not. - # Given that we know the other registers in the set write correctly, we - # can assume this one does. - - self.runCmd(f"register write gcs_features_locked 0x{locked:x}") - # Now to prove we can write gcs_features_enabled, disable GCS and continue - # past the fault. - enabled &= ~1 - self.runCmd(f"register write gcs_features_enabled {enabled}") + # past the fault we caused. Note that although the libc likely locked the + # ability to disable GCS, ptrace bypasses the lock bits. + gcs_enabled &= ~1 + self.runCmd(f"register write gcs_features_enabled {gcs_enabled}") self.expect( "register read gcs_features_enabled", - substrs=[f"gcs_features_enabled = 0x{enabled:016x}"], + substrs=[f"gcs_features_enabled = 0x{gcs_enabled:016x}"], ) + # With GCS disabled, the invalid guarded control stack pointer is not + # checked, so the program can finish normally. self.runCmd("continue") self.expect( "process status", >From b61e9322e8dc17416d2b900a836574e2bfc9102c Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Fri, 24 Jan 2025 10:41:23 +0000 Subject: [PATCH 3/3] correct var name --- lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py index 780624a45ec50b..d3d4dbecf4a2ac 100644 --- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py +++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py @@ -219,11 +219,11 @@ def check_gcs_registers( # Now to prove we can write gcs_features_enabled, disable GCS and continue # past the fault we caused. Note that although the libc likely locked the # ability to disable GCS, ptrace bypasses the lock bits. - gcs_enabled &= ~1 - self.runCmd(f"register write gcs_features_enabled {gcs_enabled}") + enabled &= ~1 + self.runCmd(f"register write gcs_features_enabled {enabled}") self.expect( "register read gcs_features_enabled", - substrs=[f"gcs_features_enabled = 0x{gcs_enabled:016x}"], + substrs=[f"gcs_features_enabled = 0x{enabled:016x}"], ) # With GCS disabled, the invalid guarded control stack pointer is not _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits