All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
@ 2026-11-02 13:05 Ondrej Mosnáček
  2026-11-02 13:13 ` Ondrej Mosnáček
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Ondrej Mosnáček @ 2026-11-02 13:05 UTC (permalink / raw)
  To: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski
  Cc: Paul Moore, Stephen Smalley, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
mechanism, the security_file_receive() LSM hook is calledto allow LSMs
like SELinux to validate the receiver's access to the received file
(similarly to a process inheriting file descriptors through execve(2)).

However, there are cases (such as dbus daemon/dbus-broker) where the
receiver doesn't intend to access the received file and merely wants to
forward it to a third process. In this case the forwarding process would
need to have the appropriate r/w access to the file in order to receive
it, even though it will never actually acess it. That leads to the
security policy having to grant the forwarder access to a potentially
wide range of files (in case of dbus-broker basically all files), which
is not ideal.

This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
recvmsg(2), wraps any received files into an opaque anonymous inode with
the name "[envelope]", skipping the security_file_receive() LSM check.
The returned "[envelope]" file descriptor can't be used in any
meaningful way other than being passed forward via sendmsg(2) with the
MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
sent via SCM_RIGHTS to the receiver. The receiver can then either
receive the file as normal (subject to the LSM check) or again "sealed"
in the "[envelope]" anon inode for further forwarding.

Note that sending the "[envelope]" file is only allowed with the
MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
unexpected behavior. Attempting to send it without this flag will fail
with an error. This also avoids recursive nesting of "[envelope]" files,
which would only make this feature harder to audit for possible issues.

This patch also adds a simple read-only sockopt for UNIX sockets that
allows userspace to determine if this feature is present (passing
unknown CMSG flags unfortunately doesn't lead to an error currently).
The minimal code to do so can look like this:

    #ifndef SO_RIGHTS_SEALED_SUPPORTED
    #define SO_RIGHTS_SEALED_SUPPORTED 88
    #endif

    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdbool.h>

    bool MSG_CMSG_SEALED_supported(void)
    {
        int sock, ret;
        socklen_t optlen = 0;

        sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sock < 0) {
            /* handle error */
            /* ... */
        }

        ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
                         NULL, &optlen);
        close(sock);

        return ret == 0;
    }

Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
---
 arch/alpha/include/uapi/asm/socket.h    |  2 +
 arch/mips/include/uapi/asm/socket.h     |  2 +
 arch/parisc/include/uapi/asm/socket.h   |  2 +
 arch/sparc/include/uapi/asm/socket.h    |  2 +
 include/linux/socket.h                  |  4 +-
 include/net/scm.h                       |  2 +-
 include/uapi/asm-generic/socket.h       |  2 +
 io_uring/net.c                          |  2 +-
 net/compat.c                            |  3 +-
 net/core/scm.c                          | 81 +++++++++++++++++++++----
 net/core/sock.c                         |  9 +++
 net/socket.c                            |  2 +-
 tools/include/uapi/asm-generic/socket.h |  2 +
 13 files changed, 97 insertions(+), 18 deletions(-)

diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 5d3524c26b2b..cd9fce1d9432 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -161,6 +161,8 @@
 
 #define SO_PEERPIDFD_THREAD    87
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index 245a43f52fb1..393898479ce6 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -172,6 +172,8 @@
 
 #define SO_PEERPIDFD_THREAD    87
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index f23710e1c671..055b532ccc14 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -153,6 +153,8 @@
 
 #define SO_PEERPIDFD_THREAD	0x4055
 
+#define SO_RIGHTS_SEALED_SUPPORTED	0x4056
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index b35b25bdefc2..e152e2bdf42e 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -154,6 +154,8 @@
 
 #define SO_PEERPIDFD_THREAD      0x0060
 
+#define SO_RIGHTS_SEALED_SUPPORTED	0x0061
+
 #if !defined(__KERNEL__)
 
 
diff --git a/include/linux/socket.h b/include/linux/socket.h
index b17fdb7d38a7..90e4bc5d3f91 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -345,7 +345,9 @@ struct ucred {
 #define MSG_SOCK_DEVMEM 0x2000000	/* Receive devmem skbs as cmsg */
 #define MSG_ZEROCOPY	0x4000000	/* Use user data in kernel path */
 #define MSG_SPLICE_PAGES 0x8000000	/* Splice the pages from the iterator in sendmsg() */
-#define MSG_FASTOPEN	0x20000000	/* Send data in TCP SYN */
+#define MSG_CMSG_SEALED  0x10000000	/* Seal file descriptors received
+					   through SCM_RIGHTS */
+#define MSG_FASTOPEN     0x20000000	/* Send data in TCP SYN */
 #define MSG_CMSG_CLOEXEC 0x40000000	/* Set close_on_exec for file
 					   descriptor received through
 					   SCM_RIGHTS */
diff --git a/include/net/scm.h b/include/net/scm.h
index b68a143f42de..53b999868104 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -108,7 +108,7 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 		   struct scm_cookie *scm, int flags);
 
 int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
-		    bool notrunc);
+		    bool notrunc, bool sealed);
 
 #endif /* __LINUX_NET_SCM_H */
 
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 56fed7ab27ab..14bf06367ef1 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -156,6 +156,8 @@
 
 #define SO_PEERPIDFD_THREAD	87
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/io_uring/net.c b/io_uring/net.c
index eeab88e77fd8..b579d542b7e2 100644
--- a/io_uring/net.c
+++ b/io_uring/net.c
@@ -992,7 +992,7 @@ static int io_recvmsg_multishot(struct socket *sock, struct io_sr_msg *io,
 
 	if (kmsg->namelen)
 		kmsg->msg.msg_name = &hdr.addr;
-	kmsg->msg.msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);
+	kmsg->msg.msg_flags = flags & (MSG_CMSG_CLOEXEC | MSG_CMSG_SEALED | MSG_CMSG_COMPAT);
 	kmsg->msg.msg_namelen = 0;
 
 	if (sock->file->f_flags & O_NONBLOCK)
diff --git a/net/compat.c b/net/compat.c
index 6bdf4a2c9077..29816b47f675 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -296,7 +296,8 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notr
 	int err = 0, i;
 
 	for (i = 0; i < fdmax; i++) {
-		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
+		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc,
+				      msg->msg_flags & MSG_CMSG_SEALED);
 		if (err < 0)
 			break;
 	}
diff --git a/net/core/scm.c b/net/core/scm.c
index ae38f3c1165c..08822b2a1f22 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -29,6 +29,7 @@
 #include <linux/slab.h>
 #include <linux/errqueue.h>
 #include <linux/io_uring.h>
+#include <linux/anon_inodes.h>
 
 #include <linux/uaccess.h>
 
@@ -66,7 +67,19 @@ static __inline__ int scm_check_creds(struct ucred *creds)
 	return -EPERM;
 }
 
-static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
+static int envelope_release(struct inode *inode, struct file *file)
+{
+	struct file *inner = file->private_data;
+
+	fput(inner);
+	return 0;
+}
+
+static const struct file_operations envelope_ops = {
+	.release = envelope_release,
+};
+
+static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp, bool sealed)
 {
 	int *fdp = (int*)CMSG_DATA(cmsg);
 	struct scm_fp_list *fpl = *fplp;
@@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
 
 		if (fd < 0 || !(file = fget_raw(fd)))
 			return -EBADF;
-		/* don't allow io_uring files */
-		if (io_is_uring_fops(file)) {
-			fput(file);
-			return -EINVAL;
+
+		if (sealed) {
+			/* Only allow sealed files */
+			if (file->f_op != &envelope_ops)
+				return -EINVAL;
+
+			file = (struct file *)file->private_data;
+
+			/*
+			 * No need to check for io_uring files here as the
+			 * underlying file had already successfully passed the
+			 * check when it was first sent.
+			 */
+		} else {
+			/* Don't allow sealed files */
+			if (file->f_op == &envelope_ops)
+				return -EINVAL;
+
+			/* don't allow io_uring files */
+			if (io_is_uring_fops(file)) {
+				fput(file);
+				return -EINVAL;
+			}
 		}
 		if (unix_get_socket(file))
 			fpl->count_unix++;
@@ -203,7 +235,8 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
 		case SCM_RIGHTS:
 			if (!ops || ops->family != PF_UNIX)
 				goto error;
-			err=scm_fp_copy(cmsg, &p->fp);
+			err = scm_fp_copy(cmsg, &p->fp,
+					  msg->msg_flags & MSG_CMSG_SEALED);
 			if (err<0)
 				goto error;
 			break;
@@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
 }
 
 int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
-		    bool notrunc)
+		    bool notrunc, bool sealed)
 {
+	struct file *f_received;
 	int error;
 
 	if (!ufd)
 		return -EFAULT;
 
-	error = security_file_receive(f);
-	if (error)
-		return notrunc ? put_user(error, ufd) : error;
+	if (sealed) {
+		/*
+		 * Receive fd sealed in an "envelope" anonymous fd.
+		 * This will install a special file that can be unsealed
+		 * upon sending to another process, otherwise the underlying
+		 * file will not be accessible to this process, avoiding the
+		 * need to pass the LSM check below.
+		 */
+		f_received = anon_inode_getfile("[envelope]", &envelope_ops, f,
+						O_RDONLY);
+		if (IS_ERR(f_received))
+			return PTR_ERR(f_received);
+
+		get_file(f);
+	} else {
+		error = security_file_receive(f);
+		if (error)
+			return notrunc ? put_user(error, ufd) : error;
 
-	FD_PREPARE(fdf, flags, get_file(f));
+		f_received = get_file(f);
+	}
+
+	FD_PREPARE(fdf, flags, f_received);
 	if (fdf->fd < 0)
 		return fdf->fd;
 
@@ -384,7 +436,9 @@ int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
 	if (error)
 		return error;
 
-	__receive_sock(fdf->file);
+	if (!sealed)
+		__receive_sock(fdf->file);
+
 	return fd_publish(fdf);
 }
 
@@ -407,7 +461,8 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)
 	}
 
 	for (i = 0; i < fdmax; i++) {
-		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
+		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc,
+				      msg->msg_flags & MSG_CMSG_SEALED);
 		if (err < 0)
 			break;
 	}
diff --git a/net/core/sock.c b/net/core/sock.c
index e7405be8941f..68a447d8bf52 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1959,6 +1959,15 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
 		v.val = sk->sk_scm_rights;
 		break;
 
+	case SO_RIGHTS_SEALED_SUPPORTED:
+		if (!sk_is_unix(sk))
+			return -EOPNOTSUPP;
+
+		/* Just succeed without returning anything, signaling
+		 * support.
+		 */
+		break;
+
 	case SO_PEERCRED:
 	{
 		struct ucred peercred;
diff --git a/net/socket.c b/net/socket.c
index 2e3b5737c481..dbb3c7c6ff66 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -2921,7 +2921,7 @@ static int ____sys_recvmsg(struct socket *sock, struct msghdr *msg_sys,
 
 	msg_sys->msg_name = &addr;
 	cmsg_ptr = (unsigned long)msg_sys->msg_control;
-	msg_sys->msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);
+	msg_sys->msg_flags = flags & (MSG_CMSG_CLOEXEC | MSG_CMSG_SEALED | MSG_CMSG_COMPAT);
 
 	/* We assume all kernel code knows the size of sockaddr_storage */
 	msg_sys->msg_namelen = 0;
diff --git a/tools/include/uapi/asm-generic/socket.h b/tools/include/uapi/asm-generic/socket.h
index f333a0ac4ee4..fb9e98a7b370 100644
--- a/tools/include/uapi/asm-generic/socket.h
+++ b/tools/include/uapi/asm-generic/socket.h
@@ -147,6 +147,8 @@
 
 #define SO_PASSRIGHTS		83
 
+#define SO_RIGHTS_SEALED_SUPPORTED	88
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 13:05 [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED Ondrej Mosnáček
@ 2026-11-02 13:13 ` Ondrej Mosnáček
  2026-11-02 15:14 ` Stephen Smalley
  2026-11-03 13:05 ` sashiko-bot
  2 siblings, 0 replies; 6+ messages in thread
From: Ondrej Mosnáček @ 2026-11-02 13:13 UTC (permalink / raw)
  To: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski
  Cc: Paul Moore, Stephen Smalley, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

On Mon, 2 Nov 2026 at 14:05, Ondrej Mosnáček <omosnacek@gmail.com> wrote:
>
> Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> like SELinux to validate the receiver's access to the received file
> (similarly to a process inheriting file descriptors through execve(2)).
>
> However, there are cases (such as dbus daemon/dbus-broker) where the
> receiver doesn't intend to access the received file and merely wants to
> forward it to a third process. In this case the forwarding process would
> need to have the appropriate r/w access to the file in order to receive
> it, even though it will never actually acess it. That leads to the
> security policy having to grant the forwarder access to a potentially
> wide range of files (in case of dbus-broker basically all files), which
> is not ideal.
>
> This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> recvmsg(2), wraps any received files into an opaque anonymous inode with
> the name "[envelope]", skipping the security_file_receive() LSM check.
> The returned "[envelope]" file descriptor can't be used in any
> meaningful way other than being passed forward via sendmsg(2) with the
> MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> sent via SCM_RIGHTS to the receiver. The receiver can then either
> receive the file as normal (subject to the LSM check) or again "sealed"
> in the "[envelope]" anon inode for further forwarding.
>
> Note that sending the "[envelope]" file is only allowed with the
> MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> unexpected behavior. Attempting to send it without this flag will fail
> with an error. This also avoids recursive nesting of "[envelope]" files,
> which would only make this feature harder to audit for possible issues.
>
> This patch also adds a simple read-only sockopt for UNIX sockets that
> allows userspace to determine if this feature is present (passing
> unknown CMSG flags unfortunately doesn't lead to an error currently).
> The minimal code to do so can look like this:
>
>     #ifndef SO_RIGHTS_SEALED_SUPPORTED
>     #define SO_RIGHTS_SEALED_SUPPORTED 88
>     #endif
>
>     #include <sys/socket.h>
>     #include <unistd.h>
>     #include <stdbool.h>
>
>     bool MSG_CMSG_SEALED_supported(void)
>     {
>         int sock, ret;
>         socklen_t optlen = 0;
>
>         sock = socket(AF_UNIX, SOCK_STREAM, 0);
>         if (sock < 0) {
>             /* handle error */
>             /* ... */
>         }
>
>         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
>                          NULL, &optlen);
>         close(sock);
>
>         return ret == 0;
>     }
>
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> ---
>  arch/alpha/include/uapi/asm/socket.h    |  2 +
>  arch/mips/include/uapi/asm/socket.h     |  2 +
>  arch/parisc/include/uapi/asm/socket.h   |  2 +
>  arch/sparc/include/uapi/asm/socket.h    |  2 +
>  include/linux/socket.h                  |  4 +-
>  include/net/scm.h                       |  2 +-
>  include/uapi/asm-generic/socket.h       |  2 +
>  io_uring/net.c                          |  2 +-
>  net/compat.c                            |  3 +-
>  net/core/scm.c                          | 81 +++++++++++++++++++++----
>  net/core/sock.c                         |  9 +++
>  net/socket.c                            |  2 +-
>  tools/include/uapi/asm-generic/socket.h |  2 +
>  13 files changed, 97 insertions(+), 18 deletions(-)

