https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/123918
>From c89a16ab405d4aad7bf0a59afb433dd5ecd1a402 Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Tue, 20 Aug 2024 16:06:34 +0100 Subject: [PATCH 1/4] [lldb][AArch64] Fix expression evaluation with Guarded Control Stacks When the Guarded Control Stack (GCS) is enabled, returns cause the processor to validate that the address at the location pointed to by gcspr_el0 matches the one in the link register. ``` ret (lr=A) << pc | GCS | +=====+ | A | | B | << gcspr_el0 Fault: tried to return to A when you should have returned to B. ``` Therefore when an expression wraper function tries to return to the expression return address (usually `_start` if there is a libc), it would fault. ``` ret (lr=_start) << pc | GCS | +============+ | user_func1 | | user_func2 | << gcspr_el0 Fault: tried to return to _start when you should have return to user_func2. ``` To fix this we must push that return address to the GCS in PrepareTrivialCall. This value is then consumed by the final return and the expression completes as expected. ``` ret (lr=_start) << pc | GCS | +============+ | user_func1 | | user_func2 | | _start | << gcspr_el0 No fault, we return to _start as normal. ``` The gcspr_el0 register will be restored after expression evaluation so that the program can continue correctly. However, due to restrictions in the Linux GCS ABI, we will not restore the enable bit of gcs_features_enabled. Re-enabling GCS via ptrace is not supported because it requires memory to be allocated. We could disable GCS if the expression enabled GCS, however this would use up that state transition that the program might later rely on. And generally it is cleaner to ignore the whole bit rather than one state transition of it. We will also not restore the GCS entry that was overwritten with the expression's return address. On the grounds that: * This entry will never be used by the program. If the program branches, the entry will be overwritten. If the program returns, gcspr_el0 will point to the entry before the expression return address and that entry will instead be validated. * Any expression that calls functions will overwrite even more entries, so the user needs to be aware of that anyway if they want to preserve the contents of the GCS for inspection. * An expression could leave the program in a state where restoring the value makes the situation worse. Especially if we ever support this in bare metal debugging. I will later document all this on https://lldb.llvm.org/use/aarch64-linux.html as well. Tests have been added for: * A function call that does not interact with GCS. * A call that does, and disables it (we do not re-enable it). * A call that does, and enables it (we do not disable it again). --- .../Plugins/ABI/AArch64/ABISysV_arm64.cpp | 56 +++++ .../NativeRegisterContextLinux_arm64.cpp | 20 +- .../linux/aarch64/gcs/TestAArch64LinuxGCS.py | 196 ++++++++++++++---- lldb/test/API/linux/aarch64/gcs/main.c | 47 ++++- 4 files changed, 278 insertions(+), 41 deletions(-) diff --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp index 93b8141e97ef86..d843e718b6875e 100644 --- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp +++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp @@ -60,6 +60,59 @@ ABISysV_arm64::CreateInstance(lldb::ProcessSP process_sp, const ArchSpec &arch) return ABISP(); } +static bool PushToLinuxGuardedControlStack(addr_t return_addr, + RegisterContext *reg_ctx, + Thread &thread) { + // If the Guarded Control Stack extension is enabled we need to put the return + // address onto that stack. + const RegisterInfo *gcs_features_enabled_info = + reg_ctx->GetRegisterInfoByName("gcs_features_enabled"); + if (!gcs_features_enabled_info) + return false; + + uint64_t gcs_features_enabled = reg_ctx->ReadRegisterAsUnsigned( + gcs_features_enabled_info, LLDB_INVALID_ADDRESS); + if (gcs_features_enabled == LLDB_INVALID_ADDRESS) + return false; + + // Only attempt this if GCS is enabled. If it's not enabled then gcspr_el0 + // may point to unmapped memory. + if ((gcs_features_enabled & 1) == 0) + return false; + + const RegisterInfo *gcspr_el0_info = + reg_ctx->GetRegisterInfoByName("gcspr_el0"); + if (!gcspr_el0_info) + return false; + + uint64_t gcspr_el0 = + reg_ctx->ReadRegisterAsUnsigned(gcspr_el0_info, LLDB_INVALID_ADDRESS); + if (gcspr_el0 == LLDB_INVALID_ADDRESS) + return false; + + // A link register entry on the GCS is 8 bytes. + gcspr_el0 -= 8; + if (!reg_ctx->WriteRegisterFromUnsigned(gcspr_el0_info, gcspr_el0)) + return false; + + Status error; + size_t wrote = thread.GetProcess()->WriteMemory(gcspr_el0, &return_addr, + sizeof(return_addr), error); + if ((wrote != sizeof(return_addr) || error.Fail())) + return false; + + Log *log = GetLog(LLDBLog::Expressions); + LLDB_LOGF(log, + "Pushed return address 0x%" PRIx64 "to Guarded Control Stack. " + "gcspr_el0 was 0%" PRIx64 ", is now 0x%" PRIx64 ".", + return_addr, gcspr_el0 + 8, gcspr_el0); + + // gcspr_el0 will be restored to the original value by lldb-server after + // the call has finished, which serves as the "pop". + + return true; +} + bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp, addr_t func_addr, addr_t return_addr, llvm::ArrayRef<addr_t> args) const { @@ -103,6 +156,9 @@ bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp, return_addr)) return false; + if (GetProcessSP()->GetTarget().GetArchitecture().GetTriple().isOSLinux()) + PushToLinuxGuardedControlStack(return_addr, reg_ctx, thread); + // Set "sp" to the requested value if (!reg_ctx->WriteRegisterFromUnsigned( reg_ctx->GetRegisterInfo(eRegisterKindGeneric, diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp index efd3385c46e92f..884c7d4b9e3590 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -1063,9 +1063,27 @@ Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues( std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this)); break; case RegisterSetType::GCS: + // It is not permitted to enable GCS via ptrace. We can disable it, but + // to keep things simple we will not revert any change to the + // PR_SHADOW_STACK_ENABLE bit. Instead patch in the current enable bit + // into the registers we are about to restore. + m_gcs_is_valid = false; + error = ReadGCS(); + if (error.Fail()) + return error; + + uint64_t enable_bit = m_gcs_regs.features_enabled & 1UL; + gcs_regs new_gcs_regs = *reinterpret_cast<const gcs_regs *>(src); + new_gcs_regs.features_enabled = + (new_gcs_regs.features_enabled & ~1UL) | enable_bit; + + const uint8_t *new_gcs_src = + reinterpret_cast<const uint8_t *>(&new_gcs_regs); error = RestoreRegisters( - GetGCSBuffer(), &src, GetGCSBufferSize(), m_gcs_is_valid, + GetGCSBuffer(), &new_gcs_src, GetGCSBufferSize(), m_gcs_is_valid, std::bind(&NativeRegisterContextLinux_arm64::WriteGCS, this)); + src += GetGCSBufferSize(); + break; } diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py index d3d4dbecf4a2ac..72d63c6a83817d 100644 --- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py +++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py @@ -84,6 +84,40 @@ def test_gcs_fault(self): ], ) + def check_gcs_registers( + self, + 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 + + # This helper reads all the GCS registers and optionally compares them + # against a previous state, then returns the current register values. @skipUnlessArch("aarch64") @skipUnlessPlatform(["linux"]) def test_gcs_registers(self): @@ -108,40 +142,7 @@ 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, - 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() + enabled, locked, spr_el0 = self.check_gcs_registers() # Features enabled should have at least the enable bit set, it could have # others depending on what the C library did, but we can't rely on always @@ -164,7 +165,7 @@ def check_gcs_registers( substrs=["stopped", "stop reason = breakpoint"], ) - _, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8) + _, _, spr_el0 = self.check_gcs_registers(enabled, locked, spr_el0 - 8) # 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, @@ -235,3 +236,128 @@ def check_gcs_registers( "exited with status = 0", ], ) + + @skipUnlessPlatform(["linux"]) + def test_gcs_expression_simple(self): + if not self.isAArch64GCS(): + self.skipTest("Target must support GCS.") + + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + # Break before GCS has been enabled. + self.runCmd("b main") + # And after it has been enabled. + lldbutil.run_break_set_by_file_and_line( + self, + "main.c", + line_number("main.c", "// Set break point at this line."), + num_expected_locations=1, + ) + + 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"], + ) + + # GCS has not been enabled yet and the ABI plugin should know not to + # attempt pushing to the control stack. + before = self.check_gcs_registers() + expr_cmd = "p get_gcs_status()" + self.expect(expr_cmd, substrs=["(unsigned long) 0"]) + self.check_gcs_registers(*before) + + # Continue to when GCS has been enabled. + self.runCmd("continue") + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + + # This time we do need to push to the GCS and having done so, we can + # return from this expression without causing a fault. + before = self.check_gcs_registers() + self.expect(expr_cmd, substrs=["(unsigned long) 1"]) + self.check_gcs_registers(*before) + + @skipUnlessPlatform(["linux"]) + def test_gcs_expression_disable_gcs(self): + if not self.isAArch64GCS(): + self.skipTest("Target must support GCS.") + + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + # Break after GCS is enabled. + lldbutil.run_break_set_by_file_and_line( + self, + "main.c", + line_number("main.c", "// Set break point at this line."), + num_expected_locations=1, + ) + + 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"], + ) + + # Unlock all features so the expression can enable them again. + self.runCmd("register write gcs_features_locked 0") + # Disable all features, but keep GCS itself enabled. + PR_SHADOW_STACK_ENABLE = 1 + self.runCmd(f"register write gcs_features_enabled 0x{PR_SHADOW_STACK_ENABLE:x}") + + enabled, locked, spr_el0 = self.check_gcs_registers() + # We restore everything apart GCS being enabled, as we are not allowed to + # go from disabled -> enabled via ptrace. + self.expect("p change_gcs_config(false)", substrs=["true"]) + enabled &= ~1 + self.check_gcs_registers(enabled, locked, spr_el0) + + @skipUnlessPlatform(["linux"]) + def test_gcs_expression_enable_gcs(self): + if not self.isAArch64GCS(): + self.skipTest("Target must support GCS.") + + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + # Break before GCS is enabled. + self.runCmd("b main") + + 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"], + ) + + # Unlock all features so the expression can enable them again. + self.runCmd("register write gcs_features_locked 0") + # Disable all features. The program needs PR_SHADOW_STACK_PUSH, but it + # will enable that itself. + self.runCmd(f"register write gcs_features_enabled 0") + + enabled, locked, spr_el0 = self.check_gcs_registers() + self.expect("p change_gcs_config(true)", substrs=["true"]) + # Though we could disable GCS with ptrace, we choose not to to be + # consistent with the disabled -> enabled behaviour. + enabled |= 1 + self.check_gcs_registers(enabled, locked, spr_el0) diff --git a/lldb/test/API/linux/aarch64/gcs/main.c b/lldb/test/API/linux/aarch64/gcs/main.c index 09354639af376f..396aef7499ca9e 100644 --- a/lldb/test/API/linux/aarch64/gcs/main.c +++ b/lldb/test/API/linux/aarch64/gcs/main.c @@ -1,4 +1,5 @@ #include <asm/hwcap.h> +#include <stdbool.h> #include <sys/auxv.h> #include <sys/prctl.h> @@ -8,7 +9,12 @@ #define PR_GET_SHADOW_STACK_STATUS 74 #define PR_SET_SHADOW_STACK_STATUS 75 -#define PR_SHADOW_STACK_ENABLE (1UL) +#define PR_LOCK_SHADOW_STACK_STATUS 76 + +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#define PR_SHADOW_STACK_WRITE (1UL << 1) +#define PR_SHADOW_STACK_PUSH (1UL << 2) + #define PRCTL_SYSCALL_NO 167 // Once we enable GCS, we cannot return from the function that made the syscall @@ -36,6 +42,36 @@ unsigned long get_gcs_status() { return mode; } +extern void _start(); +bool change_gcs_config(bool enable) { + // The test unlocks and disables all features (excluding the main enable bit) + // before calling this expression. Enable them again. + unsigned long new_status = + enable | PR_SHADOW_STACK_PUSH | PR_SHADOW_STACK_WRITE; + + if (enable) { + // We would not be able to return from prctl(). + my_prctl(PR_SET_SHADOW_STACK_STATUS, new_status, 0, 0, 0); + + // This is a stack, so we must push in reverse order to the pops we want to + // have later. So push the return of __lldb_expr (_start), then the return + // address of this function (__lldb_expr). + __asm__ __volatile__("sys #3, C7, C7, #0, %0\n" // gcspushm _start + "sys #3, C7, C7, #0, x30\n" // gcspushm x30 + : + : "r"(_start)); + } else { + if (prctl(PR_SET_SHADOW_STACK_STATUS, new_status, 0, 0, 0) != 0) + return false; + } + + // Turn back on all locks. + if (prctl(PR_LOCK_SHADOW_STACK_STATUS, ~(0UL), 0, 0, 0) != 0) + return false; + + return true; +} + void gcs_signal() { // If we enabled GCS manually, then we could just return from main to generate // a signal. However, if the C library enabled it, then we'd just exit @@ -50,10 +86,11 @@ void gcs_signal() { } // 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; } +// the fault we cause by changing its value. Also used to check expression +// eval can handle function calls. +int test_func2() { return 99; } -void test_func() { test_func2(); } +int test_func() { return test_func2(); } int main() { if (!(getauxval(AT_HWCAP) & HWCAP_GCS)) @@ -71,7 +108,7 @@ int main() { // By now we should have one memory region where the GCS is stored. // For register read/write tests. - test_func(); + volatile int i = 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 >From 3fe267ff14b3bcf2431b1289a91f91f9050e1d8f Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Fri, 24 Jan 2025 11:27:42 +0000 Subject: [PATCH 2/4] return status from gcs setup --- .../Plugins/ABI/AArch64/ABISysV_arm64.cpp | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp index d843e718b6875e..07ff179d60d861 100644 --- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp +++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp @@ -60,46 +60,49 @@ ABISysV_arm64::CreateInstance(lldb::ProcessSP process_sp, const ArchSpec &arch) return ABISP(); } -static bool PushToLinuxGuardedControlStack(addr_t return_addr, - RegisterContext *reg_ctx, - Thread &thread) { - // If the Guarded Control Stack extension is enabled we need to put the return - // address onto that stack. +static Status PushToLinuxGuardedControlStack(addr_t return_addr, + RegisterContext *reg_ctx, + Thread &thread) { + Status err; + + // If the Guarded Control Stack extension is present we may need to put the + // return address onto that stack. const RegisterInfo *gcs_features_enabled_info = reg_ctx->GetRegisterInfoByName("gcs_features_enabled"); if (!gcs_features_enabled_info) - return false; + return err; uint64_t gcs_features_enabled = reg_ctx->ReadRegisterAsUnsigned( gcs_features_enabled_info, LLDB_INVALID_ADDRESS); if (gcs_features_enabled == LLDB_INVALID_ADDRESS) - return false; + return Status("Could not read GCS features enabled register."); // Only attempt this if GCS is enabled. If it's not enabled then gcspr_el0 // may point to unmapped memory. if ((gcs_features_enabled & 1) == 0) - return false; + return err; const RegisterInfo *gcspr_el0_info = reg_ctx->GetRegisterInfoByName("gcspr_el0"); if (!gcspr_el0_info) - return false; + return Status("Could not get register info for gcspr_el0."); uint64_t gcspr_el0 = reg_ctx->ReadRegisterAsUnsigned(gcspr_el0_info, LLDB_INVALID_ADDRESS); if (gcspr_el0 == LLDB_INVALID_ADDRESS) - return false; + return Status("Could not read gcspr_el0."); // A link register entry on the GCS is 8 bytes. gcspr_el0 -= 8; if (!reg_ctx->WriteRegisterFromUnsigned(gcspr_el0_info, gcspr_el0)) - return false; + return Status( + "Attempted to decrement gcspr_el0, but could not write to it."); Status error; size_t wrote = thread.GetProcess()->WriteMemory(gcspr_el0, &return_addr, sizeof(return_addr), error); if ((wrote != sizeof(return_addr) || error.Fail())) - return false; + return Status("Failed to write new Guarded Control Stack entry."); Log *log = GetLog(LLDBLog::Expressions); LLDB_LOGF(log, @@ -110,7 +113,7 @@ static bool PushToLinuxGuardedControlStack(addr_t return_addr, // gcspr_el0 will be restored to the original value by lldb-server after // the call has finished, which serves as the "pop". - return true; + return err; } bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp, @@ -156,8 +159,15 @@ bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp, return_addr)) return false; - if (GetProcessSP()->GetTarget().GetArchitecture().GetTriple().isOSLinux()) - PushToLinuxGuardedControlStack(return_addr, reg_ctx, thread); + if (GetProcessSP()->GetTarget().GetArchitecture().GetTriple().isOSLinux()) { + Status err = PushToLinuxGuardedControlStack(return_addr, reg_ctx, thread); + // If we could not manage the GCS, the expression will certainly fail, + // and if we just carried on, that failure would be a lot more cryptic. + if (err.Fail()) { + LLDB_LOGF(log, "Failed to setup Guarded Call Stack: %s", err.AsCString()); + return false; + } + } // Set "sp" to the requested value if (!reg_ctx->WriteRegisterFromUnsigned( >From 3436294db831d78dddacb186c93007ec05c0cfee Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Fri, 24 Jan 2025 13:25:57 +0000 Subject: [PATCH 3/4] Check what happens if we fail to setup the gcs --- .../Plugins/ABI/AArch64/ABISysV_arm64.cpp | 35 ++++++++++++------- .../linux/aarch64/gcs/TestAArch64LinuxGCS.py | 15 ++++++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp index 07ff179d60d861..74047ea65788cf 100644 --- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp +++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp @@ -101,14 +101,21 @@ static Status PushToLinuxGuardedControlStack(addr_t return_addr, Status error; size_t wrote = thread.GetProcess()->WriteMemory(gcspr_el0, &return_addr, sizeof(return_addr), error); - if ((wrote != sizeof(return_addr) || error.Fail())) + if ((wrote != sizeof(return_addr) || error.Fail())) { + // When PrepareTrivialCall fails, the register context is not restored, + // unlike when an expression fails to execute. This is arguably a bug, + // see https://github.com/llvm/llvm-project/issues/124269. + // For now we are handling this here specifically. We can assume this + // write will work as the one to decrement the register did. + reg_ctx->WriteRegisterFromUnsigned(gcspr_el0_info, gcspr_el0 + 8); return Status("Failed to write new Guarded Control Stack entry."); + } Log *log = GetLog(LLDBLog::Expressions); LLDB_LOGF(log, - "Pushed return address 0x%" PRIx64 "to Guarded Control Stack. " + "Pushed return address 0x%" PRIx64 " to Guarded Control Stack. " "gcspr_el0 was 0%" PRIx64 ", is now 0x%" PRIx64 ".", - return_addr, gcspr_el0 + 8, gcspr_el0); + return_addr, gcspr_el0 - 8, gcspr_el0); // gcspr_el0 will be restored to the original value by lldb-server after // the call has finished, which serves as the "pop". @@ -143,6 +150,18 @@ bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp, if (args.size() > 8) return false; + // Do this first, as it's got the most chance of failing (though still very + // low). + if (GetProcessSP()->GetTarget().GetArchitecture().GetTriple().isOSLinux()) { + Status err = PushToLinuxGuardedControlStack(return_addr, reg_ctx, thread); + // If we could not manage the GCS, the expression will certainly fail, + // and if we just carried on, that failure would be a lot more cryptic. + if (err.Fail()) { + LLDB_LOGF(log, "Failed to setup Guarded Call Stack: %s", err.AsCString()); + return false; + } + } + for (size_t i = 0; i < args.size(); ++i) { const RegisterInfo *reg_info = reg_ctx->GetRegisterInfo( eRegisterKindGeneric, LLDB_REGNUM_GENERIC_ARG1 + i); @@ -159,16 +178,6 @@ bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp, return_addr)) return false; - if (GetProcessSP()->GetTarget().GetArchitecture().GetTriple().isOSLinux()) { - Status err = PushToLinuxGuardedControlStack(return_addr, reg_ctx, thread); - // If we could not manage the GCS, the expression will certainly fail, - // and if we just carried on, that failure would be a lot more cryptic. - if (err.Fail()) { - LLDB_LOGF(log, "Failed to setup Guarded Call Stack: %s", err.AsCString()); - return false; - } - } - // Set "sp" to the requested value if (!reg_ctx->WriteRegisterFromUnsigned( reg_ctx->GetRegisterInfo(eRegisterKindGeneric, diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py index 72d63c6a83817d..9592249b112a41 100644 --- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py +++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py @@ -281,12 +281,27 @@ def test_gcs_expression_simple(self): substrs=["stopped", "stop reason = breakpoint"], ) + # If we fail to setup the GCS entry, we should not leave any of the GCS registers + # changed. The last thing we do is write a new GCS entry to memory and + # to simulate the failure of that, temporarily point the GCS to the zero page. + # + # We use the value 8 here because LLDB will decrement it by 8 so it points to + # what we think will be an empty entry on the guarded control stack. + _, _, original_gcspr = self.check_gcs_registers() + self.runCmd("register write gcspr_el0 8") + before = self.check_gcs_registers() + self.expect(expr_cmd, error=True) + self.check_gcs_registers(*before) + # Point to the valid shadow stack region again. + self.runCmd(f"register write gcspr_el0 {original_gcspr}") + # This time we do need to push to the GCS and having done so, we can # return from this expression without causing a fault. before = self.check_gcs_registers() self.expect(expr_cmd, substrs=["(unsigned long) 1"]) self.check_gcs_registers(*before) + @skipUnlessPlatform(["linux"]) def test_gcs_expression_disable_gcs(self): if not self.isAArch64GCS(): >From b9c7b98298570aee8cc6af3f73f4bf3ed241f22b Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Mon, 27 Jan 2025 13:02:46 +0000 Subject: [PATCH 4/4] formatting --- lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py index 9592249b112a41..fd46a42b3c69fe 100644 --- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py +++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py @@ -3,7 +3,6 @@ extension is enabled. """ - import lldb from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * @@ -301,7 +300,6 @@ def test_gcs_expression_simple(self): self.expect(expr_cmd, substrs=["(unsigned long) 1"]) self.check_gcs_registers(*before) - @skipUnlessPlatform(["linux"]) def test_gcs_expression_disable_gcs(self): if not self.isAArch64GCS(): _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits