[RFC PATCH v3 01/19] landlock: Support socket access-control
Mickaël Salaün
mic at digikod.net
Thu Nov 28 20:52:05 UTC 2024
On Thu, Nov 28, 2024 at 03:01:52PM +0300, Mikhail Ivanov wrote:
> On 11/27/2024 9:43 PM, Mickaël Salaün wrote:
> > On Mon, Nov 25, 2024 at 02:04:09PM +0300, Mikhail Ivanov wrote:
> > > On 11/22/2024 8:45 PM, Günther Noack wrote:
> > > > Hello Mikhail,
> > > >
> > > > sorry for the delayed response;
> > > > I am very happy to see activity on this patch set! :)
> > >
> > > Hello Günther,
> > > No problem, thanks a lot for your feedback!
> > >
> > > >
> > > > On Mon, Nov 11, 2024 at 07:29:49PM +0300, Mikhail Ivanov wrote:
> > > > > On 9/4/2024 1:48 PM, Mikhail Ivanov wrote:
> > > > > > Landlock implements the `LANDLOCK_RULE_NET_PORT` rule type, which provides
> > > > > > fine-grained control of actions for a specific protocol. Any action or
> > > > > > protocol that is not supported by this rule can not be controlled. As a
> > > > > > result, protocols for which fine-grained control is not supported can be
> > > > > > used in a sandboxed system and lead to vulnerabilities or unexpected
> > > > > > behavior.
> > > > > >
> > > > > > Controlling the protocols used will allow to use only those that are
> > > > > > necessary for the system and/or which have fine-grained Landlock control
> > > > > > through others types of rules (e.g. TCP bind/connect control with
> > > > > > `LANDLOCK_RULE_NET_PORT`, UNIX bind control with
> > > > > > `LANDLOCK_RULE_PATH_BENEATH`). Consider following examples:
> > > > > >
> > > > > > * Server may want to use only TCP sockets for which there is fine-grained
> > > > > > control of bind(2) and connect(2) actions [1].
> > > > > > * System that does not need a network or that may want to disable network
> > > > > > for security reasons (e.g. [2]) can achieve this by restricting the use
> > > > > > of all possible protocols.
> > > > > >
> > > > > > This patch implements such control by restricting socket creation in a
> > > > > > sandboxed process.
> > > > > >
> > > > > > Add `LANDLOCK_RULE_SOCKET` rule type that restricts actions on sockets.
> > > > > > This rule uses values of address family and socket type (Cf. socket(2))
> > > > > > to determine sockets that should be restricted. This is represented in a
> > > > > > landlock_socket_attr struct:
> > > > > >
> > > > > > struct landlock_socket_attr {
> > > > > > __u64 allowed_access;
> > > > > > int family; /* same as domain in socket(2) */
> > > > > > int type; /* see socket(2) */
> > > > > > };
> > > > >
> > > > > Hello! I'd like to consider another approach to define this structure
> > > > > before sending the next version of this patchset.
> > > > >
> > > > > Currently, it has following possible issues:
> > > > >
> > > > > First of all, there is a lack of protocol granularity. It's impossible
> > > > > to (for example) deny creation of ICMP and SCTP sockets and allow TCP
> > > > > and UDP. Since the values of address family and socket type do not
> > > > > completely define the protocol for the restriction, we may gain
> > > > > incomplete control of the network actions. AFAICS, this is limited to
> > > > > only a couple of IP protocol cases (e.g. it's impossible to deny SCTP
> > > > > and SMC sockets to only allow TCP, deny ICMP and allow UDP).
> > > > >
> > > > > But one of the main advantages of socket access rights is the ability to
> > > > > allow only those protocols for which there is a fine-grained control
> > > > > over their actions (TCP bind/connect). It can be inconvenient
> > > > > (and unsafe) for SCTP to be unrestricted, while sandboxed process only
> > > > > needs TCP sockets.
> > > >
> > > > That is a good observation which I had missed.
> > > >
> > > > I agree with your analysis, I also see the main use case of socket()
> > > > restrictions in:
> > > >
> > > > (a) restricting socket creating altogether
> > > > (b) only permitting socket types for which there is fine grained control
> > > >
> > > > and I also agree that it would be very surprising when the same socket types
> > > > that provide fine grained control would also open the door for unrestricted
> > > > access to SMC, SCTP or other protocols. We should instead strive for a
> > > > socket() access control with which these additional protocols weren't
> > > > accessible.
> > > >
> > > >
> > > > > Adding protocol (Cf. socket(2)) field was considered a bit during the
> > > > > initial discussion:
> > > > > https://lore.kernel.org/all/CABi2SkVWU=Wxb2y3fP702twyHBD3kVoySPGSz2X22VckvcHeXw@mail.gmail.com/
> > > >
> > > > So adding "protocol" to the rule attributes would suffice to restrict the use of
> > > > SMC and SCTP then? (Sorry, I lost context on these protocols a bit in the
> > > > meantime, I was so far under the impression that these were using different
> > > > values for family and type than TCP and UDP do.)
> > >
> > > Yeap. Following rule will be enough to allow TCP sockets only:
> > >
> > > const struct landlock_socket_attr create_socket_attr = {
> > > .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> > > .family = AF_INET{,6},
> > > .type = SOCK_STREAM,
> > > .protocol = 0
> > > };
> >
> > We should indeed include the protocol type in the rule definition.
> >
> > >
> > > Btw, creation of SMC sockets via IP stack was added quite recently.
> > > So far, creation has been possible only with AF_SMC family.
> > >
> > > https://lore.kernel.org/all/1718301630-63692-1-git-send-email-alibuda@linux.alibaba.com/
> > >
> > > >
> > > >
> > > > > Secondly, I'm not really sure if socket type granularity is required
> > > > > for most of the protocols. It may be more convenient for the end user
> > > > > to be able to completely restrict the address family without specifying
> > > > > whether restriction is dedicated to stream or dgram sockets (e.g. for
> > > > > BLUETOOTH, VSOCK sockets). However, this is not a big issue for the
> > > > > current design, since address family can be restricted by specifying
> > > > > type = SOCK_TYPE_MASK.
> >
> > It looks like SOCK_TYPE_MASK is not part of UAPI, which means it could
> > change with kernel versions (even while being in UAPI in fact). This
> > new socket creation control should allow to deny any socket creation
> > known or unknow at the time of the user space program build, and
> > whatever the available C headers.
>
> Agreed
>
> >
> > This also means that Landlock should accept any domain, type, and
> > protocols defined in rules. Indeed, we don't want to reject rules for
> > which some protocols are not allowed.
>
> Do you mean that Landlock should not make any assumptions about this
> values during a build time? Currently, patchset provides boundary checks
> for domain (< AF_MAX) and type (< SOCK_MAX) in landlock_add_rule().
The *running kernel* may not support some socket's domains or types,
which may be confusing for users if the rule was tested on a kernel
supporting such domains/types.
For the bitmask of domains or types, the issues to keep boundary checks
would be when a subset of them is not supported. Landlock would reject
such rule and it would be difficult for users to identify the cause.
I'm still wondering if the landlock_append_net_rule()'s -EAFNOSUPPORT
return value for kernels without CONFIG_INET was a good idea. We should
probably return 0 in this case, which would be similar to not checking
socket's domains nor types.
>
> >
> > What about using bitmasks for the domain and type fields (renamed to
> > "domains" and "types")? The last protocol is currently 45/MCTP so a
> > 64-bit field is enough, and 10/SOCK_PACKET also fits for the last socket
> > type.
> >
> > We cannot do the same with the protocol because the higher one is
> > 262/MPTCP though. But it looks like a value of 0 (default protocol)
> > should be enough for most use cases, and users could specify a protocol
> > (but this time as a number, not a bitmask).
> >
> > To sum up, we could have something like this:
> >
> > const struct landlock_socket_attr create_socket_attr = {
> > .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> > .families = 1 << AF_INET | 1 << AF_INET6,
> > .types = 1 << SOCK_STREAM,
> > .protocol = IPPROTO_SCTP
> > };
>
> Looks good! I think it's a nice approach which will provide a sufficient
> level of flexibility to define a single rule for a specific protocol (or
> for related protocols).
>
> But, this adds possibility to define a single rule for the set of
> unrelated protocols:
>
> /* Allows TCP, UDP and UNIX sockets. */
> const struct landlock_socket_attr create_socket_attr = {
> .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> .families = 1 << AF_INET | 1 << AF_INET6 | 1 << AF_UNIX,
> .types = 1 << SOCK_STREAM | 1 << SOCK_DGRAM,
> .protocol = 0
> };
>
> Perhaps limiting the addition of one rule to only one address family
> would be more clear in terms of rule semantics?:
>
> /* Allows TCP, UDP, UNIX STREAM, UNIX DGRAM sockets. */
> const struct landlock_socket_attr create_socket_attrs[] = {
> {
> /* Allows IPv4 TCP and UDP sockets. */
> .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> .family = AF_INET,
> .types = 1 << SOCK_STREAM | 1 << SOCK_DGRAM,
> .protocol = 0
> },
> {
> /* Allows IPv6 TCP and UDP sockets. */
> .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> .family = AF_INET6,
> .types = 1 << SOCK_STREAM | 1 << SOCK_DGRAM,
> .protocol = 0
> },
> {
> /* Allows UNIX sockets. */
> .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> .family = AF_UNIX,
> .types = 1 << SOCK_STREAM | 1 << SOCK_DGRAM,
> .protocol = 0
> },
> };
Because we are already mixing bitmasks and (protocol) value, I'm not
sure it will help much. I think in most cases the "families" bitmask
would handle IPv4 and IPv6 the same (e.g. to only allow TCP with one
rule). I think this is also required to be able to have a 1:1 mapping
with SELinux's socket_type_to_security_class().
>
> >
> >
> > > >
> > > > Whether the user is adding one rule to permit AF_INET+*, or whether the user is
> > > > adding two rules to permit (1) AF_INET+SOCK_STREAM and (2) AF_INET+SOCK_DGRAM,
> > > > that does not seem like a big deal to me as long as the list of such
> > > > combinations is so low?
> > >
> > > Agreed
> >
> > I also agree, but this might change if users have to set a combination
> > of families, types, and protocols. This should be OK with the bitmask
> > approach though.
> >
> > >
> > > >
> > > >
> > > > > I suggest implementing something close to selinux socket classes for the
> > > > > struct landlock_socket_attr (Cf. socket_type_to_security_class()). This
> > > > > will provide protocol granularity and may be simpler and more convenient
> > > > > in the terms of determining access rights. WDYT?
> > > >
> > > > I see that this is a longer switch statement that maps to this enum, it would be
> > > > an additional data table that would have to be documented separately for users.
> > >
> > > This table is the general drawback, since it makes API a bit more
> > > complex.
> > >
> > > >
> > > > Do you have an example for how such a "security class enum" would map to the
> > > > combinations of family, type and socket for the protocols discussed above?
> > >
> > > I think the socket_type_to_security_class() has a pretty good mapping
> > > for UNIX and IP families.
> >
> > The mapping looks good indeed, and it has been tested for a long time
> > with many applications. However, this would make the kernel
> > implementation more complex, and I think this mapping could easily be
> > implemented in user space libraries with the bitmask approach, if really
> > needed, which I'm not sure.
>
> I agree, implementing this in a library is a better approach. Thanks for
> the catch!
>
> >
> > >
> > > >
> > > > If this is just a matter of actually mapping (family, type, protocol)
> > > > combinations in a more flexible way, could we get away by allowing a special
> > > > "wildcard" value for the "protocol" field, when it is used within a ruleset?
> > > > Then the LSM would have to look up whether there is a rule for (family, type,
> > > > protocol) and the only change would be that it now needs to also check whether
> > > > there is a rule for (family, type, *)?
> > >
> > > Something like this?
> > >
> > > const struct landlock_socket_attr create_socket_attr = {
> > > .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> > > .family = AF_INET6,
> > > .type = SOCK_DGRAM,
> > > .protocol = LANDLOCK_SOCKET_PROTO_ALL
> > > };
> > >
> > > >
> > > > —Günther
> > >
>
More information about the Linux-security-module-archive
mailing list