diff --git a/fs/exec.c b/fs/exec.c index c7b8f2d6366c..d01523d0d8b4 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -854,6 +855,7 @@ static int exec_mmap(struct linux_binprm *bprm) /* Notify parent that we're no longer interested in the old VM */ tsk = current; old_mm = current->mm; + /* Clean up futexes and release the mm */ exec_mm_release(tsk, old_mm); ret = down_write_killable(&tsk->signal->exec_update_lock); @@ -902,9 +904,10 @@ static int exec_mmap(struct linux_binprm *bprm) BUG_ON(active_mm != old_mm); /* Defer teardown to setup_new_exec(), outside the exec locks. */ bprm->old_mm = old_mm; - return 0; + } else { + mmdrop_lazy_tlb(active_mm); } - mmdrop_lazy_tlb(active_mm); + futex_exec_done(tsk); return 0; } diff --git a/include/linux/futex.h b/include/linux/futex.h index 51f4ccdc9092..51d5faa1266f 100644 --- a/include/linux/futex.h +++ b/include/linux/futex.h @@ -73,6 +73,7 @@ static inline void futex_init_task(struct task_struct *tsk) void futex_exit_recursive(struct task_struct *tsk); void futex_exit_release(struct task_struct *tsk); void futex_exec_release(struct task_struct *tsk); +void futex_exec_done(struct task_struct *tsk); long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout, u32 __user *uaddr2, u32 val2, u32 val3); @@ -91,6 +92,7 @@ static inline void futex_init_task(struct task_struct *tsk) { } static inline void futex_exit_recursive(struct task_struct *tsk) { } static inline void futex_exit_release(struct task_struct *tsk) { } static inline void futex_exec_release(struct task_struct *tsk) { } +static inline void futex_exec_done(struct task_struct *tsk) { } static inline long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout, u32 __user *uaddr2, u32 val2, u32 val3) { diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 0ea2c1a89f6c..3c1562df0370 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1524,11 +1524,11 @@ static void futex_cleanup_begin(struct task_struct *tsk) raw_spin_unlock_irq(&tsk->pi_lock); } -static void futex_cleanup_end(struct task_struct *tsk, int state) +static void futex_cleanup_end(struct task_struct *tsk) __releases(&tsk->futex.exit_mutex) { scoped_guard(raw_spinlock_irq, &tsk->pi_lock) - tsk->futex.state = state; + tsk->futex.state = FUTEX_STATE_DEAD; /* * Drop the exit protection. This unblocks waiters which observed @@ -1537,29 +1537,49 @@ static void futex_cleanup_end(struct task_struct *tsk, int state) mutex_unlock(&tsk->futex.exit_mutex); } -void futex_exec_release(struct task_struct *tsk) -{ - /* - * The state handling is done for consistency, but in the case of - * exec() there is no way to prevent further damage as the PID stays - * the same. But for the unlikely and arguably buggy case that a - * futex is held on exec(), this provides at least as much state - * consistency protection which is possible. - */ - futex_cleanup_begin(tsk); - futex_cleanup(tsk); - /* - * Reset the state to FUTEX_STATE_OK. The task is alive and about - * exec a new binary. - */ - futex_cleanup_end(tsk, FUTEX_STATE_OK); -} - void futex_exit_release(struct task_struct *tsk) { futex_cleanup_begin(tsk); futex_cleanup(tsk); - futex_cleanup_end(tsk, FUTEX_STATE_DEAD); + futex_cleanup_end(tsk); +} + +void futex_exec_release(struct task_struct *tsk) +{ + /* + * exec() makes it interesting for futexes because the TID of the task + * stays the same, but from a futex perspective the task has to be + * treated like an exiting task. This is especially important for the + * sanity check for private futexes in attach_to_pi_owner() which + * compares the owner's mm with the waiter's mm. + * + * That check would give the wrong answer if futex_cleanup_end() would + * set the state to FUTEX_STATE_OK as long as the task still has the old + * mm. + * + * After the task has switched to the new mm it sets it to + * FUTEX_STATE_OK again in futex_exec_done(). + */ + futex_exit_release(tsk); +} + +/* + * exec() has switched to the new mm. Futex operations are safe again. + */ +void futex_exec_done(struct task_struct *tsk) +{ + /* + * This store does not have to take tsk::futex::exit_mutex because the + * phase where waiters block on it during state FUTEX_STATE_EXITING has + * been finished when futex_cleanup_end() set the state to + * FUTEX_STATE_DEAD. + * + * This transitions back from FUTEX_STATE_DEAD to FUTEX_STATE_OK. The + * ordering guarantee required here is that the previous store to + * tsk::mm in the calling code cannot be reordered against this store. + */ + guard(raw_spinlock_irq)(&tsk->pi_lock); + tsk->futex.state = FUTEX_STATE_OK; } static void futex_hash_bucket_init(struct futex_hash_bucket *fhb) diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c index 2731e55f99e5..88788e584ec8 100644 --- a/kernel/futex/pi.c +++ b/kernel/futex/pi.c @@ -200,15 +200,25 @@ void put_pi_state(struct futex_pi_state *pi_state) * * - FUTEX_STATE_OK when the task is alive and waiters can be attached * - * - FUTEX_STATE_EXITING when the task cleans up the robust list and pi + * - FUTEX_STATE_EXITING when the task cleans up the robust list and PI * state. Concurrent waiters cannot attach anymore and have to wait until the - * cleanup is finished to re-evaluate the potential changes of robust list and - * pi state cleanups. + * cleanup is finished to re-evaluate the potential changes caused by the + * robust list and PI state cleanups. * - * - FUTEX_STATE_DEAD when the task has cleaned up the robust list and - * is about to fully exit. + * - FUTEX_STATE_DEAD when the task has cleaned up the robust list. This state + * is set independent of exit() or exec(). In the exit() case the task is + * gone. In the exec() case this ensures that nothing can attach to the task + * after cleaning up the robust list and PI state before it has switched to + * the new mm. From a futex point of view the task is dead until it sets the + * state to FUTEX_STATE_OK again after switching to the new mm. * - * exec() switches back to FUTEX_STATE_OK after the cleanup. + * The valid state transitions for exit(): + * + * FUTEX_STATE_OK -> FUTEX_STATE_EXITING -> FUTEX_STATE_DEAD + * + * The valid state transitions for exec(): + * + * FUTEX_STATE_OK -> FUTEX_STATE_EXITING -> FUTEX_STATE_DEAD -> FUTEX_STATE_OK * * The state has two related locks: *