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

Commit 3037a52f authored by Martin Schwidefsky's avatar Martin Schwidefsky
Browse files

s390/nmi: do register validation as early as possible



The validation of the CPU registers in the machine check handler is
currently split into two parts. The first part is done at the start
of the low level mcck_int_handler function, this includes the CPU
timer register and the general purpose registers.
The second part is done a bit later in s390_do_machine_check for all
the other registers, including the control registers, floating pointer
control, vector or floating pointer registers, the access registers,
the guarded storage registers, the TOD programmable registers and the
clock comparator.

This is working fine to far but in theory a future extensions could
cause the C code to use registers that are not validated yet. A better
approach is to validate all CPU registers in "safe" assembler code
before any C function is called.

Reviewed-by: default avatarHeiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent 6c81511c
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -9,6 +9,8 @@

#include <linux/const.h>

#define CR2_GUARDED_STORAGE		_BITUL(63 - 59)

#define CR14_CHANNEL_REPORT_SUBMASK	_BITUL(63 - 35)
#define CR14_RECOVERY_SUBMASK		_BITUL(63 - 36)
#define CR14_DEGRADATION_SUBMASK	_BITUL(63 - 37)
+3 −0
Original line number Diff line number Diff line
@@ -25,6 +25,9 @@
#define MCCK_CODE_CPU_TIMER_VALID	_BITUL(63 - 46)
#define MCCK_CODE_PSW_MWP_VALID		_BITUL(63 - 20)
#define MCCK_CODE_PSW_IA_VALID		_BITUL(63 - 23)
#define MCCK_CODE_CR_VALID		_BITUL(63 - 29)
#define MCCK_CODE_GS_VALID		_BITUL(63 - 36)
#define MCCK_CODE_FC_VALID		_BITUL(63 - 43)

#ifndef __ASSEMBLY__

+5 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <asm/vdso.h>
#include <asm/pgtable.h>
#include <asm/gmap.h>
#include <asm/nmi.h>

/*
 * Make sure that the compiler is new enough. We want a compiler that
@@ -158,6 +159,7 @@ int main(void)
	OFFSET(__LC_LAST_UPDATE_CLOCK, lowcore, last_update_clock);
	OFFSET(__LC_INT_CLOCK, lowcore, int_clock);
	OFFSET(__LC_MCCK_CLOCK, lowcore, mcck_clock);
	OFFSET(__LC_CLOCK_COMPARATOR, lowcore, clock_comparator);
	OFFSET(__LC_BOOT_CLOCK, lowcore, boot_clock);
	OFFSET(__LC_CURRENT, lowcore, current_task);
	OFFSET(__LC_KERNEL_STACK, lowcore, kernel_stack);
@@ -193,6 +195,9 @@ int main(void)
	OFFSET(__LC_CREGS_SAVE_AREA, lowcore, cregs_save_area);
	OFFSET(__LC_PGM_TDB, lowcore, pgm_tdb);
	BLANK();
	/* extended machine check save area */
	OFFSET(__MCESA_GS_SAVE_AREA, mcesa, guarded_storage_save_area);
	BLANK();
	/* gmap/sie offsets */
	OFFSET(__GMAP_ASCE, gmap, asce);
	OFFSET(__SIE_PROG0C, kvm_s390_sie_block, prog0c);
+53 −7
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include <linux/linkage.h>
#include <asm/processor.h>
#include <asm/cache.h>
#include <asm/ctl_reg.h>
#include <asm/errno.h>
#include <asm/ptrace.h>
#include <asm/thread_info.h>
@@ -948,15 +949,56 @@ load_fpu_regs:
 */
