xref: /llvm-project/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py (revision c5840cc609a3674cf7453a45946f7e4a2a73590b)
1"""
2Check that lldb features work when the AArch64 Guarded Control Stack (GCS)
3extension is enabled.
4"""
5
6import lldb
7from lldbsuite.test.decorators import *
8from lldbsuite.test.lldbtest import *
9from lldbsuite.test import lldbutil
10
11
12class AArch64LinuxGCSTestCase(TestBase):
13    NO_DEBUG_INFO_TESTCASE = True
14
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 files
22        # that include "VmFlags:" lines.
23        # AArch64 kernel config defaults to enabling smaps with
24        # 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 there
49        # should be one region marked for use by it (we cannot predict exactly
50        # where it will be).
51        self.runCmd("memory region --all")
52        found_ss = False
53        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 = True
58
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 exit
62        # 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 them
87    # 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_el0
119
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 have
147        # others depending on what the C library did, but we can't rely on always
148        # 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 remains
152        # 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 been
158        # 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 C
170        # 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 = 2
174        # 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 stack
179        # push locked (if it was previously unlocked), or unlocked (if it was
180        # 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() to
189        # enable or disable the stack push feature here, but because the libc
190        # may or may not have locked it, it's tricky to coordinate this. Given
191        # that we know the other registers can be written and their values are
192        # seen by the process, we can assume this is too.
193
194        # Restore the original lock bits, as the libc may rely on being able
195        # 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 += 8
200        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 signal
206        # 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 continue
220        # past the fault we caused. Note that although the libc likely locked the
221        # ability to disable GCS, ptrace bypasses the lock bits.
222        enabled &= ~1
223        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 not
233        # 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 to
272        # 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 registers
287        # changed. The last thing we do is write a new GCS entry to memory and
288        # 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 to
291        # 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 can
301        # 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 = 1
337        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 to
341        # go from disabled -> enabled via ptrace.
342        self.expect("p change_gcs_config(false)", substrs=["true"])
343        enabled &= ~1
344        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 it
371        # 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 be
377        # consistent with the disabled -> enabled behaviour.
378        enabled |= 1
379        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 a
384        # machine with GCS enabled. Note that because the kernel decides where
385        # the GCS is stored, the value of gcspr_el0 and which memory region it
386        # 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 the
416        # shadow stack "ss" flag. gcspr_el0 should at least point to some mapped
417        # region.
418        self.expect(
419            "memory region $gcspr_el0",
420            substrs=["[0x0000ffffa8000000-0x0000ffffa8400000) rw-"],
421        )
422