diff --git a/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp b/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp
index 3f25dbc6abbbe..eaecc84df15d4 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 b425c9e548ee5..0928ff8e14e00 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 9633ed2838f9e..32a9b07c20743 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;
 }