Skip to content

Commit e6faeef

Browse files
committed
better document test case
1 parent 951a389 commit e6faeef

File tree

1 file changed

+43
-25
lines changed

1 file changed

+43
-25
lines changed

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

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def test_gcs_registers(self):
108108

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

111+
# This helper reads all the GCS registers and optionally compares them
112+
# against a previous state, then returns the current register values.
111113
def check_gcs_registers(
112114
expected_gcs_features_enabled=None,
113115
expected_gcs_features_locked=None,
@@ -142,7 +144,8 @@ def check_gcs_registers(
142144
enabled, locked, spr_el0 = check_gcs_registers()
143145

144146
# Features enabled should have at least the enable bit set, it could have
145-
# others depending on what the C library did.
147+
# others depending on what the C library did, but we can't rely on always
148+
# having them.
146149
self.assertTrue(enabled & 1, "Expected GCS enable bit to be set.")
147150

148151
# Features locked we cannot predict, we will just assert that it remains
@@ -163,15 +166,44 @@ def check_gcs_registers(
163166

164167
_, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8)
165168

166-
# Modify the control stack pointer to cause a fault.
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.
167199
spr_el0 += 8
168200
self.runCmd(f"register write gcspr_el0 {spr_el0}")
169201
self.expect(
170202
"register read gcspr_el0", substrs=[f"gcspr_el0 = 0x{spr_el0:016x}"]
171203
)
172204

173-
# If we wrote it back correctly, we will now fault but don't pass this
174-
# signal to the application.
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.
175207
self.runCmd("process handle SIGSEGV --pass false")
176208
self.runCmd("continue")
177209

@@ -184,32 +216,18 @@ def check_gcs_registers(
184216
],
185217
)
186218

187-
# Any combination of lock bits could be set. Flip then restore one of them.
188-
STACK_PUSH = 2
189-
stack_push = bool((locked >> STACK_PUSH) & 1)
190-
new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH)
191-
self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}")
192-
self.expect(
193-
f"register read gcs_features_locked",
194-
substrs=[f"gcs_features_locked = 0x{new_locked:016x}"],
195-
)
196-
197-
# We could prove the write made it to hardware by trying to prctl to change
198-
# the feature here, but we cannot know if the libc locked it or not.
199-
# Given that we know the other registers in the set write correctly, we
200-
# can assume this one does.
201-
202-
self.runCmd(f"register write gcs_features_locked 0x{locked:x}")
203-
204219
# Now to prove we can write gcs_features_enabled, disable GCS and continue
205-
# past the fault.
206-
enabled &= ~1
207-
self.runCmd(f"register write gcs_features_enabled {enabled}")
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+
gcs_enabled &= ~1
223+
self.runCmd(f"register write gcs_features_enabled {gcs_enabled}")
208224
self.expect(
209225
"register read gcs_features_enabled",
210-
substrs=[f"gcs_features_enabled = 0x{enabled:016x}"],
226+
substrs=[f"gcs_features_enabled = 0x{gcs_enabled:016x}"],
211227
)
212228

229+
# With GCS disabled, the invalid guarded control stack pointer is not
230+
# checked, so the program can finish normally.
213231
self.runCmd("continue")
214232
self.expect(
215233
"process status",

0 commit comments

Comments
 (0)