422 lines · python
1"""2Check that lldb features work when the AArch64 Guarded Control Stack (GCS)3extension is enabled.4"""5 6import lldb7from lldbsuite.test.decorators import *8from lldbsuite.test.lldbtest import *9from lldbsuite.test import lldbutil10 11 12class AArch64LinuxGCSTestCase(TestBase):13 NO_DEBUG_INFO_TESTCASE = True14 15 @skipUnlessArch("aarch64")16 @skipUnlessPlatform(["linux"])17 def test_gcs_region(self):18 if not self.isAArch64GCS():19 self.skipTest("Target must support GCS.")20 21 # This test assumes that we have /proc/<PID>/smaps files22 # that include "VmFlags:" lines.23 # AArch64 kernel config defaults to enabling smaps with24 # PROC_PAGE_MONITOR and "VmFlags" was added in kernel 3.8,25 # before GCS was supported at all.26 27 self.build()28 self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)29 30 lldbutil.run_break_set_by_file_and_line(31 self,32 "main.c",33 line_number("main.c", "// Set break point at this line."),34 num_expected_locations=1,35 )36 37 self.runCmd("run", RUN_SUCCEEDED)38 39 if self.process().GetState() == lldb.eStateExited:40 self.fail("Test program failed to run.")41 42 self.expect(43 "thread list",44 STOPPED_DUE_TO_BREAKPOINT,45 substrs=["stopped", "stop reason = breakpoint"],46 )47 48 # By now either the program or the system C library enabled GCS and there49 # should be one region marked for use by it (we cannot predict exactly50 # where it will be).51 self.runCmd("memory region --all")52 found_ss = False53 for line in self.res.GetOutput().splitlines():54 if line.strip() == "shadow stack: yes":55 if found_ss:56 self.fail("Found more than one shadow stack region.")57 found_ss = True58 59 self.assertTrue(found_ss, "Failed to find a shadow stack region.")60 61 # Note that we must let the debugee get killed here as it cannot exit62 # cleanly if GCS was manually enabled.63 64 @skipUnlessArch("aarch64")65 @skipUnlessPlatform(["linux"])66 def test_gcs_fault(self):67 if not self.isAArch64GCS():68 self.skipTest("Target must support GCS.")69 70 self.build()71 self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)72 self.runCmd("run", RUN_SUCCEEDED)73 74 if self.process().GetState() == lldb.eStateExited:75 self.fail("Test program failed to run.")76 77 self.expect(78 "thread list",79 "Expected stopped by SIGSEGV.",80 substrs=[81 "stopped",82 "stop reason = signal SIGSEGV: control protection fault",83 ],84 )85 86 # This helper reads all the GCS registers and optionally compares them87 # against a previous state, then returns the current register values.88 def check_gcs_registers(89 self,90 expected_gcs_features_enabled=None,91 expected_gcs_features_locked=None,92 expected_gcspr_el0=None,93 ):94 thread = self.dbg.GetSelectedTarget().process.GetThreadAtIndex(0)95 registerSets = thread.GetFrameAtIndex(0).GetRegisters()96 gcs_registers = registerSets.GetFirstValueByName(97 r"Guarded Control Stack Registers"98 )99 100 gcs_features_enabled = gcs_registers.GetChildMemberWithName(101 "gcs_features_enabled"102 ).GetValueAsUnsigned()103 if expected_gcs_features_enabled is not None:104 self.assertEqual(expected_gcs_features_enabled, gcs_features_enabled)105 106 gcs_features_locked = gcs_registers.GetChildMemberWithName(107 "gcs_features_locked"108 ).GetValueAsUnsigned()109 if expected_gcs_features_locked is not None:110 self.assertEqual(expected_gcs_features_locked, gcs_features_locked)111 112 gcspr_el0 = gcs_registers.GetChildMemberWithName(113 "gcspr_el0"114 ).GetValueAsUnsigned()115 if expected_gcspr_el0 is not None:116 self.assertEqual(expected_gcspr_el0, gcspr_el0)117 118 return gcs_features_enabled, gcs_features_locked, gcspr_el0119 120 @skipUnlessArch("aarch64")121 @skipUnlessPlatform(["linux"])122 def test_gcs_registers(self):123 if not self.isAArch64GCS():124 self.skipTest("Target must support GCS.")125 126 self.build()127 self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)128 129 self.runCmd("b test_func")130 self.runCmd("b test_func2")131 self.runCmd("run", RUN_SUCCEEDED)132 133 if self.process().GetState() == lldb.eStateExited:134 self.fail("Test program failed to run.")135 136 self.expect(137 "thread list",138 STOPPED_DUE_TO_BREAKPOINT,139 substrs=["stopped", "stop reason = breakpoint"],140 )141 142 self.expect("register read --all", substrs=["Guarded Control Stack Registers:"])143 144 enabled, locked, spr_el0 = self.check_gcs_registers()145 146 # Features enabled should have at least the enable bit set, it could have147 # others depending on what the C library did, but we can't rely on always148 # having them.149 self.assertTrue(enabled & 1, "Expected GCS enable bit to be set.")150 151 # Features locked we cannot predict, we will just assert that it remains152 # the same as we continue.153 154 # spr_el0 will point to some memory region that is a shadow stack region.155 self.expect(f"memory region {spr_el0}", substrs=["shadow stack: yes"])156 157 # Continue into test_func2, where the GCS pointer should have been158 # decremented, and the other registers remain the same.159 self.runCmd("continue")160 161 self.expect(162 "thread list",163 STOPPED_DUE_TO_BREAKPOINT,164 substrs=["stopped", "stop reason = breakpoint"],165 )166 167 _, _, spr_el0 = self.check_gcs_registers(enabled, locked, spr_el0 - 8)168 169 # Any combination of GCS feature lock bits might have been set by the C170 # library, and could be set to 0 or 1. To check that we can modify them,171 # invert one of those bits then write it back to the lock register.172 # The stack pushing feature is bit 2 of that register.173 STACK_PUSH = 2174 # Get the original value of the stack push lock bit.175 stack_push = bool((locked >> STACK_PUSH) & 1)176 # Invert the value and put it back into the set of lock bits.177 new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH)178 # Write the new lock bits, which are the same as before, only with stack179 # push locked (if it was previously unlocked), or unlocked (if it was180 # previously locked).181 self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}")182 # We should be able to read back this new set of lock bits.183 self.expect(184 f"register read gcs_features_locked",185 substrs=[f"gcs_features_locked = 0x{new_locked:016x}"],186 )187 188 # We could prove the write made it to hardware by trying to prctl() to189 # enable or disable the stack push feature here, but because the libc190 # may or may not have locked it, it's tricky to coordinate this. Given191 # that we know the other registers can be written and their values are192 # seen by the process, we can assume this is too.193 194 # Restore the original lock bits, as the libc may rely on being able195 # to use certain features during program execution.196 self.runCmd(f"register write gcs_features_locked 0x{locked:x}")197 198 # Modify the guarded control stack pointer to cause a fault.199 spr_el0 += 8200 self.runCmd(f"register write gcspr_el0 {spr_el0}")201 self.expect(202 "register read gcspr_el0", substrs=[f"gcspr_el0 = 0x{spr_el0:016x}"]203 )204 205 # If we wrote it back correctly, we will now fault. Don't pass this signal206 # to the application, as we will continue past it later.207 self.runCmd("process handle SIGSEGV --pass false")208 self.runCmd("continue")209 210 self.expect(211 "thread list",212 "Expected stopped by SIGSEGV.",213 substrs=[214 "stopped",215 "stop reason = signal SIGSEGV: control protection fault",216 ],217 )218 219 # Now to prove we can write gcs_features_enabled, disable GCS and continue220 # past the fault we caused. Note that although the libc likely locked the221 # ability to disable GCS, ptrace bypasses the lock bits.222 enabled &= ~1223 self.runCmd(f"register write gcs_features_enabled {enabled}")224 self.expect(225 "register read gcs_features_enabled",226 substrs=[227 f"gcs_features_enabled = 0x{enabled:016x}",228 f"= (PUSH = {(enabled >> 2) & 1}, WRITE = {(enabled >> 1) & 1}, ENABLE = {enabled & 1})",229 ],230 )231 232 # With GCS disabled, the invalid guarded control stack pointer is not233 # checked, so the program can finish normally.234 self.runCmd("continue")235 self.expect(236 "process status",237 substrs=[238 "exited with status = 0",239 ],240 )241 242 @skipUnlessPlatform(["linux"])243 def test_gcs_expression_simple(self):244 if not self.isAArch64GCS():245 self.skipTest("Target must support GCS.")246 247 self.build()248 self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)249 250 # Break before GCS has been enabled.251 self.runCmd("b main")252 # And after it has been enabled.253 lldbutil.run_break_set_by_file_and_line(254 self,255 "main.c",256 line_number("main.c", "// Set break point at this line."),257 num_expected_locations=1,258 )259 260 self.runCmd("run", RUN_SUCCEEDED)261 262 if self.process().GetState() == lldb.eStateExited:263 self.fail("Test program failed to run.")264 265 self.expect(266 "thread list",267 STOPPED_DUE_TO_BREAKPOINT,268 substrs=["stopped", "stop reason = breakpoint"],269 )270 271 # GCS has not been enabled yet and the ABI plugin should know not to272 # attempt pushing to the control stack.273 before = self.check_gcs_registers()274 expr_cmd = "p get_gcs_status()"275 self.expect(expr_cmd, substrs=["(unsigned long) 0"])276 self.check_gcs_registers(*before)277 278 # Continue to when GCS has been enabled.279 self.runCmd("continue")280 self.expect(281 "thread list",282 STOPPED_DUE_TO_BREAKPOINT,283 substrs=["stopped", "stop reason = breakpoint"],284 )285 286 # If we fail to setup the GCS entry, we should not leave any of the GCS registers287 # changed. The last thing we do is write a new GCS entry to memory and288 # to simulate the failure of that, temporarily point the GCS to the zero page.289 #290 # We use the value 8 here because LLDB will decrement it by 8 so it points to291 # what we think will be an empty entry on the guarded control stack.292 _, _, original_gcspr = self.check_gcs_registers()293 self.runCmd("register write gcspr_el0 8")294 before = self.check_gcs_registers()295 self.expect(expr_cmd, error=True)296 self.check_gcs_registers(*before)297 # Point to the valid shadow stack region again.298 self.runCmd(f"register write gcspr_el0 {original_gcspr}")299 300 # This time we do need to push to the GCS and having done so, we can301 # return from this expression without causing a fault.302 before = self.check_gcs_registers()303 self.expect(expr_cmd, substrs=["(unsigned long) 1"])304 self.check_gcs_registers(*before)305 306 @skipUnlessPlatform(["linux"])307 def test_gcs_expression_disable_gcs(self):308 if not self.isAArch64GCS():309 self.skipTest("Target must support GCS.")310 311 self.build()312 self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)313 314 # Break after GCS is enabled.315 lldbutil.run_break_set_by_file_and_line(316 self,317 "main.c",318 line_number("main.c", "// Set break point at this line."),319 num_expected_locations=1,320 )321 322 self.runCmd("run", RUN_SUCCEEDED)323 324 if self.process().GetState() == lldb.eStateExited:325 self.fail("Test program failed to run.")326 327 self.expect(328 "thread list",329 STOPPED_DUE_TO_BREAKPOINT,330 substrs=["stopped", "stop reason = breakpoint"],331 )332 333 # Unlock all features so the expression can enable them again.334 self.runCmd("register write gcs_features_locked 0")335 # Disable all features, but keep GCS itself enabled.336 PR_SHADOW_STACK_ENABLE = 1337 self.runCmd(f"register write gcs_features_enabled 0x{PR_SHADOW_STACK_ENABLE:x}")338 339 enabled, locked, spr_el0 = self.check_gcs_registers()340 # We restore everything apart GCS being enabled, as we are not allowed to341 # go from disabled -> enabled via ptrace.342 self.expect("p change_gcs_config(false)", substrs=["true"])343 enabled &= ~1344 self.check_gcs_registers(enabled, locked, spr_el0)345 346 @skipUnlessPlatform(["linux"])347 def test_gcs_expression_enable_gcs(self):348 if not self.isAArch64GCS():349 self.skipTest("Target must support GCS.")350 351 self.build()352 self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)353 354 # Break before GCS is enabled.355 self.runCmd("b main")356 357 self.runCmd("run", RUN_SUCCEEDED)358 359 if self.process().GetState() == lldb.eStateExited:360 self.fail("Test program failed to run.")361 362 self.expect(363 "thread list",364 STOPPED_DUE_TO_BREAKPOINT,365 substrs=["stopped", "stop reason = breakpoint"],366 )367 368 # Unlock all features so the expression can enable them again.369 self.runCmd("register write gcs_features_locked 0")370 # Disable all features. The program needs PR_SHADOW_STACK_PUSH, but it371 # will enable that itself.372 self.runCmd(f"register write gcs_features_enabled 0")373 374 enabled, locked, spr_el0 = self.check_gcs_registers()375 self.expect("p change_gcs_config(true)", substrs=["true"])376 # Though we could disable GCS with ptrace, we choose not to to be377 # consistent with the disabled -> enabled behaviour.378 enabled |= 1379 self.check_gcs_registers(enabled, locked, spr_el0)380 381 @skipIfLLVMTargetMissing("AArch64")382 def test_gcs_core_file(self):383 # To re-generate the core file, build the test file and run it on a384 # machine with GCS enabled. Note that because the kernel decides where385 # the GCS is stored, the value of gcspr_el0 and which memory region it386 # points to may change between runs.387 388 self.runCmd("target create --core corefile")389 390 self.expect(391 "bt",392 substrs=["stop reason = SIGSEGV: control protection fault"],393 )394 395 self.expect(396 "register read --all",397 substrs=[398 "Guarded Control Stack Registers:",399 "gcs_features_enabled = 0x0000000000000001",400 "gcs_features_locked = 0x0000000000000000",401 "gcspr_el0 = 0x0000ffffa83ffff0",402 ],403 )404 405 # Should get register fields for both. They have the same fields.406 self.expect(407 "register read gcs_features_enabled",408 substrs=["= (PUSH = 0, WRITE = 0, ENABLE = 1)"],409 )410 self.expect(411 "register read gcs_features_locked",412 substrs=["= (PUSH = 0, WRITE = 0, ENABLE = 0)"],413 )414 415 # Core files do not include /proc/pid/smaps, so we cannot see the416 # shadow stack "ss" flag. gcspr_el0 should at least point to some mapped417 # region.418 self.expect(419 "memory region $gcspr_el0",420 substrs=["[0x0000ffffa8000000-0x0000ffffa8400000) rw-"],421 )422