@@ -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