s390: fix handling of runtime instrumentation psw bit

Fix the following bugs:
- When returning from a signal the signal handler copies the saved psw mask
  from user space and uses parts of it. Especially it restores the RI bit
  unconditionally. If however the machine doesn't support RI, or RI is
  disabled for the task, the last lpswe instruction which returns to user
  space will generate a specification exception.
  To fix this check if the RI bit is allowed to be set and kill the task
  if not.
- In the compat mode signal handler code the RI bit of the psw mask gets
  propagated to the mask of the return psw: if user space enables RI in the
  signal handler, RI will also be enabled after the signal handler is
  finished.
  This is a different behaviour than with 64 bit tasks. So change this to
  match the 64 bit semantics, which restores the original RI bit value.
- Fix similar oddities within the ptrace code as well.

Reviewed-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
Heiko Carstens 2013-10-16 09:58:01 +02:00 committed by Martin Schwidefsky
parent 4725c86055
commit 5ebf250dab
6 changed files with 37 additions and 15 deletions

View File

@ -22,6 +22,7 @@
#define PSW32_MASK_ASC 0x0000C000UL #define PSW32_MASK_ASC 0x0000C000UL
#define PSW32_MASK_CC 0x00003000UL #define PSW32_MASK_CC 0x00003000UL
#define PSW32_MASK_PM 0x00000f00UL #define PSW32_MASK_PM 0x00000f00UL
#define PSW32_MASK_RI 0x00000080UL
#define PSW32_MASK_USER 0x0000FF00UL #define PSW32_MASK_USER 0x0000FF00UL

View File

@ -175,6 +175,9 @@ unsigned long get_wchan(struct task_struct *p);
#define KSTK_EIP(tsk) (task_pt_regs(tsk)->psw.addr) #define KSTK_EIP(tsk) (task_pt_regs(tsk)->psw.addr)
#define KSTK_ESP(tsk) (task_pt_regs(tsk)->gprs[15]) #define KSTK_ESP(tsk) (task_pt_regs(tsk)->gprs[15])
/* Has task runtime instrumentation enabled ? */
#define is_ri_task(tsk) (!!(tsk)->thread.ri_cb)
static inline unsigned short stap(void) static inline unsigned short stap(void)
{ {
unsigned short cpu_address; unsigned short cpu_address;

View File

@ -263,7 +263,7 @@ typedef struct
#define PSW_MASK_EA 0x0000000100000000UL #define PSW_MASK_EA 0x0000000100000000UL
#define PSW_MASK_BA 0x0000000080000000UL #define PSW_MASK_BA 0x0000000080000000UL
#define PSW_MASK_USER 0x0000FF8180000000UL #define PSW_MASK_USER 0x0000FF0180000000UL
#define PSW_ADDR_AMODE 0x0000000000000000UL #define PSW_ADDR_AMODE 0x0000000000000000UL
#define PSW_ADDR_INSN 0xFFFFFFFFFFFFFFFFUL #define PSW_ADDR_INSN 0xFFFFFFFFFFFFFFFFUL

View File

@ -156,8 +156,9 @@ static int save_sigregs32(struct pt_regs *regs, _sigregs32 __user *sregs)
_sigregs32 user_sregs; _sigregs32 user_sregs;
int i; int i;
user_sregs.regs.psw.mask = psw32_user_bits | user_sregs.regs.psw.mask = (__u32)(regs->psw.mask >> 32);
((__u32)(regs->psw.mask >> 32) & PSW32_MASK_USER); user_sregs.regs.psw.mask &= PSW32_MASK_USER | PSW32_MASK_RI;
user_sregs.regs.psw.mask |= psw32_user_bits;
user_sregs.regs.psw.addr = (__u32) regs->psw.addr | user_sregs.regs.psw.addr = (__u32) regs->psw.addr |
(__u32)(regs->psw.mask & PSW_MASK_BA); (__u32)(regs->psw.mask & PSW_MASK_BA);
for (i = 0; i < NUM_GPRS; i++) for (i = 0; i < NUM_GPRS; i++)
@ -185,6 +186,9 @@ static int restore_sigregs32(struct pt_regs *regs,_sigregs32 __user *sregs)
if (__copy_from_user(&user_sregs, &sregs->regs, sizeof(user_sregs))) if (__copy_from_user(&user_sregs, &sregs->regs, sizeof(user_sregs)))
return -EFAULT; return -EFAULT;
if (!is_ri_task(current) && (user_sregs.regs.psw.mask & PSW32_MASK_RI))
return -EINVAL;
/* Loading the floating-point-control word can fail. Do that first. */ /* Loading the floating-point-control word can fail. Do that first. */
if (restore_fp_ctl(&user_sregs.fpregs.fpc)) if (restore_fp_ctl(&user_sregs.fpregs.fpc))
return -EINVAL; return -EINVAL;
@ -192,6 +196,7 @@ static int restore_sigregs32(struct pt_regs *regs,_sigregs32 __user *sregs)
/* Use regs->psw.mask instead of PSW_USER_BITS to preserve PER bit. */ /* Use regs->psw.mask instead of PSW_USER_BITS to preserve PER bit. */
regs->psw.mask = (regs->psw.mask & ~PSW_MASK_USER) | regs->psw.mask = (regs->psw.mask & ~PSW_MASK_USER) |
(__u64)(user_sregs.regs.psw.mask & PSW32_MASK_USER) << 32 | (__u64)(user_sregs.regs.psw.mask & PSW32_MASK_USER) << 32 |
(__u64)(user_sregs.regs.psw.mask & PSW32_MASK_RI) << 32 |
(__u64)(user_sregs.regs.psw.addr & PSW32_ADDR_AMODE); (__u64)(user_sregs.regs.psw.addr & PSW32_ADDR_AMODE);
/* Check for invalid user address space control. */ /* Check for invalid user address space control. */
if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_HOME) if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_HOME)

