Skip to content

Commit 31cae1e

Browse files
Peter Zijlstraebiederm
authored andcommitted
sched,signal,ptrace: Rework TASK_TRACED, TASK_STOPPED state
Currently ptrace_stop() / do_signal_stop() rely on the special states TASK_TRACED and TASK_STOPPED resp. to keep unique state. That is, this state exists only in task->__state and nowhere else. There's two spots of bother with this: - PREEMPT_RT has task->saved_state which complicates matters, meaning task_is_{traced,stopped}() needs to check an additional variable. - An alternative freezer implementation that itself relies on a special TASK state would loose TASK_TRACED/TASK_STOPPED and will result in misbehaviour. As such, add additional state to task->jobctl to track this state outside of task->__state. NOTE: this doesn't actually fix anything yet, just adds extra state. --EWB * didn't add a unnecessary newline in signal.h * Update t->jobctl in signal_wake_up and ptrace_signal_wake_up instead of in signal_wake_up_state. This prevents the clearing of TASK_STOPPED and TASK_TRACED from getting lost. * Added warnings if JOBCTL_STOPPED or JOBCTL_TRACED are not cleared Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Link: https://lkml.kernel.org/r/[email protected] Tested-by: Kees Cook <[email protected]> Reviewed-by: Oleg Nesterov <[email protected]> Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Eric W. Biederman <[email protected]>
1 parent 5b4197c commit 31cae1e

File tree

5 files changed

+45
-14
lines changed

5 files changed

+45
-14
lines changed

include/linux/sched.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,9 @@ struct task_group;
118118

119119
#define task_is_running(task) (READ_ONCE((task)->__state) == TASK_RUNNING)
120120

121-
#define task_is_traced(task) ((READ_ONCE(task->__state) & __TASK_TRACED) != 0)
122-
123-
#define task_is_stopped(task) ((READ_ONCE(task->__state) & __TASK_STOPPED) != 0)
124-
125-
#define task_is_stopped_or_traced(task) ((READ_ONCE(task->__state) & (__TASK_STOPPED | __TASK_TRACED)) != 0)
121+
#define task_is_traced(task) ((READ_ONCE(task->jobctl) & JOBCTL_TRACED) != 0)
122+
#define task_is_stopped(task) ((READ_ONCE(task->jobctl) & JOBCTL_STOPPED) != 0)
123+
#define task_is_stopped_or_traced(task) ((READ_ONCE(task->jobctl) & (JOBCTL_STOPPED | JOBCTL_TRACED)) != 0)
126124

127125
/*
128126
* Special states are those that do not use the normal wait-loop pattern. See

include/linux/sched/jobctl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ struct task_struct;
2121
#define JOBCTL_TRAP_FREEZE_BIT 23 /* trap for cgroup freezer */
2222
#define JOBCTL_PTRACE_FROZEN_BIT 24 /* frozen for ptrace */
2323

24+
#define JOBCTL_STOPPED_BIT 26 /* do_signal_stop() */
25+
#define JOBCTL_TRACED_BIT 27 /* ptrace_stop() */
26+
2427
#define JOBCTL_STOP_DEQUEUED (1UL << JOBCTL_STOP_DEQUEUED_BIT)
2528
#define JOBCTL_STOP_PENDING (1UL << JOBCTL_STOP_PENDING_BIT)
2629
#define JOBCTL_STOP_CONSUME (1UL << JOBCTL_STOP_CONSUME_BIT)
@@ -31,6 +34,9 @@ struct task_struct;
3134
#define JOBCTL_TRAP_FREEZE (1UL << JOBCTL_TRAP_FREEZE_BIT)
3235
#define JOBCTL_PTRACE_FROZEN (1UL << JOBCTL_PTRACE_FROZEN_BIT)
3336

37+
#define JOBCTL_STOPPED (1UL << JOBCTL_STOPPED_BIT)
38+
#define JOBCTL_TRACED (1UL << JOBCTL_TRACED_BIT)
39+
3440
#define JOBCTL_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY)
3541
#define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)
3642

