diff options
Diffstat (limited to 'kernel/ptrace.c')
-rw-r--r-- | kernel/ptrace.c | 180 |
1 files changed, 64 insertions, 116 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 0692ab5a0d67..61c78b2c07ba 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c | |||
@@ -25,16 +25,6 @@ | |||
25 | 25 | ||
26 | 26 | ||
27 | /* | 27 | /* |
28 | * Initialize a new task whose father had been ptraced. | ||
29 | * | ||
30 | * Called from copy_process(). | ||
31 | */ | ||
32 | void ptrace_fork(struct task_struct *child, unsigned long clone_flags) | ||
33 | { | ||
34 | arch_ptrace_fork(child, clone_flags); | ||
35 | } | ||
36 | |||
37 | /* | ||
38 | * ptrace a task: make the debugger its new parent and | 28 | * ptrace a task: make the debugger its new parent and |
39 | * move it to the ptrace list. | 29 | * move it to the ptrace list. |
40 | * | 30 | * |
@@ -177,66 +167,82 @@ bool ptrace_may_access(struct task_struct *task, unsigned int mode) | |||
177 | int ptrace_attach(struct task_struct *task) | 167 | int ptrace_attach(struct task_struct *task) |
178 | { | 168 | { |
179 | int retval; | 169 | int retval; |
180 | unsigned long flags; | ||
181 | 170 | ||
182 | audit_ptrace(task); | 171 | audit_ptrace(task); |
183 | 172 | ||
184 | retval = -EPERM; | 173 | retval = -EPERM; |
174 | if (unlikely(task->flags & PF_KTHREAD)) | ||
175 | goto out; | ||
185 | if (same_thread_group(task, current)) | 176 | if (same_thread_group(task, current)) |
186 | goto out; | 177 | goto out; |
187 | 178 | ||
188 | /* Protect exec's credential calculations against our interference; | 179 | /* |
189 | * SUID, SGID and LSM creds get determined differently under ptrace. | 180 | * Protect exec's credential calculations against our interference; |
181 | * interference; SUID, SGID and LSM creds get determined differently | ||
182 | * under ptrace. | ||
190 | */ | 183 | */ |
191 | retval = mutex_lock_interruptible(&task->cred_exec_mutex); | 184 | retval = mutex_lock_interruptible(&task->cred_guard_mutex); |
192 | if (retval < 0) | 185 | if (retval < 0) |
193 | goto out; | 186 | goto out; |
194 | 187 | ||
195 | retval = -EPERM; | ||
196 | repeat: | ||
197 | /* | ||
198 | * Nasty, nasty. | ||
199 | * | ||
200 | * We want to hold both the task-lock and the | ||
201 | * tasklist_lock for writing at the same time. | ||
202 | * But that's against the rules (tasklist_lock | ||
203 | * is taken for reading by interrupts on other | ||
204 | * cpu's that may have task_lock). | ||
205 | */ | ||
206 | task_lock(task); | 188 | task_lock(task); |
207 | if (!write_trylock_irqsave(&tasklist_lock, flags)) { | ||
208 | task_unlock(task); | ||
209 | do { | ||
210 | cpu_relax(); | ||
211 | } while (!write_can_lock(&tasklist_lock)); | ||
212 | goto repeat; | ||
213 | } | ||
214 | |||
215 | if (!task->mm) | ||
216 | goto bad; | ||
217 | /* the same process cannot be attached many times */ | ||
218 | if (task->ptrace & PT_PTRACED) | ||
219 | goto bad; | ||
220 | retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH); | 189 | retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH); |
190 | task_unlock(task); | ||
221 | if (retval) | 191 | if (retval) |
222 | goto bad; | 192 | goto unlock_creds; |
193 | |||
194 | write_lock_irq(&tasklist_lock); | ||
195 | retval = -EPERM; | ||
196 | if (unlikely(task->exit_state)) | ||
197 | goto unlock_tasklist; | ||
198 | if (task->ptrace) | ||
199 | goto unlock_tasklist; | ||
223 | 200 | ||
224 | /* Go */ | 201 | task->ptrace = PT_PTRACED; |
225 | task->ptrace |= PT_PTRACED; | ||
226 | if (capable(CAP_SYS_PTRACE)) | 202 | if (capable(CAP_SYS_PTRACE)) |
227 | task->ptrace |= PT_PTRACE_CAP; | 203 | task->ptrace |= PT_PTRACE_CAP; |
228 | 204 | ||
229 | __ptrace_link(task, current); | 205 | __ptrace_link(task, current); |
230 | |||
231 | send_sig_info(SIGSTOP, SEND_SIG_FORCED, task); | 206 | send_sig_info(SIGSTOP, SEND_SIG_FORCED, task); |
232 | bad: | 207 | |
233 | write_unlock_irqrestore(&tasklist_lock, flags); | 208 | retval = 0; |
234 | task_unlock(task); | 209 | unlock_tasklist: |
235 | mutex_unlock(&task->cred_exec_mutex); | 210 | write_unlock_irq(&tasklist_lock); |
211 | unlock_creds: | ||
212 | mutex_unlock(&task->cred_guard_mutex); | ||
236 | out: | 213 | out: |
237 | return retval; | 214 | return retval; |
238 | } | 215 | } |
239 | 216 | ||
217 | /** | ||
218 | * ptrace_traceme -- helper for PTRACE_TRACEME | ||
219 | * | ||
220 | * Performs checks and sets PT_PTRACED. | ||
221 | * Should be used by all ptrace implementations for PTRACE_TRACEME. | ||
222 | */ | ||
223 | int ptrace_traceme(void) | ||
224 | { | ||
225 | int ret = -EPERM; | ||
226 | |||
227 | write_lock_irq(&tasklist_lock); | ||
228 | /* Are we already being traced? */ | ||
229 | if (!current->ptrace) { | ||
230 | ret = security_ptrace_traceme(current->parent); | ||
231 | /* | ||
232 | * Check PF_EXITING to ensure ->real_parent has not passed | ||
233 | * exit_ptrace(). Otherwise we don't report the error but | ||
234 | * pretend ->real_parent untraces us right after return. | ||
235 | */ | ||
236 | if (!ret && !(current->real_parent->flags & PF_EXITING)) { | ||
237 | current->ptrace = PT_PTRACED; | ||
238 | __ptrace_link(current, current->real_parent); | ||
239 | } | ||
240 | } | ||
241 | write_unlock_irq(&tasklist_lock); | ||
242 | |||
243 | return ret; | ||
244 | } | ||
245 | |||
240 | /* | 246 | /* |
241 | * Called with irqs disabled, returns true if childs should reap themselves. | 247 | * Called with irqs disabled, returns true if childs should reap themselves. |
242 | */ | 248 | */ |
@@ -304,6 +310,8 @@ int ptrace_detach(struct task_struct *child, unsigned int data) | |||
304 | if (child->ptrace) { | 310 | if (child->ptrace) { |
305 | child->exit_code = data; | 311 | child->exit_code = data; |
306 | dead = __ptrace_detach(current, child); | 312 | dead = __ptrace_detach(current, child); |
313 | if (!child->exit_state) | ||
314 | wake_up_process(child); | ||
307 | } | 315 | } |
308 | write_unlock_irq(&tasklist_lock); | 316 | write_unlock_irq(&tasklist_lock); |
309 | 317 | ||
@@ -416,37 +424,33 @@ static int ptrace_setoptions(struct task_struct *child, long data) | |||
416 | 424 | ||
417 | static int ptrace_getsiginfo(struct task_struct *child, siginfo_t *info) | 425 | static int ptrace_getsiginfo(struct task_struct *child, siginfo_t *info) |
418 | { | 426 | { |
427 | unsigned long flags; | ||
419 | int error = -ESRCH; | 428 | int error = -ESRCH; |
420 | 429 | ||
421 | read_lock(&tasklist_lock); | 430 | if (lock_task_sighand(child, &flags)) { |
422 | if (likely(child->sighand != NULL)) { | ||
423 | error = -EINVAL; | 431 | error = -EINVAL; |
424 | spin_lock_irq(&child->sighand->siglock); | ||
425 | if (likely(child->last_siginfo != NULL)) { | 432 | if (likely(child->last_siginfo != NULL)) { |
426 | *info = *child->last_siginfo; | 433 | *info = *child->last_siginfo; |
427 | error = 0; | 434 | error = 0; |
428 | } | 435 | } |
429 | spin_unlock_irq(&child->sighand->siglock); | 436 | unlock_task_sighand(child, &flags); |
430 | } | 437 | } |
431 | read_unlock(&tasklist_lock); | ||
432 | return error; | 438 | return error; |
433 | } | 439 | } |
434 | 440 | ||
435 | static int ptrace_setsiginfo(struct task_struct *child, const siginfo_t *info) | 441 | static int ptrace_setsiginfo(struct task_struct *child, const siginfo_t *info) |
436 | { | 442 | { |
443 | unsigned long flags; | ||
437 | int error = -ESRCH; | 444 | int error = -ESRCH; |
438 | 445 | ||
439 | read_lock(&tasklist_lock); | 446 | if (lock_task_sighand(child, &flags)) { |
440 | if (likely(child->sighand != NULL)) { | ||
441 | error = -EINVAL; | 447 | error = -EINVAL; |
442 | spin_lock_irq(&child->sighand->siglock); | ||
443 | if (likely(child->last_siginfo != NULL)) { | 448 | if (likely(child->last_siginfo != NULL)) { |
444 | *child->last_siginfo = *info; | 449 | *child->last_siginfo = *info; |
445 | error = 0; | 450 | error = 0; |
446 | } | 451 | } |
447 | spin_unlock_irq(&child->sighand->siglock); | 452 | unlock_task_sighand(child, &flags); |
448 | } | 453 | } |
449 | read_unlock(&tasklist_lock); | ||
450 | return error; | 454 | return error; |
451 | } | 455 | } |
452 | 456 | ||
@@ -573,72 +577,16 @@ int ptrace_request(struct task_struct *child, long request, | |||
573 | return ret; | 577 | return ret; |
574 | } | 578 | } |
575 | 579 | ||
576 | /** | 580 | static struct task_struct *ptrace_get_task_struct(pid_t pid) |
577 | * ptrace_traceme -- helper for PTRACE_TRACEME | ||
578 | * | ||
579 | * Performs checks and sets PT_PTRACED. | ||
580 | * Should be used by all ptrace implementations for PTRACE_TRACEME. | ||
581 | */ | ||
582 | int ptrace_traceme(void) | ||
583 | { | ||
584 | int ret = -EPERM; | ||
585 | |||
586 | /* | ||
587 | * Are we already being traced? | ||
588 | */ | ||
589 | repeat: | ||
590 | task_lock(current); | ||
591 | if (!(current->ptrace & PT_PTRACED)) { | ||
592 | /* | ||
593 | * See ptrace_attach() comments about the locking here. | ||
594 | */ | ||
595 | unsigned long flags; | ||
596 | if (!write_trylock_irqsave(&tasklist_lock, flags)) { | ||
597 | task_unlock(current); | ||
598 | do { | ||
599 | cpu_relax(); | ||
600 | } while (!write_can_lock(&tasklist_lock)); | ||
601 | goto repeat; | ||
602 | } | ||
603 | |||
604 | ret = security_ptrace_traceme(current->parent); | ||
605 | |||
606 | /* | ||
607 | * Check PF_EXITING to ensure ->real_parent has not passed | ||
608 | * exit_ptrace(). Otherwise we don't report the error but | ||
609 | * pretend ->real_parent untraces us right after return. | ||
610 | */ | ||
611 | if (!ret && !(current->real_parent->flags & PF_EXITING)) { | ||
612 | current->ptrace |= PT_PTRACED; | ||
613 | __ptrace_link(current, current->real_parent); | ||
614 | } | ||
615 | |||
616 | write_unlock_irqrestore(&tasklist_lock, flags); | ||
617 | } | ||
618 | task_unlock(current); | ||
619 | return ret; | ||
620 | } | ||
621 | |||
622 | /** | ||
623 | * ptrace_get_task_struct -- grab a task struct reference for ptrace | ||
624 | * @pid: process id to grab a task_struct reference of | ||
625 | * | ||
626 | * This function is a helper for ptrace implementations. It checks | ||
627 | * permissions and then grabs a task struct for use of the actual | ||
628 | * ptrace implementation. | ||
629 | * | ||
630 | * Returns the task_struct for @pid or an ERR_PTR() on failure. | ||
631 | */ | ||
632 | struct task_struct *ptrace_get_task_struct(pid_t pid) | ||
633 | { | 581 | { |
634 | struct task_struct *child; | 582 | struct task_struct *child; |
635 | 583 | ||
636 | read_lock(&tasklist_lock); | 584 | rcu_read_lock(); |
637 | child = find_task_by_vpid(pid); | 585 | child = find_task_by_vpid(pid); |
638 | if (child) | 586 | if (child) |
639 | get_task_struct(child); | 587 | get_task_struct(child); |
588 | rcu_read_unlock(); | ||
640 | 589 | ||
641 | read_unlock(&tasklist_lock); | ||
642 | if (!child) | 590 | if (!child) |
643 | return ERR_PTR(-ESRCH); | 591 | return ERR_PTR(-ESRCH); |
644 | return child; | 592 | return child; |