[PATCH RFC v3 67/90] af_unix: deliver ancillary data only within a cell
From: Josef Bacik
Date: Thu Nov 05 2026 - 10:36:16 EST
Once a cell has started, no file descriptor and no identity should cross
its boundary. A unix socket can connect two cells, either because both can
see the directory that it is bound in, or because one end was given to a
new task. Such a socket can carry both.
Record in every skb the cell that it was sent from. The receiver gets the
files, the credentials, the pidfd and the security label only if it is in
the same cell.
This rule needs no state on the socket. So there is no marking that could
be forgotten, and no race with setsockopt(). It also covers a file that
was queued on a socket before the socket was given away.
Messages from different cells are never merged into a single read.
Otherwise the ancillary data of one message could be lost, or delivered,
because of the other.
The cell id fills skb->cb completely. Move the small members of struct
unix_skb_parms next to each other to make room.
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
net/unix/af_unix.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
net/unix/af_unix.h | 14 ++++++++++----
2 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 98fcfd7301f6..58fef56c0f7e 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -78,6 +78,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bpf-cgroup.h>
+#include <linux/cell.h>
#include <linux/btf_ids.h>
#include <linux/dcache.h>
#include <linux/errno.h>
@@ -797,6 +798,37 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
spin_unlock(&sk->sk_peer_lock);
}
+/*
+ * Between cells a socket carries bytes and nothing else: no file, no
+ * credential, no pidfd, no label.
+ *
+ * The rule is enforced where ancillary data is received, and it keeps no state:
+ * every message says which cell it was sent from, and whoever receives it gets
+ * the ancillary data only if that is its own cell. However a socket came to
+ * span two cells, by a connect() through a tree both can see or by being given
+ * to a new task, there is nothing to forget to mark.
+ */
+#ifdef CONFIG_CELLS
+static inline void unix_skb_set_cell(struct sk_buff *skb)
+{
+ UNIXCB(skb).cell_id = current_cell_id();
+}
+
+static inline u64 unix_skb_cell(const struct sk_buff *skb)
+{
+ return UNIXCB(skb).cell_id;
+}
+
+static inline bool unix_skb_foreign(const struct sk_buff *skb)
+{
+ return unix_skb_cell(skb) != current_cell_id();
+}
+#else
+static inline void unix_skb_set_cell(struct sk_buff *skb) { }
+static inline u64 unix_skb_cell(const struct sk_buff *skb) { return 0; }
+static inline bool unix_skb_foreign(const struct sk_buff *skb) { return false; }
+#endif
+
static bool unix_may_passcred(const struct sock *sk)
{
return sk->sk_scm_credentials || sk->sk_scm_pidfd ||
@@ -1993,6 +2025,7 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
UNIXCB(skb).uid = scm->creds.uid;
UNIXCB(skb).gid = scm->creds.gid;
UNIXCB(skb).fp = NULL;
+ unix_skb_set_cell(skb);
unix_get_secdata(scm, skb);
if (scm->fp && send_fds)
err = unix_attach_fds(scm, skb);
@@ -2670,7 +2703,10 @@ int __unix_dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t size,
}
err = (flags & MSG_TRUNC) ? skb->len - skip : size;
- scm_recv_unix(sock, msg, &scm, flags);
+ if (unix_skb_foreign(skb))
+ scm_destroy(&scm);
+ else
+ scm_recv_unix(sock, msg, &scm, flags);
out_free:
skb_free_datagram(sk, skb);
@@ -2929,6 +2965,8 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
size_t size = state->size;
int flags = state->flags;
bool check_creds = false;
+ bool foreign = false, glued = false;
+ u64 from = 0;
struct scm_cookie scm;
struct unix_sock *u;
int copied = 0;
@@ -3032,6 +3070,18 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
unix_state_unlock(sk);
+ /*
+ * Never glue messages from different cells: what comes with
+ * one is not to be lost, or let through, on account of another.
+ */
+ if (!glued) {
+ glued = true;
+ from = unix_skb_cell(skb);
+ foreign = unix_skb_foreign(skb);
+ } else if (unix_skb_cell(skb) != from) {
+ break;
+ }
+
if (check_creds) {
/* Never glue messages from different writers */
if (!unix_skb_scm_eq(sk, skb, &scm))
@@ -3113,7 +3163,10 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
if (msg) {
bool do_cmsg = READ_ONCE(u->recvmsg_inq);
- scm_recv_unix(sock, msg, &scm, flags);
+ if (foreign)
+ scm_destroy(&scm);
+ else
+ scm_recv_unix(sock, msg, &scm, flags);
if ((do_cmsg | msg->msg_get_inq) && (copied ?: err) >= 0) {
msg->msg_inq = READ_ONCE(u->inq_len);
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 402742895acc..d9c393ac58cd 100644
--- a/net/unix/af_unix.h
+++ b/net/unix/af_unix.h
@@ -13,13 +13,19 @@ struct sock *unix_peer_get(struct sock *sk);
struct unix_skb_parms {
DECLARE_PIDS(pid, PIDTYPE_TGID); /* skb credentials by pid type */
- kuid_t uid;
- kgid_t gid;
struct scm_fp_list *fp; /* Passed files */
+#ifdef CONFIG_CELLS
+ u64 cell_id; /* of the cell it was sent from */
+#endif
+ /* This fills skb->cb to the last byte. Keep the small ones together. */
+ struct {
+ kuid_t uid;
+ kgid_t gid;
#ifdef CONFIG_SECURITY_NETWORK
- u32 secid; /* Security ID */
+ u32 secid; /* Security ID */
#endif
- u32 consumed;
+ u32 consumed;
+ };
} __randomize_layout;
#define UNIXCB(skb) (*(struct unix_skb_parms *)&((skb)->cb))
--
2.55.0