include/linux/sched/signal.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,10 @@ static inline int kernel_dequeue_signal(void)
294294
static inline void kernel_signal_stop(void)
295295
{
296296
spin_lock_irq(&current->sighand->siglock);
297-
if (current->jobctl & JOBCTL_STOP_DEQUEUED)
297+
if (current->jobctl & JOBCTL_STOP_DEQUEUED) {
298+
current->jobctl |= JOBCTL_STOPPED;
298299
set_special_state(TASK_STOPPED);
300+
}
299301
spin_unlock_irq(&current->sighand->siglock);
300302

301303
schedule();
@@ -437,12 +439,21 @@ extern void signal_wake_up_state(struct task_struct *t, unsigned int state);
437439

438440
static inline void signal_wake_up(struct task_struct *t, bool fatal)
439441
{
440-
fatal = fatal && !(t->jobctl & JOBCTL_PTRACE_FROZEN);
441-
signal_wake_up_state(t, fatal ? TASK_WAKEKILL | __TASK_TRACED : 0);
442+
unsigned int state = 0;
443+
if (fatal && !(t->jobctl & JOBCTL_PTRACE_FROZEN)) {
444+
t->jobctl &= ~(JOBCTL_STOPPED | JOBCTL_TRACED);
445+
state = TASK_WAKEKILL | __TASK_TRACED;
446+
}
447+
signal_wake_up_state(t, state);
442448
}
443449
static inline void ptrace_signal_wake_up(struct task_struct *t, bool resume)
444450
{
445-
signal_wake_up_state(t, resume ? __TASK_TRACED : 0);
451+
unsigned int state = 0;
452+
if (resume) {
453+
t->jobctl &= ~JOBCTL_TRACED;
454+
state = __TASK_TRACED;
455+
}
456+
signal_wake_up_state(t, state);
446457
}
447458

448459
void task_join_group_stop(struct task_struct *task);

kernel/ptrace.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,12 @@ static bool looks_like_a_spurious_pid(struct task_struct *task)
185185
return true;
186186
}
187187

188-
/* Ensure that nothing can wake it up, even SIGKILL */
188+
/*
189+
* Ensure that nothing can wake it up, even SIGKILL
190+
*
191+
* A task is switched to this state while a ptrace operation is in progress;
192+
* such that the ptrace operation is uninterruptible.
193+
*/
189194
static bool ptrace_freeze_traced(struct task_struct *task)
190195
{
191196
bool ret = false;
@@ -216,8 +221,10 @@ static void ptrace_unfreeze_traced(struct task_struct *task)
216221
*/
217222
if (lock_task_sighand(task, &flags)) {
218223
task->jobctl &= ~JOBCTL_PTRACE_FROZEN;
219-
if (__fatal_signal_pending(task))
224+
if (__fatal_signal_pending(task)) {
225+
task->jobctl &= ~TASK_TRACED;
220226
wake_up_state(task, __TASK_TRACED);
227+
}
221228
unlock_task_sighand(task, &flags);
222229
}
223230
}
@@ -462,8 +469,10 @@ static int ptrace_attach(struct task_struct *task, long request,
462469
* in and out of STOPPED are protected by siglock.
463470
*/
464471
if (task_is_stopped(task) &&
465-
task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING))
472+
task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING)) {
473+
task->jobctl &= ~JOBCTL_STOPPED;
466474
signal_wake_up_state(task, __TASK_STOPPED);
475+
}
467476

468477
spin_unlock(&task->sighand->siglock);
469478

@@ -875,6 +884,7 @@ static int ptrace_resume(struct task_struct *child, long request,
875884
*/
876885
spin_lock_irq(&child->sighand->siglock);
877886
child->exit_code = data;
887+
child->jobctl &= ~JOBCTL_TRACED;
878888
wake_up_state(child, __TASK_TRACED);
879889
spin_unlock_irq(&child->sighand->siglock);
880890

kernel/signal.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,10 @@ static int dequeue_synchronous_signal(kernel_siginfo_t *info)
762762
*/
763763
void signal_wake_up_state(struct task_struct *t, unsigned int state)
764764
{
765+
lockdep_assert_held(&t->sighand->siglock);
766+
765767
set_tsk_thread_flag(t, TIF_SIGPENDING);
768+
766769
/*
767770
* TASK_WAKEKILL also means wake it up in the stopped/traced/killable
768771
* case. We don't check t->state here because there is a race with it
@@ -930,9 +933,10 @@ static bool prepare_signal(int sig, struct task_struct *p, bool force)
930933
for_each_thread(p, t) {
931934
flush_sigqueue_mask(&flush, &t->pending);
932935
task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING);
933-
if (likely(!(t->ptrace & PT_SEIZED)))
936+
if (likely(!(t->ptrace & PT_SEIZED))) {
937+
t->jobctl &= ~JOBCTL_STOPPED;
934938
wake_up_state(t, __TASK_STOPPED);
935-
else
939+
} else
936940
ptrace_trap_notify(t);
937941
}
938942

@@ -2218,6 +2222,7 @@ static int ptrace_stop(int exit_code, int why, unsigned long message,
22182222
return exit_code;
22192223

22202224
set_special_state(TASK_TRACED);
2225+
current->jobctl |= JOBCTL_TRACED;
22212226

22222227
/*
22232228
* We're committing to trapping. TRACED should be visible before
@@ -2436,6 +2441,7 @@ static bool do_signal_stop(int signr)
24362441
if (task_participate_group_stop(current))
24372442
notify = CLD_STOPPED;
24382443

2444+
current->jobctl |= JOBCTL_STOPPED;
24392445
set_special_state(TASK_STOPPED);
24402446
spin_unlock_irq(&current->sighand->siglock);
24412447

0 commit comments

Comments
 (0)