|
|
Message-ID: <7d2a24a239d7a464d0d12e91f33e9f6e@bu.edu>
Date: Mon, 10 Aug 2026 20:18:38 +0000
From: Khosro Moeini <khosro@...edu>
To: musl@...ts.openwall.com
Cc: khosro@...edu
Subject: [PATCH] x86_64: add CET shadow stack support
Enable CET shadow stack on x86_64 when the executable and
all loaded shared objects have the GNU_PROPERTY_X86_FEATURE_1_SHSTK bit set
in their .note.gnu.property note. If shadow stack is enabled for a process,
dlopen of an object without the shadow stack property note fails.
Unlike glibc, this implementation does not check environment variables,
but this could be changed.
The patch enables the shadow stack and locks it using the arch_prctl syscall.
The function that enables the shadow stack cannot return, so SHSTK_ENABLE
is defined as a macro and used in libc_start_main_stage2.
setjmp and sigsetjmp are modified to store the shadow stack pointer in __ss[2].
longjmp restores the shadow stack pointer.
In vfork, in child, jmp to caller to avoid changing the shadow stack
which is shared with parent.
All the changes are guarded by SHSTK_ENABLED which is set through
the --enable-cet configuration option.
Please CC on reply.
---
Makefile | 11 +-
arch/x86_64/note.s | 12 ++
configure | 29 ++++
include/elf.h | 5 +
ldso/dynlink.c | 25 +++
src/env/__libc_start_main.c | 11 ++
src/internal/shstk.h | 39 +++++
src/ldso/x86_64/shstk.c | 143 ++++++++++++++++++
src/process/x86_64/{vfork.s => vfork.S} | 13 ++
src/setjmp/x86_64/{longjmp.s => longjmp.S} | 24 +++
src/setjmp/x86_64/{setjmp.s => setjmp.S} | 6 +
.../x86_64/{sigsetjmp.s => sigsetjmp.S} | 17 +++
12 files changed, 331 insertions(+), 4 deletions(-)
create mode 100644 arch/x86_64/note.s
create mode 100644 src/internal/shstk.h
create mode 100644 src/ldso/x86_64/shstk.c
rename src/process/x86_64/{vfork.s => vfork.S} (35%)
rename src/setjmp/x86_64/{longjmp.s => longjmp.S} (52%)
rename src/setjmp/x86_64/{setjmp.s => setjmp.S} (82%)
rename src/signal/x86_64/{sigsetjmp.s => sigsetjmp.S} (56%)
diff --git a/Makefile b/Makefile
index 3ad88b3..2d69328 100644
--- a/Makefile
+++ b/Makefile
@@ -45,6 +45,7 @@ CPPFLAGS =
CFLAGS =
CFLAGS_AUTO = -Os -pipe
CFLAGS_C99FSE = -std=c99 -ffreestanding -nostdinc
+ASM_NOTE =
CFLAGS_ALL = $(CFLAGS_C99FSE)
CFLAGS_ALL += -D_XOPEN_SOURCE=700 -I$(srcdir)/arch/$(ARCH) -I$(srcdir)/arch/generic -Iobj/src/internal -I$(srcdir)/src/include -I$(srcdir)/src/internal -Iobj/include -I$(srcdir)/include
@@ -135,16 +136,18 @@ CC_CMD = $(CC) $(CFLAGS_ALL) -c -o $@ $<
# Choose invocation of assembler to be used
ifeq ($(ADD_CFI),yes)
- AS_CMD = LC_ALL=C awk -f $(srcdir)/tools/add-cfi.common.awk -f $(srcdir)/tools/add-cfi.$(ARCH).awk $< | $(CC) $(CFLAGS_ALL) -x assembler -c -o $@ -
+ AS_CMD = LC_ALL=C awk -f $(srcdir)/tools/add-cfi.common.awk -f $(srcdir)/tools/add-cfi.$(ARCH).awk $< $(ASM_NOTE) | $(CC) $(CFLAGS_ALL) -x assembler -c -o $@ -
else
- AS_CMD = $(CC_CMD)
+ AS_CMD = cat $< $(ASM_NOTE) | $(CC) $(CFLAGS_ALL) -x assembler -c -o $@ -
endif
+ASCPP_CMD = cat $< $(ASM_NOTE) | $(CC) $(CFLAGS_ALL) -x assembler-with-cpp -c -o $@ -
+
obj/%.o: $(srcdir)/%.s
$(AS_CMD)
obj/%.o: $(srcdir)/%.S
- $(CC_CMD)
+ $(ASCPP_CMD)
obj/%.o: $(srcdir)/%.c $(GENH) $(IMPH)
$(CC_CMD)
@@ -153,7 +156,7 @@ obj/%.lo: $(srcdir)/%.s
$(AS_CMD)
obj/%.lo: $(srcdir)/%.S
- $(CC_CMD)
+ $(ASCPP_CMD)
obj/%.lo: $(srcdir)/%.c $(GENH) $(IMPH)
$(CC_CMD)
diff --git a/arch/x86_64/note.s b/arch/x86_64/note.s
new file mode 100644
index 0000000..cb03e5a
--- /dev/null
+++ b/arch/x86_64/note.s
@@ -0,0 +1,12 @@
+/* .note.gnu.property is 8-byte aligned in 64-bit objects */
+.section .note.gnu.property,"a"
+.balign 8
+.long 4 /* n_namesz: sizeof "GNU" */
+.long 16 /* n_descsz: one 8-byte-padded property */
+.long 5 /* n_type: NT_GNU_PROPERTY_TYPE_0 */
+.asciz "GNU"
+.long 0xc0000002 /* pr_type: GNU_PROPERTY_X86_FEATURE_1_AND */
+.long 4 /* pr_datasz */
+.long 2 /* GNU_PROPERTY_X86_FEATURE_1_SHSTK */
+.long 0 /* padding to 8-byte alignment */
+.previous
diff --git a/configure b/configure
index bc9fbe4..a278c81 100755
--- a/configure
+++ b/configure
@@ -34,6 +34,7 @@ Optional features:
--enable-wrapper=... build given musl toolchain wrapper [auto]
--disable-shared inhibit building shared library [enabled]
--disable-static inhibit building static library [enabled]
+ --enable-cet build with CET shadow stack support [disabled]
Optional packages:
--with-malloc=... choose malloc implementation [mallocng]
@@ -142,6 +143,7 @@ static=yes
wrapper=auto
gcc_wrapper=no
clang_wrapper=no
+cet=no
malloc_dir=mallocng
for arg ; do
@@ -172,6 +174,8 @@ case "$arg" in
--disable-wrapper|--enable-wrapper=no) wrapper=no ;;
--enable-gcc-wrapper|--enable-gcc-wrapper=yes) wrapper=yes ; gcc_wrapper=yes ;;
--disable-gcc-wrapper|--enable-gcc-wrapper=no) wrapper=no ;;
+--enable-cet|--enable-cet=yes) cet=yes ;;
+--disable-cet|--enable-cet=no) cet=no ;;
--with-malloc=*) malloc_dir=${arg#*=} ;;
--enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;;
--host=*|--target=*) target=${arg#*=} ;;
@@ -759,6 +763,30 @@ fi
test "$SUBARCH" \
&& printf "configured for %s variant: %s\n" "$ARCH" "$ARCH$SUBARCH"
+ASM_NOTE=
+if test "$cet" = yes ; then
+
+if test "$ARCH" != "x86_64" ; then
+fail "$0: error: --enable-cet is only supported on x86_64"
+fi
+
+tryflag CFLAGS_AUTO -fcf-protection=return \
+|| fail "$0: error: --enable-cet requires compiler support for -fcf-protection"
+
+printf "checking whether assembler supports rdssp/incssp instructions... "
+echo "__asm__(\"xor %eax,%eax ; rdsspq %rax ; incsspq %rax\");" > "$tmpc"
+if $CC -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+printf "yes\n"
+else
+printf "no\n"
+fail "$0: error: --enable-cet requires assembler support for rdssp/incssp"
+fi
+
+CFLAGS_AUTO="$CFLAGS_AUTO -DSHSTK_ENABLED=1"
+
+ASM_NOTE="arch/$ARCH/note.s"
+fi
+
#
# Some archs (powerpc) have different possible long double formats
# that the compiler can be configured for. The logic for whether this
@@ -820,6 +848,7 @@ CFLAGS_AUTO = $CFLAGS_AUTO
CFLAGS_C99FSE = $CFLAGS_C99FSE
CFLAGS_MEMOPS = $CFLAGS_MEMOPS
CFLAGS_NOSSP = $CFLAGS_NOSSP
+ASM_NOTE = $ASM_NOTE
CPPFLAGS = $CPPFLAGS
LDFLAGS = $LDFLAGS
LDFLAGS_AUTO = $LDFLAGS_AUTO
diff --git a/include/elf.h b/include/elf.h
index 2555b90..622d1e0 100644
--- a/include/elf.h
+++ b/include/elf.h
@@ -1112,6 +1112,11 @@ typedef struct {
#define NT_GNU_GOLD_VERSION 4
#define NT_GNU_PROPERTY_TYPE_0 5
+#define GNU_PROPERTY_X86_FEATURE_1_AND 0xc0000002
+
+#define GNU_PROPERTY_X86_FEATURE_1_IBT (1U << 0)
+#define GNU_PROPERTY_X86_FEATURE_1_SHSTK (1U << 1)
+
typedef struct {
diff --git a/ldso/dynlink.c b/ldso/dynlink.c
index 10471b2..292af2a 100644
--- a/ldso/dynlink.c
+++ b/ldso/dynlink.c
@@ -23,6 +23,7 @@
#include "fork_impl.h"
#include "libc.h"
#include "dynlink.h"
+#include "shstk.h"
static size_t ldso_page_size;
/* libc.h may have defined a macro for dynamic PAGE_SIZE already, but
@@ -702,6 +703,9 @@ static void *map_library(int fd, struct dso *dso)
size_t dyn=0;
size_t tls_image=0;
size_t i;
+#if SHSTK_ENABLED
+ Phdr *gnu_prop_ph=0;
+#endif
ssize_t l = read(fd, buf, sizeof buf);
eh = buf;
@@ -741,6 +745,10 @@ static void *map_library(int fd, struct dso *dso)
ph->p_memsz < DEFAULT_STACK_MAX ?
ph->p_memsz : DEFAULT_STACK_MAX;
}
+#if SHSTK_ENABLED
+ } else if (ph->p_type == PT_GNU_PROPERTY) {
+ gnu_prop_ph = ph;
+#endif
}
if (ph->p_type != PT_LOAD) continue;
nsegs++;
@@ -862,6 +870,13 @@ done_mapping:
dso->base = base;
dso->dynv = laddr(dso, dyn);
if (dso->tls.size) dso->tls.image = laddr(dso, tls_image);
+#if SHSTK_ENABLED
+ int ret = __try_update_x86_feature_1_and((size_t)base, gnu_prop_ph);
+ if (ret) {
+ errno = ret;
+ goto error;
+ }
+#endif
free(allocated_buf);
return map;
noexec:
@@ -1455,6 +1470,10 @@ static void kernel_mapped_dso(struct dso *p)
ph->p_memsz < DEFAULT_STACK_MAX ?
ph->p_memsz : DEFAULT_STACK_MAX;
}
+#if SHSTK_ENABLED
+ } else if (ph->p_type == PT_GNU_PROPERTY) {
+ __update_x86_feature_1_and((size_t)p->base, ph);
+#endif
}
if (ph->p_type != PT_LOAD) continue;
if (ph->p_vaddr < min_addr)
@@ -1637,6 +1656,12 @@ void __init_tls(size_t *auxv)
{
}
+#if SHSTK_ENABLED
+void __init_x86_feature_1_and(void)
+{
+}
+#endif
+
static void update_tls_size()
{
libc.tls_cnt = tls_cnt;
diff --git a/src/env/__libc_start_main.c b/src/env/__libc_start_main.c
index c5b277b..968f128 100644
--- a/src/env/__libc_start_main.c
+++ b/src/env/__libc_start_main.c
@@ -6,6 +6,7 @@
#include "syscall.h"
#include "atomic.h"
#include "libc.h"
+#include "shstk.h"
static void dummy(void) {}
weak_alias(dummy, _init);
@@ -89,6 +90,16 @@ int __libc_start_main(int (*main)(int,char **,char **), int argc, char **argv,
static int libc_start_main_stage2(int (*main)(int,char **,char **), int argc, char **argv)
{
char **envp = argv+argc+1;
+
+#if SHSTK_ENABLED
+ __init_x86_feature_1_and();
+ if (__x86_feature_1_and & GNU_PROPERTY_X86_FEATURE_1_SHSTK) {
+ SHSTK_ENABLE();
+ __lock_shstk();
+ }
+ __init_shstk_status();
+#endif
+
__libc_start_init();
/* Pass control to the application */
diff --git a/src/internal/shstk.h b/src/internal/shstk.h
new file mode 100644
index 0000000..8495a51
--- /dev/null
+++ b/src/internal/shstk.h
@@ -0,0 +1,39 @@
+#ifndef SHSTK_H
+#define SHSTK_H
+
+#if SHSTK_ENABLED
+
+#include <elf.h>
+#include <stddef.h>
+#include <features.h>
+
+#define ARCH_SHSTK_ENABLE 0x5001
+#define ARCH_SHSTK_LOCK 0x5003
+#define ARCH_SHSTK_STATUS 0x5005
+
+#define ARCH_SHSTK_SHSTK 1
+
+extern hidden unsigned __x86_feature_1_and;
+extern hidden unsigned long __shstk_status;
+
+hidden void __init_x86_feature_1_and(void);
+
+hidden void __update_x86_feature_1_and(size_t base, Elf64_Phdr *ph);
+
+hidden int __try_update_x86_feature_1_and(size_t base, Elf64_Phdr *ph);
+
+hidden int __init_shstk_status(void);
+
+hidden int __lock_shstk(void);
+
+#define SHSTK_ENABLE() do { \
+ unsigned long ret; \
+ __asm__ __volatile__ ( "syscall" \
+ : "=a"(ret) \
+ : "a"(SYS_arch_prctl), "D"(ARCH_SHSTK_ENABLE), "S"(ARCH_SHSTK_SHSTK) \
+ : "rcx", "r11", "memory" ); \
+ } while (0)
+
+#endif
+
+#endif
diff --git a/src/ldso/x86_64/shstk.c b/src/ldso/x86_64/shstk.c
new file mode 100644
index 0000000..91d0044
--- /dev/null
+++ b/src/ldso/x86_64/shstk.c
@@ -0,0 +1,143 @@
+#if SHSTK_ENABLED
+
+#include <elf.h>
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include "libc.h"
+#include "shstk.h"
+#include "syscall.h"
+
+hidden unsigned __x86_feature_1_and = -1;
+hidden unsigned long __shstk_status = 0;
+
+#define ALIGNMENT sizeof(Elf64_Addr)
+#define ALIGN_UP(x,y) ((x)+(y)-1 & -(y))
+
+/* gnu properties are a sequence of type/size/value */
+static unsigned feature_from_desc(const unsigned char *p, size_t len)
+{
+ while (len >= 8) {
+ uint32_t type, datasz;
+ type = *(uint32_t *)p;
+ p += sizeof(uint32_t);
+ datasz = *(uint32_t *)p;
+ p += sizeof(uint32_t);
+ len -= 2*sizeof(uint32_t);
+ if (datasz > len) return 0;
+ if (type == GNU_PROPERTY_X86_FEATURE_1_AND) {
+ uint32_t feature;
+ if (datasz != 4) return 0;
+ feature = *(uint32_t *)p;
+ return feature;
+ }
+ if (type > GNU_PROPERTY_X86_FEATURE_1_AND) return 0;
+ size_t adv = ALIGN_UP(datasz, ALIGNMENT);
+ if (adv > len) return 0;
+ p += adv;
+ len -= adv;
+ }
+ return 0;
+}
+
+static unsigned feature_from_segment(const unsigned char *p, size_t len)
+{
+ while (len >= sizeof(Elf64_Nhdr)) {
+ Elf64_Nhdr *nh = (Elf64_Nhdr *)p;
+ size_t skip;
+
+ size_t desc_offset = ALIGN_UP(sizeof(Elf64_Nhdr) + nh->n_namesz, ALIGNMENT);
+ if (nh->n_namesz == 4
+ && nh->n_type == NT_GNU_PROPERTY_TYPE_0
+ && len >= desc_offset
+ && !memcmp(p + sizeof(Elf64_Nhdr), "GNU", 4)) {
+ if (nh->n_descsz < ALIGNMENT || nh->n_descsz % ALIGNMENT
+ || nh->n_descsz > len - desc_offset)
+ return 0;
+ return feature_from_desc(p + desc_offset, nh->n_descsz);
+ }
+
+ skip = ALIGN_UP(sizeof(Elf64_Nhdr) + (size_t)nh->n_namesz, ALIGNMENT);
+ if (skip > len) return 0;
+ len -= skip;
+ p += skip;
+ skip = ALIGN_UP((size_t)nh->n_descsz, ALIGNMENT);
+ if (skip > len) return 0;
+ len -= skip;
+ p += skip;
+ }
+ return 0;
+}
+
+static unsigned get_feature_from_header(size_t base, void *ph0)
+{
+ Elf64_Phdr *ph = ph0;
+ if (!ph || ph->p_align != ALIGNMENT) return 0;
+ return feature_from_segment((void *)(base + ph->p_vaddr), ph->p_memsz);
+}
+
+/* four cases here:
+ * (1) feature set, shstk set -> dlopen, should fail if no shstk note
+ * (2) feature set, shstk not set -> initial loading, should update feature
+ * (3) feature not set, shstk not set -> no update needed
+ * (4) feature not set, shstk set -> impossible */
+hidden int __try_update_x86_feature_1_and(size_t base, Elf64_Phdr *ph)
+{
+ if (!(__x86_feature_1_and & GNU_PROPERTY_X86_FEATURE_1_SHSTK))
+ return 0;
+ /* feature is set */
+ unsigned long dso_features = get_feature_from_header(base, ph);
+ if (!(__shstk_status & ARCH_SHSTK_SHSTK)) {
+ __x86_feature_1_and &= dso_features;
+ return 0;
+ }
+ /* feature and shstk are set */
+ if (dso_features & GNU_PROPERTY_X86_FEATURE_1_SHSTK)
+ return 0;
+ return ENOTSUP;
+}
+
+hidden void __update_x86_feature_1_and(size_t base, Elf64_Phdr *ph)
+{
+ __x86_feature_1_and &= get_feature_from_header(base, ph);
+}
+
+#define AUX_CNT 38
+extern weak hidden const size_t _DYNAMIC[];
+
+static void static_init_x86_feature_1_and(void)
+{
+ size_t i, aux[AUX_CNT] = { 0 };
+ unsigned char *p;
+ size_t n, base = 0;
+ Elf64_Phdr *gnu_prop_ph = 0;
+
+ for (i=0; libc.auxv[i]; i+=2)
+ if (libc.auxv[i]<AUX_CNT) aux[libc.auxv[i]] = libc.auxv[i+1];
+
+ for (p=(void *)aux[AT_PHDR], n=aux[AT_PHNUM]; n; n--, p+=aux[AT_PHENT]) {
+ Elf64_Phdr *ph = (void *)p;
+ if (ph->p_type == PT_PHDR)
+ base = aux[AT_PHDR] - ph->p_vaddr;
+ if (ph->p_type == PT_DYNAMIC && _DYNAMIC)
+ base = (size_t)_DYNAMIC - ph->p_vaddr;
+ if (ph->p_type == PT_GNU_PROPERTY)
+ gnu_prop_ph = ph;
+ }
+
+ __x86_feature_1_and = get_feature_from_header(base, gnu_prop_ph);
+}
+
+weak_alias(static_init_x86_feature_1_and, __init_x86_feature_1_and);
+
+hidden int __init_shstk_status(void)
+{
+ return syscall(SYS_arch_prctl, ARCH_SHSTK_STATUS, &__shstk_status);
+}
+
+hidden int __lock_shstk(void)
+{
+ return syscall(SYS_arch_prctl, ARCH_SHSTK_LOCK, -1UL);
+}
+
+#endif
diff --git a/src/process/x86_64/vfork.s b/src/process/x86_64/vfork.S
similarity index 35%
rename from src/process/x86_64/vfork.s
rename to src/process/x86_64/vfork.S
index 9114439..da4aea5 100644
--- a/src/process/x86_64/vfork.s
+++ b/src/process/x86_64/vfork.S
@@ -4,6 +4,19 @@ vfork:
pop %rdx
mov $58,%eax
syscall
+#if SHSTK_ENABLED
+ /* If parent normal return */
+ test %eax,%eax
+ jnz 1f
+ /* If child and no shstk normal return */
+ xor %ecx,%ecx
+ rdsspq %rcx
+ test %rcx,%rcx
+ jz 1f
+ /* If child and shstk jmp instead, no need to call __syscall_ret */
+ jmp *%rdx
+1:
+#endif
push %rdx
mov %rax,%rdi
.hidden __syscall_ret
diff --git a/src/setjmp/x86_64/longjmp.s b/src/setjmp/x86_64/longjmp.S
similarity index 52%
rename from src/setjmp/x86_64/longjmp.s
rename to src/setjmp/x86_64/longjmp.S
index 1b2661c..9169996 100644
--- a/src/setjmp/x86_64/longjmp.s
+++ b/src/setjmp/x86_64/longjmp.S
@@ -5,6 +5,30 @@
.type longjmp,@function
_longjmp:
longjmp:
+#if SHSTK_ENABLED
+ xor %ecx,%ecx
+ rdsspq %rcx
+ test %rcx,%rcx
+ jz 2f
+ /* if shstk is active */
+ mov 88(%rdi),%rdx /* shadow stack pointer saved by setjmp */
+ sub %rcx,%rdx
+ /* target below current */
+ jb 2f
+ /* slots between current and target */
+ /* 3^2 = 8 = q in incsspq */
+ shr $3,%rdx
+ /* plus the slot setjmp itself returned to */
+ add $1,%rdx
+ /* incssp pops at most 255 slots at a time */
+1: mov $255,%rcx
+ cmp %rcx,%rdx
+ cmovb %rdx,%rcx
+ incsspq %rcx
+ sub %rcx,%rdx
+ ja 1b
+2:
+#endif
xor %eax,%eax
cmp $1,%esi /* CF = val ? 0 : 1 */
adc %esi,%eax /* eax = val + !val */
diff --git a/src/setjmp/x86_64/setjmp.s b/src/setjmp/x86_64/setjmp.S
similarity index 82%
rename from src/setjmp/x86_64/setjmp.s
rename to src/setjmp/x86_64/setjmp.S
index d95e485..47ce57a 100644
--- a/src/setjmp/x86_64/setjmp.s
+++ b/src/setjmp/x86_64/setjmp.S
@@ -18,5 +18,11 @@ setjmp:
mov %rdx,48(%rdi)
mov (%rsp),%rdx /* save return addr ptr for new rip */
mov %rdx,56(%rdi)
+#if SHSTK_ENABLED
+ /* store shstk pointer in __ss[2] = (72 + 2*8) = 88 */
+ xor %eax,%eax
+ rdsspq %rax
+ mov %rax,88(%rdi)
+#endif
xor %eax,%eax /* always return 0 */
ret
diff --git a/src/signal/x86_64/sigsetjmp.s b/src/signal/x86_64/sigsetjmp.S
similarity index 56%
rename from src/signal/x86_64/sigsetjmp.s
rename to src/signal/x86_64/sigsetjmp.S
index 9a7695f..e88d277 100644
--- a/src/signal/x86_64/sigsetjmp.s
+++ b/src/signal/x86_64/sigsetjmp.S
@@ -18,7 +18,24 @@ __sigsetjmp:
mov %eax,%esi
mov 72+8(%rbx),%rbx
+#if SHSTK_ENABLED
+ test %eax,%eax
+ jnz 2f
+
+ /* original call */
+ xor %edx,%edx
+ rdsspq %rdx
+ /* store shstk pointer in __ss[2] */
+ /* rdi holds the jmp_buf now */
+ mov %rdx,88(%rdi)
+#endif
.hidden __sigsetjmp_tail
jmp __sigsetjmp_tail
+#if SHSTK_ENABLED
+2: call __sigsetjmp_tail
+ pop %rdx
+ jmp *%rdx
+#endif
+
1: jmp setjmp@PLT
--
2.49.1
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.