Re: [RFC PATCH 00/20] BPF interface for applying Landlock rulesets

Justin Suess
2026-04-13T15:06:15Z

On Wed, Apr 08, 2026 at 09:21:11PM +0200, Mickaël Salaün wrote:
> On Wed, Apr 08, 2026 at 01:10:28PM -0400, Justin Suess wrote:
> >
> > Add a flag LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, which executes
> > task_set_no_new_privs on the current credentials, but only if
> > the process lacks the CAP_SYS_ADMIN capability.
> >
> > While this operation is redundant for code running from userspace
> > (indeed callers may achieve the same logic by calling
> > prctl w/ PR_SET_NO_NEW_PRIVS), this flag enables callers without access
> > to the syscall abi (defined in subsequent patches) to restrict processes
> > from gaining additional capabilities. This is important to ensure that
> > consumers can meet the task_no_new_privs || CAP_SYS_ADMIN invariant
> > enforced by Landlock without having syscall access.
> >
> > This is done by hooking bprm_committing_creds along with a
> > landlock_cred_security flag to indicate that the next execution should
> > task_set_no_new_privs if the process doesn't possess CAP_SYS_ADMIN. This
> > is done to ensure that task_set_no_new_privs is being done past the
> > point of no return.
> >
> > Cc: Mickaël Salaün <mic@digikod.net>
> > Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> > ---
> >
> > On Wed, Apr 08, 2026 at 02:00:00 -0000, Mickaël Salaün wrote:
> > > > Points of Feedback
> > > > ===
> > > >
> > > > First, the new set_nnp_on_point_of_no_return field in struct linux_binprm.
> > > > This field was needed to request that task_set_no_new_privs be set during an
> > > > execution, but only after the execution has proceeded beyond the point of no
> > > > return. I couldn't find a way to express this semantic without adding a new
> > > > bitfield to struct linux_binprm and a conditional in fs/exec.c. Please see
> > > > patch 2.
> >
> > > What about using security_bprm_committing_creds()?
> >
> > Good idea. Definitely cleaner.
> >
> > Something like this? Then dropping the "execve: Add set_nnp_on_point_of_no_return"
> > commit.
> >
> > This adds a bitfield to the landlock_cred_security struct to indicate that the flag
> > should be set on the next exec(s).
> >
> > include/uapi/linux/landlock.h | 14 ++++++++++++++
> > security/landlock/cred.c | 13 +++++++++++++
> > security/landlock/cred.h | 7 +++++++
> > security/landlock/limits.h | 2 +-
> > security/landlock/ruleset.c | 15 ++++++++++++---
> > security/landlock/syscalls.c | 5 +++++
> > 6 files changed, 52 insertions(+), 4 deletions(-)
> >
> > diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> > index f88fa1f68b77..edd9d9a7f60e 100644
> > --- a/include/uapi/linux/landlock.h
> > +++ b/include/uapi/linux/landlock.h
> > @@ -129,12 +129,26 @@ struct landlock_ruleset_attr {
> > *
> > * If the calling thread is running with no_new_privs, this operation
> > * enables no_new_privs on the sibling threads as well.
> > + *
> > + * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
> > + * Sets no_new_privs on the calling thread before applying the Landlock domain.
> > + * This flag is useful for convenience as well as for applying a ruleset from
> > + * an outside context (e.g BPF). This flag only has an effect on when both
> > + * no_new_privs isn't already set and the caller doesn't possess CAP_SYS_ADMIN.
> > + *
> > + * This flag has slightly different behavior when used from BPF. Instead of
> > + * setting no_new_privs on the current task, it sets a flag on the bprm so that
> > + * no_new_privs is set on the task at exec point-of-no-return. This guarantees
> > + * that the current execution is unaffected, and may escalate as usual until the
> > + * next exec, but the resulting task cannot gain more privileges through later
> > + * exec transitions.
> > */
> > /* clang-format off */
> > #define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (1U << 0)
> > #define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1)
> > #define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (1U << 2)
> > #define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3)
> > +#define LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS (1U << 4)
> > /* clang-format on */
> >
> > /**
> > diff --git a/security/landlock/cred.c b/security/landlock/cred.c
> > index 0cb3edde4d18..bcc9b716916f 100644
> > --- a/security/landlock/cred.c
> > +++ b/security/landlock/cred.c
> > @@ -43,6 +43,18 @@ static void hook_cred_free(struct cred *const cred)
> > landlock_put_ruleset_deferred(dom);
> > }
> >
> > +static void hook_bprm_committing_creds(const struct linux_binprm *bprm)
> > +{
> > + struct landlock_cred_security *const llcred = landlock_cred(bprm->cred);
> > +
> > + if (llcred->set_nnp_on_committing_creds &&
> > + !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) {
>
> If asked by the caller, NNP must be set, whatever the capabilities of
> the task.
>
> > + task_set_no_new_privs(current);
> > + /* Don't need to set it again for subsequent execution. */
> > + llcred->set_nnp_on_committing_creds = false;
> > + }
>
> Thinking more about it, it would make more sense to add another flag to
> enforce restriction on the next exec. This new cred bit would then be
> generic and enforce both NNP (if set) and the domain once we know the
> execution is ok. That should also bring the required plumbing to
> create the domain at syscall (or kfunc) time and handle memory
> allocation issue there, but only enforce it at exec time with
> security_bprm_committing_creds() (without any possible error).
>

I did some more consideration as well over the weekend.

For no new privs post point of new return:

It still seems to me we can't have post point-of-no-return setting of
NNP from userspace without CAP_SYS_ADMIN for the security reason
listed previously. The BPF side may not need to be subject to that
restriction, since it's in a higher security boundary.

For ruleset enforcement post point of no return:

The post point-of-no-return enforcement of a ruleset from
userspace would be OK, as long as the existing task_no_new_privs ||
CAP_SYS_ADMIN invarient is enforced.

The way I'm thinking of implementing this is storing two pointers to
unmerged rulesets in struct landlock_cred_security. One for the BPF side
and one for the userspace side. If landlock_restrict_self is called with
LANDLOCK_RESTRICT_SELF_EXECTIME (proposed name for this flag), then the
domain would be copied and the pointer to the copy and stored there.

The BPF side would have a seperate pointer, and do the same copy and
store.

Repeated calls to landlock_restrict_self LANDLOCK_RESTRICT_SELF_EXECTIME
would put the reference (and hence free) on the stored unmerged domain,
then store the new one.

When we reach the security_bprm_committing_creds hook, we can merge the
domains in a deterministic order:

1. Existing domain (if any)
2. The domain stored from bpf_landlock_restrict_bprm (if any)
3. The domain stored from landlock_restrict_self w/
LANDLOCK_RESTRICT_SELF_EXECTIME (if any)

Then set the domain pointer to the newly merged domain.

Then we release the references on the stored domains and reset the
pointers to null.

Some implementation details:

1. LANDLOCK_RESTRICT_SELF_EXECTIME w/ bpf_landlock_restrict_binprm is
redundant since the kfunc is designed to apply there anyway so we can return an error
if it is explictly set when used with that kfunc. (Or always require
it be set)
2. The existing LANDLOCK_RESTRICT_SELF_LOG_* flags would be set on the
stored domain.
3. The TSYNC flags would be sort of misleading for either of these two
flags and should be mutually exclusive with both of the NO_NEW_PRIVS
and EXECTIME flags.
4. Common enforcement and merge path for bpf and userspace as you stated
earlier

I can make a separate series with one or both of these flags if you
wish once we hear about the preferred tree that this needs to be based
on. Or keep it as one (very large) series.

Justin

> > …