brintos

brintos / llvm-project-archived public Read only

0
0
Text · 15.8 KiB · f5a2ca3 Raw
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