Skip to content

Commit 5431fdd

Browse files
author
Peter Zijlstra
committed
ptrace: Convert ptrace_attach() to use lock guards
Created as testing for the conditional guard infrastructure. Specifically this makes use of the following form: scoped_cond_guard (mutex_intr, return -ERESTARTNOINTR, &task->signal->cred_guard_mutex) { ... } ... return 0; Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Reviewed-by: Oleg Nesterov <oleg@redhat.com> Link: https://lkml.kernel.org/r/20231102110706.568467727%40infradead.org
1 parent 18caaed commit 5431fdd

3 files changed

Lines changed: 89 additions & 67 deletions

File tree

include/linux/sched/task.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,6 @@ static inline void task_unlock(struct task_struct *p)
226226
spin_unlock(&p->alloc_lock);
227227
}
228228

229+
DEFINE_GUARD(task_lock, struct task_struct *, task_lock(_T), task_unlock(_T))
230+
229231
#endif /* _LINUX_SCHED_TASK_H */

include/linux/spinlock.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,5 +548,31 @@ DEFINE_LOCK_GUARD_1(spinlock_irqsave, spinlock_t,
548548
DEFINE_LOCK_GUARD_1_COND(spinlock_irqsave, _try,
549549
spin_trylock_irqsave(_T->lock, _T->flags))
550550

551+
DEFINE_LOCK_GUARD_1(read_lock, rwlock_t,
552+
read_lock(_T->lock),
553+
read_unlock(_T->lock))
554+
555+
DEFINE_LOCK_GUARD_1(read_lock_irq, rwlock_t,
556+
read_lock_irq(_T->lock),
557+
read_unlock_irq(_T->lock))
558+
559+
DEFINE_LOCK_GUARD_1(read_lock_irqsave, rwlock_t,
560+
read_lock_irqsave(_T->lock, _T->flags),
561+
read_unlock_irqrestore(_T->lock, _T->flags),
562+
unsigned long flags)
563+
564+
DEFINE_LOCK_GUARD_1(write_lock, rwlock_t,
565+
write_lock(_T->lock),
566+
write_unlock(_T->lock))
567+
568+
DEFINE_LOCK_GUARD_1(write_lock_irq, rwlock_t,
569+
write_lock_irq(_T->lock),
570+
write_unlock_irq(_T->lock))
571+
572+
DEFINE_LOCK_GUARD_1(write_lock_irqsave, rwlock_t,
573+
write_lock_irqsave(_T->lock, _T->flags),
574+
write_unlock_irqrestore(_T->lock, _T->flags),
575+
unsigned long flags)
576+
551577
#undef __LINUX_INSIDE_SPINLOCK_H
552578
#endif /* __LINUX_SPINLOCK_H */

kernel/ptrace.c

Lines changed: 61 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -386,24 +386,52 @@ static int check_ptrace_options(unsigned long data)
386386
return 0;
387387
}
388388

389+
static inline void ptrace_set_stopped(struct task_struct *task)
390+
{
391+
guard(spinlock)(&task->sighand->siglock);
392+
393+
/*
394+
* If the task is already STOPPED, set JOBCTL_TRAP_STOP and
395+
* TRAPPING, and kick it so that it transits to TRACED. TRAPPING
396+
* will be cleared if the child completes the transition or any
397+
* event which clears the group stop states happens. We'll wait
398+
* for the transition to complete before returning from this
399+
* function.
400+
*
401+
* This hides STOPPED -> RUNNING -> TRACED transition from the
402+
* attaching thread but a different thread in the same group can
403+
* still observe the transient RUNNING state. IOW, if another
404+
* thread's WNOHANG wait(2) on the stopped tracee races against
405+
* ATTACH, the wait(2) may fail due to the transient RUNNING.
406+
*
407+
* The following task_is_stopped() test is safe as both transitions
408+
* in and out of STOPPED are protected by siglock.
409+
*/
410+
if (task_is_stopped(task) &&
411+
task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING)) {
412+
task->jobctl &= ~JOBCTL_STOPPED;
413+
signal_wake_up_state(task, __TASK_STOPPED);
414+
}
415+
}
416+
389417
static int ptrace_attach(struct task_struct *task, long request,
390418
unsigned long addr,
391419
unsigned long flags)
392420
{
393421
bool seize = (request == PTRACE_SEIZE);
394422
int retval;
395423

396-
retval = -EIO;
397424
if (seize) {
398425
if (addr != 0)
399-
goto out;
426+
return -EIO;
400427
/*
401428
* This duplicates the check in check_ptrace_options() because
402429
* ptrace_attach() and ptrace_setoptions() have historically
403430
* used different error codes for unknown ptrace options.
404431
*/
405432
if (flags & ~(unsigned long)PTRACE_O_MASK)
406-
goto out;
433+
return -EIO;
434+
407435
retval = check_ptrace_options(flags);
408436
if (retval)
409437
return retval;
@@ -414,88 +442,54 @@ static int ptrace_attach(struct task_struct *task, long request,
414442

415443
audit_ptrace(task);
416444

417-
retval = -EPERM;
418445
if (unlikely(task->flags & PF_KTHREAD))
419-
goto out;
446+
return -EPERM;
420447
if (same_thread_group(task, current))
421-
goto out;
448+
return -EPERM;
422449

423450
/*
424451
* Protect exec's credential calculations against our interference;
425452
* SUID, SGID and LSM creds get determined differently
426453
* under ptrace.
427454
*/
428-
retval = -ERESTARTNOINTR;
429-
if (mutex_lock_interruptible(&task->signal->cred_guard_mutex))
430-
goto out;
455+
scoped_cond_guard (mutex_intr, return -ERESTARTNOINTR,
456+
&task->signal->cred_guard_mutex) {
431457

432-
task_lock(task);
433-
retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS);
434-
task_unlock(task);
435-
if (retval)
436-
goto unlock_creds;
458+
scoped_guard (task_lock, task) {
459+
retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS);
460+
if (retval)
461+
return retval;
462+
}
437463

438-
write_lock_irq(&tasklist_lock);
439-
retval = -EPERM;
440-
if (unlikely(task->exit_state))
441-
goto unlock_tasklist;
442-
if (task->ptrace)
443-
goto unlock_tasklist;
464+
scoped_guard (write_lock_irq, &tasklist_lock) {
465+
if (unlikely(task->exit_state))
466+
return -EPERM;
467+
if (task->ptrace)
468+
return -EPERM;
444469

445-
task->ptrace = flags;
470+
task->ptrace = flags;
446471

447-
ptrace_link(task, current);
472+
ptrace_link(task, current);
448473

449-
/* SEIZE doesn't trap tracee on attach */
450-
if (!seize)
451-
send_sig_info(SIGSTOP, SEND_SIG_PRIV, task);
474+
/* SEIZE doesn't trap tracee on attach */
475+
if (!seize)
476+
send_sig_info(SIGSTOP, SEND_SIG_PRIV, task);
452477

453-
spin_lock(&task->sighand->siglock);
478+
ptrace_set_stopped(task);
479+
}
480+
}
454481

455482
/*
456-
* If the task is already STOPPED, set JOBCTL_TRAP_STOP and
457-
* TRAPPING, and kick it so that it transits to TRACED. TRAPPING
458-
* will be cleared if the child completes the transition or any
459-
* event which clears the group stop states happens. We'll wait
460-
* for the transition to complete before returning from this
461-
* function.
462-
*
463-
* This hides STOPPED -> RUNNING -> TRACED transition from the
464-
* attaching thread but a different thread in the same group can
465-
* still observe the transient RUNNING state. IOW, if another
466-
* thread's WNOHANG wait(2) on the stopped tracee races against
467-
* ATTACH, the wait(2) may fail due to the transient RUNNING.
468-
*
469-
* The following task_is_stopped() test is safe as both transitions
470-
* in and out of STOPPED are protected by siglock.
483+
* We do not bother to change retval or clear JOBCTL_TRAPPING
484+
* if wait_on_bit() was interrupted by SIGKILL. The tracer will
485+
* not return to user-mode, it will exit and clear this bit in
486+
* __ptrace_unlink() if it wasn't already cleared by the tracee;
487+
* and until then nobody can ptrace this task.
471488
*/
472-
if (task_is_stopped(task) &&
473-
task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING)) {
474-
task->jobctl &= ~JOBCTL_STOPPED;
475-
signal_wake_up_state(task, __TASK_STOPPED);
476-
}
477-
478-
spin_unlock(&task->sighand->siglock);
479-
480-
retval = 0;
481-
unlock_tasklist:
482-
write_unlock_irq(&tasklist_lock);
483-
unlock_creds:
484-
mutex_unlock(&task->signal->cred_guard_mutex);
485-
out:
486-
if (!retval) {
487-
/*
488-
* We do not bother to change retval or clear JOBCTL_TRAPPING
489-
* if wait_on_bit() was interrupted by SIGKILL. The tracer will
490-
* not return to user-mode, it will exit and clear this bit in
491-
* __ptrace_unlink() if it wasn't already cleared by the tracee;
492-
* and until then nobody can ptrace this task.
493-
*/
494-
wait_on_bit(&task->jobctl, JOBCTL_TRAPPING_BIT, TASK_KILLABLE);
495-
proc_ptrace_connector(task, PTRACE_ATTACH);
496-
}
489+
wait_on_bit(&task->jobctl, JOBCTL_TRAPPING_BIT, TASK_KILLABLE);
490+
proc_ptrace_connector(task, PTRACE_ATTACH);
497491

498-
return retval;
492+
return 0;
499493
}
500494

501495
/**

0 commit comments

Comments
 (0)