================ @@ -83,3 +83,278 @@ def test_gcs_fault(self): "stop reason = signal SIGSEGV: control protection fault", ], ) + + 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 + + @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:"]) + + 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. + 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 = self.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", + ], + ) + + @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"], + ) + + # If we fail to setup the GCS entry, we should not leave any of the GCS registers ---------------- DavidSpickett wrote:
Test for failing to setup the GCS entry. https://github.com/llvm/llvm-project/pull/123918 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits