![Linus Torvalds](/assets/img/avatar_default.png)
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
428 lines
10 KiB
C
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 = ®s->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,
|
|
®s->ctx.AX3, 4*22, 4*24);
|
|
if (ret)
|
|
goto out;
|
|
/* PC */
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
|
|
®s->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,
|
|
®s->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 = ®s->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,
|
|
®s->ctx.AX3, 4*22, 4*24);
|
|
if (ret)
|
|
goto out;
|
|
/* PC */
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
|
|
®s->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,
|
|
®s->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 *)®s->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 *)®s->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);
|
|
}
|