Skip to content

Commit c89a16a

Browse files
committed
[lldb][AArch64] Fix expression evaluation with Guarded Control Stacks
When the Guarded Control Stack (GCS) is enabled, returns cause the processor to validate that the address at the location pointed to by gcspr_el0 matches the one in the link register. ``` ret (lr=A) << pc | GCS | +=====+ | A | | B | << gcspr_el0 Fault: tried to return to A when you should have returned to B. ``` Therefore when an expression wraper function tries to return to the expression return address (usually `_start` if there is a libc), it would fault. ``` ret (lr=_start) << pc | GCS | +============+ | user_func1 | | user_func2 | << gcspr_el0 Fault: tried to return to _start when you should have return to user_func2. ``` To fix this we must push that return address to the GCS in PrepareTrivialCall. This value is then consumed by the final return and the expression completes as expected. ``` ret (lr=_start) << pc | GCS | +============+ | user_func1 | | user_func2 | | _start | << gcspr_el0 No fault, we return to _start as normal. ``` The gcspr_el0 register will be restored after expression evaluation so that the program can continue correctly. However, due to restrictions in the Linux GCS ABI, we will not restore the enable bit of gcs_features_enabled. Re-enabling GCS via ptrace is not supported because it requires memory to be allocated. We could disable GCS if the expression enabled GCS, however this would use up that state transition that the program might later rely on. And generally it is cleaner to ignore the whole bit rather than one state transition of it. We will also not restore the GCS entry that was overwritten with the expression's return address. On the grounds that: * This entry will never be used by the program. If the program branches, the entry will be overwritten. If the program returns, gcspr_el0 will point to the entry before the expression return address and that entry will instead be validated. * Any expression that calls functions will overwrite even more entries, so the user needs to be aware of that anyway if they want to preserve the contents of the GCS for inspection. * An expression could leave the program in a state where restoring the value makes the situation worse. Especially if we ever support this in bare metal debugging. I will later document all this on https://lldb.llvm.org/use/aarch64-linux.html as well. Tests have been added for: * A function call that does not interact with GCS. * A call that does, and disables it (we do not re-enable it). * A call that does, and enables it (we do not disable it again).
1 parent b7286db commit c89a16a

File tree

4 files changed

+278
-41
lines changed

4 files changed

+278
-41
lines changed

lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,59 @@ ABISysV_arm64::CreateInstance(lldb::ProcessSP process_sp, const ArchSpec &arch)
6060
return ABISP();
6161
}
6262

63+
static bool PushToLinuxGuardedControlStack(addr_t return_addr,
64+
RegisterContext *reg_ctx,
65+
Thread &thread) {
66+
// If the Guarded Control Stack extension is enabled we need to put the return
67+
// address onto that stack.
68+
const RegisterInfo *gcs_features_enabled_info =
69+
reg_ctx->GetRegisterInfoByName("gcs_features_enabled");
70+
if (!gcs_features_enabled_info)
71+
return false;
72+
73+
uint64_t gcs_features_enabled = reg_ctx->ReadRegisterAsUnsigned(
74+
gcs_features_enabled_info, LLDB_INVALID_ADDRESS);
75+
if (gcs_features_enabled == LLDB_INVALID_ADDRESS)
76+
return false;
77+
78+
// Only attempt this if GCS is enabled. If it's not enabled then gcspr_el0
79+
// may point to unmapped memory.
80+
if ((gcs_features_enabled & 1) == 0)
81+
return false;
82+
83+
const RegisterInfo *gcspr_el0_info =
84+
reg_ctx->GetRegisterInfoByName("gcspr_el0");
85+
if (!gcspr_el0_info)
86+
return false;
87+
88+
uint64_t gcspr_el0 =
89+
reg_ctx->ReadRegisterAsUnsigned(gcspr_el0_info, LLDB_INVALID_ADDRESS);
90+
if (gcspr_el0 == LLDB_INVALID_ADDRESS)
91+
return false;
92+
93+
// A link register entry on the GCS is 8 bytes.
94+
gcspr_el0 -= 8;
95+
if (!reg_ctx->WriteRegisterFromUnsigned(gcspr_el0_info, gcspr_el0))
96+
return false;
97+
98+
Status error;
99+
size_t wrote = thread.GetProcess()->WriteMemory(gcspr_el0, &return_addr,
100+
sizeof(return_addr), error);
101+
if ((wrote != sizeof(return_addr) || error.Fail()))
102+
return false;
103+
104+
Log *log = GetLog(LLDBLog::Expressions);
105+
LLDB_LOGF(log,
106+
"Pushed return address 0x%" PRIx64 "to Guarded Control Stack. "
107+
"gcspr_el0 was 0%" PRIx64 ", is now 0x%" PRIx64 ".",
108+
return_addr, gcspr_el0 + 8, gcspr_el0);
109+
110+
// gcspr_el0 will be restored to the original value by lldb-server after
111+
// the call has finished, which serves as the "pop".
112+
113+
return true;
114+
}
115+
63116
bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp,
64117
addr_t func_addr, addr_t return_addr,
65118
llvm::ArrayRef<addr_t> args) const {
@@ -103,6 +156,9 @@ bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp,
103156
return_addr))
104157
return false;
105158

