Skip to content

Commit b1751fa

Browse files
[lldb][Linux] Mark memory regions used for shadow stacks (#117861)
This is intended for use with Arm's Guarded Control Stack extension (GCS). Which reuses some existing shadow stack support in Linux. It should also work with the x86 equivalent. A "ss" flag is added to the "VmFlags" line of shadow stack memory regions in `/proc/<pid>/smaps`. To keep the naming generic I've called it shadow stack instead of guarded control stack. Also the wording is "shadow stack: yes" because the shadow stack region is just where it's stored. It's enabled for the whole process or it isn't. As opposed to memory tagging which can be enabled per region, so "memory tagging: enabled" fits better for that. I've added a test case that is also intended to be the start of a set of tests for GCS. This should help me avoid duplicating the inline assembly needed. Note that no special compiler support is needed for the test. However, for the intial enabling of GCS (assuming the libc isn't doing it) we do need to use an inline assembly version of prctl. This is because as soon as you enable GCS, all returns are checked against the GCS. If the GCS is empty, the program will fault. In other words, you can never return from the function that enabled GCS, unless you push values onto it (which is possible but not needed here). So you cannot use the libc's prctl wrapper for this reason. You can use that wrapper for anything else, as we do to check if GCS is enabled.
1 parent 5e26ff3 commit b1751fa

File tree

14 files changed

+316
-142
lines changed

14 files changed

+316
-142
lines changed

lldb/include/lldb/Target/MemoryRegionInfo.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ class MemoryRegionInfo {
2929
OptionalBool execute, OptionalBool shared,
3030
OptionalBool mapped, ConstString name, OptionalBool flash,
3131
lldb::offset_t blocksize, OptionalBool memory_tagged,
32-
OptionalBool stack_memory)
32+
OptionalBool stack_memory, OptionalBool shadow_stack)
3333
: m_range(range), m_read(read), m_write(write), m_execute(execute),
3434
m_shared(shared), m_mapped(mapped), m_name(name), m_flash(flash),
3535
m_blocksize(blocksize), m_memory_tagged(memory_tagged),
36-
m_is_stack_memory(stack_memory) {}
36+
m_is_stack_memory(stack_memory), m_is_shadow_stack(shadow_stack) {}
3737

3838
RangeType &GetRange() { return m_range; }
3939

@@ -55,6 +55,8 @@ class MemoryRegionInfo {
5555

5656
OptionalBool GetMemoryTagged() const { return m_memory_tagged; }
5757

58+
OptionalBool IsShadowStack() const { return m_is_shadow_stack; }
59+
5860
void SetReadable(OptionalBool val) { m_read = val; }
5961

6062
void SetWritable(OptionalBool val) { m_write = val; }
@@ -77,6 +79,8 @@ class MemoryRegionInfo {
7779

7880
void SetMemoryTagged(OptionalBool val) { m_memory_tagged = val; }
7981

82+
void SetIsShadowStack(OptionalBool val) { m_is_shadow_stack = val; }
83+
8084
// Get permissions as a uint32_t that is a mask of one or more bits from the
8185
// lldb::Permissions
8286
uint32_t GetLLDBPermissions() const {
@@ -106,7 +110,8 @@ class MemoryRegionInfo {
106110
m_blocksize == rhs.m_blocksize &&
107111
m_memory_tagged == rhs.m_memory_tagged &&
108112
m_pagesize == rhs.m_pagesize &&
109-
m_is_stack_memory == rhs.m_is_stack_memory;
113+
m_is_stack_memory == rhs.m_is_stack_memory &&
114+
m_is_shadow_stack == rhs.m_is_shadow_stack;
110115
}
111116

112117
bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); }
@@ -148,6 +153,7 @@ class MemoryRegionInfo {
148153
lldb::offset_t m_blocksize = 0;
149154
OptionalBool m_memory_tagged = eDontKnow;
150155
OptionalBool m_is_stack_memory = eDontKnow;
156+
OptionalBool m_is_shadow_stack = eDontKnow;
151157
int m_pagesize = 0;
152158
std::optional<std::vector<lldb::addr_t>> m_dirty_pages;
153159
};

lldb/packages/Python/lldbsuite/test/lldbtest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,9 @@ def isAArch64SMEFA64(self):
13641364
def isAArch64MTE(self):
13651365
return self.isAArch64() and "mte" in self.getCPUInfo()
13661366

1367+
def isAArch64GCS(self):
1368+
return self.isAArch64() and "gcs" in self.getCPUInfo()
1369+
13671370
def isAArch64PAuth(self):
13681371
if self.getArchitecture() == "arm64e":
13691372
return True

lldb/source/Commands/CommandObjectMemory.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,9 @@ class CommandObjectMemoryRegion : public CommandObjectParsed {
16641664
MemoryRegionInfo::OptionalBool memory_tagged = range_info.GetMemoryTagged();
16651665
if (memory_tagged == MemoryRegionInfo::OptionalBool::eYes)
16661666
result.AppendMessage("memory tagging: enabled");
1667+
MemoryRegionInfo::OptionalBool is_shadow_stack = range_info.IsShadowStack();
1668+
if (is_shadow_stack == MemoryRegionInfo::OptionalBool::eYes)
1669+
result.AppendMessage("shadow stack: yes");
16671670

16681671
const std::optional<std::vector<addr_t>> &dirty_page_list =
16691672
range_info.GetDirtyPageList();

lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,17 @@ void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap,
164164
if (!name.contains(' ')) {
165165
if (region) {
166166
if (name == "VmFlags") {
167-
if (value.contains("mt"))
168-
region->SetMemoryTagged(MemoryRegionInfo::eYes);
169-
else
170-
region->SetMemoryTagged(MemoryRegionInfo::eNo);
167+
region->SetMemoryTagged(MemoryRegionInfo::eNo);
168+
region->SetIsShadowStack(MemoryRegionInfo::eNo);
169+
170+
llvm::SmallVector<llvm::StringRef> flags;
171+
value.split(flags, ' ', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
172+
for (llvm::StringRef flag : flags)
173+
if (flag == "mt")
174+
region->SetMemoryTagged(MemoryRegionInfo::eYes);
175+
else if (flag == "ss")
176+
region->SetIsShadowStack(MemoryRegionInfo::eYes);
171177
}
172-
// Ignore anything else
173178
} else {
174179
// Orphaned settings line
175180
callback(ProcMapError(

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,7 @@ Status GDBRemoteCommunicationClient::GetMemoryRegionInfo(
16211621
region_info.SetName(name.c_str());
16221622
} else if (name == "flags") {
16231623
region_info.SetMemoryTagged(MemoryRegionInfo::eNo);
1624+
region_info.SetIsShadowStack(MemoryRegionInfo::eNo);
16241625

16251626
llvm::StringRef flags = value;
16261627
llvm::StringRef flag;
@@ -1629,10 +1630,10 @@ Status GDBRemoteCommunicationClient::GetMemoryRegionInfo(
16291630
std::tie(flag, flags) = flags.split(' ');
16301631
// To account for trailing whitespace
16311632
if (flag.size()) {
1632-
if (flag == "mt") {
1633+
if (flag == "mt")
16331634
region_info.SetMemoryTagged(MemoryRegionInfo::eYes);
1634-
break;
1635-
}
1635+
else if (flag == "ss")
1636+
region_info.SetIsShadowStack(MemoryRegionInfo::eYes);
16361637
}
16371638
}
16381639
} else if (name == "type") {

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,11 +2796,18 @@ GDBRemoteCommunicationServerLLGS::Handle_qMemoryRegionInfo(
27962796
// Flags
27972797
MemoryRegionInfo::OptionalBool memory_tagged =
27982798
region_info.GetMemoryTagged();
2799-
if (memory_tagged != MemoryRegionInfo::eDontKnow) {
2799+
MemoryRegionInfo::OptionalBool is_shadow_stack =
2800+
region_info.IsShadowStack();
2801+
2802+
if (memory_tagged != MemoryRegionInfo::eDontKnow ||
2803+
is_shadow_stack != MemoryRegionInfo::eDontKnow) {
28002804
response.PutCString("flags:");
2801-
if (memory_tagged == MemoryRegionInfo::eYes) {
2802-
response.PutCString("mt");
2803-
}
2805+
// Space is the separator.
2806+
if (memory_tagged == MemoryRegionInfo::eYes)
2807+
response.PutCString("mt ");
2808+
if (is_shadow_stack == MemoryRegionInfo::eYes)
2809+
response.PutCString("ss ");
2810+
28042811
response.PutChar(';');
28052812
}
28062813

lldb/source/Target/MemoryRegionInfo.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ using namespace lldb_private;
1313
llvm::raw_ostream &lldb_private::operator<<(llvm::raw_ostream &OS,
1414
const MemoryRegionInfo &Info) {
1515
return OS << llvm::formatv("MemoryRegionInfo([{0}, {1}), {2:r}{3:w}{4:x}, "
16-
"{5}, `{6}`, {7}, {8}, {9})",
16+
"{5}, `{6}`, {7}, {8}, {9}, {10}, {11})",
1717
Info.GetRange().GetRangeBase(),
1818
Info.GetRange().GetRangeEnd(), Info.GetReadable(),
1919
Info.GetWritable(), Info.GetExecutable(),
2020
Info.GetMapped(), Info.GetName(), Info.GetFlash(),
21-
Info.GetBlocksize(), Info.GetMemoryTagged());
21+
Info.GetBlocksize(), Info.GetMemoryTagged(),
22+
Info.IsStackMemory(), Info.IsShadowStack());
2223
}
2324

2425
void llvm::format_provider<MemoryRegionInfo::OptionalBool>::format(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
Check that lldb features work when the AArch64 Guarded Control Stack (GCS)
3+
extension is enabled.
4+
"""
5+
6+
7+
import lldb
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
from lldbsuite.test import lldbutil
11+
12+
13+
class AArch64LinuxGCSTestCase(TestBase):
14+
NO_DEBUG_INFO_TESTCASE = True
15+
16+
@skipUnlessArch("aarch64")
17+
@skipUnlessPlatform(["linux"])
18+
def test_gcs_region(self):
19+
if not self.isAArch64GCS():
20+
self.skipTest("Target must support GCS.")
21+
22+
# This test assumes that we have /proc/<PID>/smaps files
23+
# that include "VmFlags:" lines.
24+
# AArch64 kernel config defaults to enabling smaps with
25+
# PROC_PAGE_MONITOR and "VmFlags" was added in kernel 3.8,
26+
# before GCS was supported at all.
27+
28+
self.build()
29+
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
30+
31+
lldbutil.run_break_set_by_file_and_line(
32+
self,
33+
"main.c",
34+
line_number("main.c", "// Set break point at this line."),
35+
num_expected_locations=1,
36+
)
37+
38+
self.runCmd("run", RUN_SUCCEEDED)
39+
40+
if self.process().GetState() == lldb.eStateExited:
41+
self.fail("Test program failed to run.")
42+
43+
self.expect(
44+
"thread list",
45+
STOPPED_DUE_TO_BREAKPOINT,
46+
substrs=["stopped", "stop reason = breakpoint"],
47+
)
48+
49+
# By now either the program or the system C library enabled GCS and there
50+
# should be one region marked for use by it (we cannot predict exactly
51+
# where it will be).
52+
self.runCmd("memory region --all")
53+
found_ss = False
54+
for line in self.res.GetOutput().splitlines():
55+
if line.strip() == "shadow stack: yes":
56+
if found_ss:
57+
self.fail("Found more than one shadow stack region.")
58+
found_ss = True
59+
60+
self.assertTrue(found_ss, "Failed to find a shadow stack region.")
61+
62+
# Note that we must let the debugee get killed here as it cannot exit
63+
# cleanly if GCS was manually enabled.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include <asm/hwcap.h>
2+
#include <sys/auxv.h>
3+
#include <sys/prctl.h>
4+
5+
#ifndef HWCAP2_GCS
6+
#define HWCAP2_GCS (1UL << 63)
7+
#endif
8+
9+
#define PR_GET_SHADOW_STACK_STATUS 74
10+
#define PR_SET_SHADOW_STACK_STATUS 75
11+
#define PR_SHADOW_STACK_ENABLE (1UL)
12+
#define PRCTL_SYSCALL_NO 167
13+
14+
// Once we enable GCS, we cannot return from the function that made the syscall
15+
// to enable it. This is because the control stack is empty, there is no valid
16+
// address for us to return to. So for the initial enable we must use inline asm
17+
// instead of the libc's prctl wrapper function.
18+
#define my_prctl(option, arg2, arg3, arg4, arg5) \
19+
({ \
20+
register unsigned long x0 __asm__("x0") = option; \
21+
register unsigned long x1 __asm__("x1") = arg2; \
22+
register unsigned long x2 __asm__("x2") = arg3; \
23+
register unsigned long x3 __asm__("x3") = arg4; \
24+
register unsigned long x4 __asm__("x4") = arg5; \
25+
register unsigned long x8 __asm__("x8") = PRCTL_SYSCALL_NO; \
26+
__asm__ __volatile__("svc #0\n" \
27+
: "=r"(x0) \
28+
: "r"(x0), "r"(x1), "r"(x2), "r"(x3), "r"(x4), \
29+
"r"(x8) \
30+
: "cc", "memory"); \
31+
})
32+
33+
unsigned long get_gcs_status() {
34+
unsigned long mode = 0;
35+
prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
36+
return mode;
37+
}
38+
39+
int main() {
40+
if (!(getauxval(AT_HWCAP2) & HWCAP2_GCS))
41+
return 1;
42+
43+
unsigned long mode = get_gcs_status();
44+
if ((mode & 1) == 0) {
45+
// If GCS wasn't already enabled by the C library, enable it.
46+
my_prctl(PR_SET_SHADOW_STACK_STATUS, PR_SHADOW_STACK_ENABLE, 0, 0, 0);
47+
// From this point on, we cannot return from main without faulting because
48+
// the return address from main, and every function before that, is not on
49+
// the guarded control stack.
50+
}
51+
52+
// By now we should have one memory region where the GCS is stored.
53+
return 0; // Set break point at this line.
54+
}

0 commit comments

Comments
 (0)