Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bdec97a8 authored by Will Deacon's avatar Will Deacon
Browse files

arm64: compat: fix vfp save/restore across signal handlers in big-endian



When saving/restoring the VFP registers from a compat (AArch32)
signal frame, we rely on the compat registers forming a prefix of the
native register file and therefore make use of copy_{to,from}_user to
transfer between the native fpsimd_state and the compat_vfp_sigframe.

Unfortunately, this doesn't work so well in a big-endian environment.
Our fpsimd save/restore code operates directly on 128-bit quantities
(Q registers) whereas the compat_vfp_sigframe represents the registers
as an array of 64-bit (D) registers. The architecture packs the compat D
registers into the Q registers, with the least significant bytes holding
the lower register. Consequently, we need to swap the 64-bit halves when
converting between these two representations on a big-endian machine.

This patch replaces the __copy_{to,from}_user invocations in our
compat VFP signal handling code with explicit __put_user loops that
operate on 64-bit values and swap them accordingly.

Cc: <stable@vger.kernel.org>
Reviewed-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
Signed-off-by: default avatarWill Deacon <will.deacon@arm.com>
parent e56d82a1
Loading
Loading
Loading
Loading
+36 −11
Original line number Original line Diff line number Diff line
@@ -212,14 +212,32 @@ int copy_siginfo_from_user32(siginfo_t *to, compat_siginfo_t __user *from)


/*
/*
 * VFP save/restore code.
 * VFP save/restore code.
 *
 * We have to be careful with endianness, since the fpsimd context-switch
 * code operates on 128-bit (Q) register values whereas the compat ABI
 * uses an array of 64-bit (D) registers. Consequently, we need to swap
 * the two halves of each Q register when running on a big-endian CPU.
 */
 */
union __fpsimd_vreg {
	__uint128_t	raw;
	struct {
#ifdef __AARCH64EB__
		u64	hi;
		u64	lo;
#else
		u64	lo;
		u64	hi;
#endif
	};
};

static int compat_preserve_vfp_context(struct compat_vfp_sigframe __user *frame)
static int compat_preserve_vfp_context(struct compat_vfp_sigframe __user *frame)
{
{
	struct fpsimd_state *fpsimd = &current->thread.fpsimd_state;
	struct fpsimd_state *fpsimd = &current->thread.fpsimd_state;
	compat_ulong_t magic = VFP_MAGIC;
	compat_ulong_t magic = VFP_MAGIC;
	compat_ulong_t size = VFP_STORAGE_SIZE;
	compat_ulong_t size = VFP_STORAGE_SIZE;
	compat_ulong_t fpscr, fpexc;
	compat_ulong_t fpscr, fpexc;
	int err = 0;
	int i, err = 0;


	/*
	/*
	 * Save the hardware registers to the fpsimd_state structure.
	 * Save the hardware registers to the fpsimd_state structure.
@@ -235,10 +253,15 @@ static int compat_preserve_vfp_context(struct compat_vfp_sigframe __user *frame)
	/*
	/*
	 * Now copy the FP registers. Since the registers are packed,
	 * Now copy the FP registers. Since the registers are packed,
	 * we can copy the prefix we want (V0-V15) as it is.
	 * we can copy the prefix we want (V0-V15) as it is.
	 * FIXME: Won't work if big endian.
	 */
	 */
	err |= __copy_to_user(&frame->ufp.fpregs, fpsimd->vregs,
	for (i = 0; i < ARRAY_SIZE(frame->ufp.fpregs); i += 2) {
			      sizeof(frame->ufp.fpregs));
		union __fpsimd_vreg vreg = {
			.raw = fpsimd->vregs[i >> 1],
		};

		__put_user_error(vreg.lo, &frame->ufp.fpregs[i], err);
		__put_user_error(vreg.hi, &frame->ufp.fpregs[i + 1], err);
	}


	/* Create an AArch32 fpscr from the fpsr and the fpcr. */
	/* Create an AArch32 fpscr from the fpsr and the fpcr. */
	fpscr = (fpsimd->fpsr & VFP_FPSCR_STAT_MASK) |
	fpscr = (fpsimd->fpsr & VFP_FPSCR_STAT_MASK) |
@@ -263,7 +286,7 @@ static int compat_restore_vfp_context(struct compat_vfp_sigframe __user *frame)
	compat_ulong_t magic = VFP_MAGIC;
	compat_ulong_t magic = VFP_MAGIC;
	compat_ulong_t size = VFP_STORAGE_SIZE;
	compat_ulong_t size = VFP_STORAGE_SIZE;
	compat_ulong_t fpscr;
	compat_ulong_t fpscr;
	int err = 0;
	int i, err = 0;


	__get_user_error(magic, &frame->magic, err);
	__get_user_error(magic, &frame->magic, err);
	__get_user_error(size, &frame->size, err);
	__get_user_error(size, &frame->size, err);
@@ -273,12 +296,14 @@ static int compat_restore_vfp_context(struct compat_vfp_sigframe __user *frame)
	if (magic != VFP_MAGIC || size != VFP_STORAGE_SIZE)
	if (magic != VFP_MAGIC || size != VFP_STORAGE_SIZE)
		return -EINVAL;
		return -EINVAL;


	/*
	/* Copy the FP registers into the start of the fpsimd_state. */
	 * Copy the FP registers into the start of the fpsimd_state.
	for (i = 0; i < ARRAY_SIZE(frame->ufp.fpregs); i += 2) {
	 * FIXME: Won't work if big endian.
		union __fpsimd_vreg vreg;
	 */

	err |= __copy_from_user(fpsimd.vregs, frame->ufp.fpregs,
		__get_user_error(vreg.lo, &frame->ufp.fpregs[i], err);
				sizeof(frame->ufp.fpregs));
		__get_user_error(vreg.hi, &frame->ufp.fpregs[i + 1], err);
		fpsimd.vregs[i >> 1] = vreg.raw;
	}


	/* Extract the fpsr and the fpcr from the fpscr */
	/* Extract the fpsr and the fpcr from the fpscr */
	__get_user_error(fpscr, &frame->ufp.fpscr, err);
	__get_user_error(fpscr, &frame->ufp.fpscr, err);