Re: [PATCH v10 23/40] arm64/signal: Set up and restore the GCS context for signal handlers

Dave Martin
2024-08-14T14:51:49Z

On Thu, Aug 01, 2024 at 01:06:50PM +0100, Mark Brown wrote:
> When invoking a signal handler we use the GCS configuration and stack
> for the current thread.
>
> Since we implement signal return by calling the signal handler with a
> return address set up pointing to a trampoline in the vDSO we need to
> also configure any active GCS for this by pushing a frame for the
> trampoline onto the GCS. If we do not do this then signal return will
> generate a GCS protection fault.
>
> In order to guard against attempts to bypass GCS protections via signal
> return we only allow returning with GCSPR_EL0 pointing to an address
> where it was previously preempted by a signal. We do this by pushing a
> cap onto the GCS, this takes the form of an architectural GCS cap token
> with the top bit set and token type of 0 which we add on signal entry
> and validate and pop off on signal return. The combination of the top
> bit being set and the token type mean that this can't be interpreted as
> a valid token or address.
>
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Signed-off-by: Mark Brown <broonie@kernel.org>
> ---
> arch/arm64/include/asm/gcs.h | 1 +
> arch/arm64/kernel/signal.c | 134 +++++++++++++++++++++++++++++++++++++++++--
> arch/arm64/mm/gcs.c | 1 +
> 3 files changed, 131 insertions(+), 5 deletions(-)
>
> diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c

[...]

> +#ifdef CONFIG_ARM64_GCS
> +
> +static int gcs_signal_entry(__sigrestore_t sigtramp, struct ksignal *ksig)
> +{
> + unsigned long __user *gcspr_el0;
> + int ret = 0;
> +
> + if (!system_supports_gcs())
> + return 0;
> +
> + if (!task_gcs_el0_enabled(current))
> + return 0;
> +
> + /*
> + * We are entering a signal handler, current register state is
> + * active.
> + */
> + gcspr_el0 = (unsigned long __user *)read_sysreg_s(SYS_GCSPR_EL0);
> +
> + /*
> + * Push a cap and the GCS entry for the trampoline onto the GCS.
> + */
> + put_user_gcs((unsigned long)sigtramp, gcspr_el0 - 2, &ret);
> + put_user_gcs(GCS_SIGNAL_CAP(gcspr_el0 - 1), gcspr_el0 - 1, &ret);
> + if (ret != 0)
> + return ret;

What happens if we went wrong here, or if the signal we are delivering
was caused by a GCS overrun or bad GCSPR_EL0 in the first place?

It feels like a program has no way to rescue itself from excessive
recursion in some thread. Is there something equivalent to
sigaltstack()?

Or is the shadow stack always supposed to be big enough to cope with
recursion that exhausts the main stack and alternate signal stack (and
if so, how is this ensured)?

> +
> + gcsb_dsync();
> +
> + gcspr_el0 -= 2;
> + write_sysreg_s((unsigned long)gcspr_el0, SYS_GCSPR_EL0);
> +
> + return 0;
> +}

[...]

Cheers
---Dave