Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions compel/arch/aarch64/src/lib/include/uapi/asm/gcs-types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef __UAPI_ASM_GCS_TYPES_H__
#define __UAPI_ASM_GCS_TYPES_H__

#ifndef NT_ARM_GCS
#define NT_ARM_GCS 0x410 /* ARM GCS state */
#endif

/* Shadow Stack/Guarded Control Stack interface */
#define PR_GET_SHADOW_STACK_STATUS 74
#define PR_SET_SHADOW_STACK_STATUS 75
#define PR_LOCK_SHADOW_STACK_STATUS 76

/* When set PR_SHADOW_STACK_ENABLE flag allocates a Guarded Control Stack */
#ifndef PR_SHADOW_STACK_ENABLE
#define PR_SHADOW_STACK_ENABLE (1UL << 0)
#endif

/* Allows explicit GCS stores (eg. using GCSSTR) */
#ifndef PR_SHADOW_STACK_WRITE
#define PR_SHADOW_STACK_WRITE (1UL << 1)
#endif

/* Allows explicit GCS pushes (eg. using GCSPUSHM) */
#ifndef PR_SHADOW_STACK_PUSH
#define PR_SHADOW_STACK_PUSH (1UL << 2)
#endif

#ifndef SHADOW_STACK_SET_TOKEN
#define SHADOW_STACK_SET_TOKEN 0x1 /* Set up a restore token in the shadow stack */
#endif

#define PR_SHADOW_STACK_ALL_MODES \
PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH

/* copied from: arch/arm64/include/asm/sysreg.h */
#define GCS_CAP_VALID_TOKEN 0x1
#define GCS_CAP_ADDR_MASK 0xFFFFFFFFFFFFF000ULL
#define GCS_CAP(x) ((((unsigned long)x) & GCS_CAP_ADDR_MASK) | GCS_CAP_VALID_TOKEN)
#define GCS_SIGNAL_CAP(addr) (((unsigned long)addr) & GCS_CAP_ADDR_MASK)

#include <asm/hwcap.h>

#ifndef HWCAP_GCS
#else
#define HWCAP_GCS (1UL << 32)
#endif

#endif /* __UAPI_ASM_GCS_TYPES_H__ */
23 changes: 22 additions & 1 deletion compel/arch/aarch64/src/lib/include/uapi/asm/infect-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
*/

typedef struct user_pt_regs user_regs_struct_t;
typedef struct user_fpsimd_state user_fpregs_struct_t;

struct user_fpregs_struct {
struct user_fpsimd_state fpstate;
struct user_gcs gcs;
};
typedef struct user_fpregs_struct user_fpregs_struct_t;

#define __compel_arch_fetch_thread_area(tid, th) 0
#define compel_arch_fetch_thread_area(tctl) 0
Expand All @@ -39,4 +44,20 @@ typedef struct user_fpsimd_state user_fpregs_struct_t;
__NR_##syscall; \
})

/*
* GCS (Guarded Control Stack)
*/
struct parasite_ctl;
struct user_gcs {
__u64 features_enabled;
__u64 features_locked;
__u64 gcspr_el0;
};
extern bool __compel_gcs_enabled(struct user_gcs *gcs);
#define compel_gcs_enabled __compel_gcs_enabled

extern int __parasite_setup_shstk(struct parasite_ctl *ctl,
user_fpregs_struct_t *ext_regs);
#define parasite_setup_shstk __parasite_setup_shstk

#endif /* UAPI_COMPEL_ASM_TYPES_H__ */
10 changes: 10 additions & 0 deletions compel/arch/aarch64/src/lib/include/uapi/asm/sigframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@
/* Copied from the kernel header arch/arm64/include/uapi/asm/sigcontext.h */

#define FPSIMD_MAGIC 0x46508001
#define GCS_MAGIC 0x47435300

typedef struct fpsimd_context fpu_state_t;

struct gcs_context {
struct _aarch64_ctx head;
__u64 gcspr;
__u64 features_enabled;
__u64 reserved;
};

