diff options
Diffstat (limited to 'kernel/ptrace.c')
-rw-r--r-- | kernel/ptrace.c | 163 |
1 files changed, 59 insertions, 104 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c index f6d8b8cb5e34..082c320e4dbf 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c | |||
@@ -167,67 +167,82 @@ bool ptrace_may_access(struct task_struct *task, unsigned int mode) | |||
167 | int ptrace_attach(struct task_struct *task) | 167 | int ptrace_attach(struct task_struct *task) |
168 | { | 168 | { |
169 | int retval; | 169 | int retval; |
170 | unsigned long flags; | ||
171 | 170 | ||
172 | audit_ptrace(task); | 171 | audit_ptrace(task); |
173 | 172 | ||
174 | retval = -EPERM; | 173 | retval = -EPERM; |
174 | if (unlikely(task->flags & PF_KTHREAD)) | ||
175 | goto out; | ||
175 | if (same_thread_group(task, current)) | 176 | if (same_thread_group(task, current)) |
176 | goto out; | 177 | goto out; |
177 | 178 | ||
178 | /* Protect the target's credential calculations against our | 179 | /* |
180 | * Protect exec's credential calculations against our interference; | ||
179 | * interference; SUID, SGID and LSM creds get determined differently | 181 | * interference; SUID, SGID and LSM creds get determined differently |
180 | * under ptrace. | 182 | * under ptrace. |
181 | */ | 183 | */ |
182 | retval = mutex_lock_interruptible(&task->cred_guard_mutex); | 184 | retval = -ERESTARTNOINTR; |
183 | if (retval < 0) | 185 | if (mutex_lock_interruptible(&task->cred_guard_mutex)) |
184 | goto out; | 186 | goto out; |
185 | 187 | ||
186 | retval = -EPERM; | ||
187 | repeat: | ||
188 | /* | ||
189 | * Nasty, nasty. | ||
190 | * | ||
191 | * We want to hold both the task-lock and the | ||
192 | * tasklist_lock for writing at the same time. | ||
193 | * But that's against the rules (tasklist_lock | ||
194 | * is taken for reading by interrupts on other | ||
195 | * cpu's that may have task_lock). | ||
196 | */ | ||
197 | task_lock(task); | 188 | task_lock(task); |
198 | if (!write_trylock_irqsave(&tasklist_lock, flags)) { | ||
199 | task_unlock(task); | ||
200 | do { | ||
201 | cpu_relax(); | ||
202 | } while (!write_can_lock(&tasklist_lock)); | ||
203 | goto repeat; | ||
204 | } | ||
205 | |||
206 | if (!task->mm) | ||
207 | goto bad; | ||
208 | /* the same process cannot be attached many times */ | ||
209 | if (task->ptrace & PT_PTRACED) | ||
210 | goto bad; | ||
211 | retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH); | 189 | retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH); |
190 | task_unlock(task); | ||
212 | if (retval) | 191 | if (retval) |
213 | goto bad; | 192 | goto unlock_creds; |
214 | 193 | ||
215 | /* Go */ | 194 | write_lock_irq(&tasklist_lock); |
216 | task->ptrace |= PT_PTRACED; | 195 | retval = -EPERM; |
196 | if (unlikely(task->exit_state)) | ||
197 | goto unlock_tasklist; | ||
198 | if (task->ptrace) | ||
199 | goto unlock_tasklist; | ||
200 | |||
201 | task->ptrace = PT_PTRACED; | ||
217 | if (capable(CAP_SYS_PTRACE)) | 202 | if (capable(CAP_SYS_PTRACE)) |
218 | task->ptrace |= PT_PTRACE_CAP; | 203 | task->ptrace |= PT_PTRACE_CAP; |
219 | 204 | ||
220 | __ptrace_link(task, current); | 205 | __ptrace_link(task, current); |
221 | |||
222 | send_sig_info(SIGSTOP, SEND_SIG_FORCED, task); | 206 | send_sig_info(SIGSTOP, SEND_SIG_FORCED, task); |
223 | bad: | 207 | |
224 | write_unlock_irqrestore(&tasklist_lock, flags); | 208 | retval = 0; |
225 | task_unlock(task); | 209 | unlock_tasklist: |
210 | write_unlock_irq(&tasklist_lock); | ||
211 | unlock_creds: | ||
226 | mutex_unlock(&task->cred_guard_mutex); | 212 | mutex_unlock(&task->cred_guard_mutex); |
227 | out: | 213 | out: |
228 | return retval; | 214 | return retval; |
229 | } | 215 | } |
230 | 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 | |||
231 | /* | 246 | /* |
232 | * Called with irqs disabled, returns true if childs should reap themselves. | 247 | * Called with irqs disabled, returns true if childs should reap themselves. |
233 | */ | 248 | */ |
@@ -409,37 +424,33 @@ static int ptrace_setoptions(struct task_struct *child, long data) | |||
409 | 424 | ||
410 | static int ptrace_getsiginfo(struct task_struct *child, siginfo_t *info) | 425 | static int ptrace_getsiginfo(struct task_struct *child, siginfo_t *info) |
411 | { | 426 | { |
427 | unsigned long flags; | ||
412 | int error = -ESRCH; | 428 | int error = -ESRCH; |
413 | 429 | ||
414 | read_lock(&tasklist_lock); | 430 | if (lock_task_sighand(child, &flags)) { |
415 | if (likely(child->sighand != NULL)) { | ||
416 | error = -EINVAL; | 431 | error = -EINVAL; |
417 | spin_lock_irq(&child->sighand->siglock); | ||
418 | if (likely(child->last_siginfo != NULL)) { | 432 | if (likely(child->last_siginfo != NULL)) { |
419 | *info = *child->last_siginfo; | 433 | *info = *child->last_siginfo; |
420 | error = 0; | 434 | error = 0; |
421 | } | 435 | } |
422 | spin_unlock_irq(&child->sighand->siglock); | 436 | unlock_task_sighand(child, &flags); |
423 | } | 437 | } |
424 | read_unlock(&tasklist_lock); | ||
425 | return error; | 438 | return error; |
426 | } | 439 | } |
427 | 440 | ||
428 | 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) |
429 | { | 442 | { |
443 | unsigned long flags; | ||
430 | int error = -ESRCH; | 444 | int error = -ESRCH; |
431 | 445 | ||
432 | read_lock(&tasklist_lock); | 446 | if (lock_task_sighand(child, &flags)) { |
433 | if (likely(child->sighand != NULL)) { | ||
434 | error = -EINVAL; | 447 | error = -EINVAL; |
435 | spin_lock_irq(&child->sighand->siglock); | ||
436 | if (likely(child->last_siginfo != NULL)) { | 448 | if (likely(child->last_siginfo != NULL)) { |
437 | *child->last_siginfo = *info; | 449 | *child->last_siginfo = *info; |
438 | error = 0; | 450 | error = 0; |
439 | } | 451 | } |
440 | spin_unlock_irq(&child->sighand->siglock); | 452 | unlock_task_sighand(child, &flags); |
441 | } | 453 | } |
442 | read_unlock(&tasklist_lock); | ||
443 | return error; | 454 | return error; |
444 | } | 455 | } |
445 | 456 | ||
@@ -566,72 +577,16 @@ int ptrace_request(struct task_struct *child, long request, | |||
566 | return ret; | 577 | return ret; |
567 | } | 578 | } |
568 | 579 | ||
569 | /** | 580 | static struct task_struct *ptrace_get_task_struct(pid_t pid) |
570 | * ptrace_traceme -- helper for PTRACE_TRACEME | ||
571 | * | ||
572 | * Performs checks and sets PT_PTRACED. | ||
573 | * Should be used by all ptrace implementations for PTRACE_TRACEME. | ||
574 | */ | ||
575 | int ptrace_traceme(void) | ||
576 | { | ||
577 | int ret = -EPERM; | ||
578 | |||
579 | /* | ||
580 | * Are we already being traced? | ||
581 | */ | ||
582 | repeat: | ||
583 | task_lock(current); | ||
584 | if (!(current->ptrace & PT_PTRACED)) { | ||
585 | /* | ||
586 | * See ptrace_attach() comments about the locking here. | ||
587 | */ | ||
588 | unsigned long flags; | ||
589 | if (!write_trylock_irqsave(&tasklist_lock, flags)) { | ||
590 | task_unlock(current); | ||
591 | do { | ||
592 | cpu_relax(); | ||
593 | } while (!write_can_lock(&tasklist_lock)); | ||
594 | goto repeat; | ||
595 | } | ||
596 | |||
597 | ret = security_ptrace_traceme(current->parent); | ||
598 | |||
599 | /* | ||
600 | * Check PF_EXITING to ensure ->real_parent has not passed | ||
601 | * exit_ptrace(). Otherwise we don't report the error but | ||
602 | * pretend ->real_parent untraces us right after return. | ||
603 | */ | ||
604 | if (!ret && !(current->real_parent->flags & PF_EXITING)) { | ||
605 | current->ptrace |= PT_PTRACED; | ||
606 | __ptrace_link(current, current->real_parent); | ||
607 | } | ||
608 | |||
609 | write_unlock_irqrestore(&tasklist_lock, flags); | ||
610 | } | ||
611 | task_unlock(current); | ||
612 | return ret; | ||
613 | } | ||
614 | |||
615 | /** | ||
616 | * ptrace_get_task_struct -- grab a task struct reference for ptrace | ||
617 | * @pid: process id to grab a task_struct reference of | ||
618 | * | ||
619 | * This function is a helper for ptrace implementations. It checks | ||
620 | * permissions and then grabs a task struct for use of the actual | ||
621 | * ptrace implementation. | ||
622 | * | ||
623 | * Returns the task_struct for @pid or an ERR_PTR() on failure. | ||
624 | */ | ||
625 | struct task_struct *ptrace_get_task_struct(pid_t pid) | ||
626 | { | 581 | { |
627 | struct task_struct *child; | 582 | struct task_struct *child; |
628 | 583 | ||
629 | read_lock(&tasklist_lock); | 584 | rcu_read_lock(); |
630 | child = find_task_by_vpid(pid); | 585 | child = find_task_by_vpid(pid); |
631 | if (child) | 586 | if (child) |
632 | get_task_struct(child); | 587 | get_task_struct(child); |
588 | rcu_read_unlock(); | ||
633 | 589 | ||
634 | read_unlock(&tasklist_lock); | ||
635 | if (!child) | 590 | if (!child) |
636 | return ERR_PTR(-ESRCH); | 591 | return ERR_PTR(-ESRCH); |
637 | return child; | 592 | return child; |