159+
if (GetProcessSP()->GetTarget().GetArchitecture().GetTriple().isOSLinux())
160+
PushToLinuxGuardedControlStack(return_addr, reg_ctx, thread);
161+
106162
// Set "sp" to the requested value
107163
if (!reg_ctx->WriteRegisterFromUnsigned(
108164
reg_ctx->GetRegisterInfo(eRegisterKindGeneric,

lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,9 +1063,27 @@ Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues(
10631063
std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this));
10641064
break;
10651065
case RegisterSetType::GCS:
1066+
// It is not permitted to enable GCS via ptrace. We can disable it, but
1067+
// to keep things simple we will not revert any change to the
1068+
// PR_SHADOW_STACK_ENABLE bit. Instead patch in the current enable bit
1069+
// into the registers we are about to restore.
1070+
m_gcs_is_valid = false;
1071+
error = ReadGCS();
1072+
if (error.Fail())
1073+
return error;
1074+
1075+
uint64_t enable_bit = m_gcs_regs.features_enabled & 1UL;
1076+
gcs_regs new_gcs_regs = *reinterpret_cast<const gcs_regs *>(src);
1077+
new_gcs_regs.features_enabled =
1078+
(new_gcs_regs.features_enabled & ~1UL) | enable_bit;
1079+
1080+
const uint8_t *new_gcs_src =
1081+
reinterpret_cast<const uint8_t *>(&new_gcs_regs);
10661082
error = RestoreRegisters(
1067-
GetGCSBuffer(), &src, GetGCSBufferSize(), m_gcs_is_valid,
1083+
GetGCSBuffer(), &new_gcs_src, GetGCSBufferSize(), m_gcs_is_valid,
10681084
std::bind(&NativeRegisterContextLinux_arm64::WriteGCS, this));
1085+
src += GetGCSBufferSize();
1086+
10691087
break;
10701088
}
10711089

lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py

Lines changed: 161 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,40 @@ def test_gcs_fault(self):
8484
],
8585
)
8686

87+
def check_gcs_registers(
88+
self,
89+
expected_gcs_features_enabled=None,
90+
expected_gcs_features_locked=None,
91+
expected_gcspr_el0=None,
92+
):
93+
thread = self.dbg.GetSelectedTarget().process.GetThreadAtIndex(0)
94+
registerSets = thread.GetFrameAtIndex(0).GetRegisters()
95+
gcs_registers = registerSets.GetFirstValueByName(
96+
r"Guarded Control Stack Registers"
97+
)
98+
99+
gcs_features_enabled = gcs_registers.GetChildMemberWithName(
100+
"gcs_features_enabled"
101+
).GetValueAsUnsigned()
102+
if expected_gcs_features_enabled is not None:
103+
self.assertEqual(expected_gcs_features_enabled, gcs_features_enabled)
104+
105+
gcs_features_locked = gcs_registers.GetChildMemberWithName(
106+
"gcs_features_locked"
107+
).GetValueAsUnsigned()
108+
if expected_gcs_features_locked is not None:
109+
self.assertEqual(expected_gcs_features_locked, gcs_features_locked)
110+
111+
gcspr_el0 = gcs_registers.GetChildMemberWithName(
112+
"gcspr_el0"
113+
).GetValueAsUnsigned()
114+
if expected_gcspr_el0 is not None:
115+
self.assertEqual(expected_gcspr_el0, gcspr_el0)
116+
117+
return gcs_features_enabled, gcs_features_locked, gcspr_el0
118+
119+
# This helper reads all the GCS registers and optionally compares them
120+
# against a previous state, then returns the current register values.
87121
@skipUnlessArch("aarch64")
88122
@skipUnlessPlatform(["linux"])
89123
def test_gcs_registers(self):
@@ -108,40 +142,7 @@ def test_gcs_registers(self):
108142