struct aux_context {
struct fpsimd_context fpsimd;
struct gcs_context gcs;
/* additional context to be added before "end" */
struct _aarch64_ctx end;
};
Expand Down Expand Up @@ -63,6 +72,7 @@ struct cr_sigcontext {
#define RT_SIGFRAME_AUX_CONTEXT(rt_sigframe) ((struct aux_context *)&(RT_SIGFRAME_SIGCONTEXT(rt_sigframe)->__reserved))
#define RT_SIGFRAME_FPU(rt_sigframe) (&RT_SIGFRAME_AUX_CONTEXT(rt_sigframe)->fpsimd)
#define RT_SIGFRAME_OFFSET(rt_sigframe) 0
#define RT_SIGFRAME_GCS(rt_sigframe) (&RT_SIGFRAME_AUX_CONTEXT(rt_sigframe)->gcs)

#define rt_sigframe_erase_sigset(sigframe) memset(&sigframe->uc.uc_sigmask, 0, sizeof(k_rtsigset_t))
#define rt_sigframe_copy_sigset(sigframe, from) memcpy(&sigframe->uc.uc_sigmask, from, sizeof(k_rtsigset_t))
Expand Down
129 changes: 124 additions & 5 deletions compel/arch/aarch64/src/lib/infect.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "infect.h"
#include "infect-priv.h"
#include "asm/breakpoints.h"
#include "asm/gcs-types.h"
#include <linux/prctl.h>

unsigned __page_size = 0;
unsigned __page_shift = 0;
Expand All @@ -36,21 +38,37 @@ static inline void __always_unused __check_code_syscall(void)
int sigreturn_prep_regs_plain(struct rt_sigframe *sigframe, user_regs_struct_t *regs, user_fpregs_struct_t *fpregs)
{
struct fpsimd_context *fpsimd = RT_SIGFRAME_FPU(sigframe);
struct gcs_context *gcs = RT_SIGFRAME_GCS(sigframe);

memcpy(sigframe->uc.uc_mcontext.regs, regs->regs, sizeof(regs->regs));

pr_debug("sigreturn_prep_regs_plain: sp %lx pc %lx\n", (long)regs->sp, (long)regs->pc);

sigframe->uc.uc_mcontext.sp = regs->sp;
sigframe->uc.uc_mcontext.pc = regs->pc;
sigframe->uc.uc_mcontext.pstate = regs->pstate;

memcpy(fpsimd->vregs, fpregs->vregs, 32 * sizeof(__uint128_t));
memcpy(fpsimd->vregs, fpregs->fpstate.vregs, 32 * sizeof(__uint128_t));

fpsimd->fpsr = fpregs->fpsr;
fpsimd->fpcr = fpregs->fpcr;
fpsimd->fpsr = fpregs->fpstate.fpsr;
fpsimd->fpcr = fpregs->fpstate.fpcr;

fpsimd->head.magic = FPSIMD_MAGIC;
fpsimd->head.size = sizeof(*fpsimd);

if (__compel_gcs_enabled(&fpregs->gcs)) {
gcs->head.magic = GCS_MAGIC;
gcs->head.size = sizeof(*gcs);
gcs->reserved = 0;
gcs->gcspr = fpregs->gcs.gcspr_el0 - 8;
gcs->features_enabled = fpregs->gcs.features_enabled;

pr_debug("sigframe gcspr=%llx features_enabled=%llx\n", fpregs->gcs.gcspr_el0 - 8, fpregs->gcs.features_enabled);
} else {
pr_debug("sigframe gcspr=[disabled]\n");
memset(gcs, 0, sizeof(*gcs));
}

return 0;
}

Expand All @@ -74,13 +92,27 @@ int compel_get_task_regs(pid_t pid, user_regs_struct_t *regs, user_fpregs_struct
goto err;
}

iov.iov_base = fpsimd;
iov.iov_len = sizeof(*fpsimd);
iov.iov_base = &fpsimd->fpstate;
iov.iov_len = sizeof(fpsimd->fpstate);
if ((ret = ptrace(PTRACE_GETREGSET, pid, NT_PRFPREG, &iov))) {
pr_perror("Failed to obtain FPU registers for %d", pid);
goto err;
}

memset(&fpsimd->gcs, 0, sizeof(fpsimd->gcs));
Copy link
Member

Choose a reason for hiding this comment

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

we don't memset(0) other register structs, why there is a need to clear fpsimd->gcs?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've explicitly zeroed out to be sure GCS fields won't contain garbage values that would falsely indicate it's enabled


iov.iov_base = &fpsimd->gcs;
iov.iov_len = sizeof(fpsimd->gcs);
if (ptrace(PTRACE_GETREGSET, pid, NT_ARM_GCS, &iov) == 0) {
pr_info("gcs: GCSPR_EL0 for %d: 0x%llx, features: 0x%llx\n",
pid, fpsimd->gcs.gcspr_el0, fpsimd->gcs.features_enabled);

if (!__compel_gcs_enabled(&fpsimd->gcs))
pr_info("gcs: GCS is NOT enabled\n");
} else {
pr_info("gcs: GCS state not available for %d\n", pid);
}

ret = save(pid, arg, regs, fpsimd);
err:
return ret;
Expand All @@ -101,6 +133,26 @@ int compel_set_task_ext_regs(pid_t pid, user_fpregs_struct_t *ext_regs)
return 0;
}

