Skip to content

Commit

Permalink
powerpc: Fix DAR reporting when alignment handler faults
Browse files Browse the repository at this point in the history
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.

Cc: stable@vger.kernel.org
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Reviewed-by: Paul Mackerras <paulus@ozlabs.org>
  • Loading branch information
Michael Ellerman committed Aug 31, 2017
1 parent afb5519 commit f9effe9
Showing 1 changed file with 74 additions and 45 deletions.
119 changes: 74 additions & 45 deletions arch/powerpc/kernel/align.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,28 @@ static int emulate_dcbz(struct pt_regs *regs, unsigned char __user *addr)

#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,
Expand Down Expand Up @@ -263,9 +285,10 @@ static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr,
} 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;
Expand Down Expand Up @@ -309,31 +332,31 @@ static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr,
((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 = &regs->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 = &regs->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;
}
}
Expand All @@ -345,29 +368,32 @@ static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr,
* 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 *) &current->thread.TS_FPR(reg);
char *ptr1 = (char *) &current->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 */
}

Expand All @@ -377,24 +403,27 @@ static int emulate_lq_stq(struct pt_regs *regs, unsigned char __user *addr,
{
char *ptr0 = (char *)&regs->gpr[reg];
char *ptr1 = (char *)&regs->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 */
Expand Down Expand Up @@ -687,9 +716,14 @@ static int emulate_vsx(unsigned char __user *addr, unsigned int reg,
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__
Expand Down Expand Up @@ -739,7 +773,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;
Expand Down Expand Up @@ -936,7 +970,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 */
Expand Down Expand Up @@ -966,15 +1000,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);
Expand Down Expand Up @@ -1046,15 +1077,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
Expand Down

0 comments on commit f9effe9

Please sign in to comment.