109143
self.expect("register read --all", substrs=["Guarded Control Stack Registers:"])
110144

111-
# This helper reads all the GCS registers and optionally compares them
112-
# against a previous state, then returns the current register values.
113-
def check_gcs_registers(
114-
expected_gcs_features_enabled=None,
115-
expected_gcs_features_locked=None,
116-
expected_gcspr_el0=None,
117-
):
118-
thread = self.dbg.GetSelectedTarget().process.GetThreadAtIndex(0)
119-
registerSets = thread.GetFrameAtIndex(0).GetRegisters()
120-
gcs_registers = registerSets.GetFirstValueByName(
121-
r"Guarded Control Stack Registers"
122-
)
123-
124-
gcs_features_enabled = gcs_registers.GetChildMemberWithName(
125-
"gcs_features_enabled"
126-
).GetValueAsUnsigned()
127-
if expected_gcs_features_enabled is not None:
128-
self.assertEqual(expected_gcs_features_enabled, gcs_features_enabled)
129-
130-
gcs_features_locked = gcs_registers.GetChildMemberWithName(
131-
"gcs_features_locked"
132-
).GetValueAsUnsigned()
133-
if expected_gcs_features_locked is not None:
134-
self.assertEqual(expected_gcs_features_locked, gcs_features_locked)
135-
136-
gcspr_el0 = gcs_registers.GetChildMemberWithName(
137-
"gcspr_el0"
138-
).GetValueAsUnsigned()
139-
if expected_gcspr_el0 is not None:
140-
self.assertEqual(expected_gcspr_el0, gcspr_el0)
141-
142-
return gcs_features_enabled, gcs_features_locked, gcspr_el0
143-
144-
enabled, locked, spr_el0 = check_gcs_registers()
145+
enabled, locked, spr_el0 = self.check_gcs_registers()
145146

146147
# Features enabled should have at least the enable bit set, it could have
147148
# others depending on what the C library did, but we can't rely on always
@@ -164,7 +165,7 @@ def check_gcs_registers(
164165
substrs=["stopped", "stop reason = breakpoint"],
165166
)
166167

167-
_, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8)
168+
_, _, spr_el0 = self.check_gcs_registers(enabled, locked, spr_el0 - 8)
168169

