linux-user/aarch64: Generate GCS signal records

Here we must push and pop a cap on the GCS stack as
well as the gcs record on the normal stack.

Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20251008215613.300150-70-richard.henderson@linaro.org
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Richard Henderson 2025-10-08 14:56:09 -07:00 committed by Peter Maydell
parent 37897b29b3
commit ef110c3070

View file

@ -22,6 +22,7 @@
#include "signal-common.h"
#include "linux-user/trace.h"
#include "target/arm/cpu-features.h"
#include "gcs-internal.h"
struct target_sigcontext {
uint64_t fault_address;
@ -152,6 +153,16 @@ struct target_zt_context {
QEMU_BUILD_BUG_ON(TARGET_ZT_SIG_REG_BYTES != \
sizeof_field(CPUARMState, za_state.zt0));
#define TARGET_GCS_MAGIC 0x47435300
#define GCS_SIGNAL_CAP(X) ((X) & TARGET_PAGE_MASK)
struct target_gcs_context {
struct target_aarch64_ctx head;
uint64_t gcspr;
uint64_t features_enabled;
uint64_t reserved;
};
struct target_rt_sigframe {
struct target_siginfo info;
struct target_ucontext uc;
@ -322,6 +333,35 @@ static void target_setup_zt_record(struct target_zt_context *zt,
}
}
static bool target_setup_gcs_record(struct target_gcs_context *ctx,
CPUARMState *env, uint64_t return_addr)
{
uint64_t mode = gcs_get_el0_mode(env);
uint64_t gcspr = env->cp15.gcspr_el[0];
if (mode & PR_SHADOW_STACK_ENABLE) {
/* Push a cap for the signal frame. */
gcspr -= 8;
if (put_user_u64(GCS_SIGNAL_CAP(gcspr), gcspr)) {
return false;
}
/* Push a gcs entry for the trampoline. */
if (put_user_u64(return_addr, gcspr - 8)) {
return false;
}
env->cp15.gcspr_el[0] = gcspr - 8;
}
__put_user(TARGET_GCS_MAGIC, &ctx->head.magic);
__put_user(sizeof(*ctx), &ctx->head.size);
__put_user(gcspr, &ctx->gcspr);
__put_user(mode, &ctx->features_enabled);
__put_user(0, &ctx->reserved);
return true;
}
static void target_restore_general_frame(CPUARMState *env,
struct target_rt_sigframe *sf)
{
@ -502,6 +542,64 @@ static bool target_restore_zt_record(CPUARMState *env,
return true;
}
static bool target_restore_gcs_record(CPUARMState *env,
struct target_gcs_context *ctx,
bool *rebuild_hflags)
{
TaskState *ts = get_task_state(env_cpu(env));
uint64_t cur_mode = gcs_get_el0_mode(env);
uint64_t new_mode, gcspr;
__get_user(new_mode, &ctx->features_enabled);
__get_user(gcspr, &ctx->gcspr);
/*
* The kernel pushes the value through the hw register:
* write_sysreg_s(gcspr, SYS_GCSPR_EL0) in restore_gcs_context,
* then read_sysreg_s(SYS_GCSPR_EL0) in gcs_restore_signal.
* Since the bottom 3 bits are RES0, this can (CONSTRAINED UNPREDICTABLE)
* force align the value. Mirror the choice from gcspr_write().
*/
gcspr &= ~7;
if (new_mode & ~(PR_SHADOW_STACK_ENABLE |
PR_SHADOW_STACK_WRITE |
PR_SHADOW_STACK_PUSH)) {
return false;
}
if ((new_mode ^ cur_mode) & ts->gcs_el0_locked) {
return false;
}
if (new_mode & ~cur_mode & PR_SHADOW_STACK_ENABLE) {
return false;
}
if (new_mode & PR_SHADOW_STACK_ENABLE) {
uint64_t cap;
/* Pop and clear the signal cap. */
if (get_user_u64(cap, gcspr)) {
return false;
}
if (cap != GCS_SIGNAL_CAP(gcspr)) {
return false;
}
if (put_user_u64(0, gcspr)) {
return false;
}
gcspr += 8;
} else {
new_mode = 0;
}
env->cp15.gcspr_el[0] = gcspr;
if (new_mode != cur_mode) {
*rebuild_hflags = true;
gcs_set_el0_mode(env, new_mode);
}
return true;
}
static int target_restore_sigframe(CPUARMState *env,
struct target_rt_sigframe *sf)
{
@ -511,8 +609,10 @@ static int target_restore_sigframe(CPUARMState *env,
struct target_za_context *za = NULL;
struct target_tpidr2_context *tpidr2 = NULL;
struct target_zt_context *zt = NULL;
struct target_gcs_context *gcs = NULL;
uint64_t extra_datap = 0;
bool used_extra = false;
bool rebuild_hflags = false;
int sve_size = 0;
int za_size = 0;
int zt_size = 0;
@ -582,6 +682,15 @@ static int target_restore_sigframe(CPUARMState *env,
zt_size = size;
break;
case TARGET_GCS_MAGIC:
if (gcs
|| size != sizeof(struct target_gcs_context)
|| !cpu_isar_feature(aa64_gcs, env_archcpu(env))) {
goto err;
}
gcs = (struct target_gcs_context *)ctx;
break;
case TARGET_EXTRA_MAGIC:
if (extra || size != sizeof(struct target_extra_context)) {
goto err;
@ -612,6 +721,10 @@ static int target_restore_sigframe(CPUARMState *env,
goto err;
}
if (gcs && !target_restore_gcs_record(env, gcs, &rebuild_hflags)) {
goto err;
}
/* SVE data, if present, overwrites FPSIMD data. */
if (sve && !target_restore_sve_record(env, sve, sve_size, &svcr)) {
goto err;
@ -631,6 +744,9 @@ static int target_restore_sigframe(CPUARMState *env,
}
if (env->svcr != svcr) {
env->svcr = svcr;
rebuild_hflags = true;
}
if (rebuild_hflags) {
arm_rebuild_hflags(env);
}
unlock_user(extra, extra_datap, 0);
@ -701,7 +817,7 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
uc.tuc_mcontext.__reserved),
};
int fpsimd_ofs, fr_ofs, sve_ofs = 0, za_ofs = 0, tpidr2_ofs = 0;
int zt_ofs = 0, esr_ofs = 0;
int zt_ofs = 0, esr_ofs = 0, gcs_ofs = 0;
int sve_size = 0, za_size = 0, tpidr2_size = 0, zt_size = 0;
struct target_rt_sigframe *frame;
struct target_rt_frame_record *fr;
@ -720,6 +836,11 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
&layout);
}
if (env->cp15.gcspr_el[0]) {
gcs_ofs = alloc_sigframe_space(sizeof(struct target_gcs_context),
&layout);
}
/* SVE state needs saving only if it exists. */
if (cpu_isar_feature(aa64_sve, env_archcpu(env)) ||
cpu_isar_feature(aa64_sme, env_archcpu(env))) {
@ -779,6 +900,12 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
goto give_sigsegv;
}
if (ka->sa_flags & TARGET_SA_RESTORER) {
return_addr = ka->sa_restorer;
} else {
return_addr = default_rt_sigreturn;
}
target_setup_general_frame(frame, env, set);
target_setup_fpsimd_record((void *)frame + fpsimd_ofs, env);
if (esr_ofs) {
@ -786,6 +913,10 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
/* Leave ESR_EL1 clear while it's not relevant. */
env->cp15.esr_el[1] = 0;
}
if (gcs_ofs &&
!target_setup_gcs_record((void *)frame + gcs_ofs, env, return_addr)) {
goto give_sigsegv;
}
target_setup_end_record((void *)frame + layout.std_end_ofs);
if (layout.extra_ofs) {
target_setup_extra_record((void *)frame + layout.extra_ofs,
@ -811,11 +942,6 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
__put_user(env->xregs[29], &fr->fp);
__put_user(env->xregs[30], &fr->lr);
if (ka->sa_flags & TARGET_SA_RESTORER) {
return_addr = ka->sa_restorer;
} else {
return_addr = default_rt_sigreturn;
}
env->xregs[0] = usig;
env->xregs[29] = frame_addr + fr_ofs;
env->xregs[30] = return_addr;