From: Matthieu Buffet <matthieu@buffet.re>
To: Mickael Salaun <mic@digikod.net>
Cc: Gunther Noack <gnoack@google.com>,
Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>,
konstantin.meskhidze@huawei.com, Paul Moore <paul@paul-moore.com>,
linux-security-module@vger.kernel.org, netdev@vger.kernel.org,
netfilter-devel@vger.kernel.org,
Pablo Neira Ayuso <pablo@netfilter.org>,
Jozsef Kadlecsik <kadlec@netfilter.org>
Subject: Re: [PATCH v2 3/6] landlock: Add UDP sendmsg access control
Date: Mon, 20 Jan 2025 23:30:09 +0100 [thread overview]
Message-ID: <d77d347c-de99-42b4-a6f5-6982ed2d413f@buffet.re> (raw)
In-Reply-To: <20241214184540.3835222-4-matthieu@buffet.re>
Hi,
(for netfilter folks added a bit late: this should be self-contained but
original patch is here[1], it now raises a question about netfilter hook
execution context at the end of this email - you can just skip to it if
not interested in the LSM part)
On 12/14/2024 7:45 PM, Matthieu Buffet wrote:
> Add support for a LANDLOCK_ACCESS_NET_SENDTO_UDP access right,
> complementing the two previous LANDLOCK_ACCESS_NET_CONNECT_UDP and
> LANDLOCK_ACCESS_NET_BIND_UDP.
> It allows denying and delegating the right to sendto() datagrams with an
> explicit destination address and port, without requiring to connect() the
> socket first.
> [...]
> +static int hook_socket_sendmsg(struct socket *const sock,
> + struct msghdr *const msg, const int size)
> +{
> + const struct landlock_ruleset *const dom =
> + landlock_get_applicable_domain(landlock_get_current_domain(),
> + any_net);
> + const struct sockaddr *address = (const struct sockaddr *)msg->msg_name;
> + const int addrlen = msg->msg_namelen;
> + __be16 port;
> + [...]
> + if (!sk_is_udp(sock->sk))
> + return 0;
> +
> + /* Checks for minimal header length to safely read sa_family. */
> + if (addrlen < offsetofend(typeof(*address), sa_family))
> + return -EINVAL;
> +
> + switch (address->sa_family) {
> + case AF_UNSPEC:
> + /*
> + * Parsed as "no address" in udpv6_sendmsg(), which means
> + * we fall back into the case checked earlier: policy was
> + * enforced at connect() time, nothing to enforce here.
> + */
> + if (sock->sk->sk_prot == &udpv6_prot)
> + return 0;
> + /* Parsed as "AF_INET" in udp_sendmsg() */
> + fallthrough;
> + case AF_INET:
> + if (addrlen < sizeof(struct sockaddr_in))
> + return -EINVAL;
> + port = ((struct sockaddr_in *)address)->sin_port;
> + break;
> +
> +#if IS_ENABLED(CONFIG_IPV6)
> + case AF_INET6:
> + if (addrlen < SIN6_LEN_RFC2133)
> + return -EINVAL;
> + port = ((struct sockaddr_in6 *)address)->sin6_port;
> + break;
> +#endif /* IS_ENABLED(CONFIG_IPV6) */
> +
> + default:
> + return -EAFNOSUPPORT;
> + }
> +
> + return check_access_port(dom, LANDLOCK_ACCESS_NET_SENDTO_UDP, port);
> +}
> +
> static struct security_hook_list landlock_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(socket_bind, hook_socket_bind),
> LSM_HOOK_INIT(socket_connect, hook_socket_connect),
> + LSM_HOOK_INIT(socket_sendmsg, hook_socket_sendmsg),
> };
Looking back at this part of the patch to fix the stupid #ifdef, I
noticed sk->sk_prot can change under our feet, just like sk->sk_family
as highlighted by Mikhail in [2] due to setsockopt(IPV6_ADDRFORM).
Replacing the check with READ_ONCE(sock->sk->sk_family) == AF_INET6 or
even taking the socket's lock would not change anything:
setsockopt(IPV6_ADDRFORM) runs concurrently and locklessly.
So with this patch, any Landlock domain with rights to connect(port A)
and no port allowed to be set explicitly in sendto() could actually
sendto(arbitrary port B) :
1. create an IPv6 UDP socket
2. connect it to (any IPv4-mapped-IPv6 like ::ffff:127.0.0.1, port A)
3a. sendmsg(AF_UNSPEC + actual IPv4 target, port B)
3b. race setsockopt(IPV6_ADDRFORM) on another thread
4. retry from 1. until sendmsg() succeeds
I've put together a quick PoC, the race works. SELinux does not have
this problem because it uses a netfilter hook, later down the packet
path. I see three "fixes", I probably missed some others:
A: block IPV6_ADDRFORM support in a setsockopt() hook, if UDP_SENDMSG is
handled. AFAIU, not an option since this breaks a userland API
B: remove sendmsg(AF_UNSPEC) support on IPv6 sockets. Same problem as A
C: use a netfilter NF_INET_LOCAL_OUT hook like selinux_ip_output()
instead of an LSM hook
For C, problem is to get the sender process' credentials, and ideally to
avoid tagging sockets (what SELinux uses to fetch its security context,
also why it does not have this problem). Otherwise, we would add another
case of varying semantics (like rights to truncate/ioctl) to keep in
mind for Landlock users, this time with sockets kept after enforcing a
new ruleset, or passed to/from another domain - not a fan.
I don't know if it is safe to assume for UDP that NF_INET_LOCAL_OUT
executes in process context: [3] doesn't specify, and [4] mentions the
possibility to execute in interrupt context due to e.g. retransmits, but
that does not apply to UDP. Looking at the code, it looks like it has to
run in process context to be able to make the syscall return EPERM if
the verdict is NF_DROP, but I don't know if that's something that can be
relied upon to be always true, including in future revisions. Could use
some input from someone knowledgeable in netfilter.
What do you think?
[1] https://lore.kernel.org/all/20241214184540.3835222-1-matthieu@buffet.re/
[2] https://lore.kernel.org/netdev/20241212.zoh7Eezee9ka@digikod.net/T/
[3]
https://www.netfilter.org/documentation/HOWTO/netfilter-hacking-HOWTO-4.html#ss4.6
[4]
https://netfilter-devel.vger.kernel.narkive.com/yZHiFEVh/execution-context-in-netfilter-hooks#post5
next prev parent reply other threads:[~2025-01-20 22:33 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-12-14 18:45 [PATCH v2 0/6] landlock: Add UDP access control support Matthieu Buffet
2024-12-14 18:45 ` [PATCH v2 1/6] landlock: Add UDP bind+connect access control Matthieu Buffet
2025-01-24 11:01 ` Mikhail Ivanov
2024-12-14 18:45 ` [PATCH v2 2/6] selftests/landlock: Adapt existing bind/connect for UDP Matthieu Buffet
2024-12-14 18:45 ` [PATCH v2 3/6] landlock: Add UDP sendmsg access control Matthieu Buffet
2024-12-15 15:33 ` kernel test robot
2024-12-15 22:38 ` kernel test robot
2025-01-20 22:30 ` Matthieu Buffet [this message]
2025-01-24 9:59 ` Mikhail Ivanov
2025-01-24 11:05 ` Mikhail Ivanov
2024-12-14 18:45 ` [PATCH v2 4/6] selftests/landlock: Add ACCESS_NET_SENDTO_UDP Matthieu Buffet
2024-12-14 18:45 ` [PATCH v2 5/6] samples/landlock: Add sandboxer UDP access control Matthieu Buffet
2025-01-24 11:06 ` Mikhail Ivanov
2024-12-14 18:45 ` [PATCH v2 6/6] doc: Add landlock UDP support Matthieu Buffet
2025-01-24 10:54 ` [PATCH v2 0/6] landlock: Add UDP access control support Mikhail Ivanov
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=d77d347c-de99-42b4-a6f5-6982ed2d413f@buffet.re \
--to=matthieu@buffet.re \
--cc=gnoack@google.com \
--cc=ivanov.mikhail1@huawei-partners.com \
--cc=kadlec@netfilter.org \
--cc=konstantin.meskhidze@huawei.com \
--cc=linux-security-module@vger.kernel.org \
--cc=mic@digikod.net \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=pablo@netfilter.org \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).