ENTRY(mcck_int_handler)
	STCK	__LC_MCCK_CLOCK
	la	%r1,4095		# revalidate r1
	spt	__LC_CPU_TIMER_SAVE_AREA-4095(%r1)	# revalidate cpu timer
	lmg	%r0,%r15,__LC_GPREGS_SAVE_AREA-4095(%r1)# revalidate gprs
	la	%r1,4095		# validate r1
	spt	__LC_CPU_TIMER_SAVE_AREA-4095(%r1)	# validate cpu timer
	sckc	__LC_CLOCK_COMPARATOR			# validate comparator
	lam	%a0,%a15,__LC_AREGS_SAVE_AREA-4095(%r1) # validate acrs
	lmg	%r0,%r15,__LC_GPREGS_SAVE_AREA-4095(%r1)# validate gprs
	lg	%r12,__LC_CURRENT
	larl	%r13,cleanup_critical
	lmg	%r8,%r9,__LC_MCK_OLD_PSW
	TSTMSK	__LC_MCCK_CODE,MCCK_CODE_SYSTEM_DAMAGE
	jo	.Lmcck_panic		# yes -> rest of mcck code invalid
	lghi	%r14,__LC_CPU_TIMER_SAVE_AREA
	TSTMSK	__LC_MCCK_CODE,MCCK_CODE_CR_VALID
	jno	.Lmcck_panic		# control registers invalid -> panic
	la	%r14,4095
	lctlg	%c0,%c15,__LC_CREGS_SAVE_AREA-4095(%r14) # validate ctl regs
	ptlb
	lg	%r11,__LC_MCESAD	# extended machine check save area
	nill	%r11,0xfc00		# MCESA_ORIGIN_MASK
	TSTMSK	__LC_CREGS_SAVE_AREA+16-4095(%r14),CR2_GUARDED_STORAGE
	jno	0f
	TSTMSK	__LC_MCCK_CODE,MCCK_CODE_GS_VALID
	jno	0f
	.insn	 rxy,0xe3000000004d,0,__MCESA_GS_SAVE_AREA(%r11) # LGSC
0:	l	%r14,__LC_FP_CREG_SAVE_AREA-4095(%r14)
	TSTMSK	__LC_MCCK_CODE,MCCK_CODE_FC_VALID
	jo	0f
	sr	%r14,%r14
0:	sfpc	%r14
	TSTMSK	__LC_MACHINE_FLAGS,MACHINE_FLAG_VX
	jo	0f
	lghi	%r14,__LC_FPREGS_SAVE_AREA
	ld	%f0,0(%r14)
	ld	%f1,8(%r14)
	ld	%f2,16(%r14)
	ld	%f3,24(%r14)
	ld	%f4,32(%r14)
	ld	%f5,40(%r14)
	ld	%f6,48(%r14)
	ld	%f7,56(%r14)
	ld	%f8,64(%r14)
	ld	%f9,72(%r14)
	ld	%f10,80(%r14)
	ld	%f11,88(%r14)
	ld	%f12,96(%r14)
	ld	%f13,104(%r14)
	ld	%f14,112(%r14)
	ld	%f15,120(%r14)
	j	1f
0:	VLM	%v0,%v15,0,%r11
	VLM	%v16,%v31,256,%r11
1:	lghi	%r14,__LC_CPU_TIMER_SAVE_AREA
	mvc	__LC_MCCK_ENTER_TIMER(8),0(%r14)
	TSTMSK	__LC_MCCK_CODE,MCCK_CODE_CPU_TIMER_VALID
	jo	3f
@@ -972,9 +1014,13 @@ ENTRY(mcck_int_handler)
	la	%r14,__LC_LAST_UPDATE_TIMER
2:	spt	0(%r14)
	mvc	__LC_MCCK_ENTER_TIMER(8),0(%r14)
3:	TSTMSK	__LC_MCCK_CODE,(MCCK_CODE_PSW_MWP_VALID|MCCK_CODE_PSW_IA_VALID)
	jno	.Lmcck_panic		# no -> skip cleanup critical
	SWITCH_ASYNC __LC_GPREGS_SAVE_AREA+64,__LC_MCCK_ENTER_TIMER