int compel_set_task_gcs_regs(pid_t pid, user_fpregs_struct_t *ext_regs)
{
struct iovec iov;

pr_info("gcs: restoring GCS registers for %d\n", pid);
pr_info("gcs: restoring GCS: gcspr=%llx features=%llx\n",
ext_regs->gcs.gcspr_el0, ext_regs->gcs.features_enabled);

iov.iov_base = &ext_regs->gcs;
iov.iov_len = sizeof(ext_regs->gcs);

if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_GCS, &iov)) {
pr_perror("gcs: Failed to set GCS registers for %d", pid);
return -1;
}

return 0;
}


int compel_syscall(struct parasite_ctl *ctl, int nr, long *ret, unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4, unsigned long arg5, unsigned long arg6)
{
Expand Down Expand Up @@ -286,3 +338,70 @@ int ptrace_flush_breakpoints(pid_t pid)

return 0;
}

bool __compel_gcs_enabled(struct user_gcs *gcs)
{
return gcs && (gcs->features_enabled & PR_SHADOW_STACK_ENABLE) != 0;
}

int inject_gcs_cap_token(struct parasite_ctl *ctl, pid_t pid, struct user_gcs *gcs)
{
struct iovec gcs_iov = { .iov_base = gcs, .iov_len = sizeof(*gcs) };

uint64_t token_addr = gcs->gcspr_el0 - 8;
uint64_t sigtramp_addr = gcs->gcspr_el0 - 16;

uint64_t cap_token = ALIGN_DOWN(GCS_SIGNAL_CAP(token_addr), 8);
unsigned long restorer_addr;

pr_info("gcs: (setup) CAP token: 0x%lx at addr: 0x%lx\n", cap_token, token_addr);

/* Inject capability token at gcspr_el0 - 8 */
if (ptrace(PTRACE_POKEDATA, pid, (void*)token_addr, cap_token)) {
pr_perror("gcs: (setup) Inject GCS cap token failed");
return -1;
}

/* Inject restorer trampoline address (gcspr_el0 - 16) */
restorer_addr = ctl->parasite_ip;
if (ptrace(PTRACE_POKEDATA, pid, (void*)sigtramp_addr, restorer_addr)) {
pr_perror("gcs: (setup) Inject GCS restorer failed");
return -1;
}

/* Update GCSPR_EL0 */
gcs->gcspr_el0 = token_addr;
if(ptrace(PTRACE_SETREGSET, pid, NT_ARM_GCS, &gcs_iov)) {
pr_perror("gcs: PTRACE_SETREGS FAILED");
return -1;
}

pr_debug("gcs: parasite_ip=%#lx sp=%#llx gcspr_el0=%#llx\n",
ctl->parasite_ip, ctl->orig.regs.sp, gcs->gcspr_el0);

return 0;
}

int parasite_setup_shstk(struct parasite_ctl *ctl, user_fpregs_struct_t *ext_regs)
{
struct user_gcs gcs;
struct iovec gcs_iov = { .iov_base = &gcs, .iov_len = sizeof(gcs) };
pid_t pid = ctl->rpid;

if (ptrace(PTRACE_GETREGSET, pid, NT_ARM_GCS, &gcs_iov) != 0) {
pr_perror("GCS state not available for %d\n", pid);
return -1;
}

if (!__compel_gcs_enabled(&gcs))
return 0;

if (inject_gcs_cap_token(ctl, pid, &gcs)) {
pr_perror("Failed to inject GCS cap token for %d\n", pid);
return -1;
}

pr_info("gcs: GCS enabled for %d\n", pid);

return 0;
}
1 change: 1 addition & 0 deletions compel/arch/arm/plugins/std/syscalls/syscall.def
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,4 @@ openat2 437 437 (int dirfd, char *pathname, struct open_how *how, size_t size
pidfd_getfd 438 438 (int pidfd, int targetfd, unsigned int flags)
rseq 293 398 (void *rseq, uint32_t rseq_len, int flags, uint32_t sig)
membarrier 283 389 (int cmd, unsigned int flags, int cpu_id)
map_shadow_stack 453 ! (unsigned long addr, unsigned long size, unsigned int flags)
2 changes: 1 addition & 1 deletion compel/arch/x86/src/lib/infect.c
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ bool __compel_shstk_enabled(user_fpregs_struct_t *ext_regs)
return false;
}

int parasite_setup_shstk(struct parasite_ctl *ctl, user_fpregs_struct_t *ext_regs)
int parasite_setup_shstk(struct parasite_ctl *ctl, __maybe_unused user_fpregs_struct_t *ext_regs)
Copy link
Member

Choose a reason for hiding this comment

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

looks like ext_regs are really unused :)

