Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb][Linux] Add Control Protection Fault signal #122917

Merged
merged 1 commit into from
Jan 21, 2025

Conversation

DavidSpickett
Copy link
Collaborator

This will be sent by Arm's Guarded Control Stack extension when an invalid return is executed.

The signal does have an address we could show, but it's the PC at which the fault occured. The debugger has plenty of ways to show you that already, so I've left it out.

(lldb) c
Process 460 resuming
Process 460 stopped
* thread #1, name = 'test', stop reason = signal SIGSEGV: control protection fault
    frame #0: 0x0000000000400784 test`main at main.c:57:1
   54  	  afunc();
   55  	  printf("return from main\n");
   56  	  return 0;
-> 57  	}
(lldb) dis
<...>
->  0x400784 <+100>: ret

The new test case generates the signal by corrupting the link register then attempting to return. This will work whether we manually enable GCS or the C library does it for us.

(in the former case you could just return from main and it would fault)

This will be sent by Arm's Guarded Control Stack extension
when an invalid return is executed.

The signal does have an address we could show, but it's the
PC at which the fault occured. The debugger has plenty of ways
to show you that already, so I've left it out.

```
(lldb) c
Process 460 resuming
Process 460 stopped
* thread #1, name = 'test', stop reason = signal SIGSEGV: control protection fault
    frame #0: 0x0000000000400784 test`main at main.c:57:1
   54  	  afunc();
   55  	  printf("return from main\n");
   56  	  return 0;
-> 57  	}
(lldb) dis
<...>
->  0x400784 <+100>: ret
```

The new test case generates the signal by corrupting the link
register then attempting to return. This will work whether we
manually enable GCS or the C library does it for us.

(in the former case you could just return from main and it would
fault)
@llvmbot
Copy link
Member

llvmbot commented Jan 14, 2025

@llvm/pr-subscribers-lldb

Author: David Spickett (DavidSpickett)

Changes

This will be sent by Arm's Guarded Control Stack extension when an invalid return is executed.

The signal does have an address we could show, but it's the PC at which the fault occured. The debugger has plenty of ways to show you that already, so I've left it out.

(lldb) c
Process 460 resuming
Process 460 stopped
* thread #<!-- -->1, name = 'test', stop reason = signal SIGSEGV: control protection fault
    frame #<!-- -->0: 0x0000000000400784 test`main at main.c:57:1
   54  	  afunc();
   55  	  printf("return from main\n");
   56  	  return 0;
-&gt; 57  	}
(lldb) dis
&lt;...&gt;
-&gt;  0x400784 &lt;+100&gt;: ret

The new test case generates the signal by corrupting the link register then attempting to return. This will work whether we manually enable GCS or the C library does it for us.

(in the former case you could just return from main and it would fault)


Full diff: https://github.com/llvm/llvm-project/pull/122917.diff

3 Files Affected:

  • (modified) lldb/source/Plugins/Process/Utility/LinuxSignals.cpp (+4)
  • (modified) lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py (+22)
  • (modified) lldb/test/API/linux/aarch64/gcs/main.c (+16-1)
diff --git a/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp b/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp
index 3f25dbc6abbbe3..eaecc84df15d4c 100644
--- a/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp
+++ b/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp
@@ -20,6 +20,9 @@
 #ifndef SEGV_MTESERR
 #define SEGV_MTESERR 9
 #endif
+#ifndef SEGV_CPERR
+#define SEGV_CPERR 10
+#endif
 
 #define ADD_SIGCODE(signal_name, signal_value, code_name, code_value, ...)     \
   static_assert(signal_name == signal_value,                                   \
@@ -82,6 +85,7 @@ void LinuxSignals::Reset() {
   ADD_SIGCODE(SIGSEGV, 11, SEGV_BNDERR,  3, "failed address bounds checks", SignalCodePrintOption::Bounds);
   ADD_SIGCODE(SIGSEGV, 11, SEGV_MTEAERR, 8, "async tag check fault");
   ADD_SIGCODE(SIGSEGV, 11, SEGV_MTESERR, 9, "sync tag check fault", SignalCodePrintOption::Address);
+  ADD_SIGCODE(SIGSEGV, 11, SEGV_CPERR,  10, "control protection fault");
   // Some platforms will occasionally send nonstandard spurious SI_KERNEL
   // codes. One way to get this is via unaligned SIMD loads. Treat it as invalid address.
   ADD_SIGCODE(SIGSEGV, 11, SI_KERNEL, 0x80, "invalid address", SignalCodePrintOption::Address);
diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
index b425c9e548ee51..0928ff8e14e000 100644
--- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
+++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
@@ -61,3 +61,25 @@ def test_gcs_region(self):
 
         # 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",
+            ],
+        )
diff --git a/lldb/test/API/linux/aarch64/gcs/main.c b/lldb/test/API/linux/aarch64/gcs/main.c
index 9633ed2838f9e8..32a9b07c207436 100644
--- a/lldb/test/API/linux/aarch64/gcs/main.c
+++ b/lldb/test/API/linux/aarch64/gcs/main.c
@@ -36,6 +36,19 @@ unsigned long get_gcs_status() {
   return mode;
 }
 
+void gcs_signal() {
+  // If we enabled GCS manually, then we could just return from main to generate
+  // a signal. However, if the C library enabled it, then we'd just exit
+  // normally. Assume the latter, and try to return to some bogus address to
+  // generate the signal.
+  __asm__ __volatile__(
+      // Corrupt the link register. This could be many numbers but 16 is a
+      // nicely aligned value that is unlikely to result in a fault because the
+      // PC is misaligned, which would hide the GCS fault.
+      "add x30, x30, #10\n"
+      "ret\n");
+}
+
 int main() {
   if (!(getauxval(AT_HWCAP2) & HWCAP2_GCS))
     return 1;
@@ -50,5 +63,7 @@ int main() {
   }
 
   // By now we should have one memory region where the GCS is stored.
-  return 0; // Set break point at this line.
+  gcs_signal(); // Set break point at this line.
+
+  return 0;
 }

@@ -50,5 +63,7 @@ int main() {
}

// By now we should have one memory region where the GCS is stored.
return 0; // Set break point at this line.
gcs_signal(); // Set break point at this line.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previously added test_gcs_region will stop here and then kill the process instead of continuing. So it will work as it did before.

@DavidSpickett DavidSpickett merged commit 5658bc4 into llvm:main Jan 21, 2025
9 checks passed
@DavidSpickett DavidSpickett deleted the gcs-signal branch January 21, 2025 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants