This will be sent by Arm's Guarded Control Stack extension when an invalid return is executed. The signal does have an address we could show, but it's the PC at which the fault occured. The debugger has plenty of ways to show you that already, so I've left it out. ``` (lldb) c Process 460 resuming Process 460 stopped * thread #1, name = 'test', stop reason = signal SIGSEGV: control protection fault frame #0: 0x0000000000400784 test`main at main.c:57:1 54 afunc(); 55 printf("return from main\n"); 56 return 0; -> 57 } (lldb) dis <...> -> 0x400784 <+100>: ret ``` The new test case generates the signal by corrupting the link register then attempting to return. This will work whether we manually enable GCS or the C library does it for us. (in the former case you could just return from main and it would fault)
86 lines
2.7 KiB
Python
86 lines
2.7 KiB
Python
"""
|
|
Check that lldb features work when the AArch64 Guarded Control Stack (GCS)
|
|
extension is enabled.
|
|
"""
|
|
|
|
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
|
|
|
|
class AArch64LinuxGCSTestCase(TestBase):
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
@skipUnlessArch("aarch64")
|
|
@skipUnlessPlatform(["linux"])
|
|
def test_gcs_region(self):
|
|
if not self.isAArch64GCS():
|
|
self.skipTest("Target must support GCS.")
|
|
|
|
# This test assumes that we have /proc/<PID>/smaps files
|
|
# that include "VmFlags:" lines.
|
|
# AArch64 kernel config defaults to enabling smaps with
|
|
# PROC_PAGE_MONITOR and "VmFlags" was added in kernel 3.8,
|
|
# before GCS was supported at all.
|
|
|
|
self.build()
|
|
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
|
|
|
|
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"],
|
|
)
|
|
|
|
# By now either the program or the system C library enabled GCS and there
|
|
# should be one region marked for use by it (we cannot predict exactly
|
|
# where it will be).
|
|
self.runCmd("memory region --all")
|
|
found_ss = False
|
|
for line in self.res.GetOutput().splitlines():
|
|
if line.strip() == "shadow stack: yes":
|
|
if found_ss:
|
|
self.fail("Found more than one shadow stack region.")
|
|
found_ss = True
|
|
|
|
self.assertTrue(found_ss, "Failed to find a shadow stack region.")
|
|
|
|
# Note that we must let the debugee get killed here as it cannot exit
|
|
# cleanly if GCS was manually enabled.
|
|
|
|
@skipUnlessArch("aarch64")
|
|
@skipUnlessPlatform(["linux"])
|
|
def test_gcs_fault(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("run", RUN_SUCCEEDED)
|
|
|
|
if self.process().GetState() == lldb.eStateExited:
|
|
self.fail("Test program failed to run.")
|
|
|
|
self.expect(
|
|
"thread list",
|
|
"Expected stopped by SIGSEGV.",
|
|
substrs=[
|
|
"stopped",
|
|
"stop reason = signal SIGSEGV: control protection fault",
|
|
],
|
|
)
|