Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ptrace: Properly initialize ptracer_cred on fork

When I introduced ptracer_cred I failed to consider the weirdness of
fork where the task_struct copies the old value by default. This
winds up leaving ptracer_cred set even when a process forks and
the child process does not wind up being ptraced.

Because ptracer_cred is not set on non-ptraced processes whose
parents were ptraced this has broken the ability of the enlightenment
window manager to start setuid children.

Fix this by properly initializing ptracer_cred in ptrace_init_task

This must be done with a little bit of care to preserve the current value
of ptracer_cred when ptrace carries through fork. Re-reading the
ptracer_cred from the ptracing process at this point is inconsistent
with how PT_PTRACE_CAP has been maintained all of these years.

Tested-by: Takashi Iwai <tiwai@suse.de>
Fixes: 64b875f7ac8a ("ptrace: Capture the ptracer's creds not PT_PTRACE_CAP")
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>

+18 -9
+5 -2
include/linux/ptrace.h
··· 54 54 unsigned long addr, unsigned long data); 55 55 extern void ptrace_notify(int exit_code); 56 56 extern void __ptrace_link(struct task_struct *child, 57 - struct task_struct *new_parent); 57 + struct task_struct *new_parent, 58 + const struct cred *ptracer_cred); 58 59 extern void __ptrace_unlink(struct task_struct *child); 59 60 extern void exit_ptrace(struct task_struct *tracer, struct list_head *dead); 60 61 #define PTRACE_MODE_READ 0x01 ··· 207 206 208 207 if (unlikely(ptrace) && current->ptrace) { 209 208 child->ptrace = current->ptrace; 210 - __ptrace_link(child, current->parent); 209 + __ptrace_link(child, current->parent, current->ptracer_cred); 211 210 212 211 if (child->ptrace & PT_SEIZED) 213 212 task_set_jobctl_pending(child, JOBCTL_TRAP_STOP); ··· 216 215 217 216 set_tsk_thread_flag(child, TIF_SIGPENDING); 218 217 } 218 + else 219 + child->ptracer_cred = NULL; 219 220 } 220 221 221 222 /**
+13 -7
kernel/ptrace.c
··· 60 60 } 61 61 62 62 63 + void __ptrace_link(struct task_struct *child, struct task_struct *new_parent, 64 + const struct cred *ptracer_cred) 65 + { 66 + BUG_ON(!list_empty(&child->ptrace_entry)); 67 + list_add(&child->ptrace_entry, &new_parent->ptraced); 68 + child->parent = new_parent; 69 + child->ptracer_cred = get_cred(ptracer_cred); 70 + } 71 + 63 72 /* 64 73 * ptrace a task: make the debugger its new parent and 65 74 * move it to the ptrace list. 66 75 * 67 76 * Must be called with the tasklist lock write-held. 68 77 */ 69 - void __ptrace_link(struct task_struct *child, struct task_struct *new_parent) 78 + static void ptrace_link(struct task_struct *child, struct task_struct *new_parent) 70 79 { 71 - BUG_ON(!list_empty(&child->ptrace_entry)); 72 - list_add(&child->ptrace_entry, &new_parent->ptraced); 73 - child->parent = new_parent; 74 80 rcu_read_lock(); 75 - child->ptracer_cred = get_cred(__task_cred(new_parent)); 81 + __ptrace_link(child, new_parent, __task_cred(new_parent)); 76 82 rcu_read_unlock(); 77 83 } 78 84 ··· 392 386 flags |= PT_SEIZED; 393 387 task->ptrace = flags; 394 388 395 - __ptrace_link(task, current); 389 + ptrace_link(task, current); 396 390 397 391 /* SEIZE doesn't trap tracee on attach */ 398 392 if (!seize) ··· 465 459 */ 466 460 if (!ret && !(current->real_parent->flags & PF_EXITING)) { 467 461 current->ptrace = PT_PTRACED; 468 - __ptrace_link(current, current->real_parent); 462 + ptrace_link(current, current->real_parent); 469 463 } 470 464 } 471 465 write_unlock_irq(&tasklist_lock);