Subject: Re: [PATCH v3] landlock: Add abstract unix socket connect restriction

From: Mickaël Salaün <[email-masked]>
Date: Mon, 10 Jun 2024 18:36:08 +0200

On Fri, Jun 07, 2024 at 01:41:39PM -0600, Tahera Fahimi wrote:
> On Fri, Jun 07, 2024 at 10:28:35AM +0200, Günther Noack wrote:
> > Hello Tahera!
> > 
> > Thanks for sending another revision of your patch set!
> Hello Günther, 
> Thanks for your feedback.
> 
> > On Thu, Jun 06, 2024 at 05:44:46PM -0600, Tahera Fahimi wrote:
> > > Abstract unix sockets are used for local inter-process communications
> > > without on a filesystem. Currently a sandboxed process can connect to a
> > > socket outside of the sandboxed environment, since landlock has no
> > > restriction for connecting to a unix socket in the abstract namespace.
> > > Access to such sockets for a sandboxed process should be scoped the same
> > > way ptrace is limited.
> > > 
> > > Because of compatibility reasons and since landlock should be flexible,
> > > we extend the user space interface by adding a new "scoped" field. This
> > > field optionally contains a "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to
> > > specify that the ruleset will deny any connection from within the
> > > sandbox to its parents(i.e. any parent sandbox or non-sandbox processes)
> > > 
> > > Closes: https://github.com/landlock-lsm/linux/issues/7
> > > Signed-off-by: Tahera Fahimi <[email-masked]>
> > > 
> > > -------
> > > V3: Added "scoped" field to landlock_ruleset_attr
> > > V2: Remove wrapper functions
> > > 
> > > -------
> > > ---
> > >  include/uapi/linux/landlock.h | 28 +++++++++++++++++++++++
> > >  security/landlock/limits.h    |  5 ++++
> > >  security/landlock/ruleset.c   | 15 ++++++++----
> > >  security/landlock/ruleset.h   | 28 +++++++++++++++++++++--
> > >  security/landlock/syscalls.c  | 12 +++++++---
> > >  security/landlock/task.c      | 43 +++++++++++++++++++++++++++++++++++
> > >  6 files changed, 121 insertions(+), 10 deletions(-)
> > > 
> > > diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> > > index 68625e728f43..d887e67dc0ed 100644
> > > --- a/include/uapi/linux/landlock.h
> > > +++ b/include/uapi/linux/landlock.h
> > > @@ -37,6 +37,12 @@ struct landlock_ruleset_attr {
> > >  	 * rule explicitly allow them.
> > >  	 */
> > >  	__u64 handled_access_net;
> > > +	/**
> > > +	 * scoped: Bitmask of actions (cf. `Scope access flags`_)
> > > +	 * that is handled by this ruleset and should be permitted
> > > +	 * by default if no rule explicitly deny them.
> > > +	 */
> > > +	__u64 scoped;
> > 
> > I have trouble understanding what this docstring means.
> > 
> > If those are "handled" things, shouldn't the name also start with "handled_", in
> > line with the other fields?  Also, I don't see any way to manipulate these
> > rights with a Landlock rule in this ?
> 
> .scoped attribute is not defined as .handled_scope since there is no
> rule to handle/manipulate it, simply because this attribute shows either
> action is permitted or denied. 

Correct.  Günther, what do you think about the naming?

> 
> > How about:
> > 
> > /**
> >  * handled_scoped: Bitmask of IPC actions (cf. `Scoped access flags`_)
> >  * which are confined to only affect the current Landlock domain.
> >  */
> 
> This is a good docstring. I will use it. 
> 
> > __u64 handled_scoped;
> > 
> > >  };
> > >  
> > >  /*
> > > @@ -266,4 +272,26 @@ struct landlock_net_port_attr {
> > >  #define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
> > >  #define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
> > >  /* clang-format on */
> > > +
> > > +/**
> > > + * DOC: scoped
> > > + *
> > > + * Scoped handles a set of restrictions on kernel IPCs.
> > > + *
> > > + * Scope access flags
> > 
> > Scoped with a "d"?
> Scoped meant to point to .scoped attribute.  

Right, but a "d" was missing.

> > > + * ~~~~~~~~~~~~~~~~~~~~
> > > + * 
> > > + * These flags enable to restrict a sandboxed process from a set of
> > > + * inter-process communications actions. Setting a flag in a landlock
> > > + * domain will isolate the Landlock domain to forbid connections
> > > + * to resources outside the domain.
> > > + *
> > > + * IPCs with scoped actions:
> > > + * - %LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET: Restrict a sandbox process to
> > > + *   connect to a process outside of the sandbox domain through abstract
> > > + *   unix sockets.
> > > + */
> > > +/* clang-format off */
> > > +#define LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET		(1ULL << 0)
> > 
> > Should the name of this #define indicate the direction that we are restricting?
> 
> Since the domain of a process specifies if a process can connect or not,
> the direction of the connection does not matter. This restriction is the
> same as ptrace.
> 
> > If I understand your documentation correctly, this is about *connecting out* of
> > the current Landlock domain, but incoming connections from more privileged
> > domains are OK, right?
> 
> Yes, Incoming connections are allowed if they are from a higher
> privileged domain (or no domain). Consider two process P1 and P2 where
> P1 wants to connect to P2. If P1 is not landlocked, it can connect to P2
> regardless of whether P2 has a domain. If P1 is landlocked, it must have
> an equal or less domain than P2 to connect to P2. We disscussed about
> direction in [2]
> https://lore.kernel.org/outreachy/[email-masked]/T/#m6d5c5e65e43eaa1c8c38309f1225d169be3d6f87