3:	TSTMSK	__LC_MCCK_CODE,MCCK_CODE_PSW_MWP_VALID
	jno	.Lmcck_panic
	tmhh	%r8,0x0001		# interrupting from user ?
	jnz	4f
	TSTMSK	__LC_MCCK_CODE,MCCK_CODE_PSW_IA_VALID
	jno	.Lmcck_panic
4:	SWITCH_ASYNC __LC_GPREGS_SAVE_AREA+64,__LC_MCCK_ENTER_TIMER
.Lmcck_skip:
	lghi	%r14,__LC_GPREGS_SAVE_AREA+64
	stmg	%r0,%r7,__PT_R0(%r11)
+22 −88
Original line number Diff line number Diff line
@@ -184,19 +184,16 @@ void s390_handle_mcck(void)
EXPORT_SYMBOL_GPL(s390_handle_mcck);

/*
 * returns 0 if all registers could be validated
 * returns 0 if all required registers are available
 * returns 1 otherwise
 */
static int notrace s390_validate_registers(union mci mci, int umode)
static int notrace s390_check_registers(union mci mci, int umode)
{
	union ctlreg2 cr2;
	int kill_task;
	u64 zero;
	void *fpt_save_area;
	struct mcesa *mcesa;

	kill_task = 0;
	zero = 0;

	if (!mci.gr) {
		/*
@@ -207,18 +204,13 @@ static int notrace s390_validate_registers(union mci mci, int umode)
			s390_handle_damage();
		kill_task = 1;
	}
	/* Validate control registers */
	/* Check control registers */
	if (!mci.cr) {
		/*
		 * Control registers have unknown contents.
		 * Can't recover and therefore stopping machine.
		 */
		s390_handle_damage();
	} else {
		asm volatile(
			"	lctlg	0,15,0(%0)\n"
			"	ptlb\n"
			: : "a" (&S390_lowcore.cregs_save_area) : "memory");
	}
	if (!mci.fp) {
		/*
@@ -226,7 +218,6 @@ static int notrace s390_validate_registers(union mci mci, int umode)
		 * kernel currently uses floating point registers the
		 * system is stopped. If the process has its floating
		 * pointer registers loaded it is terminated.
		 * Otherwise just revalidate the registers.
		 */
		if (S390_lowcore.fpu_flags & KERNEL_VXR_V0V7)
			s390_handle_damage();
@@ -240,72 +231,29 @@ static int notrace s390_validate_registers(union mci mci, int umode)
		 * If the kernel currently uses the floating pointer
		 * registers and needs the FPC register the system is
		 * stopped. If the process has its floating pointer
		 * registers loaded it is terminated. Otherwiese the
		 * FPC is just revalidated.
		 * registers loaded it is terminated.
		 */
		if (S390_lowcore.fpu_flags & KERNEL_FPC)
			s390_handle_damage();
		asm volatile("lfpc %0" : : "Q" (zero));
		if (!test_cpu_flag(CIF_FPU))
			kill_task = 1;
	} else {
		asm volatile("lfpc %0"
			     : : "Q" (S390_lowcore.fpt_creg_save_area));
	}

	mcesa = (struct mcesa *)(S390_lowcore.mcesad & MCESA_ORIGIN_MASK);
	if (!MACHINE_HAS_VX) {
		/* Validate floating point registers */
		asm volatile(
			"	ld	0,0(%0)\n"
			"	ld	1,8(%0)\n"
			"	ld	2,16(%0)\n"
			"	ld	3,24(%0)\n"
			"	ld	4,32(%0)\n"
			"	ld	5,40(%0)\n"
			"	ld	6,48(%0)\n"
			"	ld	7,56(%0)\n"
			"	ld	8,64(%0)\n"
			"	ld	9,72(%0)\n"
			"	ld	10,80(%0)\n"
			"	ld	11,88(%0)\n"
			"	ld	12,96(%0)\n"
			"	ld	13,104(%0)\n"
			"	ld	14,112(%0)\n"
			"	ld	15,120(%0)\n"
			: : "a" (fpt_save_area) : "memory");
	} else {
		/* Validate vector registers */
		union ctlreg0 cr0;
	}

	if (MACHINE_HAS_VX) {
		if (!mci.vr) {
			/*
			 * Vector registers can't be restored. If the kernel
			 * currently uses vector registers the system is
			 * stopped. If the process has its vector registers
			 * loaded it is terminated. Otherwise just revalidate
			 * the registers.
			 * loaded it is terminated.
			 */
			if (S390_lowcore.fpu_flags & KERNEL_VXR)
				s390_handle_damage();
			if (!test_cpu_flag(CIF_FPU))
				kill_task = 1;
		}
		cr0.val = S390_lowcore.cregs_save_area[0];
		cr0.afp = cr0.vx = 1;
		__ctl_load(cr0.val, 0, 0);
		asm volatile(
			"	la	1,%0\n"
			"	.word	0xe70f,0x1000,0x0036\n"	/* vlm 0,15,0(1) */
			"	.word	0xe70f,0x1100,0x0c36\n"	/* vlm 16,31,256(1) */
			: : "Q" (*(struct vx_array *) mcesa->vector_save_area)
			: "1");
		__ctl_load(S390_lowcore.cregs_save_area[0], 0, 0);
	}
	/* Validate access registers */
	asm volatile(
		"	lam	0,15,0(%0)"
		: : "a" (&S390_lowcore.access_regs_save_area));
	}
	/* Check if access registers are valid */
	if (!mci.ar) {
		/*
		 * Access registers have unknown contents.
@@ -313,55 +261,41 @@ static int notrace s390_validate_registers(union mci mci, int umode)
		 */
		kill_task = 1;
	}
	/* Validate guarded storage registers */
	/* Check guarded storage registers */
	cr2.val = S390_lowcore.cregs_save_area[2];
	if (cr2.gse) {
		if (!mci.gs)
		if (!mci.gs) {
			/*
			 * Guarded storage register can't be restored and
			 * the current processes uses guarded storage.
			 * It has to be terminated.
			 */
			kill_task = 1;
		else
			load_gs_cb((struct gs_cb *)
				   mcesa->guarded_storage_save_area);
		}
	/*
	 * We don't even try to validate the TOD register, since we simply
	 * can't write something sensible into that register.
	 */
	/*
	 * See if we can validate the TOD programmable register with its
	 * old contents (should be zero) otherwise set it to zero.
	 */
	if (!mci.pr)
		asm volatile(
			"	sr	0,0\n"
			"	sckpf"
			: : : "0", "cc");
	else
		asm volatile(
			"	l	0,%0\n"
			"	sckpf"
			: : "Q" (S390_lowcore.tod_progreg_save_area)
			: "0", "cc");
	/* Validate clock comparator register */
	set_clock_comparator(S390_lowcore.clock_comparator);
	}
	/* Check if old PSW is valid */
	if (!mci.wp)
	if (!mci.wp) {
		/*
		 * Can't tell if we come from user or kernel mode
		 * -> stopping machine.
		 */
		s390_handle_damage();
	}
	/* Check for invalid kernel instruction address */
	if (!mci.ia && !umode) {
		/*
		 * The instruction address got lost while running
		 * in the kernel -> stopping machine.
		 */
		s390_handle_damage();
	}

	if (!mci.ms || !mci.pm || !mci.ia)
		kill_task = 1;

	return kill_task;
}
NOKPROBE_SYMBOL(s390_validate_registers);
NOKPROBE_SYMBOL(s390_check_registers);

/*
 * Backup the guest's machine check info to its description block
@@ -460,7 +394,7 @@ void notrace s390_do_machine_check(struct pt_regs *regs)
			s390_handle_damage();
		}
	}
	if (s390_validate_registers(mci, user_mode(regs))) {
	if (s390_check_registers(mci, user_mode(regs))) {
		/*
		 * Couldn't restore all register contents for the
		 * user space process -> mark task for termination.