--- a/security/landlock/tsync.c +++ b/security/landlock/tsync.c @@ -447,6 +447,12 @@ int landlock_restrict_sibling_threads(const struct cred *old_cred, shared_ctx.new_cred = new_cred; shared_ctx.set_no_new_privs = task_no_new_privs(current); + /* + * Serialize concurrent TSYNC operations to prevent deadlocks + * when multiple threads call landlock_restrict_self() simultaneously. + */ + down_write(¤t->signal->exec_update_lock); + /* * We schedule a pseudo-signal task_work for each of the calling task's * sibling threads. In the task work, each thread: @@ -527,14 +533,17 @@ int landlock_restrict_sibling_threads(const struct cred *old_cred, -ERESTARTNOINTR); /* - * Cancel task works for tasks that did not start running yet, - * and decrement all_prepared and num_unfinished accordingly. + * Opportunistic improvement: try to cancel task works + * for tasks that did not start running yet. We do not + * have a guarantee that it cancels any of the enqueued + * task works (because task_work_run() might already have + * dequeued them). */ cancel_tsync_works(&works, &shared_ctx); /* - * The remaining task works have started running, so waiting for - * their completion will finish. + * We must wait for the remaining task works to finish to + * prevent a use-after-free of the local shared_ctx. */ wait_for_completion(&shared_ctx.all_prepared); } @@ -557,5 +566,7 @@ int landlock_restrict_sibling_threads(const struct cred *old_cred, tsync_works_release(&works); + up_write(¤t->signal->exec_update_lock); + return atomic_read(&shared_ctx.preparation_error); }