View File

@ -198,9 +198,11 @@ static unsigned long __peek_user(struct task_struct *child, addr_t addr)
* psw and gprs are stored on the stack * psw and gprs are stored on the stack
*/ */
tmp = *(addr_t *)((addr_t) &task_pt_regs(child)->psw + addr); tmp = *(addr_t *)((addr_t) &task_pt_regs(child)->psw + addr);
if (addr == (addr_t) &dummy->regs.psw.mask) if (addr == (addr_t) &dummy->regs.psw.mask) {
/* Return a clean psw mask. */ /* Return a clean psw mask. */
tmp = PSW_USER_BITS | (tmp & PSW_MASK_USER); tmp &= PSW_MASK_USER | PSW_MASK_RI;
tmp |= PSW_USER_BITS;
}
} else if (addr < (addr_t) &dummy->regs.orig_gpr2) { } else if (addr < (addr_t) &dummy->regs.orig_gpr2) {
/* /*
@ -320,11 +322,15 @@ static int __poke_user(struct task_struct *child, addr_t addr, addr_t data)
/* /*
* psw and gprs are stored on the stack * psw and gprs are stored on the stack
*/ */
if (addr == (addr_t) &dummy->regs.psw.mask && if (addr == (addr_t) &dummy->regs.psw.mask) {
((data & ~PSW_MASK_USER) != PSW_USER_BITS || unsigned long mask = PSW_MASK_USER;
((data & PSW_MASK_EA) && !(data & PSW_MASK_BA))))
/* Invalid psw mask. */ mask |= is_ri_task(child) ? PSW_MASK_RI : 0;
return -EINVAL; if ((data & ~mask) != PSW_USER_BITS)
return -EINVAL;
if ((data & PSW_MASK_EA) && !(data & PSW_MASK_BA))
return -EINVAL;
}
*(addr_t *)((addr_t) &task_pt_regs(child)->psw + addr) = data; *(addr_t *)((addr_t) &task_pt_regs(child)->psw + addr) = data;
} else if (addr < (addr_t) (&dummy->regs.orig_gpr2)) { } else if (addr < (addr_t) (&dummy->regs.orig_gpr2)) {
@ -556,7 +562,8 @@ static u32 __peek_user_compat(struct task_struct *child, addr_t addr)
if (addr == (addr_t) &dummy32->regs.psw.mask) { if (addr == (addr_t) &dummy32->regs.psw.mask) {
/* Fake a 31 bit psw mask. */ /* Fake a 31 bit psw mask. */
tmp = (__u32)(regs->psw.mask >> 32); tmp = (__u32)(regs->psw.mask >> 32);
tmp = psw32_user_bits | (tmp & PSW32_MASK_USER); tmp &= PSW32_MASK_USER | PSW32_MASK_RI;
tmp |= psw32_user_bits;
} else if (addr == (addr_t) &dummy32->regs.psw.addr) { } else if (addr == (addr_t) &dummy32->regs.psw.addr) {
/* Fake a 31 bit psw address. */ /* Fake a 31 bit psw address. */
tmp = (__u32) regs->psw.addr | tmp = (__u32) regs->psw.addr |
@ -653,13 +660,16 @@ static int __poke_user_compat(struct task_struct *child,
* psw, gprs, acrs and orig_gpr2 are stored on the stack * psw, gprs, acrs and orig_gpr2 are stored on the stack
*/ */
if (addr == (addr_t) &dummy32->regs.psw.mask) { if (addr == (addr_t) &dummy32->regs.psw.mask) {
__u32 mask = PSW32_MASK_USER;
mask |= is_ri_task(child) ? PSW32_MASK_RI : 0;
/* Build a 64 bit psw mask from 31 bit mask. */ /* Build a 64 bit psw mask from 31 bit mask. */
if ((tmp & ~PSW32_MASK_USER) != psw32_user_bits) if ((tmp & ~mask) != psw32_user_bits)
/* Invalid psw mask. */ /* Invalid psw mask. */
return -EINVAL; return -EINVAL;
regs->psw.mask = (regs->psw.mask & ~PSW_MASK_USER) | regs->psw.mask = (regs->psw.mask & ~PSW_MASK_USER) |
(regs->psw.mask & PSW_MASK_BA) | (regs->psw.mask & PSW_MASK_BA) |
(__u64)(tmp & PSW32_MASK_USER) << 32; (__u64)(tmp & mask) << 32;
} else if (addr == (addr_t) &dummy32->regs.psw.addr) { } else if (addr == (addr_t) &dummy32->regs.psw.addr) {
/* Build a 64 bit psw address from 31 bit address. */ /* Build a 64 bit psw address from 31 bit address. */
regs->psw.addr = (__u64) tmp & PSW32_ADDR_INSN; regs->psw.addr = (__u64) tmp & PSW32_ADDR_INSN;

View File

@ -58,7 +58,7 @@ static int save_sigregs(struct pt_regs *regs, _sigregs __user *sregs)
/* Copy a 'clean' PSW mask to the user to avoid leaking /* Copy a 'clean' PSW mask to the user to avoid leaking
information about whether PER is currently on. */ information about whether PER is currently on. */
user_sregs.regs.psw.mask = PSW_USER_BITS | user_sregs.regs.psw.mask = PSW_USER_BITS |
(regs->psw.mask & PSW_MASK_USER); (regs->psw.mask & (PSW_MASK_USER | PSW_MASK_RI));
user_sregs.regs.psw.addr = regs->psw.addr; user_sregs.regs.psw.addr = regs->psw.addr;
memcpy(&user_sregs.regs.gprs, &regs->gprs, sizeof(sregs->regs.gprs)); memcpy(&user_sregs.regs.gprs, &regs->gprs, sizeof(sregs->regs.gprs));
memcpy(&user_sregs.regs.acrs, current->thread.acrs, memcpy(&user_sregs.regs.acrs, current->thread.acrs,
@ -86,13 +86,16 @@ static int restore_sigregs(struct pt_regs *regs, _sigregs __user *sregs)
if (__copy_from_user(&user_sregs, sregs, sizeof(user_sregs))) if (__copy_from_user(&user_sregs, sregs, sizeof(user_sregs)))
return -EFAULT; return -EFAULT;
if (!is_ri_task(current) && (user_sregs.regs.psw.mask & PSW_MASK_RI))
return -EINVAL;
/* Loading the floating-point-control word can fail. Do that first. */ /* Loading the floating-point-control word can fail. Do that first. */
if (restore_fp_ctl(&user_sregs.fpregs.fpc)) if (restore_fp_ctl(&user_sregs.fpregs.fpc))
return -EINVAL; return -EINVAL;
/* Use regs->psw.mask instead of PSW_USER_BITS to preserve PER bit. */ /* Use regs->psw.mask instead of PSW_USER_BITS to preserve PER bit. */
regs->psw.mask = (regs->psw.mask & ~PSW_MASK_USER) | regs->psw.mask = (regs->psw.mask & ~PSW_MASK_USER) |
(user_sregs.regs.psw.mask & PSW_MASK_USER); (user_sregs.regs.psw.mask & (PSW_MASK_USER | PSW_MASK_RI));
/* Check for invalid user address space control. */ /* Check for invalid user address space control. */
if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_HOME) if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_HOME)
regs->psw.mask = PSW_ASC_PRIMARY | regs->psw.mask = PSW_ASC_PRIMARY |