git: 72b242ac15af - stable/13 - arm64: Use the Linux sigframe to restore registers

From: Andrew Turner <andrew_at_FreeBSD.org>
Date: Mon, 19 Feb 2024 16:44:58 UTC
The branch stable/13 has been updated by andrew:

URL: https://cgit.FreeBSD.org/src/commit/?id=72b242ac15af15bea2c6989bb911d2dd6ab11651

commit 72b242ac15af15bea2c6989bb911d2dd6ab11651
Author:     Andrew Turner <andrew@FreeBSD.org>
AuthorDate: 2023-10-25 09:50:11 +0000
Commit:     Andrew Turner <andrew@FreeBSD.org>
CommitDate: 2024-02-19 12:41:40 +0000

    arm64: Use the Linux sigframe to restore registers
    
    When returning from a Linux signal use the Linux sigframe to find the
    register values to restore.
    
    Remove the FreeBSD ucontext from the stack as it's now unneeded.
    
    Reviewed by:    dchagin, emaste
    Sponsored by:   Arm Ltd
    Differential Revision:  https://reviews.freebsd.org/D42360
    
    (cherry picked from commit 070a4ff82a34652d533f9315ae9ad0aa8f1fdeb2)
---
 sys/arm64/linux/linux_sigframe.h |  1 -
 sys/arm64/linux/linux_sysvec.c   | 96 ++++++++++++++++++++++++++++++++++++----
 2 files changed, 88 insertions(+), 9 deletions(-)

diff --git a/sys/arm64/linux/linux_sigframe.h b/sys/arm64/linux/linux_sigframe.h
index e903fea295dc..2a004f4db74d 100644
--- a/sys/arm64/linux/linux_sigframe.h
+++ b/sys/arm64/linux/linux_sigframe.h
@@ -76,7 +76,6 @@ struct l_sigframe {
 	/* frame_record */
 	uint64_t	fp;
 	uint64_t	lr;
-	ucontext_t	uc;
 };
 
 #define	LINUX_MINSIGSTKSZ	roundup(sizeof(struct l_sigframe), 16)
diff --git a/sys/arm64/linux/linux_sysvec.c b/sys/arm64/linux/linux_sysvec.c
index 4ceee760d80b..c6f9a934fc1d 100644
--- a/sys/arm64/linux/linux_sysvec.c
+++ b/sys/arm64/linux/linux_sysvec.c
@@ -205,28 +205,109 @@ linux_exec_setregs(struct thread *td, struct image_params *imgp,
 	bzero(&pcb->pcb_dbg_regs, sizeof(pcb->pcb_dbg_regs));
 }
 
+static bool
+linux_parse_sigreturn_ctx(struct thread *td, struct l_sigcontext *sc)
+{
+	struct l_fpsimd_context *fpsimd;
+	struct _l_aarch64_ctx *ctx;
+	int offset;
+
+	offset = 0;
+	while (1) {
+		/* The offset must be 16 byte aligned */
+		if ((offset & 15) != 0)
+			return (false);
+
+		/* Check for buffer overflow of the ctx */
+		if ((offset + sizeof(*ctx)) >
+		    sizeof(sc->__reserved))
+			return (false);
+
+		ctx = (struct _l_aarch64_ctx *)&sc->__reserved[offset];
+
+		/* Check for buffer overflow of the data */
+		if ((offset + ctx->size) > sizeof(sc->__reserved))
+			return (false);
+
+		switch(ctx->magic) {
+		case 0:
+			if (ctx->size != 0)
+				return (false);
+			return (true);
+		case L_ESR_MAGIC:
+			/* Ignore */
+			break;
+#ifdef VFP
+		case L_FPSIMD_MAGIC:
+			fpsimd = (struct l_fpsimd_context *)ctx;
+
+			/*
+			 * Discard any vfp state for the current thread, we
+			 * are about to override it.
+			 */
+			critical_enter();
+			vfp_discard(td);
+			critical_exit();
+
+			td->td_pcb->pcb_fpustate.vfp_fpcr = fpsimd->fpcr;
+			td->td_pcb->pcb_fpustate.vfp_fpsr = fpsimd->fpsr;
+			memcpy(td->td_pcb->pcb_fpustate.vfp_regs,
+			    fpsimd->vregs, sizeof(fpsimd->vregs));
+
+			break;
+#endif
+		default:
+			return (false);
+		}
+
+		offset += ctx->size;
+	}
+
+}
+
 int
 linux_rt_sigreturn(struct thread *td, struct linux_rt_sigreturn_args *args)
 {
+	struct l_rt_sigframe *sf;
 	struct l_sigframe *frame;
-	ucontext_t uc;
 	struct trapframe *tf;
+	sigset_t bmask;
 	int error;
 
+	sf = malloc(sizeof(*sf), M_LINUX, M_WAITOK | M_ZERO);
+
 	tf = td->td_frame;
 	frame = (struct l_sigframe *)tf->tf_sp;
+	error = copyin((void *)&frame->sf, sf, sizeof(*sf));
+	if (error != 0) {
+		free(sf, M_LINUX);
+		return (error);
+	}
 
-	if (copyin((void *)&frame->uc, &uc, sizeof(uc)))
-		return (EFAULT);
+	memcpy(tf->tf_x, sf->sf_uc.uc_sc.regs, sizeof(tf->tf_x));
+	tf->tf_lr = sf->sf_uc.uc_sc.regs[30];
+	tf->tf_sp = sf->sf_uc.uc_sc.sp;
+	tf->tf_elr = sf->sf_uc.uc_sc.pc;
 
-	error = set_mcontext(td, &uc.uc_mcontext);
-	if (error != 0)
-		return (error);
+	if ((sf->sf_uc.uc_sc.pstate & PSR_M_MASK) != PSR_M_EL0t ||
+	    (sf->sf_uc.uc_sc.pstate & PSR_AARCH32) != 0 ||
+	    (sf->sf_uc.uc_sc.pstate & PSR_DAIF) !=
+	    (td->td_frame->tf_spsr & PSR_DAIF))
+		goto einval;
+	tf->tf_spsr = sf->sf_uc.uc_sc.pstate;
+
+	if (!linux_parse_sigreturn_ctx(td, &sf->sf_uc.uc_sc))
+		goto einval;
 
 	/* Restore signal mask. */
-	kern_sigprocmask(td, SIG_SETMASK, &uc.uc_sigmask, NULL, 0);
+	linux_to_bsd_sigset(&sf->sf_uc.uc_sigmask, &bmask);
+	kern_sigprocmask(td, SIG_SETMASK, &bmask, NULL, 0);
+	free(sf, M_LINUX);
 
 	return (EJUSTRETURN);
+einval:
+	free(sf, M_LINUX);
+	return (EINVAL);
 }
 
 static void
@@ -328,7 +409,6 @@ linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask)
 	}
 
 	memcpy(&frame->sf.sf_uc.uc_stack, &uc_stack, sizeof(uc_stack));
-	memcpy(&frame->uc, &uc, sizeof(uc));
 
 	/* Copy the sigframe out to the user's stack. */
 	if (copyout(frame, fp, sizeof(*fp)) != 0) {