[RFC][PATCH 4/5] signal: Add signal->ctrl_lock for job control

Matt Fleming
2011-09-30T15:13:19Z

From: Matt Fleming <matt.fleming@intel.com>

Instead of using sighand->siglock to synchronise access to job control
data structures provide a new lock 'ctrl_lock'. This helps to reduce
contention on the per-process siglock.

Cc: Tejun Heo <tj@kernel.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Anirudh Badam <abadam@cs.princeton.edu>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
---
fs/exec.c | 17 +++---
include/linux/init_task.h | 1 +
include/linux/sched.h | 4 +-
kernel/exit.c | 19 ++++---
kernel/fork.c | 1 +
kernel/ptrace.c | 64 +++++++++++------------
kernel/signal.c | 128 ++++++++++++++++++++++++++++++++------------
security/selinux/hooks.c | 2 +
8 files changed, 150 insertions(+), 86 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 8433e5d..367d3df 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -868,7 +868,6 @@ static int de_thread(struct task_struct *tsk)
{
struct signal_struct *sig = tsk->signal;
struct sighand_struct *oldsighand = tsk->sighand;
- spinlock_t *lock = &oldsighand->siglock;

if (thread_group_empty(tsk))
goto no_thread_group;
@@ -876,13 +875,13 @@ static int de_thread(struct task_struct *tsk)
/*
* Kill all other threads in the thread group.
*/
- spin_lock_irq(lock);
+ spin_lock_irq(&sig->ctrl_lock);
if (signal_group_exit(sig)) {
/*
* Another group action in progress, just
* return so that the signal is processed.
*/
- spin_unlock_irq(lock);
+ spin_unlock_irq(&sig->ctrl_lock);
return -EAGAIN;
}

@@ -893,11 +892,11 @@ static int de_thread(struct task_struct *tsk)

while (sig->notify_count) {
__set_current_state(TASK_UNINTERRUPTIBLE);
- spin_unlock_irq(lock);
+ spin_unlock_irq(&sig->ctrl_lock);
schedule();
- spin_lock_irq(lock);
+ spin_lock_irq(&sig->ctrl_lock);
}
- spin_unlock_irq(lock);
+ spin_unlock_irq(&sig->ctrl_lock);

/*
* At this point all other threads have exited, all we have to
@@ -1845,12 +1844,12 @@ static inline int zap_threads(struct task_struct *tsk, struct mm_struct *mm,
unsigned long flags;
int nr = -EAGAIN;

- spin_lock_irq(&tsk->sighand->siglock);
+ spin_lock_irq(&tsk->signal->ctrl_lock);
if (!signal_group_exit(tsk->signal)) {
mm->core_state = core_state;
nr = zap_process(tsk, exit_code);
}
- spin_unlock_irq(&tsk->sighand->siglock);
+ spin_unlock_irq(&tsk->signal->ctrl_lock);
if (unlikely(nr < 0))
return nr;

@@ -1897,7 +1896,9 @@ static inline int zap_threads(struct task_struct *tsk, struct mm_struct *mm,
if (p->mm) {
if (unlikely(p->mm == mm)) {
lock_task_sighand(p, &flags);
+ spin_lock(&p->signal->ctrl_lock);
nr += zap_process(p, exit_code);
+ spin_unlock(&p->signal->ctrl_lock);
unlock_task_sighand(p, &flags);
}
break;
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 1a66552..80baa1d 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -33,6 +33,7 @@ extern struct fs_struct init_fs;
#define INIT_SIGNALS(sig) { \
.nr_threads = 1, \
.wait_chldexit = __WAIT_QUEUE_HEAD_INITIALIZER(sig.wait_chldexit),\
+ .ctrl_lock = __SPIN_LOCK_UNLOCKED(sig.ctrl_lock), \
.shared_pending = { \
.list = LIST_HEAD_INIT(sig.shared_pending.list), \
.signal = {{0}}}, \
diff --git a/include/linux/sched.h b/include/linux/sched.h
index e4cd6bb..e35ce4a 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -537,6 +537,8 @@ struct signal_struct {
/* shared signal handling: */
struct sigpending shared_pending;

+ spinlock_t ctrl_lock;
+
/* thread group exit support */
int group_exit_code;
/* overloaded:
@@ -1293,7 +1295,7 @@ struct task_struct {
int exit_state;
int exit_code, exit_signal;
int pdeath_signal; /* The signal sent when the parent dies */
- unsigned int jobctl; /* JOBCTL_*, siglock protected */
+ unsigned int jobctl; /* JOBCTL_*, ctrl_lock protected */
/* ??? */
unsigned int personality;
unsigned did_exec:1;
diff --git a/kernel/exit.c b/kernel/exit.c
index a8a83ac..379a13d 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -106,8 +106,10 @@ static void __exit_signal(struct task_struct *tsk)
* If there is any task waiting for the group exit
* then notify it:
*/
+ spin_lock(&sig->ctrl_lock);
if (sig->notify_count > 0 && !--sig->notify_count)
wake_up_process(sig->group_exit_task);
+ spin_unlock(&sig->ctrl_lock);

if (tsk == sig->curr_target)
sig->curr_target = next_thread(tsk);
@@ -1086,8 +1088,7 @@ do_group_exit(int exit_code)
if (signal_group_exit(sig))
exit_code = sig->group_exit_code;
else if (!thread_group_empty(current)) {
- struct sighand_struct *const sighand = current->sighand;
- spin_lock_irq(&sighand->siglock);
+ spin_lock_irq(&sig->ctrl_lock);
if (signal_group_exit(sig))
/* Another thread got here before we took the lock. */
exit_code = sig->group_exit_code;
@@ -1096,7 +1097,7 @@ do_group_exit(int exit_code)
sig->flags = SIGNAL_GROUP_EXIT;
zap_other_threads(current);
}
- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&sig->ctrl_lock);
}