See also this RFC patch for selinux-testsuite
(https://github.com/SELinuxProject/selinux-testsuite) that I used to
test this patch:
https://lore.kernel.org/selinux/20261102130834.253605-1-omosnacek@gmail.com/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 13:05 [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED Ondrej Mosnáček
  2026-11-02 13:13 ` Ondrej Mosnáček
@ 2026-11-02 15:14 ` Stephen Smalley
  2026-11-02 19:06   ` Stephen Smalley
  2026-11-02 19:39   ` Paul Moore
  2026-11-03 13:05 ` sashiko-bot
  2 siblings, 2 replies; 6+ messages in thread
From: Stephen Smalley @ 2026-11-02 15:14 UTC (permalink / raw)
  To: Ondrej Mosnáček
  Cc: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski, Paul Moore, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

On Mon, Nov 2, 2026 at 8:05 AM Ondrej Mosnáček <omosnacek@gmail.com> wrote:
>
> Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> like SELinux to validate the receiver's access to the received file
> (similarly to a process inheriting file descriptors through execve(2)).
>
> However, there are cases (such as dbus daemon/dbus-broker) where the
> receiver doesn't intend to access the received file and merely wants to
> forward it to a third process. In this case the forwarding process would
> need to have the appropriate r/w access to the file in order to receive
> it, even though it will never actually acess it. That leads to the
> security policy having to grant the forwarder access to a potentially
> wide range of files (in case of dbus-broker basically all files), which
> is not ideal.
>
> This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> recvmsg(2), wraps any received files into an opaque anonymous inode with
> the name "[envelope]", skipping the security_file_receive() LSM check.
> The returned "[envelope]" file descriptor can't be used in any
> meaningful way other than being passed forward via sendmsg(2) with the
> MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> sent via SCM_RIGHTS to the receiver. The receiver can then either
> receive the file as normal (subject to the LSM check) or again "sealed"
> in the "[envelope]" anon inode for further forwarding.
>
> Note that sending the "[envelope]" file is only allowed with the
> MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> unexpected behavior. Attempting to send it without this flag will fail
> with an error. This also avoids recursive nesting of "[envelope]" files,
> which would only make this feature harder to audit for possible issues.
>
> This patch also adds a simple read-only sockopt for UNIX sockets that
> allows userspace to determine if this feature is present (passing
> unknown CMSG flags unfortunately doesn't lead to an error currently).
> The minimal code to do so can look like this:
>
>     #ifndef SO_RIGHTS_SEALED_SUPPORTED
>     #define SO_RIGHTS_SEALED_SUPPORTED 88
>     #endif
>
>     #include <sys/socket.h>
>     #include <unistd.h>
>     #include <stdbool.h>
>
>     bool MSG_CMSG_SEALED_supported(void)
>     {
>         int sock, ret;
>         socklen_t optlen = 0;
>
>         sock = socket(AF_UNIX, SOCK_STREAM, 0);
>         if (sock < 0) {
>             /* handle error */
>             /* ... */
>         }
>
>         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
>                          NULL, &optlen);
>         close(sock);
>
>         return ret == 0;
>     }
>
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> ---

> diff --git a/net/core/scm.c b/net/core/scm.c
> index ae38f3c1165c..08822b2a1f22 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c
> @@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
>
>                 if (fd < 0 || !(file = fget_raw(fd)))
>                         return -EBADF;
> -               /* don't allow io_uring files */
> -               if (io_is_uring_fops(file)) {
> -                       fput(file);
> -                       return -EINVAL;
> +
> +               if (sealed) {
> +                       /* Only allow sealed files */
> +                       if (file->f_op != &envelope_ops)
> +                               return -EINVAL;

This leaks a file reference.

> +
> +                       file = (struct file *)file->private_data;

This fails to take a reference to the underlying file but will later get fput().

> +
> +                       /*
> +                        * No need to check for io_uring files here as the
> +                        * underlying file had already successfully passed the
> +                        * check when it was first sent.
> +                        */
> +               } else {
> +                       /* Don't allow sealed files */
> +                       if (file->f_op == &envelope_ops)
> +                               return -EINVAL;

This also leaks a file reference.

> @@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
>  }
>
>  int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> -                   bool notrunc)
> +                   bool notrunc, bool sealed)
>  {
> +       struct file *f_received;
>         int error;
>
>         if (!ufd)
>                 return -EFAULT;
>
> -       error = security_file_receive(f);
> -       if (error)
> -               return notrunc ? put_user(error, ufd) : error;

Design point: I would suggest adding and using a new LSM hook that
allows security modules (like SELinux) to still be able to check a
(new) permission that governs whether a process can pass along these
sealed fds so that we do not entirely lose control over their use.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 15:14 ` Stephen Smalley
@ 2026-11-02 19:06   ` Stephen Smalley
  2026-11-02 19:39   ` Paul Moore
  1 sibling, 0 replies; 6+ messages in thread
From: Stephen Smalley @ 2026-11-02 19:06 UTC (permalink / raw)
  To: Ondrej Mosnáček
  Cc: Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn,
	Jakub Kicinski, Paul Moore, James Morris, Serge E. Hallyn,
	Simon Horman, Jens Axboe, netdev, selinux, linux-security-module,
	io-uring, linux-fsdevel, linux-kernel

On Mon, Nov 2, 2026 at 10:14 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Mon, Nov 2, 2026 at 8:05 AM Ondrej Mosnáček <omosnacek@gmail.com> wrote:
> >
> > Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> > mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> > like SELinux to validate the receiver's access to the received file
> > (similarly to a process inheriting file descriptors through execve(2)).
> >
> > However, there are cases (such as dbus daemon/dbus-broker) where the
> > receiver doesn't intend to access the received file and merely wants to
> > forward it to a third process. In this case the forwarding process would
> > need to have the appropriate r/w access to the file in order to receive
> > it, even though it will never actually acess it. That leads to the
> > security policy having to grant the forwarder access to a potentially
> > wide range of files (in case of dbus-broker basically all files), which
> > is not ideal.
> >
> > This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> > recvmsg(2), wraps any received files into an opaque anonymous inode with
> > the name "[envelope]", skipping the security_file_receive() LSM check.
> > The returned "[envelope]" file descriptor can't be used in any
> > meaningful way other than being passed forward via sendmsg(2) with the
> > MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> > sent via SCM_RIGHTS to the receiver. The receiver can then either
> > receive the file as normal (subject to the LSM check) or again "sealed"
> > in the "[envelope]" anon inode for further forwarding.
> >
> > Note that sending the "[envelope]" file is only allowed with the
> > MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> > unexpected behavior. Attempting to send it without this flag will fail
> > with an error. This also avoids recursive nesting of "[envelope]" files,
> > which would only make this feature harder to audit for possible issues.
> >
> > This patch also adds a simple read-only sockopt for UNIX sockets that
> > allows userspace to determine if this feature is present (passing
> > unknown CMSG flags unfortunately doesn't lead to an error currently).
> > The minimal code to do so can look like this:
> >
> >     #ifndef SO_RIGHTS_SEALED_SUPPORTED
> >     #define SO_RIGHTS_SEALED_SUPPORTED 88
> >     #endif
> >
> >     #include <sys/socket.h>
> >     #include <unistd.h>
> >     #include <stdbool.h>
> >
> >     bool MSG_CMSG_SEALED_supported(void)
> >     {
> >         int sock, ret;
> >         socklen_t optlen = 0;
> >
> >         sock = socket(AF_UNIX, SOCK_STREAM, 0);
> >         if (sock < 0) {
> >             /* handle error */
> >             /* ... */
> >         }
> >
> >         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
> >                          NULL, &optlen);
> >         close(sock);
> >
> >         return ret == 0;
> >     }
> >
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> > Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> > ---
>
> > diff --git a/net/core/scm.c b/net/core/scm.c
> > index ae38f3c1165c..08822b2a1f22 100644
> > --- a/net/core/scm.c
> > +++ b/net/core/scm.c
> > @@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
> >
> >                 if (fd < 0 || !(file = fget_raw(fd)))
> >                         return -EBADF;
> > -               /* don't allow io_uring files */
> > -               if (io_is_uring_fops(file)) {
> > -                       fput(file);
> > -                       return -EINVAL;
> > +
> > +               if (sealed) {
> > +                       /* Only allow sealed files */
> > +                       if (file->f_op != &envelope_ops)
> > +                               return -EINVAL;
>
> This leaks a file reference.
>
> > +
> > +                       file = (struct file *)file->private_data;
>
> This fails to take a reference to the underlying file but will later get fput().
>
> > +
> > +                       /*
> > +                        * No need to check for io_uring files here as the
> > +                        * underlying file had already successfully passed the
> > +                        * check when it was first sent.
> > +                        */
> > +               } else {
> > +                       /* Don't allow sealed files */
> > +                       if (file->f_op == &envelope_ops)
> > +                               return -EINVAL;
>
> This also leaks a file reference.
>
> > @@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
> >  }
> >
> >  int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > -                   bool notrunc)
> > +                   bool notrunc, bool sealed)
> >  {
> > +       struct file *f_received;
> >         int error;
> >
> >         if (!ufd)
> >                 return -EFAULT;
> >
> > -       error = security_file_receive(f);
> > -       if (error)
> > -               return notrunc ? put_user(error, ufd) : error;
>
> Design point: I would suggest adding and using a new LSM hook that
> allows security modules (like SELinux) to still be able to check a
> (new) permission that governs whether a process can pass along these
> sealed fds so that we do not entirely lose control over their use.

One other design point: you are creating the envelope with
anon_inode_getfile(); consider using anon_inode_getfile_secure()
instead so that security_inode_init_security_anon() can give the
envelope a labelable context, which SELinux could then use to label
and control envelope use directly.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 15:14 ` Stephen Smalley
  2026-11-02 19:06   ` Stephen Smalley
@ 2026-11-02 19:39   ` Paul Moore
  1 sibling, 0 replies; 6+ messages in thread
From: Paul Moore @ 2026-11-02 19:39 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Ondrej Mosnáček, Eric Dumazet, Kuniyuki Iwashima,
	Paolo Abeni, Willem de Bruijn, Jakub Kicinski, James Morris,
	Serge E. Hallyn, Simon Horman, Jens Axboe, netdev, selinux,
	linux-security-module, io-uring, linux-fsdevel, linux-kernel

On Mon, Nov 2, 2026 at 10:14 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Mon, Nov 2, 2026 at 8:05 AM Ondrej Mosnáček <omosnacek@gmail.com> wrote:
> >
> > Currently, when a process receives a file through AF_UNIX's SCM_RIGHTS
> > mechanism, the security_file_receive() LSM hook is calledto allow LSMs
> > like SELinux to validate the receiver's access to the received file
> > (similarly to a process inheriting file descriptors through execve(2)).
> >
> > However, there are cases (such as dbus daemon/dbus-broker) where the
> > receiver doesn't intend to access the received file and merely wants to
> > forward it to a third process. In this case the forwarding process would
> > need to have the appropriate r/w access to the file in order to receive
> > it, even though it will never actually acess it. That leads to the
> > security policy having to grant the forwarder access to a potentially
> > wide range of files (in case of dbus-broker basically all files), which
> > is not ideal.
> >
> > This patch adds a new CMSG flag MSG_CMSG_SEALED that, when passed to
> > recvmsg(2), wraps any received files into an opaque anonymous inode with
> > the name "[envelope]", skipping the security_file_receive() LSM check.
> > The returned "[envelope]" file descriptor can't be used in any
> > meaningful way other than being passed forward via sendmsg(2) with the
> > MSG_CMSG_SEALED flag. In that case the original file is unwrapped and
> > sent via SCM_RIGHTS to the receiver. The receiver can then either
> > receive the file as normal (subject to the LSM check) or again "sealed"
> > in the "[envelope]" anon inode for further forwarding.
> >
> > Note that sending the "[envelope]" file is only allowed with the
> > MSG_CMSG_SEALED flag set, to make the unsealing explicit and avoid
> > unexpected behavior. Attempting to send it without this flag will fail
> > with an error. This also avoids recursive nesting of "[envelope]" files,
> > which would only make this feature harder to audit for possible issues.
> >
> > This patch also adds a simple read-only sockopt for UNIX sockets that
> > allows userspace to determine if this feature is present (passing
> > unknown CMSG flags unfortunately doesn't lead to an error currently).
> > The minimal code to do so can look like this:
> >
> >     #ifndef SO_RIGHTS_SEALED_SUPPORTED
> >     #define SO_RIGHTS_SEALED_SUPPORTED 88
> >     #endif
> >
> >     #include <sys/socket.h>
> >     #include <unistd.h>
> >     #include <stdbool.h>
> >
> >     bool MSG_CMSG_SEALED_supported(void)
> >     {
> >         int sock, ret;
> >         socklen_t optlen = 0;
> >
> >         sock = socket(AF_UNIX, SOCK_STREAM, 0);
> >         if (sock < 0) {
> >             /* handle error */
> >             /* ... */
> >         }
> >
> >         ret = getsockopt(sock, SOL_SOCKET, SO_RIGHTS_SEALED_SUPPORTED,
> >                          NULL, &optlen);
> >         close(sock);
> >
> >         return ret == 0;
> >     }
> >
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3286
> > Related: https://github.com/fedora-selinux/selinux-policy/issues/3287
> > Signed-off-by: Ondrej Mosnáček <omosnacek@gmail.com>
> > ---
>
> > diff --git a/net/core/scm.c b/net/core/scm.c
> > index ae38f3c1165c..08822b2a1f22 100644
> > --- a/net/core/scm.c
> > +++ b/net/core/scm.c
> > @@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
> >
> >                 if (fd < 0 || !(file = fget_raw(fd)))
> >                         return -EBADF;
> > -               /* don't allow io_uring files */
> > -               if (io_is_uring_fops(file)) {
> > -                       fput(file);
> > -                       return -EINVAL;
> > +
> > +               if (sealed) {
> > +                       /* Only allow sealed files */
> > +                       if (file->f_op != &envelope_ops)
> > +                               return -EINVAL;
>
> This leaks a file reference.
>
> > +
> > +                       file = (struct file *)file->private_data;
>
> This fails to take a reference to the underlying file but will later get fput().
>
> > +
> > +                       /*
> > +                        * No need to check for io_uring files here as the
> > +                        * underlying file had already successfully passed the
> > +                        * check when it was first sent.
> > +                        */
> > +               } else {
> > +                       /* Don't allow sealed files */
> > +                       if (file->f_op == &envelope_ops)
> > +                               return -EINVAL;
>
> This also leaks a file reference.
>
> > @@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
> >  }
> >
> >  int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> > -                   bool notrunc)
> > +                   bool notrunc, bool sealed)
> >  {
> > +       struct file *f_received;
> >         int error;
> >
> >         if (!ufd)
> >                 return -EFAULT;
> >
> > -       error = security_file_receive(f);
> > -       if (error)
> > -               return notrunc ? put_user(error, ufd) : error;
>
> Design point: I would suggest adding and using a new LSM hook that
> allows security modules (like SELinux) to still be able to check a
> (new) permission that governs whether a process can pass along these
> sealed fds so that we do not entirely lose control over their use.

I've only given this a quick look, but I'm also not excited about the
LSM hook being conditional on the "sealed" flag.  I'd prefer for the
security_file_receive() hook to catch all the files, but take an
additional bool argument indicating the sealed state; this would allow
individual LSMs to decide how best to handle this.

  security_file_receive(struct file *file, bool sealed)

-- 
paul-moore.com

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED
  2026-11-02 13:05 [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED Ondrej Mosnáček
  2026-11-02 13:13 ` Ondrej Mosnáček
  2026-11-02 15:14 ` Stephen Smalley
@ 2026-11-03 13:05 ` sashiko-bot
  2 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-11-03 13:05 UTC (permalink / raw)
  Cc: selinux, linux-security-module

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] net: scm: missing get_file() and fput() when unwrapping in scm_fp_copy()
- [High] net: scm: missing fput() on error paths in scm_fp_copy()
- [High] net: scm: file reference leak in scm_recv_one_fd() when FD_PREPARE() fails
- [Medium] net: sk_getsockopt() copies zeroed int for SO_RIGHTS_SEALED_SUPPORTED
--

