All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Günther Noack" <gnoack@google.com>
To: "Mickaël Salaün" <mic@digikod.net>
Cc: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>,
	Tingmao Wang <m@maowtm.org>, Paul Moore <paul@paul-moore.com>,
	linux-security-module@vger.kernel.org,
	Jann Horn <jannh@google.com>
Subject: Re: [PATCH v2 1/2] landlock: Multithreading support for landlock_restrict_self()
Date: Thu, 27 Nov 2025 10:56:31 +0100	[thread overview]
Message-ID: <aSggT4V8U1XE0-iI@google.com> (raw)
In-Reply-To: <20251020.fohbo6Iecahz@digikod.net>

Hello!

On Mon, Oct 20, 2025 at 10:12:44PM +0200, Mickaël Salaün wrote:
> On Wed, Oct 01, 2025 at 01:18:06PM +0200, Günther Noack wrote:
> > +/*
> > + * restrict_sibling_threads - enables a Landlock policy for all sibling threads
> > + */
> > +static int restrict_sibling_threads(const struct cred *old_cred,
> > +				    const struct cred *new_cred)
> > +{
> > +	int res;
> > +	struct task_struct *thread, *caller;
> > +	struct tsync_shared_context shared_ctx;
> > +	struct tsync_works works = {};
> > +	size_t newly_discovered_threads;
> > +	bool found_more_threads;
> > +	struct tsync_work *ctx;
> > +
> > +	atomic_set(&shared_ctx.preparation_error, 0);
> > +	init_completion(&shared_ctx.all_prepared);
> > +	init_completion(&shared_ctx.ready_to_commit);
> > +	atomic_set(&shared_ctx.num_unfinished, 0);
> > +	init_completion(&shared_ctx.all_finished);
> > +	shared_ctx.old_cred = old_cred;
> > +	shared_ctx.new_cred = new_cred;
> > +
> > +	caller = current;
> > +
> > +	/*
> > +	 * We schedule a pseudo-signal task_work for each of the calling task's
> > +	 * sibling threads.  In the task work, each thread:
> > +	 *
> > +	 * 1) runs prepare_creds() and writes back the error to
> > +	 *    shared_ctx.preparation_error, if needed.
> > +	 *
> > +	 * 2) signals that it's done with prepare_creds() to the calling task.
> > +	 *    (completion "all_prepared").
> > +	 *
> > +	 * 3) waits for the completion "ready_to_commit".  This is sent by the
> > +	 *    calling task after ensuring that all sibling threads have done
> > +	 *    with the "preparation" stage.
> > +	 *
> > +	 *    After this barrier is reached, it's safe to read
> > +	 *    shared_ctx.preparation_error.
> > +	 *
> > +	 * 4) reads shared_ctx.preparation_error and then either does
> > +	 *    commit_creds() or abort_creds().
> > +	 *
> > +	 * 5) signals that it's done altogether (barrier synchronization
> > +	 *    "all_finished")
> > +	 */
> > +	do {
> > +		found_more_threads = false;
> > +
> > +		/*
> > +		 * The "all_prepared" barrier is used locally to the inner loop,
> > +		 * this use of for_each_thread().  We can reset it on each loop
> > +		 * iteration because all previous loop iterations are done with
> > +		 * it already.
> > +		 *
> > +		 * num_preparing is initialized to 1 so that the counter can not
> > +		 * go to 0 and mark the completion as done before all task works
> > +		 * are registered.  (We decrement it at the end of this loop.)
> > +		 */
> > +		atomic_set(&shared_ctx.num_preparing, 1);
> > +		reinit_completion(&shared_ctx.all_prepared);
> > +
> 
> > +		/* In RCU read-lock, count the threads we need. */
> > +		newly_discovered_threads = 0;
> > +		rcu_read_lock();
> > +		for_each_thread(caller, thread) {
> > +			/* Skip current, since it is initiating the sync. */
> > +			if (thread == caller)
> > +				continue;
> > +
> > +			/* Skip exited threads. */
> > +			if (thread->flags & PF_EXITING)
> > +				continue;
> > +
> > +			/* Skip threads that we have already seen. */
> > +			if (tsync_works_contains_task(&works, thread))
> > +				continue;
> > +
> > +			newly_discovered_threads++;
> > +		}
> > +		rcu_read_unlock();
> 
> This RCU block could be moved in a dedicated helper that will return the
> number of newly discovered threads.  In this helper, we could use
> guard()(rcu).

Done.


> > +
> > +		if (newly_discovered_threads == 0)
> > +			break; /* done */
> > +
> > +		res = tsync_works_grow_by(&works, newly_discovered_threads,
> > +					  GFP_KERNEL_ACCOUNT);
> > +		if (res) {
> > +			atomic_set(&shared_ctx.preparation_error, res);
> > +			break;
> > +		}
> > +
> > +		rcu_read_lock();
> > +		for_each_thread(caller, thread) {
> > +			/* Skip current, since it is initiating the sync. */
> > +			if (thread == caller)
> > +				continue;
> > +
> > +			/* Skip exited threads. */
> > +			if (thread->flags & PF_EXITING)
> > +				continue;
> > +
> > +			/* Skip threads that we already looked at. */
> > +			if (tsync_works_contains_task(&works, thread))
> > +				continue;
> > +
> > +			/*
> > +			 * We found a sibling thread that is not doing its
> > +			 * task_work yet, and which might spawn new threads
> > +			 * before our task work runs, so we need at least one
> > +			 * more round in the outer loop.
> > +			 */
> > +			found_more_threads = true;
> > +
> > +			ctx = tsync_works_provide(&works, thread);
> > +			if (!ctx) {
> > +				/*
> > +				 * We ran out of preallocated contexts -- we
> > +				 * need to try again with this thread at a later
> > +				 * time!  found_more_threads is already true
> > +				 * at this point.
> > +				 */
> > +				break;
> > +			}
> > +
> > +			ctx->shared_ctx = &shared_ctx;
> > +
> > +			atomic_inc(&shared_ctx.num_preparing);
> > +			atomic_inc(&shared_ctx.num_unfinished);
> > +
> > +			init_task_work(&ctx->work,
> > +				       restrict_one_thread_callback);
> > +			res = task_work_add(thread, &ctx->work, TWA_SIGNAL);
> > +			if (res) {
> > +				/*
> > +				 * Remove the task from ctx so that we will
> > +				 * revisit the task at a later stage, if it
> > +				 * still exists.
> > +				 */
> > +				put_task_struct_rcu_user(ctx->task);
> > +				ctx->task = NULL;
> > +
> > +				atomic_set(&shared_ctx.preparation_error, res);
> > +				atomic_dec(&shared_ctx.num_preparing);
> > +				atomic_dec(&shared_ctx.num_unfinished);
> > +			}
> > +		}
> > +		rcu_read_unlock();
> 
> As for the other RCU block, it might help to move this RCU block into a
> dedicated helper.  It seems that it would look easier to read and
> maintain.

Done.


> > +		/*
> > +		 * Decrement num_preparing for current, to undo that we
> > +		 * initialized it to 1 at the beginning of the inner loop.
> > +		 */
> > +		if (atomic_dec_return(&shared_ctx.num_preparing) > 0)
> > +			wait_for_completion(&shared_ctx.all_prepared);
> > +	} while (found_more_threads &&
> > +		 !atomic_read(&shared_ctx.preparation_error));
> 
> Is it safe to prevent inconsistencies wrt execve?  seccomp uses
> cred_guard_mutex (new code should probably use exec_update_lock), why
> should Landlock not do the same?
> 
> Why shouldn't we lock sighand as well?
> 
> Answers to these questions should be explained in comments.

Added something to the top of the loop, based on Jann's explanation in [1].

[1] https://lore.kernel.org/all/CAG48ez3MxN524ge_sZeTwL0FEDASaSTb-gm1vPO8UwpijTeHqw@mail.gmail.com/

—Günther

  parent reply	other threads:[~2025-11-27  9:56 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-01 11:23 [PATCH v2 0/2] Landlock multithreaded enforcement Günther Noack
2025-10-01 11:18 ` [PATCH v2 1/2] landlock: Multithreading support for landlock_restrict_self() Günther Noack
2025-10-17 15:04   ` Mickaël Salaün
2025-10-24 21:18     ` Jann Horn
2025-11-27  9:34       ` Günther Noack
2025-11-27  9:32     ` Günther Noack
2025-10-20 20:12   ` Mickaël Salaün
2025-10-24 21:29     ` Jann Horn
2025-11-27  9:36       ` Günther Noack
2025-11-27  9:56     ` Günther Noack [this message]
2025-10-24 21:11   ` Jann Horn
2025-11-27 10:32     ` Günther Noack
2025-10-01 11:18 ` [PATCH v2 2/2] landlock: selftests for LANDLOCK_RESTRICT_SELF_TSYNC Günther Noack
2025-10-17 15:05   ` Mickaël Salaün

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aSggT4V8U1XE0-iI@google.com \
    --to=gnoack@google.com \
    --cc=jannh@google.com \
    --cc=konstantin.meskhidze@huawei.com \
    --cc=linux-security-module@vger.kernel.org \
    --cc=m@maowtm.org \
    --cc=mic@digikod.net \
    --cc=paul@paul-moore.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.