do_exit(exit_code);
@@ -1381,7 +1382,7 @@ static int *task_stopped_code(struct task_struct *p, bool ptrace)
*
* CONTEXT:
* read_lock(&tasklist_lock), which is released if return value is
- * non-zero. Also, grabs and releases @p->sighand->siglock.
+ * non-zero. Also, grabs and releases @p->signal->ctrl_lock.
*
* RETURNS:
* 0 if wait condition didn't exist and search for other wait conditions
@@ -1407,7 +1408,7 @@ static int wait_task_stopped(struct wait_opts *wo,
return 0;

exit_code = 0;
- spin_lock_irq(&p->sighand->siglock);
+ spin_lock_irq(&p->signal->ctrl_lock);

p_code = task_stopped_code(p, ptrace);
if (unlikely(!p_code))
@@ -1422,7 +1423,7 @@ static int wait_task_stopped(struct wait_opts *wo,

uid = task_uid(p);
unlock_sig:
- spin_unlock_irq(&p->sighand->siglock);
+ spin_unlock_irq(&p->signal->ctrl_lock);
if (!exit_code)
return 0;

@@ -1485,16 +1486,16 @@ static int wait_task_continued(struct wait_opts *wo, struct task_struct *p)
if (!(p->signal->flags & SIGNAL_STOP_CONTINUED))
return 0;

- spin_lock_irq(&p->sighand->siglock);
+ spin_lock_irq(&p->signal->ctrl_lock);
/* Re-check with the lock held. */
if (!(p->signal->flags & SIGNAL_STOP_CONTINUED)) {
- spin_unlock_irq(&p->sighand->siglock);
+ spin_unlock_irq(&p->signal->ctrl_lock);
return 0;
}
if (!unlikely(wo->wo_flags & WNOWAIT))
p->signal->flags &= ~SIGNAL_STOP_CONTINUED;
uid = task_uid(p);
- spin_unlock_irq(&p->sighand->siglock);
+ spin_unlock_irq(&p->signal->ctrl_lock);

pid = task_pid_vnr(p);
get_task_struct(p);
diff --git a/kernel/fork.c b/kernel/fork.c
index 8871ae8..8c5cf19 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -993,6 +993,7 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
sig->flags |= SIGNAL_UNKILLABLE;
sig->curr_target = tsk;
init_sigpending(&sig->shared_pending);
+ spin_lock_init(&sig->ctrl_lock);
INIT_LIST_HEAD(&sig->posix_timers);

hrtimer_init(&sig->real_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index eb46323..e4966b0 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -81,7 +81,7 @@ void __ptrace_unlink(struct task_struct *child)
child->parent = child->real_parent;
list_del_init(&child->ptrace_entry);

- spin_lock(&child->sighand->siglock);
+ spin_lock(&child->signal->ctrl_lock);

/*
* Clear all pending traps and TRAPPING. TRAPPING should be
@@ -108,7 +108,7 @@ void __ptrace_unlink(struct task_struct *child)
if (child->jobctl & JOBCTL_STOP_PENDING || task_is_traced(child))
signal_wake_up(child, task_is_traced(child));

- spin_unlock(&child->sighand->siglock);
+ spin_unlock(&child->signal->ctrl_lock);
}

/**
@@ -123,7 +123,7 @@ void __ptrace_unlink(struct task_struct *child)
* state.
*
* CONTEXT:
- * Grabs and releases tasklist_lock and @child->sighand->siglock.
+ * Grabs and releases tasklist_lock and @child->signal->ctrl_lock.
*
* RETURNS:
* 0 on success, -ESRCH if %child is not ready.
@@ -141,16 +141,12 @@ int ptrace_check_attach(struct task_struct *child, bool ignore_state)
*/
read_lock(&tasklist_lock);
if ((child->ptrace & PT_PTRACED) && child->parent == current) {
- /*
- * child->sighand can't be NULL, release_task()
- * does ptrace_unlink() before __exit_signal().
- */
- spin_lock_irq(&child->sighand->siglock);
+ spin_lock_irq(&child->signal->ctrl_lock);
WARN_ON_ONCE(task_is_stopped(child));
if (ignore_state || (task_is_traced(child) &&
!(child->jobctl & JOBCTL_LISTENING)))
ret = 0;
- spin_unlock_irq(&child->sighand->siglock);
+ spin_unlock_irq(&child->signal->ctrl_lock);
}
read_unlock(&tasklist_lock);

@@ -275,7 +271,7 @@ static int ptrace_attach(struct task_struct *task, long request,
if (!seize)
send_sig_info(SIGSTOP, SEND_SIG_FORCED, task);

- spin_lock(&task->sighand->siglock);
+ spin_lock(&task->signal->ctrl_lock);

/*
* If the task is already STOPPED, set JOBCTL_TRAP_STOP and
@@ -292,13 +288,13 @@ static int ptrace_attach(struct task_struct *task, long request,
* ATTACH, the wait(2) may fail due to the transient RUNNING.
*
* The following task_is_stopped() test is safe as both transitions
- * in and out of STOPPED are protected by siglock.
+ * in and out of STOPPED are protected by ctrl_lock.
*/
if (task_is_stopped(task) &&
task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING))
signal_wake_up(task, 1);

- spin_unlock(&task->sighand->siglock);
+ spin_unlock(&task->signal->ctrl_lock);

retval = 0;
unlock_tasklist:
@@ -538,32 +534,30 @@ static int ptrace_setoptions(struct task_struct *child, unsigned long data)
static int ptrace_getsiginfo(struct task_struct *child, siginfo_t *info)
{
unsigned long flags;
- int error = -ESRCH;
+ int error = -EINVAL;

- if (lock_task_sighand(child, &flags)) {
- error = -EINVAL;
- if (likely(child->last_siginfo != NULL)) {
- *info = *child->last_siginfo;
- error = 0;
- }
- unlock_task_sighand(child, &flags);
+ spin_lock_irqsave(&child->signal->ctrl_lock, flags);
+ if (likely(child->last_siginfo != NULL)) {
+ *info = *child->last_siginfo;
+ error = 0;
}
+ spin_unlock_irqrestore(&child->signal->ctrl_lock, flags);
+
return error;
}

static int ptrace_setsiginfo(struct task_struct *child, const siginfo_t *info)
{
unsigned long flags;
- int error = -ESRCH;
+ int error = -EINVAL;

- if (lock_task_sighand(child, &flags)) {
- error = -EINVAL;
- if (likely(child->last_siginfo != NULL)) {
- *child->last_siginfo = *info;
- error = 0;
- }
- unlock_task_sighand(child, &flags);
+ spin_lock_irqsave(&child->signal->ctrl_lock, flags);
+ if (likely(child->last_siginfo != NULL)) {
+ *child->last_siginfo = *info;
+ error = 0;
}
+ spin_unlock_irqrestore(&child->signal->ctrl_lock, flags);
+
return error;
}

@@ -715,7 +709,7 @@ int ptrace_request(struct task_struct *child, long request,
* The actual trap might not be PTRACE_EVENT_STOP trap but
* the pending condition is cleared regardless.
*/
- if (unlikely(!seized || !lock_task_sighand(child, &flags)))
+ if (unlikely(!seized))
break;

/*
@@ -724,10 +718,11 @@ int ptrace_request(struct task_struct *child, long request,
* STOP, this INTERRUPT should clear LISTEN and re-trap
* tracee into STOP.
*/
+ spin_lock_irqsave(&child->signal->ctrl_lock, flags);
if (likely(task_set_jobctl_pending(child, JOBCTL_TRAP_STOP)))
signal_wake_up(child, child->jobctl & JOBCTL_LISTENING);

- unlock_task_sighand(child, &flags);
+ spin_unlock_irqrestore(&child->signal->ctrl_lock, flags);
ret = 0;
break;

@@ -740,12 +735,15 @@ int ptrace_request(struct task_struct *child, long request,
* again. Alternatively, ptracer can issue INTERRUPT to
* finish listening and re-trap tracee into STOP.
*/
- if (unlikely(!seized || !lock_task_sighand(child, &flags)))
+ if (unlikely(!seized))
break;

+ spin_lock_irqsave(&child->signal->ctrl_lock, flags);
si = child->last_siginfo;
- if (unlikely(!si || si->si_code >> 8 != PTRACE_EVENT_STOP))
+ if (unlikely(!si || si->si_code >> 8 != PTRACE_EVENT_STOP)) {
+ spin_unlock_irqrestore(&child->signal->ctrl_lock, flags);
break;
+ }

child->jobctl |= JOBCTL_LISTENING;

@@ -756,7 +754,7 @@ int ptrace_request(struct task_struct *child, long request,
if (child->jobctl & JOBCTL_TRAP_NOTIFY)
signal_wake_up(child, true);

- unlock_task_sighand(child, &flags);
+ spin_unlock_irqrestore(&child->signal->ctrl_lock, flags);
ret = 0;
break;

diff --git a/kernel/signal.c b/kernel/signal.c
index b69c5a9..ca99c2d 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -50,13 +50,7 @@
* * most things under tsk->signal
*
* * tsk->last_siginfo
- * * tsk->group_stop
* * tsk->pending
- * * tsk->jobctl
- *
- * * the atomic operation of checking tsk->jobctl, tsk->pending and
- * tsk->signal->shared_pending and setting/clearing TIF_SIGPENDING,
- * see recalc_sigpending().
*
* * tsk->cpu_timers
*
@@ -66,6 +60,19 @@
* for reading, see lock_action() for when the write-lock is
* necessary.
*
+ * - signal->ctrl_lock (spinlock) protects,
+ *
+ * * tsk->signal->group_exit_code
+ * * tsk->signal->group_exit_task
+ * * tsk->signal->notify_count
+ * * tsk->signal->group_stop
+ * * tsk->signal->flags
+ * * tsk->group_stop
+ * * tsk->jobctl
+ *
+ * * the atomic operation of checking tsk->jobctl, tsk->pending and
+ * tsk->signal->shared_pending and setting/clearing TIF_SIGPENDING,
+ * see recalc_sigpending().
*/

/*
@@ -157,8 +164,23 @@ static inline int has_pending_signals(sigset_t *signal, sigset_t *blocked)

#define PENDING(p,b) has_pending_signals(&(p)->signal, (b))

+/**
+ * recalc_sigpending_tsk - check for pending signals
+ * @t: task to calculate pending signals for
+ *
+ * CONTEXT:
+ * Must be called with both t->signal->ctrl_lock and t->sighand->siglock held,
+ * because TIF_SIGPENDING can be set by a task holding either of them.
+ *
+ * RETURNS:
+ * 0 if there are no pending signals. Otherwise we return 1 and set
+ * TIF_SIGPENDING.
+ */
static int recalc_sigpending_tsk(struct task_struct *t)
{
+ assert_spin_locked(&t->sighand->siglock);
+ assert_spin_locked(&t->signal->ctrl_lock);
+
if ((t->jobctl & JOBCTL_PENDING_MASK) ||
PENDING(&t->pending, &t->blocked) ||
PENDING(&t->signal->shared_pending, &t->blocked)) {
@@ -179,15 +201,22 @@ static int recalc_sigpending_tsk(struct task_struct *t)
*/
void recalc_sigpending_and_wake(struct task_struct *t)
{
+ struct signal_struct *sig = t->signal;
+
+ spin_lock(&sig->ctrl_lock);
if (recalc_sigpending_tsk(t))
signal_wake_up(t, 0);
+ spin_unlock(&sig->ctrl_lock);
}

void recalc_sigpending(void)
{
+ struct signal_struct *sig = current->signal;
+
+ spin_lock(&sig->ctrl_lock);
if (!recalc_sigpending_tsk(current) && !freezing(current))
clear_thread_flag(TIF_SIGPENDING);
-
+ spin_unlock(&sig->ctrl_lock);
}

/* Given the mask, find the first available signal that should be serviced. */
@@ -268,7 +297,7 @@ static inline void print_dropped_signal(int sig)
* becomes noop.
*
* CONTEXT:
- * Must be called with @task->sighand->siglock held.
+ * Must be called with @task->signal->ctrl_lock held.
*
* RETURNS:
* %true if @mask is set, %false if made noop because @task was dying.
@@ -278,6 +307,7 @@ bool task_set_jobctl_pending(struct task_struct *task, unsigned int mask)
BUG_ON(mask & ~(JOBCTL_PENDING_MASK | JOBCTL_STOP_CONSUME |
JOBCTL_STOP_SIGMASK | JOBCTL_TRAPPING));
BUG_ON((mask & JOBCTL_TRAPPING) && !(mask & JOBCTL_PENDING_MASK));
+ assert_spin_locked(&task->signal->ctrl_lock);

if (unlikely(fatal_signal_pending(task) || (task->flags & PF_EXITING)))
return false;
@@ -299,10 +329,12 @@ bool task_set_jobctl_pending(struct task_struct *task, unsigned int mask)
* ptracer.
*
* CONTEXT:
- * Must be called with @task->sighand->siglock held.
+ * Must be called with @task->signal->ctrl_lock held.
*/
void task_clear_jobctl_trapping(struct task_struct *task)
{
+ assert_spin_locked(&task->signal->ctrl_lock);
+
if (unlikely(task->jobctl & JOBCTL_TRAPPING)) {
task->jobctl &= ~JOBCTL_TRAPPING;
wake_up_bit(&task->jobctl, JOBCTL_TRAPPING_BIT);
@@ -322,11 +354,12 @@ void task_clear_jobctl_trapping(struct task_struct *task)
* task_clear_jobctl_trapping().
*
* CONTEXT:
- * Must be called with @task->sighand->siglock held.
+ * Must be called with @task->signal->ctrl_lock held.
*/
void task_clear_jobctl_pending(struct task_struct *task, unsigned int mask)
{
BUG_ON(mask & ~JOBCTL_PENDING_MASK);
+ assert_spin_locked(&task->signal->ctrl_lock);

if (mask & JOBCTL_STOP_PENDING)
mask |= JOBCTL_STOP_CONSUME | JOBCTL_STOP_DEQUEUED;
@@ -347,7 +380,7 @@ void task_clear_jobctl_pending(struct task_struct *task, unsigned int mask)
* stop, the appropriate %SIGNAL_* flags are set.
*
* CONTEXT:
- * Must be called with @task->sighand->siglock held.
+ * Must be called with @task->signal->ctrl_lock held.
*
* RETURNS:
* %true if group stop completion should be notified to the parent, %false
@@ -359,6 +392,7 @@ static bool task_participate_group_stop(struct task_struct *task)
bool consume = task->jobctl & JOBCTL_STOP_CONSUME;

WARN_ON_ONCE(!(task->jobctl & JOBCTL_STOP_PENDING));
+ assert_spin_locked(&sig->ctrl_lock);

task_clear_jobctl_pending(task, JOBCTL_STOP_PENDING);

@@ -672,8 +706,10 @@ int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
}
}

+ spin_lock(&current->signal->ctrl_lock);
recalc_sigpending();
if (!signr) {
+ spin_unlock(&current->signal->ctrl_lock);
spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
return 0;
}
@@ -694,6 +730,7 @@ int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
current->jobctl |= JOBCTL_STOP_DEQUEUED;
}

+ spin_unlock(&current->signal->ctrl_lock);
spin_unlock_irqrestore(&tsk->sighand->siglock, flags);

if ((info->si_code & __SI_MASK) == __SI_TIMER && info->si_sys_private)
@@ -706,8 +743,8 @@ int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
* Tell a process that it has a new active signal..
*
* NOTE! we rely on the previous spin_lock to
- * lock interrupts for us! We can only be called with
- * "siglock" held, and the local interrupt must
+ * lock interrupts for us! We can only be called with either
+ * "siglock" or "ctrl_lock" held, and the local interrupt must
* have been disabled when that got acquired!
*
* No need to set need_resched since signal event passing
@@ -870,12 +907,12 @@ static int check_kill_permission(int sig, struct siginfo *info,
* are finished by PTRACE_CONT.
*
* CONTEXT:
- * Must be called with @task->sighand->siglock held.
+ * Must be called with @task->signal->ctrl_lock held.
*/
static void ptrace_trap_notify(struct task_struct *t)
{
WARN_ON_ONCE(!(t->ptrace & PT_SEIZED));
- assert_spin_locked(&t->sighand->siglock);
+ assert_spin_locked(&t->signal->ctrl_lock);

task_set_jobctl_pending(t, JOBCTL_TRAP_NOTIFY);
signal_wake_up(t, t->jobctl & JOBCTL_LISTENING);
@@ -896,6 +933,7 @@ static int prepare_signal(int sig, struct task_struct *p, int from_ancestor_ns)
struct signal_struct *signal = p->signal;
struct task_struct *t;

+ spin_lock(&signal->ctrl_lock);
if (unlikely(signal->flags & SIGNAL_GROUP_EXIT)) {
/*
* The process is in the middle of dying, nothing to do.
@@ -950,6 +988,7 @@ static int prepare_signal(int sig, struct task_struct *p, int from_ancestor_ns)
signal->group_exit_code = 0;
}
}
+ spin_unlock(&signal->ctrl_lock);

return !sig_ignored(p, sig, from_ancestor_ns);
}
@@ -1016,6 +1055,7 @@ static void complete_signal(int sig, struct task_struct *p, int group)
* Found a killable thread. If the signal will be fatal,
* then start taking the whole group down immediately.
*/
+ spin_lock(&signal->ctrl_lock);
if (sig_fatal(p, sig) &&
!(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&
!sigismember(&t->real_blocked, sig) &&
@@ -1039,6 +1079,7 @@ static void complete_signal(int sig, struct task_struct *p, int group)
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
} while_each_thread(p, t);
+ spin_unlock(&signal->ctrl_lock);
return;
}
}
@@ -1048,6 +1089,7 @@ static void complete_signal(int sig, struct task_struct *p, int group)
* Tell the chosen thread to wake up and dequeue it.
*/
signal_wake_up(t, sig == SIGKILL);
+ spin_unlock(&signal->ctrl_lock);
return;
}

@@ -1840,7 +1882,10 @@ static int sigkill_pending(struct task_struct *tsk)
static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
__releases(&current->sighand->siglock)
__acquires(&current->sighand->siglock)
+ __releases(&current->signal->ctrl_lock)
+ __acquires(&current->signal->ctrl_lock)
{
+ struct signal_struct *sig = current->signal;
bool gstop_done = false;

if (arch_ptrace_stop_needed(exit_code, info)) {
@@ -1855,9 +1900,11 @@ static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
* siglock. That must prevent us from sleeping in TASK_TRACED.
* So after regaining the lock, we must check for SIGKILL.
*/
+ spin_unlock(&sig->ctrl_lock);
spin_unlock_irq(&current->sighand->siglock);
arch_ptrace_stop(exit_code, info);
spin_lock_irq(&current->sighand->siglock);
+ spin_lock(&sig->ctrl_lock);
if (sigkill_pending(current))
return;
}
@@ -1892,6 +1939,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
/* entering a trap, clear TRAPPING */
task_clear_jobctl_trapping(current);

+ spin_unlock(&sig->ctrl_lock);
spin_unlock_irq(&current->sighand->siglock);
read_lock(&tasklist_lock);
if (may_ptrace_stop()) {
@@ -1947,11 +1995,12 @@ static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
try_to_freeze();

/*
- * We are back. Now reacquire the siglock before touching
+ * We are back. Now reacquire the ctrl_lock before touching
* last_siginfo, so that we are sure to have synchronized with
* any signal-sending on another CPU that wants to examine it.
*/
spin_lock_irq(&current->sighand->siglock);
+ spin_lock(&sig->ctrl_lock);
current->last_siginfo = NULL;

/* LISTENING can be set only during STOP traps, clear it */
@@ -2002,7 +2051,7 @@ void ptrace_notify(int exit_code)
* must ensure that INTERRUPT trap handling takes places afterwards.
*
* CONTEXT:
- * Must be called with @current->sighand->siglock held, which may be
+ * Must be called with @current->signal->ctrl_lock held, which may be
* released and re-acquired before returning with intervening sleep.
*
* RETURNS:
@@ -2010,8 +2059,8 @@ void ptrace_notify(int exit_code)
* %true if participated in group stop.
*/
static bool do_signal_stop(int signr)
- __releases(&current->sighand->siglock)
- __acquires(&current->sighand->siglock)
+ __releases(&current->signal->ctrl_lock)
+ __acquires(&current->signal->ctrl_lock)
{
struct signal_struct *sig = current->signal;

@@ -2084,7 +2133,7 @@ static bool do_signal_stop(int signr)
notify = CLD_STOPPED;

__set_current_state(TASK_STOPPED);
- spin_unlock_irq(&current->sighand->siglock);
+ spin_unlock_irq(&sig->ctrl_lock);

/*
* Notify the parent of the group stop completion. Because
@@ -2104,7 +2153,7 @@ static bool do_signal_stop(int signr)
/* Now we don't run again until woken by SIGCONT or SIGKILL */
schedule();

- spin_lock_irq(&current->sighand->siglock);
+ spin_lock_irq(&sig->ctrl_lock);
return true;
} else {
/*
@@ -2128,7 +2177,7 @@ static bool do_signal_stop(int signr)
* number as exit_code and no siginfo.
*
* CONTEXT:
- * Must be called with @current->sighand->siglock held, which may be
+ * Must be called with @current->signal->ctrl_lock held, which may be
* released and re-acquired before returning with intervening sleep.
*/
static void do_jobctl_trap(void)
@@ -2153,6 +2202,8 @@ static void do_jobctl_trap(void)
static int ptrace_signal(int signr, siginfo_t *info,
struct pt_regs *regs, void *cookie)
{
+ assert_spin_locked(&current->signal->ctrl_lock);
+
ptrace_signal_deliver(regs, cookie);
/*
* We do not check sig_kernel_stop(signr) but set this marker
@@ -2189,9 +2240,11 @@ static int ptrace_signal(int signr, siginfo_t *info,

/* If the (new) signal is now blocked, requeue it. */
if (sigismember(&current->blocked, signr)) {
+ spin_unlock_irq(&current->signal->ctrl_lock);
read_lock(&current->sighand->action_lock);
specific_send_sig_info(signr, info, current);
read_unlock(&current->sighand->action_lock);
+ spin_lock_irq(&current->signal->ctrl_lock);
signr = 0;
}

@@ -2254,11 +2307,10 @@ static void unlock_action(struct sighand_struct *sighand, bool write_locked)
*/
static inline bool __notify_parent(struct task_struct *task)
{
- struct sighand_struct *sighand = task->sighand;
struct signal_struct *signal = task->signal;
int why;

- spin_lock_irq(&sighand->siglock);
+ spin_lock_irq(&signal->ctrl_lock);

if (signal->flags & SIGNAL_CLD_MASK) {
if (signal->flags & SIGNAL_CLD_CONTINUED)
@@ -2268,7 +2320,7 @@ static inline bool __notify_parent(struct task_struct *task)

signal->flags &= ~SIGNAL_CLD_MASK;

- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&signal->ctrl_lock);

/*
* Notify the parent that we're continuing. This event is
@@ -2289,7 +2341,7 @@ static inline bool __notify_parent(struct task_struct *task)
return true;
}

- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&signal->ctrl_lock);
return false;
}

@@ -2324,23 +2376,23 @@ freeze:
if (unlikely(current->jobctl & JOBCTL_STOP_PENDING)) {
bool stopped = false;

- spin_lock_irq(&sighand->siglock);
+ spin_lock_irq(&signal->ctrl_lock);
if (current->jobctl & JOBCTL_STOP_PENDING)
stopped = do_signal_stop(0);
- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&signal->ctrl_lock);

if (stopped)
goto freeze;
}

if (unlikely(current->jobctl & JOBCTL_TRAP_MASK)) {
- spin_lock_irq(&sighand->siglock);
+ spin_lock_irq(&signal->ctrl_lock);
if (current->jobctl & JOBCTL_TRAP_MASK) {
do_jobctl_trap();
- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&signal->ctrl_lock);
goto freeze;
}
- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&signal->ctrl_lock);
}

signr = dequeue_signal(current, &current->blocked, info);
@@ -2349,10 +2401,10 @@ freeze:
break; /* will return 0 */

if (unlikely(current->ptrace) && signr != SIGKILL) {
- spin_lock_irq(&sighand->siglock);
+ spin_lock_irq(&signal->ctrl_lock);
signr = ptrace_signal(signr, info,
regs, cookie);
- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&signal->ctrl_lock);
if (!signr)
continue;
}
@@ -2423,9 +2475,9 @@ freeze:
goto freeze;
}

- spin_lock_irq(&sighand->siglock);
+ spin_lock_irq(&signal->ctrl_lock);
stopped = do_signal_stop(info->si_signo);
- spin_unlock_irq(&sighand->siglock);
+ spin_unlock_irq(&signal->ctrl_lock);

if (likely(stopped))
goto freeze;
@@ -2538,6 +2590,7 @@ void exit_signals(struct task_struct *tsk)
if (!signal_pending(tsk))
goto out;

+ spin_lock(&tsk->signal->ctrl_lock);
unblocked = tsk->blocked;
signotset(&unblocked);
retarget_shared_pending(tsk, &unblocked);
@@ -2545,6 +2598,7 @@ void exit_signals(struct task_struct *tsk)
if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) &&
task_participate_group_stop(tsk))
group_stop = CLD_STOPPED;
+ spin_unlock(&tsk->signal->ctrl_lock);
out:
spin_unlock_irq(&tsk->sighand->siglock);

@@ -2612,7 +2666,9 @@ void set_current_blocked(const sigset_t *newset)
struct task_struct *tsk = current;

spin_lock_irq(&tsk->sighand->siglock);
+ spin_lock(&tsk->signal->ctrl_lock);
__set_task_blocked(tsk, newset);
+ spin_unlock(&tsk->signal->ctrl_lock);
spin_unlock_irq(&tsk->sighand->siglock);
}
EXPORT_SYMBOL(set_current_blocked);
@@ -2847,8 +2903,10 @@ int do_sigtimedwait(const sigset_t *which, siginfo_t *info,
timeout = schedule_timeout_interruptible(timeout);

spin_lock_irq(&tsk->sighand->siglock);
+ spin_lock(&tsk->signal->ctrl_lock);
__set_task_blocked(tsk, &tsk->real_blocked);
siginitset(&tsk->real_blocked, 0);
+ spin_unlock(&tsk->signal->ctrl_lock);
spin_unlock_irq(&tsk->sighand->siglock);
sig = dequeue_signal(tsk, &mask, info);
}
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 574956c..47f278f 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2272,6 +2272,7 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm)
for (i = 0; i < 3; i++)
do_setitimer(i, &itimer, NULL);
spin_lock_irq(&current->sighand->siglock);
+ spin_lock(&current->signal->ctrl_lock);
if (!(current->signal->flags & SIGNAL_GROUP_EXIT)) {
__flush_signals(current);
write_lock(&current->sighand->action_lock);
@@ -2279,6 +2280,7 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm)
write_unlock(&current->sighand->action_lock);
sigemptyset(&current->blocked);
}
+ spin_unlock(&current->signal->ctrl_lock);
spin_unlock_irq(&current->sighand->siglock);
}

--
1.7.4.4