{
pid_t pid = ctl->rpid;
unsigned long sa_restorer = ctl->parasite_ip;
Expand Down
1 change: 1 addition & 0 deletions compel/include/infect-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ extern bool arch_can_dump_task(struct parasite_ctl *ctl);
extern int compel_get_task_regs(pid_t pid, user_regs_struct_t *regs, user_fpregs_struct_t *ext_regs, save_regs_t save,
void *arg, unsigned long flags);
extern int compel_set_task_ext_regs(pid_t pid, user_fpregs_struct_t *ext_regs);
extern int compel_set_task_gcs_regs(pid_t pid, user_fpregs_struct_t *ext_regs);
extern int arch_fetch_sas(struct parasite_ctl *ctl, struct rt_sigframe *s);
extern int sigreturn_prep_regs_plain(struct rt_sigframe *sigframe, user_regs_struct_t *regs,
user_fpregs_struct_t *fpregs);
Expand Down
8 changes: 8 additions & 0 deletions compel/include/uapi/infect.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,12 @@ static inline int parasite_setup_shstk(struct parasite_ctl *ctl,
#define parasite_setup_shstk parasite_setup_shstk
#endif

#ifndef compel_gcs_enabled
static inline bool compel_gcs_enabled(struct user_gcs *gcs)
Copy link
Member

Choose a reason for hiding this comment

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

It looks like we don't need neither compel_gcs_enabled() nor compel_shstk_enabled().
The both only appear in arch/ code and it seems better to just define no underscore version in compel/arch//src/lib/include/uapi/asm/infect-types.h and drop the global stubs.

{
return false;
}
#define compel_gcs_enabled compel_gcs_enabled
#endif

#endif
11 changes: 11 additions & 0 deletions compel/src/lib/infect.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <compel/plugins/std/asm/syscall-types.h>
#include "uapi/compel/plugins/std/syscall.h"
#include "asm/infect-types.h"
#include "asm/gcs-types.h"
Copy link
Member

Choose a reason for hiding this comment

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

won't compile on !aarch64

#include "asm/sigframe.h"
#include "infect.h"
#include "ptrace.h"
Expand Down Expand Up @@ -552,6 +553,9 @@ static int restore_thread_ctx(int pid, struct thread_ctx *ctx, bool restore_ext_
{
int ret = 0;

struct user_gcs gcs;
struct iovec gcs_iov = { .iov_base = &gcs, .iov_len = sizeof(gcs) };

if (ptrace_set_regs(pid, &ctx->regs)) {
pr_perror("Can't restore registers (pid: %d)", pid);
ret = -1;
Expand All @@ -560,6 +564,13 @@ static int restore_thread_ctx(int pid, struct thread_ctx *ctx, bool restore_ext_
if (restore_ext_regs && compel_set_task_ext_regs(pid, &ctx->ext_regs))
ret = -1;

if (ptrace(PTRACE_GETREGSET, pid, NT_ARM_GCS, &gcs_iov) < 0) {
pr_warn("gcs: Failed to get GCS for %d", pid);
} else {
ctx->ext_regs.gcs = gcs;
compel_set_task_gcs_regs(pid, &ctx->ext_regs);
}

if (ptrace(PTRACE_SETSIGMASK, pid, sizeof(k_rtsigset_t), &ctx->sigmask)) {
pr_perror("Can't block signals");
ret = -1;
Expand Down
7 changes: 6 additions & 1 deletion compel/test/infect/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ CFLAGS ?= -O2 -g -Wall -Werror

COMPEL := ../../../compel/compel-host

ifeq ($(GCS_ENABLE),1)
CFLAGS += -mbranch-protection=standard -DGCS_TEST_ENABLE=1
LDFLAGS += -z experimental-gcs=check
endif

all: victim spy

run:
Expand All @@ -17,7 +22,7 @@ clean:
rm -f parasite.o

victim: victim.c
$(CC) $(CFLAGS) -o $@ $^
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

spy: spy.c parasite.h
$(CC) $(CFLAGS) $(shell $(COMPEL) includes) -o $@ $< $(shell $(COMPEL) --static libs)
Expand Down
3 changes: 3 additions & 0 deletions compel/test/infect/spy.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ int main(int argc, char **argv)
return -1;
}

#ifdef GCS_TEST_ENABLE
setenv("GLIBC_TUNABLES", "glibc.cpu.aarch64_gcs=1:glibc.cpu.aarch64_gcs_policy=2", 1);
#endif
pid = vfork();
if (pid == 0) {
close(p_in[1]);
Expand Down
1 change: 1 addition & 0 deletions criu/arch/aarch64/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ obj-y += cpu.o
obj-y += crtools.o
obj-y += sigframe.o
obj-y += bitops.o
obj-y += gcs.o
Loading
Loading