Re: [PATCH v11 25/39] arm64/signal: Expose GCS state in signal frames

Catalin Marinas
2024-08-23T09:37:27Z

On Thu, Aug 22, 2024 at 02:15:28AM +0100, Mark Brown wrote:
> +static int preserve_gcs_context(struct gcs_context __user *ctx)
> +{
> + int err = 0;
> + u64 gcspr;
> +
> + /*
> + * We will add a cap token to the frame, include it in the
> + * GCSPR_EL0 we report to support stack switching via
> + * sigreturn.
> + */
> + gcs_preserve_current_state();
> + gcspr = current->thread.gcspr_el0 - 8;
> +
> + __put_user_error(GCS_MAGIC, &ctx->head.magic, err);
> + __put_user_error(sizeof(*ctx), &ctx->head.size, err);
> + __put_user_error(gcspr, &ctx->gcspr, err);
> + __put_user_error(0, &ctx->reserved, err);
> + __put_user_error(current->thread.gcs_el0_mode,
> + &ctx->features_enabled, err);
> +
> + return err;
> +}

Do we actually need to store the gcspr value after the cap token has
been pushed or just the value of the interrupted context? If we at some
point get a sigaltshadowstack() syscall, the saved GCS wouldn't point to
the new stack but rather the original one. Unwinders should be able to
get the actual GCSPR_EL0 register, no need for the sigcontext to point
to the new shadow stack.

Also in gcs_signal_entry() in the previous patch, we seem to subtract 16
rather than 8.

I admit I haven't checked the past discussions in this area, so maybe
I'm missing something.

> +static int restore_gcs_context(struct user_ctxs *user)
> +{
> + u64 gcspr, enabled;
> + int err = 0;
> +
> + if (user->gcs_size != sizeof(*user->gcs))
> + return -EINVAL;
> +
> + __get_user_error(gcspr, &user->gcs->gcspr, err);
> + __get_user_error(enabled, &user->gcs->features_enabled, err);
> + if (err)
> + return err;
> +
> + /* Don't allow unknown modes */
> + if (enabled & ~PR_SHADOW_STACK_SUPPORTED_STATUS_MASK)
> + return -EINVAL;
> +
> + err = gcs_check_locked(current, enabled);
> + if (err != 0)
> + return err;
> +
> + /* Don't allow enabling */
> + if (!task_gcs_el0_enabled(current) &&
> + (enabled & PR_SHADOW_STACK_ENABLE))
> + return -EINVAL;
> +
> + /* If we are disabling disable everything */
> + if (!(enabled & PR_SHADOW_STACK_ENABLE))
> + enabled = 0;
> +
> + current->thread.gcs_el0_mode = enabled;
> +
> + /*
> + * We let userspace set GCSPR_EL0 to anything here, we will
> + * validate later in gcs_restore_signal().
> + */
> + current->thread.gcspr_el0 = gcspr;
> + write_sysreg_s(current->thread.gcspr_el0, SYS_GCSPR_EL0);

So in preserve_gcs_context(), we subtract 8 from the gcspr_el0 value.
Where is it added back?

What I find confusing is that both restore_gcs_context() and
gcs_restore_signal() seem to touch current->thread.gcspr_el0 and the
sysreg. Which one takes priority? I should probably check the branch out
to see the end result.

> @@ -977,6 +1079,13 @@ static int setup_sigframe_layout(struct rt_sigframe_user_layout *user,
> return err;
> }
>
> + if (add_all || task_gcs_el0_enabled(current)) {
> + err = sigframe_alloc(user, &user->gcs_offset,
> + sizeof(struct gcs_context));
> + if (err)
> + return err;
> + }

I'm still not entirely convinced of this conditional saving and the
interaction with unwinders. In a previous thread you mentioned that we
need to keep the GCSPR_EL0 sysreg value up to date even after disabling
GCS for a thread as not to confuse the unwinders. We could get a signal
delivered together with a sigreturn without any context switch. Do we
lose any state?

It might help if you describe the scenario, maybe even adding a comment
in the code, otherwise I'm sure we'll forget in a few months time.

--
Catalin