alistair23-linux/arch/metag/kernel/ptrace.c
Linus Torvalds 72c33734b5 Merge branch 'regset' (PTRACE_SETREGSET data leakage)
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
2017-03-29 08:55:25 -07:00

428 lines
10 KiB
C

/*
* Copyright (C) 2005-2012 Imagination Technologies Ltd.
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file COPYING in the main directory of
* this archive for more details.
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/user.h>
#include <linux/regset.h>
#include <linux/tracehook.h>
#include <linux/elf.h>
#include <linux/uaccess.h>
#include <linux/sched/task_stack.h>
#include <trace/syscall.h>
#define CREATE_TRACE_POINTS
#include <trace/events/syscalls.h>
/*
* 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)
{
const void *ptr;
unsigned long data;
int ret;
/* D{0-1}.{0-7} */
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
regs->ctx.DX, 0, 4*16);
if (ret)
goto out;
/* A{0-1}.{0-1} */
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
regs->ctx.AX, 4*16, 4*20);
if (ret)
goto out;
/* A{0-1}.2 */
if (regs->ctx.SaveMask & TBICTX_XEXT_BIT)
ptr = regs->ctx.Ext.Ctx.pExt;
else
ptr = &regs->ctx.Ext.AX2;
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
ptr, 4*20, 4*22);
if (ret)
goto out;
/* A{0-1}.3 */
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
&regs->ctx.AX3, 4*22, 4*24);
if (ret)
goto out;
/* PC */
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
&regs->ctx.CurrPC, 4*24, 4*25);
if (ret)
goto out;
/* TXSTATUS */
data = user_txstatus(regs);
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
&data, 4*25, 4*26);
if (ret)
goto out;
/* TXRPT, TXBPOBITS, TXMODE */
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
&regs->ctx.CurrRPT, 4*26, 4*29);
if (ret)
goto out;
/* Padding */
ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
4*29, 4*30);
out:
return ret;
}
int metag_gp_regs_copyin(struct pt_regs *regs,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
void *ptr;
unsigned long data;
int ret;
/* D{0-1}.{0-7} */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
regs->ctx.DX, 0, 4*16);
if (ret)
goto out;
/* A{0-1}.{0-1} */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
regs->ctx.AX, 4*16, 4*20);
if (ret)
goto out;
/* A{0-1}.2 */
if (regs->ctx.SaveMask & TBICTX_XEXT_BIT)
ptr = regs->ctx.Ext.Ctx.pExt;
else
ptr = &regs->ctx.Ext.AX2;
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
ptr, 4*20, 4*22);
if (ret)
goto out;
/* A{0-1}.3 */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&regs->ctx.AX3, 4*22, 4*24);
if (ret)
goto out;
/* PC */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&regs->ctx.CurrPC, 4*24, 4*25);
if (ret)
goto out;
/* TXSTATUS */
data = user_txstatus(regs);
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&data, 4*25, 4*26);
if (ret)
goto out;
regs->ctx.Flags = data & 0xffff;
if (data & USER_GP_REGS_STATUS_CATCH_BIT)
regs->ctx.SaveMask |= TBICTX_XCBF_BIT | TBICTX_CBUF_BIT;
else
regs->ctx.SaveMask &= ~TBICTX_CBUF_BIT;
/* TXRPT, TXBPOBITS, TXMODE */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
&regs->ctx.CurrRPT, 4*26, 4*29);
out:
return ret;
}
static int metag_gp_regs_get(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
void *kbuf, void __user *ubuf)
{
const struct pt_regs *regs = task_pt_regs(target);
return metag_gp_regs_copyout(regs, pos, count, kbuf, ubuf);
}
static int metag_gp_regs_set(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
struct pt_regs *regs = task_pt_regs(target);
return metag_gp_regs_copyin(regs, pos, count, kbuf, ubuf);
}
int metag_cb_regs_copyout(const struct pt_regs *regs,
unsigned int pos, unsigned int count,
void *kbuf, void __user *ubuf)
{
int ret;
/* TXCATCH{0-3} */
if (regs->ctx.SaveMask & TBICTX_XCBF_BIT)
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
regs->extcb0, 0, 4*4);
else
ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
0, 4*4);
return ret;
}
int metag_cb_regs_copyin(struct pt_regs *regs,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
int ret;
/* TXCATCH{0-3} */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
regs->extcb0, 0, 4*4);
return ret;
}
static int metag_cb_regs_get(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
void *kbuf, void __user *ubuf)
{
const struct pt_regs *regs = task_pt_regs(target);
return metag_cb_regs_copyout(regs, pos, count, kbuf, ubuf);
}
static int metag_cb_regs_set(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
struct pt_regs *regs = task_pt_regs(target);
return metag_cb_regs_copyin(regs, pos, count, kbuf, ubuf);
}
int metag_rp_state_copyout(const struct pt_regs *regs,
unsigned int pos, unsigned int count,
void *kbuf, void __user *ubuf)
{
unsigned long mask;
u64 *ptr;
int ret, i;
/* Empty read pipeline */
if (!(regs->ctx.SaveMask & TBICTX_CBRP_BIT)) {
ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
0, 4*13);
goto out;
}
mask = (regs->ctx.CurrDIVTIME & TXDIVTIME_RPMASK_BITS) >>
TXDIVTIME_RPMASK_S;
/* Read pipeline entries */
ptr = (void *)&regs->extcb0[1];
for (i = 0; i < 6; ++i, ++ptr) {
if (mask & (1 << i))
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
ptr, 8*i, 8*(i + 1));
else
ret = user_regset_copyout_zero(&pos, &count, &kbuf,
&ubuf, 8*i, 8*(i + 1));
if (ret)
goto out;
}
/* Mask of entries */
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
&mask, 4*12, 4*13);
out:
return ret;
}
int metag_rp_state_copyin(struct pt_regs *regs,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
struct user_rp_state rp;
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);
if (ret)
goto out;
/* Write pipeline entries */
ptr = (void *)&regs->extcb0[1];
for (i = 0; i < 6; ++i, ++ptr)
if (rp.mask & (1 << i))
*ptr = rp.entries[i];
/* Update RPMask in TXDIVTIME */
regs->ctx.CurrDIVTIME &= ~TXDIVTIME_RPMASK_BITS;
regs->ctx.CurrDIVTIME |= (rp.mask << TXDIVTIME_RPMASK_S)
& TXDIVTIME_RPMASK_BITS;
/* Set/clear flags to indicate catch/read pipeline state */
if (rp.mask)
regs->ctx.SaveMask |= TBICTX_XCBF_BIT | TBICTX_CBRP_BIT;
else
regs->ctx.SaveMask &= ~TBICTX_CBRP_BIT;
out:
return ret;
}
static int metag_rp_state_get(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
void *kbuf, void __user *ubuf)
{
const struct pt_regs *regs = task_pt_regs(target);
return metag_rp_state_copyout(regs, pos, count, kbuf, ubuf);
}
static int metag_rp_state_set(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
{
struct pt_regs *regs = task_pt_regs(target);
return metag_rp_state_copyin(regs, pos, count, kbuf, ubuf);
}
static int metag_tls_get(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
void *kbuf, void __user *ubuf)
{
void __user *tls = target->thread.tls_ptr;
return user_regset_copyout(&pos, &count, &kbuf, &ubuf, &tls, 0, -1);
}
static int metag_tls_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;
void __user *tls = target->thread.tls_ptr;
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &tls, 0, -1);
if (ret)
return ret;
target->thread.tls_ptr = tls;
return ret;
}
enum metag_regset {
REGSET_GENERAL,
REGSET_CBUF,
REGSET_READPIPE,
REGSET_TLS,
};
static const struct user_regset metag_regsets[] = {
[REGSET_GENERAL] = {
.core_note_type = NT_PRSTATUS,
.n = ELF_NGREG,
.size = sizeof(long),
.align = sizeof(long long),
.get = metag_gp_regs_get,
.set = metag_gp_regs_set,
},
[REGSET_CBUF] = {
.core_note_type = NT_METAG_CBUF,
.n = sizeof(struct user_cb_regs) / sizeof(long),
.size = sizeof(long),
.align = sizeof(long long),
.get = metag_cb_regs_get,
.set = metag_cb_regs_set,
},
[REGSET_READPIPE] = {
.core_note_type = NT_METAG_RPIPE,
.n = sizeof(struct user_rp_state) / sizeof(long),
.size = sizeof(long),
.align = sizeof(long long),
.get = metag_rp_state_get,
.set = metag_rp_state_set,
},
[REGSET_TLS] = {
.core_note_type = NT_METAG_TLS,
.n = 1,
.size = sizeof(void *),
.align = sizeof(void *),
.get = metag_tls_get,
.set = metag_tls_set,
},
};
static const struct user_regset_view user_metag_view = {
.name = "metag",
.e_machine = EM_METAG,
.regsets = metag_regsets,
.n = ARRAY_SIZE(metag_regsets)
};
const struct user_regset_view *task_user_regset_view(struct task_struct *task)
{
return &user_metag_view;
}
/*
* Called by kernel/ptrace.c when detaching..
*
* Make sure single step bits etc are not set.
*/
void ptrace_disable(struct task_struct *child)
{
/* nothing to do.. */
}
long arch_ptrace(struct task_struct *child, long request, unsigned long addr,
unsigned long data)
{
int ret;
switch (request) {
default:
ret = ptrace_request(child, request, addr, data);
break;
}
return ret;
}
int syscall_trace_enter(struct pt_regs *regs)
{
int ret = 0;
if (test_thread_flag(TIF_SYSCALL_TRACE))
ret = tracehook_report_syscall_entry(regs);
if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT)))
trace_sys_enter(regs, regs->ctx.DX[0].U1);
return ret ? -1 : regs->ctx.DX[0].U1;
}
void syscall_trace_leave(struct pt_regs *regs)
{
if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT)))
trace_sys_exit(regs, regs->ctx.DX[0].U1);
if (test_thread_flag(TIF_SYSCALL_TRACE))
tracehook_report_syscall_exit(regs, 0);
}