[PATCH v2 0/6] Landlock: Implement scope control for pathname Unix sockets

Tingmao Wang m at maowtm.org
Tue Feb 3 21:53:11 UTC 2026


On 2/3/26 17:54, Günther Noack wrote:
> On Tue, Feb 03, 2026 at 01:26:31AM +0000, Tingmao Wang wrote:
>> On 2/2/26 22:03, Justin Suess wrote:
>>> Regardless if you merge the patch series now in 7.0 or a later version, I think there is something to be said
>>> about having the filesystem and scoped unix access right merged in the same ABI version / merge window.
>>>
>>> As you pointed out earlier, the combination of the two flags is much flexible and useful to userspace
>>> consumers than one or the other, and if the features were merged separately, there would be an
>>> awkward middle ABI where user space consumers may have to make compromises or changes to
>>> sandbox between different versions or change application behavior.
>>> [...]
>>
>> Given that the scope bit and RESOLVE_UNIX access right are in some sense
>> part of the same system (they interact in an OR manner, after all), there
>> is some positive for having them introduced in the same version, but on
>> the other hand, with my above reasoning, I don't think these two
>> mechanisms (scope bit and RESOLVE_UNIX access) being in different ABI
>> versions would be too much of a problem.  In either case, for applications
>> which require access to more "privileged" sockets, when running on a
>> kernel without the RESOLVE_UNIX access right support, no pathname socket
>> restrictions can be applied (i.e. it won't use the scope bit either, there
>> isn't much "compromise" it can make here).  On the other hand, if
>> RESOLVE_UNIX is supported, then it knows that the scope bit is also
>> supported, and can just use it.
>
> Yes, but that does require additional subtle backwards compatibility
> logic in userspace libraries, to implement the "best effort" fallbacks.
>
> Assuming the scoped bit is added in v8 and the FS_RESOLVE_UNIX right in v9,
> if a user does this (in Go-landlock syntax):
>
>   // restrict both scoped bit and FS RESOLVE_UNIX right, if possible
>   landlock.V9.BestEffort().RestrictPaths(
>     landlock.ResolveUnix("/tmp/socket"),  // allow to connect to /tmp/socket
>   )
>
> then if the system only supports ABI v8, it will have to clear both
> bits so that connections to /tmp/socket work,
> even though the scoped bit is technically supported on v8.
>
> **This requires additional logic in client libraries**,
> similar to our "refer" semantics (which users often get wrong):
>
>   if (there is a rule that allows connections by path name)
>      clear_the_scoped_bit_as_well();
>      // even though the path name rule normally only affects a different bit
>
>
> In contrast, if both the scoped bit and FS_RESOLVE_UNIX were added in
> the same ABI version, then if a user does the above call, we are
> either equal-or-above that ABI version, in which case it works, or we
> are below that ABI version, in which case the two bits already get
> cleared from the landlock_ruleset_attr through the existing backwards
> compatibility mechanism.
>
> **In my mind, Justin is right that we should ideally introduce these
> together.** We have seen users implementing the "Refer" special case
> wrongly very often, it will likely happen here too, if we require
> extra logic in userspace libraries.

Ok, this makes sense to me.  I will send the patch's next version as-is
anyway for completeness since it's basically done but I recognize that we
might change the plan based on this discussion.

>
>
> BTW, regarding the implementation: To have *OR* semantics for "within
> scope" and "allow-listed path", the implementation will be
> non-trivial, and I suspect we won't hit the merge window if we try to
> get them both in for 7.0.  But in my mind, a simple UAPI is more
> important than trying to make it in time for the next merge window.
>
> (The implementation is difficult because the path-based and
> scope-based check currently happen in different LSM hooks, and none of
> the two hooks has enough information to make the decision alone.  The
> second hook only gets called if the first returns 0.  It'll require
> some further discussion to make it work together.)

Right.  In that case, would it make sense to pass sk into the new
security_unix_find() hook, perhaps with the new argument named `struct
sock *other`?  Then we can use this hook for the scope check as well by
using landlock_cred(other->sk_socket->file->f_cred)->domain etc.

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 227467236930..db9d279b3883 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1223,24 +1223,24 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
 
 	err = -ECONNREFUSED;
 	inode = d_backing_inode(path.dentry);
 	if (!S_ISSOCK(inode->i_mode))
 		goto path_put;
 
+	err = -ECONNREFUSED;
+	sk = unix_find_socket_byinode(inode);
+	if (!sk)
+		goto path_put;
+
 	/*
 	 * We call the hook because we know that the inode is a socket
 	 * and we hold a valid reference to it via the path.
 	 */
-	err = security_unix_find(&path, type, flags);
+	err = security_unix_find(&path, sk, flags);
 	if (err)
-		goto path_put;
-
-	err = -ECONNREFUSED;
-	sk = unix_find_socket_byinode(inode);
-	if (!sk)
-		goto path_put;
+		goto sock_put;
 
 	err = -EPROTOTYPE;
 	if (sk->sk_type == type)
 		touch_atime(&path);
 	else
 		goto sock_put;

By doing this we won't even need to pass `type` separately anymore.  The
only change would be that now one can determine if a socket is bound or
not even without being allowed RESOLVE_UNIX access.  I'm not sure how much
of an issue this is, but we could also call the hook anyway with a NULL in
place of the new argument, if unix_find_socket_byinode() fails.  Other
LSMs can then decide what to do in that case (either return -ECONNREFUSED
or -EPERM).

>
>
>> Furthermore, an application / Landlock config etc can always opt to not
>> use the scope bit at all, if it "knows" all the locations where the
>> application's sockets would be placed, and just use RESOLVE_UNIX access
>> right (or nothing if it is not supported).
>>
>> (The following is a bit of a side note, not terribly relevant if we're
>> deciding to go with the patch as is.)
>>
>>>> [...]
>>>> Another way to put it is that, if FS-based and scope-based controls
>>>> interacts in the above proposed way, both mechanisms feel like "poking
>>>> holes" in the other.  But as Mickaël said, one can think of the two
>>>> mechanisms not as independent controls, but rather as two interfaces for
>>>> the same control.  The socket access control is "enabled" if either the
>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX access is handled, or the scope bit
>>>> proposed in this patch is enabled.
>>>>
>>>> With that said, I can think of some alternative ways that might make this
>>>> API look "better" (from a subjective point of view, feedback welcome),
>>>> however it does mean more delays, and specifically, these will depend on
>>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX:
>>>>
>>>> One possibility is to simply always allow a Landlock domain to connect to
>>>> its own sockets (in the case where LANDLOCK_ACCESS_FS_RESOLVE_UNIX is
>>>> handled, otherwise all sockets are allowed).  This might be reasonable, as
>>>> one can only connect to a socket it creates if it has the permission to
>>>> create it in the first place, which is already controlled by
>>>> LANDLOCK_ACCESS_FS_MAKE_SOCK, so we don't really lose any policy
>>>> flexibility here - if for some reason the sandboxer don't want to allow
>>>> access to any (pathname) sockets, even the sandboxed app's own ones, it
>>>> can just not allow LANDLOCK_ACCESS_FS_MAKE_SOCK anywhere.
>>>
>>> LANDLOCK_ACCESS_FS_MAKE_SOCK is only required to bind/listen to a
>>> socket, not to connect.  I guess you was thinking about
>>> LANDLOCK_ACCESS_FS_RESOLVE_UNIX in this case?
>>
>> In this "allow same-scope connect unconditionally" proposal, the
>> application would still be able to (bind to and) connect to its own
>> sockets, even if LANDLOCK_ACCESS_FS_RESOLVE_UNIX is handled and nothing is
>> allowed to have LANDLOCK_ACCESS_FS_RESOLVE_UNIX access.  But a sandboxer
>> which for whatever reason doesn't want this "allow same scope" default can
>> still prevent the use of (pathname) sockets by restricting
>> LANDLOCK_ACCESS_FS_MAKE_SOCK, because if an app can't connect to any
>> sockets it doesn't own, and can't create any sockets itself either, then
>> it effectively can't connect to any sockets at all.
>>
>> (Although on second thought, I guess there could be a case where an app
>> first creates some socket files before doing landlock_restrict_self(),
>> then it might still be able to bind to these even without
>> LANDLOCK_ACCESS_FS_MAKE_SOCK?)
>
> FWIW, I also really liked Tingmao's first of the two listed
> possibilities in [1], where she proposed to introduce both rights
> together.  In my understanding, the arguments we have discussed so far
> for that are:
>
> IN FAVOR:
>
> (pro1) Connecting to a UNIX socket in the same scope is always safe,
>        and it makes it possible to use named UNIX sockets between the
>        processes within a Landlock domains.  (Mickaël convinced me in
>        discussion at FOSDEM that this is true.)
>
>        If someone absolutely does not want that, they can restrict
>        LANDLOCK_ACCESS_FS_MAKE_SOCK and achieve the same effect (as
>        Tingmao said above).
>
> (pro2) The implementation of this is simpler.
>
>        (I attempted to understand how the "or" semantics would be
>        implemented, and I found it non-trivial when you try to do it
>        for all layers at once. (Kernighan's Law applies, IMHO))

I think the logic would basically be:

1. if any layers deny the access due to handled RESOLVE_UNIX but does not
have the scope bit set, then we will deny rightaway, without calling
domain_is_scoped().

2. Call domain_is_scoped() with a bitmask of "rules_covered" layers where
there are RESOLVE_UNIX rules covering the socket being accessed, and
essentially ignore those layers in the scope violation check.

I definitely agree that it is tricky, but making same-scope access be
allowed (i.e. the suggested idea above) would only get rid of step 1,
which I think is the "simpler" bit.  The extra logic in step 2 is still
needed. 

I definitely agree with pro1 tho.

>
> AGAINST:
>
> (con1) It would work differently than the other scoped access rights
>        that we already have.
>
>        A speculative feature that could potentially be built with the
>        scoped access rights is that we could add a rule to permit IPC
>        to other Landlock scopes, e.g. introducing a new rule type
>
>          struct landlock_scope_attr {
>            __u64 allowed_access;  /* for "scoped" bits */
>            /* some way to identify domains */
>          }
>
>        so that we could make IPC access to other Landlock domains
>        configurable.
>
>        If the scoped bit and the FS RESOLVE_UNIX bit were both
>        conflated in RESOLVE_UNIX, it would not be possible to make
>        UNIX connections configurable in such a way.

This exact API would no longer work, but if we give up the equivalence
between scope bits and the landlock_scope_attr struct, then we can do
something like:

struct landlock_scope_attr {
    __u64 ptrace:1;  /* Note that this is not a (user controllable) scope bit! */
    __u64 abstract_unix_socket:1;
    __u64 pathname_unix_socket:1;
    /* ... */

    __u64 allowed_signals;

    /*
     * some way to identify domains, maybe we could use the audit domain
     * ID, with 0 denoting "allow access to non-Landlocked processes?
     */
}

>
> (con2) Consistent behaviour between scoped flags and their
>        interactions with other access rights:
>
>        The existing scoped access rights (signal, abstract sockets)
>        could hypothetically be extended with a related access right of
>        another type. For instance, there could be an access right type
>
>          __u64 handled_signal_number;
>
>        and then you could add a rule to permit the use of certain
>        signal numbers.  The interaction between the scoped flags and
>        other access rights should work the same.
>
>
> Constructive Proposal for consideration: Why not both?
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I will think about the following a bit more but I'm afraid that I feel
like it might get slightly confusing.  With this, the only reason for
having LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET is for API consistency when we
later enable allowing access to other domains (if I understood correctly),
in which case I personally feel like the suggestion on landlock_scope_attr
above, where we essentially accept that it is decoupled with the scope
bits in the ruleset, might be simpler...?

>
> Why not do both what Tingmao proposed in [1] **and** reserve the
> option to add the matching "scoped flag" later?
>
>   * Introduce LANDLOCK_ACCESS_FS_RESOLVE_UNIX.
>
>     If it is handled, UNIX connections are allowed either:
>
>     (1) if the connection is to a service in the same scope, or
>     (2) if the path was allow-listed with a "path beneath" rule.
>
>   * Add LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET later, if needed.
>
>
> Let's go through the arguments again:
>
> We have observed that it is harmless to allow connections to services
> in the same scope (1), and that if users absolutely don't want that,
> they can actually prohibit it through LANDLOCK_ACCESS_FS_MAKE_SOCK
> (pro1).
>
> (con1): Can we still implement the feature idea where we poke a hole
>         to get UNIX-connect() access to other Landlock domains?
>
>   I think the answer is yes.  The implementation strategy is:
>
>     * Add the scoped bit LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET
>     * The scoped bit can now be used to allow-list connections to
>       other Landlock domains.
>
>   For users, just setting the scoped bit on its own does the same as
>   handling LANDLOCK_ACCESS_FS_RESOLVE_UNIX.  That way, the kernel-side
>   implementation can also stay simple.  The only reason why the scoped
>   bit is needed is because it makes it possible to allow-list
>   connections to other Landlock domains, but at the same time, it is
>   safe if libraries set the scoped bit once it exists, as it does not
>   have any bad runtime impact either.
>
> (con2): Consistency: Do all the scoped flags interact with their
>         corresponding access rights in the same way?
>
>   The other scope flags do not have corresponding access rights, so
>   far.
>
>   If we were to add corresponding access rights for the other scope
>   flags, I would argue that we could apply a consistent logic there,
>   because IPC access within the same scope is always safe:
>
>   - A hypothetical access right type for "signal numbers" would only
>     restrict signals that go beyond the current scope.
>
>   - A hypothetical access right type for "abstract UNIX domain socket
>     names" would only restrict connections to abstract UNIX domain
>     servers that go beyond the current scope.
>
>   I can not come up with a scenario where this doesn't work.
>
>
> In conclusion, I think the approach has significant upsides:
>
>   * Simpler UAPI: Users only have one access bit to deal with, in the
>     near future.  Once we do add a scope flag for UNIX connections, it
>     does not interact in a surprising way with the corresponding FS
>     access right, because with either of these, scoped access is
>     allowed.
>
>     If users absolutely need to restrict scoped access, they can
>     restrict LANDLOCK_ACCESS_FS_MAKE_SOCK.  It is a slightly obscure
>     API, but in line with the "make easy things easy, make hard things
>     possible" API philosophy.  And needing this should be the
>     exception rather than the norm, after all.
>
>   * Consistent behaviour between scoped flags and regular access
>     rights, also for speculative access rights affecting the existing
>     scoped flags for signals and abstract UNIX domain sockets.
>
> I know this was a slightly long mail, but I thought long and tried to
> be structured.  Please let me know what you think.
>
> —Günther
>
>
> [1] https://lore.kernel.org/all/f07fe41a-96c5-4d3a-9966-35b30b3a71f1@maowtm.org/



More information about the Linux-security-module-archive mailing list