commit bff932de78fdb71452bc8ca043d1f0a85ab5a42a
Author: Ondrej Mosnáček <omosnacek@gmail.com>

af_unix: introduce MSG_CMSG_SEALED

This commit introduces the MSG_CMSG_SEALED flag for AF_UNIX SCM_RIGHTS to
allow processes to receive file descriptors wrapped in an opaque anonymous
inode without triggering the security_file_receive() LSM hook, and to
unwrap them when forwarding via sendmsg(). It also adds the read-only
SO_RIGHTS_SEALED_SUPPORTED socket option to query support for this feature.

> diff --git a/net/core/scm.c b/net/core/scm.c
> index ae38f3c1165c8..08822b2a1f222 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c

[ ... ]

> @@ -114,10 +127,29 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
>  
>  		if (fd < 0 || !(file = fget_raw(fd)))
>  			return -EBADF;
> -		/* don't allow io_uring files */
> -		if (io_is_uring_fops(file)) {
> -			fput(file);
> -			return -EINVAL;
> +
> +		if (sealed) {
> +			/* Only allow sealed files */
> +			if (file->f_op != &envelope_ops)
> +				return -EINVAL;
> +
> +			file = (struct file *)file->private_data;

[Severity: Critical]
When unwrapping the envelope file in scm_fp_copy(), doesn't overwriting file
with file->private_data need to take a new reference on the inner struct file
with get_file() and drop the reference on the outer envelope file from
fget_raw() with fput()?

Without calling fput() on the outer envelope file, the reference acquired by
fget_raw(fd) is leaked on every sealed sendmsg(), preventing
envelope_release() from running when the envelope descriptor is closed.

At the same time, storing the inner struct file into fpl->fp[] without
calling get_file() means fpl->fp[] does not own a reference to it, even
though __scm_destroy() will later call fput() on every entry in fpl->fp[]:

net/core/scm.c:__scm_destroy() {
	...
	for (i=fpl->count-1; i>=0; i--)
		fput(fpl->fp[i]);
	...
}

If a caller sends the same envelope descriptor more than once with
MSG_CMSG_SEALED, __scm_destroy() will decrement the inner file's reference
count multiple times without matching increments, leading to a reference
count underflow and use-after-free on the inner struct file.

> +
> +			/*
> +			 * No need to check for io_uring files here as the
> +			 * underlying file had already successfully passed the
> +			 * check when it was first sent.
> +			 */
> +		} else {
> +			/* Don't allow sealed files */
> +			if (file->f_op == &envelope_ops)
> +				return -EINVAL;
> +
> +			/* don't allow io_uring files */
> +			if (io_is_uring_fops(file)) {
> +				fput(file);
> +				return -EINVAL;
> +			}
>  		}

[Severity: High]
Do the two envelope_ops checks in scm_fp_copy() leak the struct file
reference acquired by fget_raw(fd)?

When sealed is true and file->f_op != &envelope_ops, or when sealed is false
and file->f_op == &envelope_ops, file has not yet been added to fpl->fp[], so
__scm_destroy() in __scm_send()'s error path will not release it. Unlike the
io_is_uring_fops(file) check right below, returning -EINVAL directly without
calling fput(file) leaves the reference from fget_raw(fd) leaked.

[ ... ]

> @@ -365,18 +398,37 @@ static int scm_max_fds(struct msghdr *msg)
>  }
>  
>  int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
> -		    bool notrunc)
> +		    bool notrunc, bool sealed)
>  {
> +	struct file *f_received;
>  	int error;
>  
>  	if (!ufd)
>  		return -EFAULT;
>  
> -	error = security_file_receive(f);
> -	if (error)
> -		return notrunc ? put_user(error, ufd) : error;
> +	if (sealed) {
> +		/*
> +		 * Receive fd sealed in an "envelope" anonymous fd.
> +		 * This will install a special file that can be unsealed
> +		 * upon sending to another process, otherwise the underlying
> +		 * file will not be accessible to this process, avoiding the
> +		 * need to pass the LSM check below.
> +		 */
> +		f_received = anon_inode_getfile("[envelope]", &envelope_ops, f,
> +						O_RDONLY);
> +		if (IS_ERR(f_received))
> +			return PTR_ERR(f_received);
> +
> +		get_file(f);
> +	} else {
> +		error = security_file_receive(f);
> +		if (error)
> +			return notrunc ? put_user(error, ufd) : error;
>  
> -	FD_PREPARE(fdf, flags, get_file(f));
> +		f_received = get_file(f);
> +	}
> +
> +	FD_PREPARE(fdf, flags, f_received);
>  	if (fdf->fd < 0)
>  		return fdf->fd;

[Severity: High]
If get_unused_fd_flags() fails inside FD_PREPARE(), what releases f_received?

Looking at __FD_PREPARE() in include/linux/file.h:

#define __FD_PREPARE(_guard, _fdf, _fd_flags, _file_owned)		\
	struct fd_prepare _guard __cleanup(__fd_prepare_cleanup) = ({	\
		int __fd = get_unused_fd_flags(_fd_flags);		\
		__fd_prepare(__fd, __fd < 0 ? NULL : (_file_owned));	\
	});								\
	const struct fd_prepare *const _fdf = &_guard

Previously, get_file(f) was passed directly as the _file_owned argument to
FD_PREPARE(), so it was only evaluated when __fd >= 0. Because f_received is
now allocated or incremented before FD_PREPARE(), when get_unused_fd_flags()
returns a negative error (such as -EMFILE), _guard.file is initialized to
NULL and __fd_prepare_cleanup() does not call fput() on f_received, leaking
f_received (and the inner file reference in the sealed case).

> diff --git a/net/core/sock.c b/net/core/sock.c
> index e7405be8941f9..68a447d8bf52b 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -1959,6 +1959,15 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
>  		v.val = sk->sk_scm_rights;
>  		break;
>  
> +	case SO_RIGHTS_SEALED_SUPPORTED:
> +		if (!sk_is_unix(sk))
> +			return -EOPNOTSUPP;
> +
> +		/* Just succeed without returning anything, signaling
> +		 * support.
> +		 */
> +		break;

[Severity: Medium]
Should the SO_RIGHTS_SEALED_SUPPORTED case in sk_getsockopt() set lv = 0 (or
return 0 directly) instead of breaking out of the switch with lv still set to
sizeof(int)?

At the start of sk_getsockopt(), lv is initialized to sizeof(int) and v is
zeroed. Breaking out of the switch runs the shared copy-out epilogue:

	if (len > lv)
		len = lv;
	if (copy_to_sockptr(optval, &v, len))
		return -EFAULT;
lenout:
	if (copy_to_sockptr(optlen, &len, sizeof(int)))
		return -EFAULT;
	return 0;

If a caller passes a non-zero optlen, sk_getsockopt() will either return
-EFAULT when optval is NULL or write 0 into optval and update *optlen to
up to sizeof(int), rather than succeeding without returning a value as
described in the comment.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102130540.252894-1-omosnacek@gmail.com?part=1

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-11-03 13:05 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 13:05 [RFC PATCH] af_unix: introduce MSG_CMSG_SEALED Ondrej Mosnáček
2026-11-02 13:13 ` Ondrej Mosnáček
2026-11-02 15:14 ` Stephen Smalley
2026-11-02 19:06   ` Stephen Smalley
2026-11-02 19:39   ` Paul Moore
2026-11-03 13:05 ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.