diff options
Diffstat (limited to 'kernel/ptrace.c')
| -rw-r--r-- | kernel/ptrace.c | 178 |
1 files changed, 62 insertions, 116 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 42c317874cfa..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 | */ |
| @@ -418,37 +424,33 @@ static int ptrace_setoptions(struct task_struct *child, long data) | |||
| 418 | 424 | ||
| 419 | static int ptrace_getsiginfo(struct task_struct *child, siginfo_t *info) | 425 | static int ptrace_getsiginfo(struct task_struct *child, siginfo_t *info) |
| 420 | { | 426 | { |
| 427 | unsigned long flags; | ||
| 421 | int error = -ESRCH; | 428 | int error = -ESRCH; |
| 422 | 429 | ||
| 423 | read_lock(&tasklist_lock); | 430 | if (lock_task_sighand(child, &flags)) { |
| 424 | if (likely(child->sighand != NULL)) { | ||
| 425 | error = -EINVAL; | 431 | error = -EINVAL; |
| 426 | spin_lock_irq(&child->sighand->siglock); | ||
| 427 | if (likely(child->last_siginfo != NULL)) { | 432 | if (likely(child->last_siginfo != NULL)) { |
| 428 | *info = *child->last_siginfo; | 433 | *info = *child->last_siginfo; |
| 429 | error = 0; | 434 | error = 0; |
| 430 | } | 435 | } |
| 431 | spin_unlock_irq(&child->sighand->siglock); | 436 | unlock_task_sighand(child, &flags); |
| 432 | } | 437 | } |
| 433 | read_unlock(&tasklist_lock); | ||
| 434 | return error; | 438 | return error; |
| 435 | } | 439 | } |
| 436 | 440 | ||
| 437 | 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) |
| 438 | { | 442 | { |
| 443 | unsigned long flags; | ||
| 439 | int error = -ESRCH; | 444 | int error = -ESRCH; |
| 440 | 445 | ||
| 441 | read_lock(&tasklist_lock); | 446 | if (lock_task_sighand(child, &flags)) { |
| 442 | if (likely(child->sighand != NULL)) { | ||
| 443 | error = -EINVAL; | 447 | error = -EINVAL; |
| 444 | spin_lock_irq(&child->sighand->siglock); | ||
| 445 | if (likely(child->last_siginfo != NULL)) { | 448 | if (likely(child->last_siginfo != NULL)) { |
| 446 | *child->last_siginfo = *info; | 449 | *child->last_siginfo = *info; |
| 447 | error = 0; | 450 | error = 0; |
| 448 | } | 451 | } |
| 449 | spin_unlock_irq(&child->sighand->siglock); | 452 | unlock_task_sighand(child, &flags); |
| 450 | } | 453 | } |
| 451 | read_unlock(&tasklist_lock); | ||
| 452 | return error; | 454 | return error; |
| 453 | } | 455 | } |
| 454 | 456 | ||
| @@ -575,72 +577,16 @@ int ptrace_request(struct task_struct *child, long request, | |||
| 575 | return ret; | 577 | return ret; |
| 576 | } | 578 | } |
| 577 | 579 | ||
| 578 | /** | 580 | static struct task_struct *ptrace_get_task_struct(pid_t pid) |
| 579 | * ptrace_traceme -- helper for PTRACE_TRACEME | ||
| 580 | * | ||
| 581 | * Performs checks and sets PT_PTRACED. | ||
| 582 | * Should be used by all ptrace implementations for PTRACE_TRACEME. | ||
| 583 | */ | ||
| 584 | int ptrace_traceme(void) | ||
| 585 | { | ||
| 586 | int ret = -EPERM; | ||
| 587 | |||
| 588 | /* | ||
| 589 | * Are we already being traced? | ||
| 590 | */ | ||
| 591 | repeat: | ||
| 592 | task_lock(current); | ||
| 593 | if (!(current->ptrace & PT_PTRACED)) { | ||
| 594 | /* | ||
| 595 | * See ptrace_attach() comments about the locking here. | ||
| 596 | */ | ||
| 597 | unsigned long flags; | ||
| 598 | if (!write_trylock_irqsave(&tasklist_lock, flags)) { | ||
| 599 | task_unlock(current); | ||
| 600 | do { | ||
| 601 | cpu_relax(); | ||
| 602 | } while (!write_can_lock(&tasklist_lock)); | ||
| 603 | goto repeat; | ||
| 604 | } | ||
| 605 | |||
| 606 | ret = security_ptrace_traceme(current->parent); | ||
| 607 | |||
| 608 | /* | ||
| 609 | * Check PF_EXITING to ensure ->real_parent has not passed | ||
| 610 | * exit_ptrace(). Otherwise we don't report the error but | ||
| 611 | * pretend ->real_parent untraces us right after return. | ||
| 612 | */ | ||
| 613 | if (!ret && !(current->real_parent->flags & PF_EXITING)) { | ||
| 614 | current->ptrace |= PT_PTRACED; | ||
| 615 | __ptrace_link(current, current->real_parent); | ||
| 616 | } | ||
| 617 | |||
| 618 | write_unlock_irqrestore(&tasklist_lock, flags); | ||
| 619 | } | ||
| 620 | task_unlock(current); | ||
| 621 | return ret; | ||
| 622 | } | ||
| 623 | |||
| 624 | /** | ||
| 625 | * ptrace_get_task_struct -- grab a task struct reference for ptrace | ||
| 626 | * @pid: process id to grab a task_struct reference of | ||
| 627 | * | ||
| 628 | * This function is a helper for ptrace implementations. It checks | ||
| 629 | * permissions and then grabs a task struct for use of the actual | ||
| 630 | * ptrace implementation. | ||
| 631 | * | ||
| 632 | * Returns the task_struct for @pid or an ERR_PTR() on failure. | ||
| 633 | */ | ||
| 634 | struct task_struct *ptrace_get_task_struct(pid_t pid) | ||
| 635 | { | 581 | { |
| 636 | struct task_struct *child; | 582 | struct task_struct *child; |
| 637 | 583 | ||
| 638 | read_lock(&tasklist_lock); | 584 | rcu_read_lock(); |
| 639 | child = find_task_by_vpid(pid); | 585 | child = find_task_by_vpid(pid); |
| 640 | if (child) | 586 | if (child) |
| 641 | get_task_struct(child); | 587 | get_task_struct(child); |
| 588 | rcu_read_unlock(); | ||
| 642 | 589 | ||
| 643 | read_unlock(&tasklist_lock); | ||
| 644 | if (!child) | 590 | if (!child) |
| 645 | return ERR_PTR(-ESRCH); | 591 | return ERR_PTR(-ESRCH); |
| 646 | return child; | 592 | return child; |