169170
# Any combination of GCS feature lock bits might have been set by the C
170171
# library, and could be set to 0 or 1. To check that we can modify them,
@@ -235,3 +236,128 @@ def check_gcs_registers(
235236
"exited with status = 0",
236237
],
237238
)
239+
240+
@skipUnlessPlatform(["linux"])
241+
def test_gcs_expression_simple(self):
242+
if not self.isAArch64GCS():
243+
self.skipTest("Target must support GCS.")
244+
245+
self.build()
246+
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
247+
248+
# Break before GCS has been enabled.
249+
self.runCmd("b main")
250+
# And after it has been enabled.
251+
lldbutil.run_break_set_by_file_and_line(
252+
self,
253+
"main.c",
254+
line_number("main.c", "// Set break point at this line."),
255+
num_expected_locations=1,
256+
)
257+
258+
self.runCmd("run", RUN_SUCCEEDED)
259+
260+
if self.process().GetState() == lldb.eStateExited:
261+
self.fail("Test program failed to run.")
262+
263+
self.expect(
264+
"thread list",
265+
STOPPED_DUE_TO_BREAKPOINT,
266+
substrs=["stopped", "stop reason = breakpoint"],
267+
)
268+
269+
# GCS has not been enabled yet and the ABI plugin should know not to
270+
# attempt pushing to the control stack.
271+
before = self.check_gcs_registers()
272+
expr_cmd = "p get_gcs_status()"
273+
self.expect(expr_cmd, substrs=["(unsigned long) 0"])
274+
self.check_gcs_registers(*before)
275+
276+
# Continue to when GCS has been enabled.
277+
self.runCmd("continue")
278+
self.expect(
279+
"thread list",
280+
STOPPED_DUE_TO_BREAKPOINT,
281+
substrs=["stopped", "stop reason = breakpoint"],
282+
)
283+
284+
# This time we do need to push to the GCS and having done so, we can
285+
# return from this expression without causing a fault.
286+
before = self.check_gcs_registers()
287+
self.expect(expr_cmd, substrs=["(unsigned long) 1"])
288+
self.check_gcs_registers(*before)
289+
290+
@skipUnlessPlatform(["linux"])
291+
def test_gcs_expression_disable_gcs(self):
292+
if not self.isAArch64GCS():
293+
self.skipTest("Target must support GCS.")
294+
295+
self.build()
296+
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
297+
298+
# Break after GCS is enabled.
299+
lldbutil.run_break_set_by_file_and_line(
300+
self,
301+
"main.c",
302+
line_number("main.c", "// Set break point at this line."),
303+
num_expected_locations=1,
304+
)
305+
306+
self.runCmd("run", RUN_SUCCEEDED)
307+
308+
if self.process().GetState() == lldb.eStateExited:
309+
self.fail("Test program failed to run.")
310+
311+
self.expect(
312+
"thread list",
313+
STOPPED_DUE_TO_BREAKPOINT,
314+
substrs=["stopped", "stop reason = breakpoint"],
315+
)
316+
317+
# Unlock all features so the expression can enable them again.
318+
self.runCmd("register write gcs_features_locked 0")
319+
# Disable all features, but keep GCS itself enabled.
320+
PR_SHADOW_STACK_ENABLE = 1
321+
self.runCmd(f"register write gcs_features_enabled 0x{PR_SHADOW_STACK_ENABLE:x}")
322+
323+
enabled, locked, spr_el0 = self.check_gcs_registers()
324+
# We restore everything apart GCS being enabled, as we are not allowed to
325+
# go from disabled -> enabled via ptrace.
326+
self.expect("p change_gcs_config(false)", substrs=["true"])
327+
enabled &= ~1
328+
self.check_gcs_registers(enabled, locked, spr_el0)
329+
330+
@skipUnlessPlatform(["linux"])
331+
def test_gcs_expression_enable_gcs(self):
332+
if not self.isAArch64GCS():
333+
self.skipTest("Target must support GCS.")
334+
335+
self.build()
336+
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
337+
338+
# Break before GCS is enabled.
339+
self.runCmd("b main")
340+
341+
self.runCmd("run", RUN_SUCCEEDED)
342+
343+
if self.process().GetState() == lldb.eStateExited:
344+
self.fail("Test program failed to run.")
345+
346+
self.expect(
347+
"thread list",
348+
STOPPED_DUE_TO_BREAKPOINT,
349+
substrs=["stopped", "stop reason = breakpoint"],
350+
)
351+
352+
# Unlock all features so the expression can enable them again.
353+
self.runCmd("register write gcs_features_locked 0")
354+
# Disable all features. The program needs PR_SHADOW_STACK_PUSH, but it
355+
# will enable that itself.
356+
self.runCmd(f"register write gcs_features_enabled 0")
357+
358+
enabled, locked, spr_el0 = self.check_gcs_registers()
359+
self.expect("p change_gcs_config(true)", substrs=["true"])
360+
# Though we could disable GCS with ptrace, we choose not to to be
361+
# consistent with the disabled -> enabled behaviour.
362+
enabled |= 1
363+
self.check_gcs_registers(enabled, locked, spr_el0)

0 commit comments

Comments
 (0)