Skip to content

Commit

Permalink
arch/tile: don't allow user code to set the PL via ptrace or signal r…
Browse files Browse the repository at this point in the history
…eturn

The kernel was allowing any component of the pt_regs to be updated either
by signal handlers writing to the stack, or by processes writing via
PTRACE_POKEUSR or PTRACE_SETREGS, which meant they could set their PL
up from 0 to 1 and get access to kernel code and data (or, in practice,
cause a kernel panic).  We now always reset the ex1 field, allowing the
user to set their ICS bit only.

Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
  • Loading branch information
Chris Metcalf committed Nov 1, 2010
1 parent 34a89d2 commit 1deb9c5
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 18 deletions.
39 changes: 21 additions & 18 deletions arch/tile/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ long arch_ptrace(struct task_struct *child, long request,
{
unsigned long __user *datap = (long __user __force *)data;
unsigned long tmp;
int i;
long ret = -EIO;
unsigned long *childregs;
char *childreg;
struct pt_regs copyregs;
int ex1_offset;

switch (request) {

Expand All @@ -80,6 +80,16 @@ long arch_ptrace(struct task_struct *child, long request,
if (addr >= PTREGS_SIZE)
break;
childreg = (char *)task_pt_regs(child) + addr;

/* Guard against overwrites of the privilege level. */
ex1_offset = PTREGS_OFFSET_EX1;
#if defined(CONFIG_COMPAT) && defined(__BIG_ENDIAN)
if (is_compat_task()) /* point at low word */
ex1_offset += sizeof(compat_long_t);
#endif
if (addr == ex1_offset)
data = PL_ICS_EX1(USER_PL, EX1_ICS(data));

#ifdef CONFIG_COMPAT
if (is_compat_task()) {
if (addr & (sizeof(compat_long_t)-1))
Expand All @@ -96,26 +106,19 @@ long arch_ptrace(struct task_struct *child, long request,
break;

case PTRACE_GETREGS: /* Get all registers from the child. */
if (!access_ok(VERIFY_WRITE, datap, PTREGS_SIZE))
break;
childregs = (long *)task_pt_regs(child);
for (i = 0; i < sizeof(struct pt_regs)/sizeof(unsigned long);
++i) {
ret = __put_user(childregs[i], &datap[i]);
if (ret != 0)
break;
if (copy_to_user(datap, task_pt_regs(child),
sizeof(struct pt_regs)) == 0) {
ret = 0;
}
break;

case PTRACE_SETREGS: /* Set all registers in the child. */
if (!access_ok(VERIFY_READ, datap, PTREGS_SIZE))
break;
childregs = (long *)task_pt_regs(child);
for (i = 0; i < sizeof(struct pt_regs)/sizeof(unsigned long);
++i) {
ret = __get_user(childregs[i], &datap[i]);
if (ret != 0)
break;
if (copy_from_user(&copyregs, datap,
sizeof(struct pt_regs)) == 0) {
copyregs.ex1 =
PL_ICS_EX1(USER_PL, EX1_ICS(copyregs.ex1));
*task_pt_regs(child) = copyregs;
ret = 0;
}
break;

Expand Down
3 changes: 3 additions & 0 deletions arch/tile/kernel/signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ int restore_sigcontext(struct pt_regs *regs,
for (i = 0; i < sizeof(struct pt_regs)/sizeof(long); ++i)
err |= __get_user(regs->regs[i], &sc->gregs[i]);

/* Ensure that the PL is always set to USER_PL. */
regs->ex1 = PL_ICS_EX1(USER_PL, EX1_ICS(regs->ex1));

regs->faultnum = INT_SWINT_1_SIGRETURN;

err |= __get_user(*pr0, &sc->gregs[0]);
Expand Down

0 comments on commit 1deb9c5

Please sign in to comment.