aboutsummaryrefslogtreecommitdiff
path: root/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
blob: f5a2ca356bbe81f4f4106ca6a7720db7197c3f42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
"""
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",
            ],
        )

    # This helper reads all the GCS registers and optionally compares them
    # against a previous state, then returns the current register values.
    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, but we can't rely on always
        # having them.
        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)

        # Any combination of GCS feature lock bits might have been set by the C
        # library, and could be set to 0 or 1. To check that we can modify them,
        # invert one of those bits then write it back to the lock register.
        # The stack pushing feature is bit 2 of that register.
        STACK_PUSH = 2
        # Get the original value of the stack push lock bit.
        stack_push = bool((locked >> STACK_PUSH) & 1)
        # Invert the value and put it back into the set of lock bits.
        new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH)
        # Write the new lock bits, which are the same as before, only with stack
        # push locked (if it was previously unlocked), or unlocked (if it was
        # previously locked).
        self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}")
        # We should be able to read back this new set of lock bits.
        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
        # enable or disable the stack push feature here, but because the libc
        # may or may not have locked it, it's tricky to coordinate this. Given
        # that we know the other registers can be written and their values are
        # seen by the process, we can assume this is too.

        # Restore the original lock bits, as the libc may rely on being able
        # to use certain features during program execution.
        self.runCmd(f"register write gcs_features_locked 0x{locked:x}")

        # Modify the guarded 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. Don't pass this signal
        # to the application, as we will continue past it later.
        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",
            ],
        )

        # Now to prove we can write gcs_features_enabled, disable GCS and continue
        # past the fault we caused. Note that although the libc likely locked the
        # ability to disable GCS, ptrace bypasses the lock bits.
        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}",
                f"= (PUSH = {(enabled >> 2) & 1}, WRITE = {(enabled >> 1) & 1}, ENABLE = {enabled & 1})",
            ],
        )

        # With GCS disabled, the invalid guarded control stack pointer is not
        # checked, so the program can finish normally.
        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
        # changed. The last thing we do is write a new GCS entry to memory and
        # to simulate the failure of that, temporarily point the GCS to the zero page.
        #
        # We use the value 8 here because LLDB will decrement it by 8 so it points to
        # what we think will be an empty entry on the guarded control stack.
        _, _, original_gcspr = self.check_gcs_registers()
        self.runCmd("register write gcspr_el0 8")
        before = self.check_gcs_registers()
        self.expect(expr_cmd, error=True)
        self.check_gcs_registers(*before)
        # Point to the valid shadow stack region again.
        self.runCmd(f"register write gcspr_el0 {original_gcspr}")

        # This time we do need to push to the GCS and having done so, we can
        # return from this expression without causing a fault.
        before = self.check_gcs_registers()
        self.expect(expr_cmd, substrs=["(unsigned long) 1"])
        self.check_gcs_registers(*before)

    @skipUnlessPlatform(["linux"])
    def test_gcs_expression_disable_gcs(self):
        if not self.isAArch64GCS():
            self.skipTest("Target must support GCS.")

        self.build()
        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)

        # Break after GCS is 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"],
        )

        # Unlock all features so the expression can enable them again.
        self.runCmd("register write gcs_features_locked 0")
        # Disable all features, but keep GCS itself enabled.
        PR_SHADOW_STACK_ENABLE = 1
        self.runCmd(f"register write gcs_features_enabled 0x{PR_SHADOW_STACK_ENABLE:x}")

        enabled, locked, spr_el0 = self.check_gcs_registers()
        # We restore everything apart GCS being enabled, as we are not allowed to
        # go from disabled -> enabled via ptrace.
        self.expect("p change_gcs_config(false)", substrs=["true"])
        enabled &= ~1
        self.check_gcs_registers(enabled, locked, spr_el0)

    @skipUnlessPlatform(["linux"])
    def test_gcs_expression_enable_gcs(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 is enabled.
        self.runCmd("b main")

        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"],
        )

        # Unlock all features so the expression can enable them again.
        self.runCmd("register write gcs_features_locked 0")
        # Disable all features. The program needs PR_SHADOW_STACK_PUSH, but it
        # will enable that itself.
        self.runCmd(f"register write gcs_features_enabled 0")

        enabled, locked, spr_el0 = self.check_gcs_registers()
        self.expect("p change_gcs_config(true)", substrs=["true"])
        # Though we could disable GCS with ptrace, we choose not to to be
        # consistent with the disabled -> enabled behaviour.
        enabled |= 1
        self.check_gcs_registers(enabled, locked, spr_el0)

    @skipIfLLVMTargetMissing("AArch64")
    def test_gcs_core_file(self):
        # To re-generate the core file, build the test file and run it on a
        # machine with GCS enabled. Note that because the kernel decides where
        # the GCS is stored, the value of gcspr_el0 and which memory region it
        # points to may change between runs.

        self.runCmd("target create --core corefile")

        self.expect(
            "bt",
            substrs=["stop reason = SIGSEGV: control protection fault"],
        )

        self.expect(
            "register read --all",
            substrs=[
                "Guarded Control Stack Registers:",
                "gcs_features_enabled = 0x0000000000000001",
                "gcs_features_locked = 0x0000000000000000",
                "gcspr_el0 = 0x0000ffffa83ffff0",
            ],
        )

        # Should get register fields for both. They have the same fields.
        self.expect(
            "register read gcs_features_enabled",
            substrs=["= (PUSH = 0, WRITE = 0, ENABLE = 1)"],
        )
        self.expect(
            "register read gcs_features_locked",
            substrs=["= (PUSH = 0, WRITE = 0, ENABLE = 0)"],
        )

        # Core files do not include /proc/pid/smaps, so we cannot see the
        # shadow stack "ss" flag. gcspr_el0 should at least point to some mapped
        # region.
        self.expect(
            "memory region $gcspr_el0",
            substrs=["[0x0000ffffa8000000-0x0000ffffa8400000) rw-"],
        )