Correct, this is what Günther highlighted: restriction on direction.

About the name "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET", I think it should
be OK if the "scoped" docstring section clearly explains this direction.

> 
> > 
> > Also:
> > 
> > Is it intentional that you are both restricting the connection and the sending
> > with the same flag (security_unix_may_send)?  If an existing Unix Domain Socket
> > gets passed in to a program from the outside (e.g. as stdout), shouldn't it
> > still be possible that the program enables a Landlock policy and then still
> > writes to it?  (Does that work?  Am I mis-reading the patch?)

If a passed socket is already connected, then a write/send should work.

> 
> security_unix_may_send checks if AF_UNIX socket can send datagrams, so
> connecting and sending datagrams happens at the same state. I am not
> sure if I understand your example correctly. Can you please explain a
> bit more?

The concern is about using the current's or the socket's credential.

> 
> > The way that write access is normally checked for other files is at the time
> > when you open the file, not during write(), and I believe it would be more in
> > line with that normal "check at open" behaviour if we did the same here?
> 
> It checks the ability to connect to a unix socket at the point of
> connecting, so I think it is aligned with the "check at point"
> behaviour. This security check is called right before finalizing the
> connection. 

>From my point of view, the main difference between a file's FD and a
socket's FD is that a file's FD allows actions on only the referenced
file, whereas the socket's FD may not only reference a connection
because it can be disconnected (see net_test.c:protocol.connect_unspec)
and reconnected.  Well, as we can see in the test, this doesn't work
with AF_UNIX but I'm wondering if there is no other way to do it.
Anyway, this seems closer to the use of a directory's FD to open another
file: the access check is done when accessing a "new" resource.  To say
it another way, a socket may be seen as a builder object to exchange
data with a peer, and this object can be reconfigured/recycled to
exchange data with another peer.  This rationale also applies to TCP
connect and bind control.

> 
> > 
> > > diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> > > index 20fdb5ff3514..7b794b81ef05 100644
> > > --- a/security/landlock/limits.h
> > > +++ b/security/landlock/limits.h
> > > @@ -28,6 +28,11 @@
> > >  #define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
> > >  #define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
> > >  
> > > +#define LANDLOCK_LAST_ACCESS_SCOPE       LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET
> > > +#define LANDLOCK_MASK_ACCESS_SCOPE	((LANDLOCK_LAST_ACCESS_SCOPE << 1) - 1)
> > > +#define LANDLOCK_NUM_ACCESS_SCOPE         __const_hweight64(LANDLOCK_MASK_ACCESS_SCOPE)
> > > +#define LANDLOCK_SHIFT_ACCESS_SCOPE      LANDLOCK_SHIFT_ACCESS_NET
> >                                             ^^^^^^^^^^^^^^^^^^^^^^^^^
> > 
> > I believe this #define has the wrong value, and as a consequence, the code
> > suffers from the same problem as we already had on the other patch set from
> > Mikhail Ivanov -- see [1] for that discussion.
> 
> Thanks for the hint. I will definitly check this. 
> 
> > The LANDLOCK_SHIFT_ACCESS_FOO variable is used for determining the position of
> > your flag in the access_masks_t type, where all access masks are combined
> > together in one big bit vector.  If you are defining this the same for _SCOPE as
> > for _NET, I believe that we will start using the same bits in that vector for
> > both the _NET flags and the _SCOPE flags, and that will manifest in unwanted
> > interactions between the different types of restrictions.  (e.g. you will create
> > a policy to restrict _SCOPE, and you will find yourself unable to do some things
> > with TCP ports)
> > 
> > Please also see the other thread for more discussions about how we can avoid
> > such problems in the future.  (This code is easy to get wrong,
> > apparently... When we don't test what happens across multiple types of
> > restrictions, everything looks fine.)
> > 
> > [1] https://lore.kernel.org/all/[email-masked]/

Yep, good catch.

A test should be able to catch this issue.

> > —Günther