[PATCH] arm64: signal: Ensure signal delivery failure is recoverable
Dave Martin
Dave.Martin at arm.com
Tue Dec 3 03:56:27 PST 2024
On Tue, Dec 03, 2024 at 10:03:22AM +0000, Kevin Brodsky wrote:
> Commit eaf62ce1563b ("arm64/signal: Set up and restore the GCS
> context for signal handlers") introduced a potential failure point
> at the end of setup_return(). This is unfortunate as it is too late
> to deliver a SIGSEGV: if that SIGSEGV is handled, the subsequent
> sigreturn will end up returning to the original handler, which is
> not the intention (since we failed to deliver that signal).
>
> Make sure this does not happen by calling gcs_signal_entry()
> at the very beginning of setup_return(), and add a comment just
> after to discourage error cases being introduced from that point
> onwards.
>
> While at it, also take care of copy_siginfo_to_user(): since it may
> fail, we shouldn't be calling it after setup_return() either. Call
> it before setup_return() instead, and move the setting of X1/X2
> inside setup_return() where it belongs (after the "point of no
> failure").
>
> Background: the first part of setup_rt_frame(), including
> setup_sigframe(), has no impact on the execution of the interrupted
> thread. The signal frame is written to the stack, but the stack
> pointer remains unchanged. Failure at this stage can be recovered by
> a SIGSEGV handler, and sigreturn will restore the original context,
> at the point where the original signal occurred. On the other hand,
> once setup_return() has updated registers including SP, the thread's
> control flow has been modified and we must deliver the original
> signal.
>
> Fixes: eaf62ce1563b ("arm64/signal: Set up and restore the GCS context for signal handlers")
> Signed-off-by: Kevin Brodsky <kevin.brodsky at arm.com>
> ---
> Cc: broonie at kernel.org
> Cc: catalin.marinas at arm.com
> Cc: Dave.Martin at arm.com
> Cc: will at kernel.org
> ---
> arch/arm64/kernel/signal.c | 43 +++++++++++++++++++++++++-------------
> 1 file changed, 28 insertions(+), 15 deletions(-)
>
> diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c
> index 14ac6fdb872b..0adaa165b876 100644
> --- a/arch/arm64/kernel/signal.c
> +++ b/arch/arm64/kernel/signal.c
> @@ -1462,10 +1462,33 @@ static int setup_return(struct pt_regs *regs, struct ksignal *ksig,
> struct rt_sigframe_user_layout *user, int usig)
> {
> __sigrestore_t sigtramp;
> + int err;
> +
> + if (ksig->ka.sa.sa_flags & SA_RESTORER)
> + sigtramp = ksig->ka.sa.sa_restorer;
> + else
> + sigtramp = VDSO_SYMBOL(current->mm->context.vdso, sigtramp);
> +
> + err = gcs_signal_entry(sigtramp, ksig);
> + if (err)
> + return err;
> +
> + /*
> + * We must not fail from this point onwards. We are going to update
> + * registers, including SP, in order to invoke the signal handler. If
> + * we failed and attempted to deliver a nested SIGSEGV to a handler
> + * after that point, the subsequent sigreturn would end up restoring
> + * the (partial) state for the original signal handler.
> + */
The same is true at the call site of this function; could problems
creep back in there?
>
> regs->regs[0] = usig;
> + if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
> + regs->regs[1] = (unsigned long)&user->sigframe->info;
> + regs->regs[2] = (unsigned long)&user->sigframe->uc;
Nit: This looks like it should work, but it's not really anything to do
with setting up how the handler returns (we're in setup_return() here).
Would it be cleaner just to stage the destructive state changes
somewhere and apply them right at the end (as with POR_EL0)?
> + }
> regs->sp = (unsigned long)user->sigframe;
> regs->regs[29] = (unsigned long)&user->next_frame->fp;
> + regs->regs[30] = (unsigned long)sigtramp;
> regs->pc = (unsigned long)ksig->ka.sa.sa_handler;
>
> /*
> @@ -1506,14 +1529,7 @@ static int setup_return(struct pt_regs *regs, struct ksignal *ksig,
> sme_smstop();
> }
>
> - if (ksig->ka.sa.sa_flags & SA_RESTORER)
> - sigtramp = ksig->ka.sa.sa_restorer;
> - else
> - sigtramp = VDSO_SYMBOL(current->mm->context.vdso, sigtramp);
> -
> - regs->regs[30] = (unsigned long)sigtramp;
> -
> - return gcs_signal_entry(sigtramp, ksig);
> + return 0;
> }
>
> static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,
> @@ -1537,14 +1553,11 @@ static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,
>
> err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
> err |= setup_sigframe(&user, regs, set, &ua_state);
> - if (err == 0) {
> + if (ksig->ka.sa.sa_flags & SA_SIGINFO)
> + err |= copy_siginfo_to_user(&frame->info, &ksig->info);
> +
> + if (err == 0)
> err = setup_return(regs, ksig, &user, usig);
> - if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
> - err |= copy_siginfo_to_user(&frame->info, &ksig->info);
> - regs->regs[1] = (unsigned long)&frame->info;
> - regs->regs[2] = (unsigned long)&frame->uc;
> - }
> - }
>
> if (err == 0)
> set_handler_user_access_state();
[...]
Cheers
---Dave
More information about the linux-arm-kernel
mailing list