Skip to content

Commit

Permalink
Merge branch 'regset' (PTRACE_SETREGSET data leakage)
Browse files Browse the repository at this point in the history
Merge PTRACE_SETREGSET leakage fixes from Dave Martin:
 "This series is the collection of fixes I proposed on this topic, that
  have not yet appeared upstream or in the stable branches,

  The issue can leak kernel stack, but doesn't appear to allow userspace
  to attack the kernel directly.  The affected architectures are c6x,
  h8300, metag, mips and sparc.

  [ Mark Salter points out that c6x has no MMU or other mechanism to
    prevent userspace access to kernel code or data on c6x, but it
    doesn't hurt to clean that case up too. ]

  The bugs arise from use of user_regset_copyin(). Users of
  user_regset_copyin() can work in one of two ways:

   1) Copy directly to thread_struct or equivalent. (This seems to be
      the design assumption of the regset API, and is the most common
      approach.)

   2) Copy to a local variable and then transfer to thread_struct. (A
      significant minority of cases.)

  Buggy code typically involves approach 2"

* emailed patches from Dave Martin <Dave.Martin@arm.com>:
  sparc/ptrace: Preserve previous registers for short regset write
  mips/ptrace: Preserve previous registers for short regset write
  metag/ptrace: Reject partial NT_METAG_RPIPE writes
  metag/ptrace: Provide default TXSTATUS for short NT_PRSTATUS
  metag/ptrace: Preserve previous registers for short regset write
  h8300/ptrace: Fix incorrect register transfer count
  c6x/ptrace: Remove useless PTRACE_SETREGSET implementation
  • Loading branch information
Linus Torvalds committed Mar 29, 2017
2 parents fe82203 + d3805c5 commit 72c3373
Show file tree
Hide file tree
Showing 5 changed files with 23 additions and 50 deletions.
41 changes: 0 additions & 41 deletions arch/c6x/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,46 +70,6 @@ static int gpr_get(struct task_struct *target,
0, sizeof(*regs));
}

static int gpr_set(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
int ret;
struct pt_regs *regs = task_pt_regs(target);

/* Don't copyin TSR or CSR */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&regs,
0, PT_TSR * sizeof(long));
if (ret)
return ret;

ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
PT_TSR * sizeof(long),
(PT_TSR + 1) * sizeof(long));
if (ret)
return ret;

ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&regs,
(PT_TSR + 1) * sizeof(long),
PT_CSR * sizeof(long));
if (ret)
return ret;

ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
PT_CSR * sizeof(long),
(PT_CSR + 1) * sizeof(long));
if (ret)
return ret;

ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&regs,
(PT_CSR + 1) * sizeof(long), -1);
return ret;
}

enum c6x_regset {
REGSET_GPR,
};
Expand All @@ -121,7 +81,6 @@ static const struct user_regset c6x_regsets[] = {
.size = sizeof(u32),
.align = sizeof(u32),
.get = gpr_get,
.set = gpr_set
},
};

Expand Down
8 changes: 5 additions & 3 deletions arch/h8300/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ static int regs_get(struct task_struct *target,
long *reg = (long *)&regs;

/* build user regs in buffer */
for (r = 0; r < ARRAY_SIZE(register_offset); r++)
BUILD_BUG_ON(sizeof(regs) % sizeof(long) != 0);
for (r = 0; r < sizeof(regs) / sizeof(long); r++)
*reg++ = h8300_get_reg(target, r);

return user_regset_copyout(&pos, &count, &kbuf, &ubuf,
Expand All @@ -113,7 +114,8 @@ static int regs_set(struct task_struct *target,
long *reg;

/* build user regs in buffer */
for (reg = (long *)&regs, r = 0; r < ARRAY_SIZE(register_offset); r++)
BUILD_BUG_ON(sizeof(regs) % sizeof(long) != 0);
for (reg = (long *)&regs, r = 0; r < sizeof(regs) / sizeof(long); r++)
*reg++ = h8300_get_reg(target, r);

ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
Expand All @@ -122,7 +124,7 @@ static int regs_set(struct task_struct *target,
return ret;

/* write back to pt_regs */
for (reg = (long *)&regs, r = 0; r < ARRAY_SIZE(register_offset); r++)
for (reg = (long *)&regs, r = 0; r < sizeof(regs) / sizeof(long); r++)
h8300_put_reg(target, r, *reg++);
return 0;
}
Expand Down
19 changes: 15 additions & 4 deletions arch/metag/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
* user_regset definitions.
*/

static unsigned long user_txstatus(const struct pt_regs *regs)
{
unsigned long data = (unsigned long)regs->ctx.Flags;

if (regs->ctx.SaveMask & TBICTX_CBUF_BIT)
data |= USER_GP_REGS_STATUS_CATCH_BIT;

return data;
}

int metag_gp_regs_copyout(const struct pt_regs *regs,
unsigned int pos, unsigned int count,
void *kbuf, void __user *ubuf)
Expand Down Expand Up @@ -64,9 +74,7 @@ int metag_gp_regs_copyout(const struct pt_regs *regs,
if (ret)
goto out;
/* TXSTATUS */
data = (unsigned long)regs->ctx.Flags;
if (regs->ctx.SaveMask & TBICTX_CBUF_BIT)
data |= USER_GP_REGS_STATUS_CATCH_BIT;
data = user_txstatus(regs);
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
&data, 4*25, 4*26);
if (ret)
Expand Down Expand Up @@ -121,6 +129,7 @@ int metag_gp_regs_copyin(struct pt_regs *regs,
if (ret)
goto out;
/* TXSTATUS */
data = user_txstatus(regs);
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&data, 4*25, 4*26);
if (ret)
Expand Down Expand Up @@ -246,6 +255,8 @@ int metag_rp_state_copyin(struct pt_regs *regs,
unsigned long long *ptr;
int ret, i;

if (count < 4*13)
return -EINVAL;
/* Read the entire pipeline before making any changes */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&rp, 0, 4*13);
Expand Down Expand Up @@ -305,7 +316,7 @@ static int metag_tls_set(struct task_struct *target,
const void *kbuf, const void __user *ubuf)
{
int ret;
void __user *tls;
void __user *tls = target->thread.tls_ptr;

ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &tls, 0, -1);
if (ret)
Expand Down
3 changes: 2 additions & 1 deletion arch/mips/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,8 @@ static int fpr_set(struct task_struct *target,
&target->thread.fpu,
0, sizeof(elf_fpregset_t));

for (i = 0; i < NUM_FPU_REGS; i++) {
BUILD_BUG_ON(sizeof(fpr_val) != sizeof(elf_fpreg_t));
for (i = 0; i < NUM_FPU_REGS && count >= sizeof(elf_fpreg_t); i++) {
err = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&fpr_val, i * sizeof(elf_fpreg_t),
(i + 1) * sizeof(elf_fpreg_t));
Expand Down
2 changes: 1 addition & 1 deletion arch/sparc/kernel/ptrace_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ static int genregs64_set(struct task_struct *target,
}

if (!ret) {
unsigned long y;
unsigned long y = regs->y;

ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&y,
Expand Down

0 comments on commit 72c3373

Please sign in to comment.