| From: Michael Ellerman <mpe@ellerman.id.au> |
| Date: Thu, 24 Aug 2017 20:49:57 +1000 |
| Subject: powerpc: Fix DAR reporting when alignment handler faults |
| |
| commit f9effe925039cf54489b5c04e0d40073bb3a123d upstream. |
| |
| Anton noticed that if we fault part way through emulating an unaligned |
| instruction, we don't update the DAR to reflect that. |
| |
| The DAR value is eventually reported back to userspace as the address |
| in the SEGV signal, and if userspace is using that value to demand |
| fault then it can be confused by us not setting the value correctly. |
| |
| This patch is ugly as hell, but is intended to be the minimal fix and |
| back ports easily. |
| |
| Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> |
| Reviewed-by: Paul Mackerras <paulus@ozlabs.org> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| arch/powerpc/kernel/align.c | 119 +++++++++++++++++++++++++++----------------- |
| 1 file changed, 74 insertions(+), 45 deletions(-) |
| |
| --- a/arch/powerpc/kernel/align.c |
| +++ b/arch/powerpc/kernel/align.c |
| @@ -236,6 +236,28 @@ static int emulate_dcbz(struct pt_regs * |
| |
| #define SWIZ_PTR(p) ((unsigned char __user *)((p) ^ swiz)) |
| |
| +#define __get_user_or_set_dar(_regs, _dest, _addr) \ |
| + ({ \ |
| + int rc = 0; \ |
| + typeof(_addr) __addr = (_addr); \ |
| + if (__get_user_inatomic(_dest, __addr)) { \ |
| + _regs->dar = (unsigned long)__addr; \ |
| + rc = -EFAULT; \ |
| + } \ |
| + rc; \ |
| + }) |
| + |
| +#define __put_user_or_set_dar(_regs, _src, _addr) \ |
| + ({ \ |
| + int rc = 0; \ |
| + typeof(_addr) __addr = (_addr); \ |
| + if (__put_user_inatomic(_src, __addr)) { \ |
| + _regs->dar = (unsigned long)__addr; \ |
| + rc = -EFAULT; \ |
| + } \ |
| + rc; \ |
| + }) |
| + |
| static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr, |
| unsigned int reg, unsigned int nb, |
| unsigned int flags, unsigned int instr, |
| @@ -264,9 +286,10 @@ static int emulate_multiple(struct pt_re |
| } else { |
| unsigned long pc = regs->nip ^ (swiz & 4); |
| |
| - if (__get_user_inatomic(instr, |
| - (unsigned int __user *)pc)) |
| + if (__get_user_or_set_dar(regs, instr, |
| + (unsigned int __user *)pc)) |
| return -EFAULT; |
| + |
| if (swiz == 0 && (flags & SW)) |
| instr = cpu_to_le32(instr); |
| nb = (instr >> 11) & 0x1f; |
| @@ -310,31 +333,31 @@ static int emulate_multiple(struct pt_re |
| ((nb0 + 3) / 4) * sizeof(unsigned long)); |
| |
| for (i = 0; i < nb; ++i, ++p) |
| - if (__get_user_inatomic(REG_BYTE(rptr, i ^ bswiz), |
| - SWIZ_PTR(p))) |
| + if (__get_user_or_set_dar(regs, REG_BYTE(rptr, i ^ bswiz), |
| + SWIZ_PTR(p))) |
| return -EFAULT; |
| if (nb0 > 0) { |
| rptr = ®s->gpr[0]; |
| addr += nb; |
| for (i = 0; i < nb0; ++i, ++p) |
| - if (__get_user_inatomic(REG_BYTE(rptr, |
| - i ^ bswiz), |
| - SWIZ_PTR(p))) |
| + if (__get_user_or_set_dar(regs, |
| + REG_BYTE(rptr, i ^ bswiz), |
| + SWIZ_PTR(p))) |
| return -EFAULT; |
| } |
| |
| } else { |
| for (i = 0; i < nb; ++i, ++p) |
| - if (__put_user_inatomic(REG_BYTE(rptr, i ^ bswiz), |
| - SWIZ_PTR(p))) |
| + if (__put_user_or_set_dar(regs, REG_BYTE(rptr, i ^ bswiz), |
| + SWIZ_PTR(p))) |
| return -EFAULT; |
| if (nb0 > 0) { |
| rptr = ®s->gpr[0]; |
| addr += nb; |
| for (i = 0; i < nb0; ++i, ++p) |
| - if (__put_user_inatomic(REG_BYTE(rptr, |
| - i ^ bswiz), |
| - SWIZ_PTR(p))) |
| + if (__put_user_or_set_dar(regs, |
| + REG_BYTE(rptr, i ^ bswiz), |
| + SWIZ_PTR(p))) |
| return -EFAULT; |
| } |
| } |
| @@ -346,29 +369,32 @@ static int emulate_multiple(struct pt_re |
| * Only POWER6 has these instructions, and it does true little-endian, |
| * so we don't need the address swizzling. |
| */ |
| -static int emulate_fp_pair(unsigned char __user *addr, unsigned int reg, |
| - unsigned int flags) |
| +static int emulate_fp_pair(struct pt_regs *regs, unsigned char __user *addr, |
| + unsigned int reg, unsigned int flags) |
| { |
| char *ptr0 = (char *) ¤t->thread.TS_FPR(reg); |
| char *ptr1 = (char *) ¤t->thread.TS_FPR(reg+1); |
| - int i, ret, sw = 0; |
| + int i, sw = 0; |
| |
| if (reg & 1) |
| return 0; /* invalid form: FRS/FRT must be even */ |
| if (flags & SW) |
| sw = 7; |
| - ret = 0; |
| + |
| for (i = 0; i < 8; ++i) { |
| if (!(flags & ST)) { |
| - ret |= __get_user(ptr0[i^sw], addr + i); |
| - ret |= __get_user(ptr1[i^sw], addr + i + 8); |
| + if (__get_user_or_set_dar(regs, ptr0[i^sw], addr + i)) |
| + return -EFAULT; |
| + if (__get_user_or_set_dar(regs, ptr1[i^sw], addr + i + 8)) |
| + return -EFAULT; |
| } else { |
| - ret |= __put_user(ptr0[i^sw], addr + i); |
| - ret |= __put_user(ptr1[i^sw], addr + i + 8); |
| + if (__put_user_or_set_dar(regs, ptr0[i^sw], addr + i)) |
| + return -EFAULT; |
| + if (__put_user_or_set_dar(regs, ptr1[i^sw], addr + i + 8)) |
| + return -EFAULT; |
| } |
| } |
| - if (ret) |
| - return -EFAULT; |
| + |
| return 1; /* exception handled and fixed up */ |
| } |
| |
| @@ -378,24 +404,27 @@ static int emulate_lq_stq(struct pt_regs |
| { |
| char *ptr0 = (char *)®s->gpr[reg]; |
| char *ptr1 = (char *)®s->gpr[reg+1]; |
| - int i, ret, sw = 0; |
| + int i, sw = 0; |
| |
| if (reg & 1) |
| return 0; /* invalid form: GPR must be even */ |
| if (flags & SW) |
| sw = 7; |
| - ret = 0; |
| + |
| for (i = 0; i < 8; ++i) { |
| if (!(flags & ST)) { |
| - ret |= __get_user(ptr0[i^sw], addr + i); |
| - ret |= __get_user(ptr1[i^sw], addr + i + 8); |
| + if (__get_user_or_set_dar(regs, ptr0[i^sw], addr + i)) |
| + return -EFAULT; |
| + if (__get_user_or_set_dar(regs, ptr1[i^sw], addr + i + 8)) |
| + return -EFAULT; |
| } else { |
| - ret |= __put_user(ptr0[i^sw], addr + i); |
| - ret |= __put_user(ptr1[i^sw], addr + i + 8); |
| + if (__put_user_or_set_dar(regs, ptr0[i^sw], addr + i)) |
| + return -EFAULT; |
| + if (__put_user_or_set_dar(regs, ptr1[i^sw], addr + i + 8)) |
| + return -EFAULT; |
| } |
| } |
| - if (ret) |
| - return -EFAULT; |
| + |
| return 1; /* exception handled and fixed up */ |
| } |
| #endif /* CONFIG_PPC64 */ |
| @@ -688,9 +717,14 @@ static int emulate_vsx(unsigned char __u |
| for (j = 0; j < length; j += elsize) { |
| for (i = 0; i < elsize; ++i) { |
| if (flags & ST) |
| - ret |= __put_user(ptr[i^sw], addr + i); |
| + ret = __put_user_or_set_dar(regs, ptr[i^sw], |
| + addr + i); |
| else |
| - ret |= __get_user(ptr[i^sw], addr + i); |
| + ret = __get_user_or_set_dar(regs, ptr[i^sw], |
| + addr + i); |
| + |
| + if (ret) |
| + return ret; |
| } |
| ptr += elsize; |
| #ifdef __LITTLE_ENDIAN__ |
| @@ -740,7 +774,7 @@ int fix_alignment(struct pt_regs *regs) |
| unsigned int dsisr; |
| unsigned char __user *addr; |
| unsigned long p, swiz; |
| - int ret, i; |
| + int i; |
| union data { |
| u64 ll; |
| double dd; |
| @@ -923,7 +957,7 @@ int fix_alignment(struct pt_regs *regs) |
| if (flags & F) { |
| /* Special case for 16-byte FP loads and stores */ |
| PPC_WARN_ALIGNMENT(fp_pair, regs); |
| - return emulate_fp_pair(addr, reg, flags); |
| + return emulate_fp_pair(regs, addr, reg, flags); |
| } else { |
| #ifdef CONFIG_PPC64 |
| /* Special case for 16-byte loads and stores */ |
| @@ -953,15 +987,12 @@ int fix_alignment(struct pt_regs *regs) |
| } |
| |
| data.ll = 0; |
| - ret = 0; |
| p = (unsigned long)addr; |
| |
| for (i = 0; i < nb; i++) |
| - ret |= __get_user_inatomic(data.v[start + i], |
| - SWIZ_PTR(p++)); |
| - |
| - if (unlikely(ret)) |
| - return -EFAULT; |
| + if (__get_user_or_set_dar(regs, data.v[start + i], |
| + SWIZ_PTR(p++))) |
| + return -EFAULT; |
| |
| } else if (flags & F) { |
| data.ll = current->thread.TS_FPR(reg); |
| @@ -1031,15 +1062,13 @@ int fix_alignment(struct pt_regs *regs) |
| break; |
| } |
| |
| - ret = 0; |
| p = (unsigned long)addr; |
| |
| for (i = 0; i < nb; i++) |
| - ret |= __put_user_inatomic(data.v[start + i], |
| - SWIZ_PTR(p++)); |
| + if (__put_user_or_set_dar(regs, data.v[start + i], |
| + SWIZ_PTR(p++))) |
| + return -EFAULT; |
| |
| - if (unlikely(ret)) |
| - return -EFAULT; |
| } else if (flags & F) |
| current->thread.TS_FPR(reg) = data.ll; |
| else |