-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[lldb][AArch64] Add Guarded Control Stack registers #123720
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
Conversation
The Guarded Control Stack extension implements a shadow stack and the Linux kernel provides access to 3 registers for it via ptrace. struct user_gcs { __u64 features_enabled; __u64 features_locked; __u64 gcspr_el0; }; This commit adds support for reading those from a live process. The first 2 are pseudo registers based on the real control register and the 3rd is a real register. This is the stack pointer for the guarded stack. I have added a "gcs_" prefix to the "features" registers so that they have a clear name when shown individually. Also this means they will tab complete from "gcs", and be next to gcspr_el0 in any sorted lists of registers. Guarded Control Stack Registers: gcs_features_enabled = 0x0000000000000000 gcs_features_locked = 0x0000000000000000 gcspr_el0 = 0x0000000000000000 Testing is more of the usual, where possible I'm writing a register then doing something in the program to confirm the value was actually sent to ptrace.
@llvm/pr-subscribers-lldb Author: David Spickett (DavidSpickett) ChangesThe Guarded Control Stack extension implements a shadow stack and the Linux kernel provides access to 3 registers for it via ptrace. struct user_gcs { This commit adds support for reading those from a live process. The first 2 are pseudo registers based on the real control register and the 3rd is a real register. This is the stack pointer for the guarded stack. I have added a "gcs_" prefix to the "features" registers so that they have a clear name when shown individually. Also this means they will tab complete from "gcs", and be next to gcspr_el0 in any sorted lists of registers. Guarded Control Stack Registers: Testing is more of the usual, where possible I'm writing a register then doing something in the program to confirm the value was actually sent to ptrace. Patch is 22.31 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/123720.diff 8 Files Affected:
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
index 6056f3001fed6e..efd3385c46e92f 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
@@ -64,8 +64,14 @@
#define NT_ARM_FPMR 0x40e /* Floating point mode register */
#endif
+#ifndef NT_ARM_GCS
+#define NT_ARM_GCS 0x410 /* Guarded Control Stack control registers */
+#endif
+
#define HWCAP_PACA (1 << 30)
+#define HWCAP_GCS (1UL << 32)
+
#define HWCAP2_MTE (1 << 18)
#define HWCAP2_FPMR (1UL << 48)
@@ -150,6 +156,8 @@ NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskMTE);
if (*auxv_at_hwcap2 & HWCAP2_FPMR)
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskFPMR);
+ if (*auxv_at_hwcap & HWCAP_GCS)
+ opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskGCS);
}
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS);
@@ -193,6 +201,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
::memset(&m_pac_mask, 0, sizeof(m_pac_mask));
::memset(&m_tls_regs, 0, sizeof(m_tls_regs));
::memset(&m_sme_pseudo_regs, 0, sizeof(m_sme_pseudo_regs));
+ ::memset(&m_gcs_regs, 0, sizeof(m_gcs_regs));
std::fill(m_zt_reg.begin(), m_zt_reg.end(), 0);
m_mte_ctrl_reg = 0;
@@ -213,6 +222,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
m_tls_is_valid = false;
m_zt_buffer_is_valid = false;
m_fpmr_is_valid = false;
+ m_gcs_is_valid = false;
// SME adds the tpidr2 register
m_tls_size = GetRegisterInfo().IsSSVEPresent() ? sizeof(m_tls_regs)
@@ -433,6 +443,14 @@ NativeRegisterContextLinux_arm64::ReadRegister(const RegisterInfo *reg_info,
offset = reg_info->byte_offset - GetRegisterInfo().GetFPMROffset();
assert(offset < GetFPMRBufferSize());
src = (uint8_t *)GetFPMRBuffer() + offset;
+ } else if (IsGCS(reg)) {
+ error = ReadGCS();
+ if (error.Fail())
+ return error;
+
+ offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset();
+ assert(offset < GetGCSBufferSize());
+ src = (uint8_t *)GetGCSBuffer() + offset;
} else
return Status::FromErrorString(
"failed - register wasn't recognized to be a GPR or an FPR, "
@@ -657,6 +675,17 @@ Status NativeRegisterContextLinux_arm64::WriteRegister(
::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size);
return WriteFPMR();
+ } else if (IsGCS(reg)) {
+ error = ReadGCS();
+ if (error.Fail())
+ return error;
+
+ offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset();
+ assert(offset < GetGCSBufferSize());
+ dst = (uint8_t *)GetGCSBuffer() + offset;
+ ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size);
+
+ return WriteGCS();
}
return Status::FromErrorString("Failed to write register value");
@@ -672,6 +701,7 @@ enum RegisterSetType : uint32_t {
SME, // ZA only, because SVCR and SVG are pseudo registers.
SME2, // ZT only.
FPMR,
+ GCS, // Guarded Control Stack registers.
};
static uint8_t *AddRegisterSetType(uint8_t *dst,
@@ -759,6 +789,13 @@ NativeRegisterContextLinux_arm64::CacheAllRegisters(uint32_t &cached_size) {
return error;
}
+ if (GetRegisterInfo().IsGCSPresent()) {
+ cached_size += sizeof(RegisterSetType) + GetGCSBufferSize();
+ error = ReadGCS();
+ if (error.Fail())
+ return error;
+ }
+
// tpidr is always present but tpidr2 depends on SME.
cached_size += sizeof(RegisterSetType) + GetTLSBufferSize();
error = ReadTLS();
@@ -867,6 +904,11 @@ Status NativeRegisterContextLinux_arm64::ReadAllRegisterValues(
GetFPMRBufferSize());
}
+ if (GetRegisterInfo().IsGCSPresent()) {
+ dst = AddSavedRegisters(dst, RegisterSetType::GCS, GetGCSBuffer(),
+ GetGCSBufferSize());
+ }
+
dst = AddSavedRegisters(dst, RegisterSetType::TLS, GetTLSBuffer(),
GetTLSBufferSize());
@@ -1020,6 +1062,11 @@ Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues(
GetFPMRBuffer(), &src, GetFPMRBufferSize(), m_fpmr_is_valid,
std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this));
break;
+ case RegisterSetType::GCS:
+ error = RestoreRegisters(
+ GetGCSBuffer(), &src, GetGCSBufferSize(), m_gcs_is_valid,
+ std::bind(&NativeRegisterContextLinux_arm64::WriteGCS, this));
+ break;
}
if (error.Fail())
@@ -1067,6 +1114,10 @@ bool NativeRegisterContextLinux_arm64::IsFPMR(unsigned reg) const {
return GetRegisterInfo().IsFPMRReg(reg);
}
+bool NativeRegisterContextLinux_arm64::IsGCS(unsigned reg) const {
+ return GetRegisterInfo().IsGCSReg(reg);
+}
+
llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() {
if (!m_refresh_hwdebug_info) {
return llvm::Error::success();
@@ -1215,6 +1266,7 @@ void NativeRegisterContextLinux_arm64::InvalidateAllRegisters() {
m_tls_is_valid = false;
m_zt_buffer_is_valid = false;
m_fpmr_is_valid = false;
+ m_gcs_is_valid = false;
// Update SVE and ZA registers in case there is change in configuration.
ConfigureRegisterContext();
@@ -1400,6 +1452,40 @@ Status NativeRegisterContextLinux_arm64::WriteTLS() {
return WriteRegisterSet(&ioVec, GetTLSBufferSize(), NT_ARM_TLS);
}
+Status NativeRegisterContextLinux_arm64::ReadGCS() {
+ Status error;
+
+ if (m_gcs_is_valid)
+ return error;
+
+ struct iovec ioVec;
+ ioVec.iov_base = GetGCSBuffer();
+ ioVec.iov_len = GetGCSBufferSize();
+
+ error = ReadRegisterSet(&ioVec, GetGCSBufferSize(), NT_ARM_GCS);
+
+ if (error.Success())
+ m_gcs_is_valid = true;
+
+ return error;
+}
+
+Status NativeRegisterContextLinux_arm64::WriteGCS() {
+ Status error;
+
+ error = ReadGCS();
+ if (error.Fail())
+ return error;
+
+ struct iovec ioVec;
+ ioVec.iov_base = GetGCSBuffer();
+ ioVec.iov_len = GetGCSBufferSize();
+
+ m_gcs_is_valid = false;
+
+ return WriteRegisterSet(&ioVec, GetGCSBufferSize(), NT_ARM_GCS);
+}
+
Status NativeRegisterContextLinux_arm64::ReadZAHeader() {
Status error;
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
index 16190b5492582b..7ed0da85034969 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
@@ -92,6 +92,7 @@ class NativeRegisterContextLinux_arm64
bool m_pac_mask_is_valid;
bool m_tls_is_valid;
size_t m_tls_size;
+ bool m_gcs_is_valid;
struct user_pt_regs m_gpr_arm64; // 64-bit general purpose registers.
@@ -136,6 +137,12 @@ class NativeRegisterContextLinux_arm64
uint64_t m_fpmr_reg;
+ struct gcs_regs {
+ uint64_t features_enabled;
+ uint64_t features_locked;
+ uint64_t gcspr_e0;
+ } m_gcs_regs;
+
bool IsGPR(unsigned reg) const;
bool IsFPR(unsigned reg) const;
@@ -166,6 +173,10 @@ class NativeRegisterContextLinux_arm64
Status WriteZA();
+ Status ReadGCS();
+
+ Status WriteGCS();
+
// No WriteZAHeader because writing only the header will disable ZA.
// Instead use WriteZA and ensure you have the correct ZA buffer size set
// beforehand if you wish to disable it.
@@ -187,6 +198,7 @@ class NativeRegisterContextLinux_arm64
bool IsMTE(unsigned reg) const;
bool IsTLS(unsigned reg) const;
bool IsFPMR(unsigned reg) const;
+ bool IsGCS(unsigned reg) const;
uint64_t GetSVERegVG() { return m_sve_header.vl / 8; }
@@ -212,6 +224,8 @@ class NativeRegisterContextLinux_arm64
void *GetFPMRBuffer() { return &m_fpmr_reg; }
+ void *GetGCSBuffer() { return &m_gcs_regs; }
+
size_t GetSVEHeaderSize() { return sizeof(m_sve_header); }
size_t GetPACMaskSize() { return sizeof(m_pac_mask); }
@@ -234,6 +248,8 @@ class NativeRegisterContextLinux_arm64
size_t GetFPMRBufferSize() { return sizeof(m_fpmr_reg); }
+ size_t GetGCSBufferSize() { return sizeof(m_gcs_regs); }
+
llvm::Error ReadHardwareDebugInfo() override;
llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override;
diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
index 575e9c8c81cbf5..0233837f99d097 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
@@ -63,6 +63,10 @@ bool RegisterContextPOSIX_arm64::IsFPMR(unsigned reg) const {
return m_register_info_up->IsFPMRReg(reg);
}
+bool RegisterContextPOSIX_arm64::IsGCS(unsigned reg) const {
+ return m_register_info_up->IsGCSReg(reg);
+}
+
RegisterContextPOSIX_arm64::RegisterContextPOSIX_arm64(
lldb_private::Thread &thread,
std::unique_ptr<RegisterInfoPOSIX_arm64> register_info)
diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
index 35ad56c98a7aed..de46c628d836d8 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
@@ -59,6 +59,7 @@ class RegisterContextPOSIX_arm64 : public lldb_private::RegisterContext {
bool IsSME(unsigned reg) const;
bool IsMTE(unsigned reg) const;
bool IsFPMR(unsigned reg) const;
+ bool IsGCS(unsigned reg) const;
bool IsSVEZ(unsigned reg) const { return m_register_info_up->IsSVEZReg(reg); }
bool IsSVEP(unsigned reg) const { return m_register_info_up->IsSVEPReg(reg); }
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
index f51a93e1b2dcbd..c004c0f3c3cf52 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
@@ -97,6 +97,10 @@ static lldb_private::RegisterInfo g_register_infos_sme2[] = {
static lldb_private::RegisterInfo g_register_infos_fpmr[] = {
DEFINE_EXTENSION_REG(fpmr)};
+static lldb_private::RegisterInfo g_register_infos_gcs[] = {
+ DEFINE_EXTENSION_REG(gcs_features_enabled),
+ DEFINE_EXTENSION_REG(gcs_features_locked), DEFINE_EXTENSION_REG(gcspr_el0)};
+
// Number of register sets provided by this context.
enum {
k_num_gpr_registers = gpr_w28 - gpr_x0 + 1,
@@ -109,6 +113,7 @@ enum {
// only for SME1 registers.
k_num_sme_register = 3,
k_num_fpmr_register = 1,
+ k_num_gcs_register = 3,
k_num_register_sets_default = 2,
k_num_register_sets = 3
};
@@ -221,6 +226,9 @@ static const lldb_private::RegisterSet g_reg_set_sme_arm64 = {
static const lldb_private::RegisterSet g_reg_set_fpmr_arm64 = {
"Floating Point Mode Register", "fpmr", k_num_fpmr_register, nullptr};
+static const lldb_private::RegisterSet g_reg_set_gcs_arm64 = {
+ "Guarded Control Stack Registers", "gcs", k_num_gcs_register, nullptr};
+
RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
const lldb_private::ArchSpec &target_arch, lldb_private::Flags opt_regsets)
: lldb_private::RegisterInfoAndSetInterface(target_arch),
@@ -273,6 +281,9 @@ RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
if (m_opt_regsets.AllSet(eRegsetMaskFPMR))
AddRegSetFPMR();
+ if (m_opt_regsets.AllSet(eRegsetMaskGCS))
+ AddRegSetGCS();
+
m_register_info_count = m_dynamic_reg_infos.size();
m_register_info_p = m_dynamic_reg_infos.data();
m_register_set_p = m_dynamic_reg_sets.data();
@@ -434,6 +445,24 @@ void RegisterInfoPOSIX_arm64::AddRegSetFPMR() {
m_dynamic_reg_sets.back().registers = m_fpmr_regnum_collection.data();
}
+void RegisterInfoPOSIX_arm64::AddRegSetGCS() {
+ uint32_t gcs_regnum = m_dynamic_reg_infos.size();
+ for (uint32_t i = 0; i < k_num_gcs_register; i++) {
+ m_gcs_regnum_collection.push_back(gcs_regnum + i);
+ m_dynamic_reg_infos.push_back(g_register_infos_gcs[i]);
+ m_dynamic_reg_infos[gcs_regnum + i].byte_offset =
+ m_dynamic_reg_infos[gcs_regnum + i - 1].byte_offset +
+ m_dynamic_reg_infos[gcs_regnum + i - 1].byte_size;
+ m_dynamic_reg_infos[gcs_regnum + i].kinds[lldb::eRegisterKindLLDB] =
+ gcs_regnum + i;
+ }
+
+ m_per_regset_regnum_range[m_register_set_count] =
+ std::make_pair(gcs_regnum, m_dynamic_reg_infos.size());
+ m_dynamic_reg_sets.push_back(g_reg_set_gcs_arm64);
+ m_dynamic_reg_sets.back().registers = m_gcs_regnum_collection.data();
+}
+
uint32_t RegisterInfoPOSIX_arm64::ConfigureVectorLengthSVE(uint32_t sve_vq) {
// sve_vq contains SVE Quad vector length in context of AArch64 SVE.
// SVE register infos if enabled cannot be disabled by selecting sve_vq = 0.
@@ -561,6 +590,10 @@ bool RegisterInfoPOSIX_arm64::IsFPMRReg(unsigned reg) const {
return llvm::is_contained(m_fpmr_regnum_collection, reg);
}
+bool RegisterInfoPOSIX_arm64::IsGCSReg(unsigned reg) const {
+ return llvm::is_contained(m_gcs_regnum_collection, reg);
+}
+
uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEZ0() const { return sve_z0; }
uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEFFR() const { return sve_ffr; }
@@ -593,4 +626,8 @@ uint32_t RegisterInfoPOSIX_arm64::GetSMEOffset() const {
uint32_t RegisterInfoPOSIX_arm64::GetFPMROffset() const {
return m_register_info_p[m_fpmr_regnum_collection[0]].byte_offset;
-}
\ No newline at end of file
+}
+
+uint32_t RegisterInfoPOSIX_arm64::GetGCSOffset() const {
+ return m_register_info_p[m_gcs_regnum_collection[0]].byte_offset;
+}
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
index 16a951ef0935f0..d2ddf7d86d8c39 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
@@ -33,6 +33,7 @@ class RegisterInfoPOSIX_arm64
eRegsetMaskZA = 32,
eRegsetMaskZT = 64,
eRegsetMaskFPMR = 128,
+ eRegsetMaskGCS = 256,
eRegsetMaskDynamic = ~1,
};
@@ -113,6 +114,8 @@ class RegisterInfoPOSIX_arm64
void AddRegSetFPMR();
+ void AddRegSetGCS();
+
uint32_t ConfigureVectorLengthSVE(uint32_t sve_vq);
void ConfigureVectorLengthZA(uint32_t za_vq);
@@ -132,6 +135,7 @@ class RegisterInfoPOSIX_arm64
bool IsMTEPresent() const { return m_opt_regsets.AnySet(eRegsetMaskMTE); }
bool IsTLSPresent() const { return m_opt_regsets.AnySet(eRegsetMaskTLS); }
bool IsFPMRPresent() const { return m_opt_regsets.AnySet(eRegsetMaskFPMR); }
+ bool IsGCSPresent() const { return m_opt_regsets.AnySet(eRegsetMaskGCS); }
bool IsSVEReg(unsigned reg) const;
bool IsSVEZReg(unsigned reg) const;
@@ -144,6 +148,7 @@ class RegisterInfoPOSIX_arm64
bool IsSMERegZA(unsigned reg) const;
bool IsSMERegZT(unsigned reg) const;
bool IsFPMRReg(unsigned reg) const;
+ bool IsGCSReg(unsigned reg) const;
uint32_t GetRegNumSVEZ0() const;
uint32_t GetRegNumSVEFFR() const;
@@ -156,6 +161,7 @@ class RegisterInfoPOSIX_arm64
uint32_t GetTLSOffset() const;
uint32_t GetSMEOffset() const;
uint32_t GetFPMROffset() const;
+ uint32_t GetGCSOffset() const;
private:
typedef std::map<uint32_t, std::vector<lldb_private::RegisterInfo>>
@@ -188,6 +194,7 @@ class RegisterInfoPOSIX_arm64
std::vector<uint32_t> m_tls_regnum_collection;
std::vector<uint32_t> m_sme_regnum_collection;
std::vector<uint32_t> m_fpmr_regnum_collection;
+ std::vector<uint32_t> m_gcs_regnum_collection;
};
#endif
diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
index 0928ff8e14e000..e08d4821bafc4f 100644
--- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
+++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
@@ -83,3 +83,137 @@ def test_gcs_fault(self):
"stop reason = signal SIGSEGV: control protection fault",
],
)
+
+ @skipUnlessArch("aarch64")
+ @skipUnlessPlatform(["linux"])
+ def test_gcs_registers(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("b test_func")
+ self.runCmd("b test_func2")
+ self.runCmd("run", RUN_SUCCEEDED)
+
+ if self.process().GetState() == lldb.eStateExited:
+ self.fail("Test program failed to run.")
+
+ self.expect(
+ "thread list",
+ STOPPED_DUE_TO_BREAKPOINT,
+ substrs=["stopped", "stop reason = breakpoint"],
+ )
+
+ self.expect("register read --all", substrs=["Guarded Control Stack Registers:"])
+
+ def check_gcs_registers(
+ expected_gcs_features_enabled=None,
+ expected_gcs_features_locked=None,
+ expected_gcspr_el0=None,
+ ):
+ thread = self.dbg.GetSelectedTarget().process.GetThreadAtIndex(0)
+ registerSets = thread.GetFrameAtIndex(0).GetRegisters()
+ gcs_registers = registerSets.GetFirstValueByName(
+ r"Guarded Control Stack Registers"
+ )
+
+ gcs_features_enabled = gcs_registers.GetChildMemberWithName(
+ "gcs_features_enabled"
+ ).GetValueAsUnsigned()
+ if expected_gcs_features_enabled is not None:
+ self.assertEqual(expected_gcs_features_enabled, gcs_features_enabled)
+
+ gcs_features_locked = gcs_registers.GetChildMemberWithName(
+ "gcs_features_locked"
+ ).GetValueAsUnsigned()
+ if expected_gcs_features_locked is not None:
+ self.assertEqual(expected_gcs_features_locked, gcs_features_locked)
+
+ gcspr_el0 = gcs_registers.GetChildMemberWithName(
+ "gcspr_el0"
+ ).GetValueAsUnsigned()
+ if expected_gcspr_el0 is not None:
+ self.assertEqual(expected_gcspr_el0, gcspr_el0)
+
+ return gcs_features_enabled, gcs_features_locked, gcspr_el0
+
+ enabled, locked, spr_el0 = check_gcs_registers()
+
+ # Features enabled should have at least the enable bit set, it could have
+ # others depending on what the C library did.
+ self.assertTrue(enabled & 1, "Expected GCS enable bit to be set.")
+
+ # Features locked we cannot predict, we will just assert that it remains
+ # the same as we continue.
+
+ # spr_el0 will point to some memory region that is a shadow stack region.
+ self.expect(f"memory region {spr_el0}", substrs=["shadow stack: yes"])
+
+ # Continue into test_func2, where the GCS pointer should have been
+ # decremented, and the other registers remain the same.
+ self.runCmd("continue")
+
+ self.expect(
+ "thread list",
+ STOPPED_DUE_TO_BREAKPOINT,
+ substrs=["stopped", "stop reason = breakpoint"],
+ )
+
+ _, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8)
+
+ # Modify the control stack pointer to cause a fault.
+ spr_el0 += 8
+ self.runCmd(f"register write gcspr_el0 {spr_el0}")
+ self.expect(
+ "register read gcspr_el0", substrs=[f"gcspr_el0 = 0x{spr_el0:016x}"]
+ )
+
+ # If we wrote it back correctly, we will now fault but don't pass this
+ # signal to the application.
+ self.runCmd("process handle SIGSEGV --pass false")
+ self.runCmd("continue")
+
+ self.expect(
+ "thread list",
+ "Expected stopped by SIGSEGV.",
+ substrs=[
+ "stopped",
+ "stop reason = signal SIGSEGV: control protection fault",
+ ],
+ )
+
+ # Any combination of lock bits could be set. Flip then restore one of them.
+ STACK_PUSH = 2
+ stack_push = bool((locked >> STACK_PUSH) & 1)
+ new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH)
+ self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}")
+ self.expect(
+ f"register read gcs_features_lock...
[truncated]
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This look good. Just a minor nit to add more details about how GCS locking testing is being done.
The Guarded Control Stack extension implements a shadow stack and the Linux kernel provides access to 3 registers for it via ptrace.
struct user_gcs {
__u64 features_enabled;
__u64 features_locked;
__u64 gcspr_el0;
};
This commit adds support for reading those from a live process.
The first 2 are pseudo registers based on the real control register and the 3rd is a real register. This is the stack pointer for the guarded stack.
I have added a "gcs_" prefix to the "features" registers so that they have a clear name when shown individually. Also this means they will tab complete from "gcs", and be next to gcspr_el0 in any sorted lists of registers.
Guarded Control Stack Registers:
gcs_features_enabled = 0x0000000000000000
gcs_features_locked = 0x0000000000000000
gcspr_el0 = 0x0000000000000000
Testing is more of the usual, where possible I'm writing a register then doing something in the program to confirm the value was actually sent to ptrace.