s390: fix save and restore of the floating-point-control register

The FPC_VALID_MASK has been used to check the validity of the value
to be loaded into the floating-point-control register. With the
introduction of the floating-point extension facility and the
decimal-floating-point additional bits have been defined which need
to be checked in a non straight forward way. So far these bits have
been ignored which can cause an incorrect results for decimal-
floating-point operations, e.g. an incorrect rounding mode to be
set after signal return.

The static check with the FPC_VALID_MASK is replaced with a trial
load of the floating-point-control value, see test_fp_ctl.

In addition an information leak with the padding word between the
floating-point-control word and the floating-point registers in
the s390_fp_regs is fixed.

Reported-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/arch/s390/kernel/compat_signal.c b/arch/s390/kernel/compat_signal.c
index b7b60f5c..ceeaaa6 100644
--- a/arch/s390/kernel/compat_signal.c
+++ b/arch/s390/kernel/compat_signal.c
@@ -153,60 +153,61 @@
 
 static int save_sigregs32(struct pt_regs *regs, _sigregs32 __user *sregs)
 {
-	_s390_regs_common32 regs32;
-	int err, i;
+	_sigregs32 user_sregs;
+	int i;
 
-	regs32.psw.mask = psw32_user_bits |
+	user_sregs.regs.psw.mask = psw32_user_bits |
 		((__u32)(regs->psw.mask >> 32) & PSW32_MASK_USER);
-	regs32.psw.addr = (__u32) regs->psw.addr |
+	user_sregs.regs.psw.addr = (__u32) regs->psw.addr |
 		(__u32)(regs->psw.mask & PSW_MASK_BA);
 	for (i = 0; i < NUM_GPRS; i++)
-		regs32.gprs[i] = (__u32) regs->gprs[i];
+		user_sregs.regs.gprs[i] = (__u32) regs->gprs[i];
 	save_access_regs(current->thread.acrs);
-	memcpy(regs32.acrs, current->thread.acrs, sizeof(regs32.acrs));
-	err = __copy_to_user(&sregs->regs, &regs32, sizeof(regs32));
-	if (err)
-		return -EFAULT;
-	save_fp_regs(&current->thread.fp_regs);
-	/* s390_fp_regs and _s390_fp_regs32 are the same ! */
-	err = __copy_to_user(&sregs->fpregs, &current->thread.fp_regs,
-			     sizeof(_s390_fp_regs32));
-	if (err)
+	memcpy(&user_sregs.regs.acrs, current->thread.acrs,
+	       sizeof(user_sregs.regs.acrs));
+	save_fp_ctl(&current->thread.fp_regs.fpc);
+	save_fp_regs(current->thread.fp_regs.fprs);
+	memcpy(&user_sregs.fpregs, &current->thread.fp_regs,
+	       sizeof(user_sregs.fpregs));
+	if (__copy_to_user(sregs, &user_sregs, sizeof(_sigregs32)))
 		return -EFAULT;
 	return 0;
 }
 
 static int restore_sigregs32(struct pt_regs *regs,_sigregs32 __user *sregs)
 {
-	_s390_regs_common32 regs32;
-	int err, i;
+	_sigregs32 user_sregs;
+	int i;
 
 	/* Alwys make any pending restarted system call return -EINTR */
 	current_thread_info()->restart_block.fn = do_no_restart_syscall;
 
-	err = __copy_from_user(&regs32, &sregs->regs, sizeof(regs32));
-	if (err)
+	if (__copy_from_user(&user_sregs, &sregs->regs, sizeof(user_sregs)))
 		return -EFAULT;
+
+	/* Loading the floating-point-control word can fail. Do that first. */
+	if (restore_fp_ctl(&user_sregs.fpregs.fpc))
+		return -EINVAL;
+
+	/* Use regs->psw.mask instead of PSW_USER_BITS to preserve PER bit. */
 	regs->psw.mask = (regs->psw.mask & ~PSW_MASK_USER) |
-		(__u64)(regs32.psw.mask & PSW32_MASK_USER) << 32 |
-		(__u64)(regs32.psw.addr & PSW32_ADDR_AMODE);
+		(__u64)(user_sregs.regs.psw.mask & PSW32_MASK_USER) << 32 |
+		(__u64)(user_sregs.regs.psw.addr & PSW32_ADDR_AMODE);
 	/* Check for invalid user address space control. */
 	if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_HOME)
 		regs->psw.mask = PSW_ASC_PRIMARY |
 			(regs->psw.mask & ~PSW_MASK_ASC);
-	regs->psw.addr = (__u64)(regs32.psw.addr & PSW32_ADDR_INSN);
+	regs->psw.addr = (__u64)(user_sregs.regs.psw.addr & PSW32_ADDR_INSN);
 	for (i = 0; i < NUM_GPRS; i++)
-		regs->gprs[i] = (__u64) regs32.gprs[i];
-	memcpy(current->thread.acrs, regs32.acrs, sizeof(current->thread.acrs));
+		regs->gprs[i] = (__u64) user_sregs.regs.gprs[i];
+	memcpy(&current->thread.acrs, &user_sregs.regs.acrs,
+	       sizeof(current->thread.acrs));
 	restore_access_regs(current->thread.acrs);
 
-	err = __copy_from_user(&current->thread.fp_regs, &sregs->fpregs,
-			       sizeof(_s390_fp_regs32));
-	current->thread.fp_regs.fpc &= FPC_VALID_MASK;
-	if (err)
-		return -EFAULT;
+	memcpy(&current->thread.fp_regs, &user_sregs.fpregs,
+	       sizeof(current->thread.fp_regs));
 
-	restore_fp_regs(&current->thread.fp_regs);
+	restore_fp_regs(current->thread.fp_regs.fprs);
 	clear_thread_flag(TIF_SYSCALL);	/* No longer in a system call */
 	return 0;
 }