* [RFC PATCH 0/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS @ 2026-04-28 17:51 Jori Koolstra 2026-04-28 17:51 ` [RFC PATCH 1/2] " Jori Koolstra 2026-04-28 17:51 ` [RFC PATCH 2/2] selftest: Add tests for useful " Jori Koolstra 0 siblings, 2 replies; 7+ messages in thread From: Jori Koolstra @ 2026-04-28 17:51 UTC (permalink / raw) To: Alexander Viro, Christian Brauner, Jan Kara, Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S . Miller, Jakub Kicinski, Jens Axboe, Kees Cook Cc: Simon Horman, Andy Lutomirski, Will Drewry, Jeff Layton, Jori Koolstra, Oleg Nesterov, Andrei Vagin, Pavel Tikhomirov, Mateusz Guzik, Joel Granados, Charlie Mirabile, Aleksa Sarai, linux-fsdevel, linux-kernel, netdev, io-uring Right now if some LSM such as Smack denies an AF_UNIX socket peer to receive an SCM_RIGHTS fd the SCM_RIGHTS fd array will be cut short at that point, and MSG_CTRUNC is set on return of recvmsg(). This is highly problematic behaviour, because it leaves the receiver wondering what happened. As per man page MSG_CTRUNC is supposed to indicate that the control buffer was sized too short, but suddenly a permission error might result in the exact same flag being set. Moreover, the receiver has no chance to determine how many fds got originally sent and how many were suppressed.[1] Add two MSG_* flags: - MSG_RIGHTS_DENIAL is set whenever any file is rejected by the LSM during recvmsg(2) of SCM_RIGHTS fds. - If MSG_RIGHTS_FILTER is passed as a flag to recvmsg(), the SCM_RIGHTS fd array is always passed in its full original size. However, any files rejected by the LSM are replaced in this array with -EPERM instead of an assigned fd, while keeping the original order. If the flag is not set, the original truncate behavior is used. I am putting this out for RFC for two reasons: 1) The MSG_* space is quite limited. We can do without MSG_RIGHTS_DENIAL if needed. 2) Does userspace ever do anything else than bail out if MSG_CTRUNC is found set? If not, we could maybe also get rid of MSG_RIGHTS_FILTER and just make this the default behavior. [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights Jori Koolstra (2): net: af_unix: Useful handling of LSM denials on SCM_RIGHTS selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS fs/file.c | 21 +- include/linux/file.h | 4 +- include/linux/socket.h | 3 + include/net/scm.h | 8 +- io_uring/openclose.c | 2 +- kernel/pid.c | 2 +- kernel/seccomp.c | 2 +- net/compat.c | 7 +- net/core/scm.c | 11 +- .../net/af_unix/lsm_blocking/helper.h | 37 ++++ .../net/af_unix/lsm_blocking/receiver.c | 187 ++++++++++++++++++ .../net/af_unix/lsm_blocking/sender.c | 126 ++++++++++++ .../lsm_blocking/test_scm_rights_smack.sh | 172 ++++++++++++++++ 13 files changed, 563 insertions(+), 19 deletions(-) create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/helper.h create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/receiver.c create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/sender.c create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/test_scm_rights_smack.sh -- 2.54.0 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 1/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS 2026-04-28 17:51 [RFC PATCH 0/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS Jori Koolstra @ 2026-04-28 17:51 ` Jori Koolstra 2026-04-30 2:04 ` Kuniyuki Iwashima 2026-04-28 17:51 ` [RFC PATCH 2/2] selftest: Add tests for useful " Jori Koolstra 1 sibling, 1 reply; 7+ messages in thread From: Jori Koolstra @ 2026-04-28 17:51 UTC (permalink / raw) To: Alexander Viro, Christian Brauner, Jan Kara, Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S . Miller, Jakub Kicinski, Jens Axboe, Kees Cook Cc: Simon Horman, Andy Lutomirski, Will Drewry, Jeff Layton, Jori Koolstra, Oleg Nesterov, Andrei Vagin, Pavel Tikhomirov, Mateusz Guzik, Joel Granados, Charlie Mirabile, Aleksa Sarai, linux-fsdevel, linux-kernel, netdev, io-uring Right now if some LSM such as Smack denies an AF_UNIX socket peer to receive an SCM_RIGHTS fd the SCM_RIGHTS fd array will be cut short at that point, and MSG_CTRUNC is set on return of recvmsg(). This is highly problematic behaviour, because it leaves the receiver wondering what happened. As per man page MSG_CTRUNC is supposed to indicate that the control buffer was sized too short, but suddenly a permission error might result in the exact same flag being set. Moreover, the receiver has no chance to determine how many fds got originally sent and how many were suppressed.[1] Add two MSG_* flags: - MSG_RIGHTS_DENIAL is set whenever any file is rejected by the LSM during recvmsg() of SCM_RIGHTS fds. - If MSG_RIGHTS_FILTER is passed as a flag to recvmsg(), the SCM_RIGHTS fd array is always passed in its full original size. However, any files rejected by the LSM are replaced in this array with -EPERM instead of an assigned fd, while keeping the original order. If the flag is not set, the original truncate behavior is used. [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl> --- fs/file.c | 21 ++++++++++++++++++--- include/linux/file.h | 4 +++- include/linux/socket.h | 3 +++ include/net/scm.h | 8 ++++---- io_uring/openclose.c | 2 +- kernel/pid.c | 2 +- kernel/seccomp.c | 2 +- net/compat.c | 7 ++++--- net/core/scm.c | 11 ++++++----- 9 files changed, 41 insertions(+), 19 deletions(-) diff --git a/fs/file.c b/fs/file.c index 2c81c0b162d0..cc33a1e77049 100644 --- a/fs/file.c +++ b/fs/file.c @@ -1370,10 +1370,11 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags) } /** - * receive_fd() - Install received file into file descriptor table + * receive_fd_msg() - Install received file into file descriptor table * @file: struct file that was received from another process * @ufd: __user pointer to write new fd number to * @o_flags: the O_* flags to apply to the new fd entry + * @msg_flags: the MSG_* flags to set for recvmsg(2) * * Installs a received file into the file descriptor table, with appropriate * checks and count updates. Optionally writes the fd number to userspace, if @@ -1384,13 +1385,21 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags) * * Returns newly install fd or -ve on error. */ -int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags) +int receive_fd_msg(struct file *file, int __user *ufd, unsigned int o_flags, + unsigned int *msg_flags) { int error; error = security_file_receive(file); - if (error) + if (error) { + if (msg_flags) + *msg_flags |= MSG_RIGHTS_DENIAL; + + if (ufd) + put_user(-EPERM, ufd); + return error; + } FD_PREPARE(fdf, o_flags, file); if (fdf.err) @@ -1406,6 +1415,12 @@ int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags) __receive_sock(fd_prepare_file(fdf)); return fd_publish(fdf); } +EXPORT_SYMBOL_GPL(receive_fd_msg); + +int receive_fd(struct file *file, unsigned int o_flags) +{ + return receive_fd_msg(file, NULL, o_flags, NULL); +} EXPORT_SYMBOL_GPL(receive_fd); int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags) diff --git a/include/linux/file.h b/include/linux/file.h index 27484b444d31..38f022d997a6 100644 --- a/include/linux/file.h +++ b/include/linux/file.h @@ -118,7 +118,9 @@ DEFINE_FREE(fput, struct file *, if (!IS_ERR_OR_NULL(_T)) fput(_T)) extern void fd_install(unsigned int fd, struct file *file); -int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags); +int receive_fd_msg(struct file *file, int __user *ufd, unsigned int o_flags, + unsigned int *msg_flags); +int receive_fd(struct file *file, unsigned int o_flags); int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags); diff --git a/include/linux/socket.h b/include/linux/socket.h index ec4a0a025793..3809a8add2fc 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -342,6 +342,9 @@ struct ucred { * plain text and require encryption */ +#define MSG_RIGHTS_DENIAL 0x200000 +#define MSG_RIGHTS_FILTER 0x400000 + #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() */ diff --git a/include/net/scm.h b/include/net/scm.h index c52519669349..983efa952c8e 100644 --- a/include/net/scm.h +++ b/include/net/scm.h @@ -50,8 +50,8 @@ struct scm_cookie { #endif }; -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm); -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm); +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, int recv_flags); +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, int recv_flags); int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm); void __scm_destroy(struct scm_cookie *scm); struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl); @@ -108,11 +108,11 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm, int flags); static inline int scm_recv_one_fd(struct file *f, int __user *ufd, - unsigned int flags) + unsigned int o_flags, unsigned int *msg_flags) { if (!ufd) return -EFAULT; - return receive_fd(f, ufd, flags); + return receive_fd_msg(f, ufd, o_flags, msg_flags); } #endif /* __LINUX_NET_SCM_H */ diff --git a/io_uring/openclose.c b/io_uring/openclose.c index c71242915dad..1b6cb05b0e3d 100644 --- a/io_uring/openclose.c +++ b/io_uring/openclose.c @@ -308,7 +308,7 @@ int io_install_fixed_fd(struct io_kiocb *req, unsigned int issue_flags) int ret; ifi = io_kiocb_to_cmd(req, struct io_fixed_install); - ret = receive_fd(req->file, NULL, ifi->o_flags); + ret = receive_fd(req->file, ifi->o_flags); if (ret < 0) req_set_fail(req); io_req_set_res(req, ret, 0); diff --git a/kernel/pid.c b/kernel/pid.c index fd5c2d4aa349..62af6874192d 100644 --- a/kernel/pid.c +++ b/kernel/pid.c @@ -929,7 +929,7 @@ static int pidfd_getfd(struct pid *pid, int fd) if (IS_ERR(file)) return PTR_ERR(file); - ret = receive_fd(file, NULL, O_CLOEXEC); + ret = receive_fd(file, O_CLOEXEC); fput(file); return ret; diff --git a/kernel/seccomp.c b/kernel/seccomp.c index 066909393c38..ad5ab16fe2b1 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -1130,7 +1130,7 @@ static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_kn */ list_del_init(&addfd->list); if (!addfd->setfd) - fd = receive_fd(addfd->file, NULL, addfd->flags); + fd = receive_fd(addfd->file, addfd->flags); else fd = receive_fd_replace(addfd->fd, addfd->file, addfd->flags); addfd->ret = fd; diff --git a/net/compat.c b/net/compat.c index 2c9bd0edac99..056bce0927c4 100644 --- a/net/compat.c +++ b/net/compat.c @@ -287,18 +287,19 @@ static int scm_max_fds_compat(struct msghdr *msg) return (msg->msg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int); } -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm) +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, int recv_flags) { struct compat_cmsghdr __user *cm = (struct compat_cmsghdr __user *)msg->msg_control_user; unsigned int o_flags = (msg->msg_flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0; + bool filter_rights = recv_flags & MSG_RIGHTS_FILTER; int fdmax = min_t(int, scm_max_fds_compat(msg), scm->fp->count); int __user *cmsg_data = CMSG_COMPAT_DATA(cm); int err = 0, i; for (i = 0; i < fdmax; i++) { - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); - if (err < 0) + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, &msg->msg_flags); + if (err < 0 && !filter_rights) break; } diff --git a/net/core/scm.c b/net/core/scm.c index eec13f50ecaf..035329645d8f 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -351,10 +351,11 @@ static int scm_max_fds(struct msghdr *msg) return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int); } -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, int recv_flags) { struct cmsghdr __user *cm = (__force struct cmsghdr __user *)msg->msg_control_user; + bool filter_rights = recv_flags & MSG_RIGHTS_FILTER; unsigned int o_flags = (msg->msg_flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0; int fdmax = min_t(int, scm_max_fds(msg), scm->fp->count); int __user *cmsg_data = CMSG_USER_DATA(cm); @@ -365,13 +366,13 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) return; if (msg->msg_flags & MSG_CMSG_COMPAT) { - scm_detach_fds_compat(msg, scm); + scm_detach_fds_compat(msg, scm, recv_flags); return; } for (i = 0; i < fdmax; i++) { - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); - if (err < 0) + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, &msg->msg_flags); + if (err < 0 && !filter_rights) break; } @@ -524,7 +525,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg, scm_passec(sk, msg, scm); if (scm->fp) - scm_detach_fds(msg, scm); + scm_detach_fds(msg, scm, flags); return true; } -- 2.54.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 1/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS 2026-04-28 17:51 ` [RFC PATCH 1/2] " Jori Koolstra @ 2026-04-30 2:04 ` Kuniyuki Iwashima 2026-05-01 15:34 ` Jori Koolstra 0 siblings, 1 reply; 7+ messages in thread From: Kuniyuki Iwashima @ 2026-04-30 2:04 UTC (permalink / raw) To: Jori Koolstra Cc: Alexander Viro, Christian Brauner, Jan Kara, Eric Dumazet, Paolo Abeni, Willem de Bruijn, David S . Miller, Jakub Kicinski, Jens Axboe, Kees Cook, Simon Horman, Andy Lutomirski, Will Drewry, Jeff Layton, Oleg Nesterov, Andrei Vagin, Pavel Tikhomirov, Mateusz Guzik, Joel Granados, Charlie Mirabile, Aleksa Sarai, linux-fsdevel, linux-kernel, netdev, io-uring On Tue, Apr 28, 2026 at 10:51 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote: > > Right now if some LSM such as Smack denies an AF_UNIX socket peer to > receive an SCM_RIGHTS fd the SCM_RIGHTS fd array will be cut short at > that point, and MSG_CTRUNC is set on return of recvmsg(). This is > highly problematic behaviour, because it leaves the receiver > wondering what happened. As per man page MSG_CTRUNC is supposed to > indicate that the control buffer was sized too short, but suddenly > a permission error might result in the exact same flag being set. > Moreover, the receiver has no chance to determine how many fds got > originally sent and how many were suppressed.[1] > > Add two MSG_* flags: Since we only have 5 bits remaining for future extension, we need to consider the use case a bit more carefully. > - MSG_RIGHTS_DENIAL is set whenever any file is rejected by the LSM > during recvmsg() of SCM_RIGHTS fds. Is this really needed ? Even if the fd array is truncated, the application will traverse the array anyway since it has some fds already installed (to clean up in case of MSG_CTRUNC ?). Then, it will find the -EPERM entry. I assume no one uses MSG_RIGHTS_DENIAL without MSG_RIGHTS_FILTER. > - If MSG_RIGHTS_FILTER is passed as a flag to recvmsg(), the SCM_RIGHTS Does this flag need per-recvmsg() granularity ? If the application does not welcome the truncated fd array, it would have passed MSG_RIGHTS_FILTER to every recvmsg(), no ? ( and I feel _FILTER sounds like "please do filtering (truncase)". Maybe _NOTRUNC ? ) > fd array is always passed in its full original size. However, any > files rejected by the LSM are replaced in this array with -EPERM > instead of an assigned fd, while keeping the original order. If the > flag is not set, the original truncate behavior is used. > > [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights > > Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl> > --- > fs/file.c | 21 ++++++++++++++++++--- > include/linux/file.h | 4 +++- > include/linux/socket.h | 3 +++ > include/net/scm.h | 8 ++++---- > io_uring/openclose.c | 2 +- > kernel/pid.c | 2 +- > kernel/seccomp.c | 2 +- > net/compat.c | 7 ++++--- > net/core/scm.c | 11 ++++++----- > 9 files changed, 41 insertions(+), 19 deletions(-) > > diff --git a/fs/file.c b/fs/file.c > index 2c81c0b162d0..cc33a1e77049 100644 > --- a/fs/file.c > +++ b/fs/file.c > @@ -1370,10 +1370,11 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags) > } > > /** > - * receive_fd() - Install received file into file descriptor table > + * receive_fd_msg() - Install received file into file descriptor table > * @file: struct file that was received from another process > * @ufd: __user pointer to write new fd number to > * @o_flags: the O_* flags to apply to the new fd entry > + * @msg_flags: the MSG_* flags to set for recvmsg(2) > * > * Installs a received file into the file descriptor table, with appropriate > * checks and count updates. Optionally writes the fd number to userspace, if > @@ -1384,13 +1385,21 @@ int replace_fd(unsigned fd, struct file *file, unsigned flags) > * > * Returns newly install fd or -ve on error. > */ > -int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags) > +int receive_fd_msg(struct file *file, int __user *ufd, unsigned int o_flags, > + unsigned int *msg_flags) > { > int error; > > error = security_file_receive(file); > - if (error) > + if (error) { > + if (msg_flags) > + *msg_flags |= MSG_RIGHTS_DENIAL; > + > + if (ufd) > + put_user(-EPERM, ufd); > + > return error; > + } > > FD_PREPARE(fdf, o_flags, file); > if (fdf.err) > @@ -1406,6 +1415,12 @@ int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags) > __receive_sock(fd_prepare_file(fdf)); > return fd_publish(fdf); > } > +EXPORT_SYMBOL_GPL(receive_fd_msg); > + > +int receive_fd(struct file *file, unsigned int o_flags) > +{ > + return receive_fd_msg(file, NULL, o_flags, NULL); > +} > EXPORT_SYMBOL_GPL(receive_fd); > > int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags) > diff --git a/include/linux/file.h b/include/linux/file.h > index 27484b444d31..38f022d997a6 100644 > --- a/include/linux/file.h > +++ b/include/linux/file.h > @@ -118,7 +118,9 @@ DEFINE_FREE(fput, struct file *, if (!IS_ERR_OR_NULL(_T)) fput(_T)) > > extern void fd_install(unsigned int fd, struct file *file); > > -int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags); > +int receive_fd_msg(struct file *file, int __user *ufd, unsigned int o_flags, > + unsigned int *msg_flags); > +int receive_fd(struct file *file, unsigned int o_flags); > > int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags); > > diff --git a/include/linux/socket.h b/include/linux/socket.h > index ec4a0a025793..3809a8add2fc 100644 > --- a/include/linux/socket.h > +++ b/include/linux/socket.h > @@ -342,6 +342,9 @@ struct ucred { > * plain text and require encryption > */ > > +#define MSG_RIGHTS_DENIAL 0x200000 > +#define MSG_RIGHTS_FILTER 0x400000 > + > #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() */ > diff --git a/include/net/scm.h b/include/net/scm.h > index c52519669349..983efa952c8e 100644 > --- a/include/net/scm.h > +++ b/include/net/scm.h > @@ -50,8 +50,8 @@ struct scm_cookie { > #endif > }; > > -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm); > -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm); > +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, int recv_flags); > +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, int recv_flags); > int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm); > void __scm_destroy(struct scm_cookie *scm); > struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl); > @@ -108,11 +108,11 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg, > struct scm_cookie *scm, int flags); > > static inline int scm_recv_one_fd(struct file *f, int __user *ufd, > - unsigned int flags) > + unsigned int o_flags, unsigned int *msg_flags) > { > if (!ufd) > return -EFAULT; > - return receive_fd(f, ufd, flags); > + return receive_fd_msg(f, ufd, o_flags, msg_flags); > } > > #endif /* __LINUX_NET_SCM_H */ > diff --git a/io_uring/openclose.c b/io_uring/openclose.c > index c71242915dad..1b6cb05b0e3d 100644 > --- a/io_uring/openclose.c > +++ b/io_uring/openclose.c > @@ -308,7 +308,7 @@ int io_install_fixed_fd(struct io_kiocb *req, unsigned int issue_flags) > int ret; > > ifi = io_kiocb_to_cmd(req, struct io_fixed_install); > - ret = receive_fd(req->file, NULL, ifi->o_flags); > + ret = receive_fd(req->file, ifi->o_flags); > if (ret < 0) > req_set_fail(req); > io_req_set_res(req, ret, 0); > diff --git a/kernel/pid.c b/kernel/pid.c > index fd5c2d4aa349..62af6874192d 100644 > --- a/kernel/pid.c > +++ b/kernel/pid.c > @@ -929,7 +929,7 @@ static int pidfd_getfd(struct pid *pid, int fd) > if (IS_ERR(file)) > return PTR_ERR(file); > > - ret = receive_fd(file, NULL, O_CLOEXEC); > + ret = receive_fd(file, O_CLOEXEC); > fput(file); > > return ret; > diff --git a/kernel/seccomp.c b/kernel/seccomp.c > index 066909393c38..ad5ab16fe2b1 100644 > --- a/kernel/seccomp.c > +++ b/kernel/seccomp.c > @@ -1130,7 +1130,7 @@ static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_kn > */ > list_del_init(&addfd->list); > if (!addfd->setfd) > - fd = receive_fd(addfd->file, NULL, addfd->flags); > + fd = receive_fd(addfd->file, addfd->flags); > else > fd = receive_fd_replace(addfd->fd, addfd->file, addfd->flags); > addfd->ret = fd; > diff --git a/net/compat.c b/net/compat.c > index 2c9bd0edac99..056bce0927c4 100644 > --- a/net/compat.c > +++ b/net/compat.c > @@ -287,18 +287,19 @@ static int scm_max_fds_compat(struct msghdr *msg) > return (msg->msg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int); > } > > -void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm) > +void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, int recv_flags) > { > struct compat_cmsghdr __user *cm = > (struct compat_cmsghdr __user *)msg->msg_control_user; > unsigned int o_flags = (msg->msg_flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0; > + bool filter_rights = recv_flags & MSG_RIGHTS_FILTER; > int fdmax = min_t(int, scm_max_fds_compat(msg), scm->fp->count); > int __user *cmsg_data = CMSG_COMPAT_DATA(cm); > int err = 0, i; > > for (i = 0; i < fdmax; i++) { > - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); > - if (err < 0) > + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, &msg->msg_flags); > + if (err < 0 && !filter_rights) > break; > } > > diff --git a/net/core/scm.c b/net/core/scm.c > index eec13f50ecaf..035329645d8f 100644 > --- a/net/core/scm.c > +++ b/net/core/scm.c > @@ -351,10 +351,11 @@ static int scm_max_fds(struct msghdr *msg) > return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int); > } > > -void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) > +void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, int recv_flags) > { > struct cmsghdr __user *cm = > (__force struct cmsghdr __user *)msg->msg_control_user; > + bool filter_rights = recv_flags & MSG_RIGHTS_FILTER; > unsigned int o_flags = (msg->msg_flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0; > int fdmax = min_t(int, scm_max_fds(msg), scm->fp->count); > int __user *cmsg_data = CMSG_USER_DATA(cm); > @@ -365,13 +366,13 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) > return; > > if (msg->msg_flags & MSG_CMSG_COMPAT) { > - scm_detach_fds_compat(msg, scm); > + scm_detach_fds_compat(msg, scm, recv_flags); > return; > } > > for (i = 0; i < fdmax; i++) { > - err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags); > - if (err < 0) > + err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, &msg->msg_flags); > + if (err < 0 && !filter_rights) > break; > } > > @@ -524,7 +525,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg, > scm_passec(sk, msg, scm); > > if (scm->fp) > - scm_detach_fds(msg, scm); > + scm_detach_fds(msg, scm, flags); > > return true; > } > -- > 2.54.0 > ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 1/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS 2026-04-30 2:04 ` Kuniyuki Iwashima @ 2026-05-01 15:34 ` Jori Koolstra 2026-05-02 1:24 ` Kuniyuki Iwashima 0 siblings, 1 reply; 7+ messages in thread From: Jori Koolstra @ 2026-05-01 15:34 UTC (permalink / raw) To: Kuniyuki Iwashima Cc: Alexander Viro, Christian Brauner, Jan Kara, Eric Dumazet, Paolo Abeni, Willem de Bruijn, David S . Miller, Jakub Kicinski, Jens Axboe, Kees Cook, Simon Horman, Andy Lutomirski, Will Drewry, Jeff Layton, Oleg Nesterov, Andrei Vagin, Pavel Tikhomirov, Mateusz Guzik, Joel Granados, Charlie Mirabile, Aleksa Sarai, linux-fsdevel, linux-kernel, netdev, io-uring > Op 30-04-2026 04:04 CEST schreef Kuniyuki Iwashima <kuniyu@google.com>: > > > On Tue, Apr 28, 2026 at 10:51 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote: > > > > Right now if some LSM such as Smack denies an AF_UNIX socket peer to > > receive an SCM_RIGHTS fd the SCM_RIGHTS fd array will be cut short at > > that point, and MSG_CTRUNC is set on return of recvmsg(). This is > > highly problematic behaviour, because it leaves the receiver > > wondering what happened. As per man page MSG_CTRUNC is supposed to > > indicate that the control buffer was sized too short, but suddenly > > a permission error might result in the exact same flag being set. > > Moreover, the receiver has no chance to determine how many fds got > > originally sent and how many were suppressed.[1] > > > > Add two MSG_* flags: > > Since we only have 5 bits remaining for future extension, > we need to consider the use case a bit more carefully. > Right. Since it wasn't a lot of work I implemented it exactly as the request was made from userspace, and then discuss it from there. By the way, I suppose nothing can be done about that small flag space? > > > - MSG_RIGHTS_DENIAL is set whenever any file is rejected by the LSM > > during recvmsg() of SCM_RIGHTS fds. > > Is this really needed ? > > Even if the fd array is truncated, the application will traverse > the array anyway since it has some fds already installed (to > clean up in case of MSG_CTRUNC ?). > > Then, it will find the -EPERM entry. > > I assume no one uses MSG_RIGHTS_DENIAL without > MSG_RIGHTS_FILTER. > I guess that is a fair assumption to make. We can certainly do without MSG_RIGHTS_DENIAL if saving flags is important. I also suggested that we may see whether we can make MSG_RIGHTS_FILTER the default behavior. In the mean time I've found grep.app, and it turns out the answer is no. Apparently almost no one checks even for the truncation flag (mostly 1 fd is passed and then it is check the cmsg lenght). But cpython has this for instance: /* Close all descriptors coming from SCM_RIGHTS, so they don't leak. */ for (cmsgh = ((msg.msg_controllen > 0) ? CMSG_FIRSTHDR(&msg) : NULL); cmsgh != NULL; cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { cmsg_status = get_cmsg_data_len(&msg, cmsgh, &cmsgdatalen); if (cmsg_status < 0) break; if (cmsgh->cmsg_level == SOL_SOCKET && cmsgh->cmsg_type == SCM_RIGHTS) { size_t numfds; int *fdp; numfds = cmsgdatalen / sizeof(int); fdp = (int *)CMSG_DATA(cmsgh); while (numfds-- > 0) close(*fdp++); } if (cmsg_status != 0) break; } > > > - If MSG_RIGHTS_FILTER is passed as a flag to recvmsg(), the SCM_RIGHTS > > Does this flag need per-recvmsg() granularity ? > Perhaps not. What would be the alternative? A fcntl option for the socket fd? > If the application does not welcome the truncated fd array, > it would have passed MSG_RIGHTS_FILTER to every > recvmsg(), no ? > Correct. Thanks, Jori. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 1/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS 2026-05-01 15:34 ` Jori Koolstra @ 2026-05-02 1:24 ` Kuniyuki Iwashima 2026-05-04 17:43 ` Jori Koolstra 0 siblings, 1 reply; 7+ messages in thread From: Kuniyuki Iwashima @ 2026-05-02 1:24 UTC (permalink / raw) To: Jori Koolstra Cc: Alexander Viro, Christian Brauner, Jan Kara, Eric Dumazet, Paolo Abeni, Willem de Bruijn, David S . Miller, Jakub Kicinski, Jens Axboe, Kees Cook, Simon Horman, Andy Lutomirski, Will Drewry, Jeff Layton, Oleg Nesterov, Andrei Vagin, Pavel Tikhomirov, Mateusz Guzik, Joel Granados, Charlie Mirabile, Aleksa Sarai, linux-fsdevel, linux-kernel, netdev, io-uring On Fri, May 1, 2026 at 8:34 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote: > > > > Op 30-04-2026 04:04 CEST schreef Kuniyuki Iwashima <kuniyu@google.com>: > > > > > > On Tue, Apr 28, 2026 at 10:51 AM Jori Koolstra <jkoolstra@xs4all.nl> wrote: > > > > > > Right now if some LSM such as Smack denies an AF_UNIX socket peer to > > > receive an SCM_RIGHTS fd the SCM_RIGHTS fd array will be cut short at > > > that point, and MSG_CTRUNC is set on return of recvmsg(). This is > > > highly problematic behaviour, because it leaves the receiver > > > wondering what happened. As per man page MSG_CTRUNC is supposed to > > > indicate that the control buffer was sized too short, but suddenly > > > a permission error might result in the exact same flag being set. > > > Moreover, the receiver has no chance to determine how many fds got > > > originally sent and how many were suppressed.[1] > > > > > > Add two MSG_* flags: > > > > Since we only have 5 bits remaining for future extension, > > we need to consider the use case a bit more carefully. > > > > Right. Since it wasn't a lot of work I implemented it exactly as the request > was made from userspace, and then discuss it from there. By the way, I suppose > nothing can be done about that small flag space? We could reuse an existing flag (e.g. MSG_FIN, MSG_RST) if we were confident enough that the userspace does not use the flag for a specific socket type. Another option is to add another syscall, recvmsg2. > > > > > > - MSG_RIGHTS_DENIAL is set whenever any file is rejected by the LSM > > > during recvmsg() of SCM_RIGHTS fds. > > > > Is this really needed ? > > > > Even if the fd array is truncated, the application will traverse > > the array anyway since it has some fds already installed (to > > clean up in case of MSG_CTRUNC ?). > > > > Then, it will find the -EPERM entry. > > > > I assume no one uses MSG_RIGHTS_DENIAL without > > MSG_RIGHTS_FILTER. > > > > I guess that is a fair assumption to make. We can certainly do without > MSG_RIGHTS_DENIAL if saving flags is important. I also suggested that > we may see whether we can make MSG_RIGHTS_FILTER the default behavior. > In the mean time I've found grep.app, and it turns out the answer is no. > Apparently almost no one checks even for the truncation flag (mostly 1 fd > is passed and then it is check the cmsg lenght). But cpython has this for > instance: > > /* Close all descriptors coming from SCM_RIGHTS, so they don't leak. */ > for (cmsgh = ((msg.msg_controllen > 0) ? CMSG_FIRSTHDR(&msg) : NULL); > cmsgh != NULL; cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { > cmsg_status = get_cmsg_data_len(&msg, cmsgh, &cmsgdatalen); > if (cmsg_status < 0) > break; > if (cmsgh->cmsg_level == SOL_SOCKET && > cmsgh->cmsg_type == SCM_RIGHTS) { > size_t numfds; > int *fdp; > numfds = cmsgdatalen / sizeof(int); > fdp = (int *)CMSG_DATA(cmsgh); > while (numfds-- > 0) > close(*fdp++); > } > if (cmsg_status != 0) > break; > } > > > > > > - If MSG_RIGHTS_FILTER is passed as a flag to recvmsg(), the SCM_RIGHTS > > > > Does this flag need per-recvmsg() granularity ? > > > > Perhaps not. What would be the alternative? A fcntl option for the socket fd? I'd add a new socket option like setsockopt(SOL_SOCKET, SO_RIGHTS_TRUNC, &(int){0}, sizeof(int)); > > > If the application does not welcome the truncated fd array, > > it would have passed MSG_RIGHTS_FILTER to every > > recvmsg(), no ? > > > > Correct. > > > Thanks, > Jori. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH 1/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS 2026-05-02 1:24 ` Kuniyuki Iwashima @ 2026-05-04 17:43 ` Jori Koolstra 0 siblings, 0 replies; 7+ messages in thread From: Jori Koolstra @ 2026-05-04 17:43 UTC (permalink / raw) To: Kuniyuki Iwashima Cc: Alexander Viro, Christian Brauner, Jan Kara, Eric Dumazet, Paolo Abeni, Willem de Bruijn, David S . Miller, Jakub Kicinski, Jens Axboe, Kees Cook, Simon Horman, Andy Lutomirski, Will Drewry, Jeff Layton, Oleg Nesterov, Andrei Vagin, Pavel Tikhomirov, Mateusz Guzik, Joel Granados, Charlie Mirabile, Aleksa Sarai, linux-fsdevel, linux-kernel, netdev, io-uring > Op 02-05-2026 03:24 CEST schreef Kuniyuki Iwashima <kuniyu@google.com>: > > > > > > > Does this flag need per-recvmsg() granularity ? > > > > > > > Perhaps not. What would be the alternative? A fcntl option for the socket fd? > > I'd add a new socket option like > > setsockopt(SOL_SOCKET, SO_RIGHTS_TRUNC, &(int){0}, sizeof(int)); > > I think this is reasonable suggestion (and better than using the MSG_ flags). Let's just let this sit for a few days to see if anyone else has suggestions/ objections. ^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH 2/2] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS 2026-04-28 17:51 [RFC PATCH 0/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS Jori Koolstra 2026-04-28 17:51 ` [RFC PATCH 1/2] " Jori Koolstra @ 2026-04-28 17:51 ` Jori Koolstra 1 sibling, 0 replies; 7+ messages in thread From: Jori Koolstra @ 2026-04-28 17:51 UTC (permalink / raw) To: Alexander Viro, Christian Brauner, Jan Kara, Eric Dumazet, Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S . Miller, Jakub Kicinski, Jens Axboe, Kees Cook Cc: Simon Horman, Andy Lutomirski, Will Drewry, Jeff Layton, Jori Koolstra, Oleg Nesterov, Andrei Vagin, Pavel Tikhomirov, Mateusz Guzik, Joel Granados, Charlie Mirabile, Aleksa Sarai, linux-fsdevel, linux-kernel, netdev, io-uring Tests SCM_RIGHTS fd passing using Smack LSM blocking in combination with the MSG_RIGHTS_FILTER flag. Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl> --- .../net/af_unix/lsm_blocking/helper.h | 37 ++++ .../net/af_unix/lsm_blocking/receiver.c | 187 ++++++++++++++++++ .../net/af_unix/lsm_blocking/sender.c | 126 ++++++++++++ .../lsm_blocking/test_scm_rights_smack.sh | 172 ++++++++++++++++ 4 files changed, 522 insertions(+) create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/helper.h create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/receiver.c create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/sender.c create mode 100644 tools/testing/selftests/net/af_unix/lsm_blocking/test_scm_rights_smack.sh diff --git a/tools/testing/selftests/net/af_unix/lsm_blocking/helper.h b/tools/testing/selftests/net/af_unix/lsm_blocking/helper.h new file mode 100644 index 000000000000..e827560ee78d --- /dev/null +++ b/tools/testing/selftests/net/af_unix/lsm_blocking/helper.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <unistd.h> +#include <fcntl.h> + +#define MSG_RIGHTS_DENIAL 0x200000 +#define MSG_RIGHTS_FILTER 0x400000 + +#define CMSG_IS_SCM_RIGHTS(cmsg) ({ \ + typeof(cmsg) _cmsg = (cmsg); \ + _cmsg && \ + _cmsg->cmsg_level == SOL_SOCKET && \ + _cmsg->cmsg_type == SCM_RIGHTS; \ +}) + +#define MIN(a, b) ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ +}) + +#define MAX_FDS 10 + +static inline int read_current_label(char *label, size_t size) +{ + int fd = open("/proc/self/attr/current", O_RDONLY); + if (fd < 0) + return fd; + + ssize_t r = read(fd, label, size - 1); + close(fd); + if (r <= 0) + return r; + + label[r] = '\0'; + + return 0; +} diff --git a/tools/testing/selftests/net/af_unix/lsm_blocking/receiver.c b/tools/testing/selftests/net/af_unix/lsm_blocking/receiver.c new file mode 100644 index 000000000000..f5af9dcddc22 --- /dev/null +++ b/tools/testing/selftests/net/af_unix/lsm_blocking/receiver.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * receiver.c - Receive a file descriptor over a Unix domain socket via SCM_RIGHTS + * + * Usage: ./receiver <socket_path> + * + * Listens on the given Unix socket path, accepts a connection, and + * attempts to receive file descriptors via SCM_RIGHTS. Reports + * whether the fds were delivered or blocked. + * + * Used for testing LSM (Smack) blocking of fd passing. + */ + +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/xattr.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> + +#include "helper.h" + +#define RECV_LOG(fmt, ...) printf("receiver: " fmt, ##__VA_ARGS__) +#define RECV_ERR(fmt, ...) fprintf(stderr, "receiver: " fmt, ##__VA_ARGS__) + +static int recv_fds(int sock, int *fds) +{ + char buf[1]; + char ctrl[CMSG_SPACE(MAX_FDS * sizeof(int))]; + + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = ctrl, + .msg_controllen = sizeof(ctrl), + }; + + ssize_t bytes_read = recvmsg(sock, &msg, MSG_RIGHTS_FILTER); + if (bytes_read < 0) { + perror("receiver: recvmsg"); + return -1; + } + if (bytes_read == 0) { + RECV_ERR("connection closed, no data received\n"); + return -1; + } + + if (msg.msg_flags & MSG_RIGHTS_DENIAL) + RECV_LOG("MSG_RIGHTS_DENIAL set - some fds were blocked by the LSM!\n"); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (!CMSG_IS_SCM_RIGHTS(cmsg)) { + RECV_ERR("no SCM_RIGHTS in control message\n"); + return -1; + } + + int num_fd_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + memcpy(fds, CMSG_DATA(cmsg), num_fd_slots * sizeof(int)); + + RECV_LOG("got %d fd slots:", num_fd_slots); + for (int i = 0; i < num_fd_slots ; i++) + printf(" %d", fds[i]); + putchar('\n'); + + return num_fd_slots; +} + +static inline int print_current_label(void) +{ + char label[256]; + if (!read_current_label(label, sizeof(label))) { + RECV_LOG("running with Smack label '%s'\n", label); + return 0; + } + return -1; +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <socket_path>\n", argv[0]); + return -1; + } + + if (print_current_label()) { + RECV_ERR("cannot read process Smack label"); + return -1; + } + + int listen_sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (listen_sock < 0) { + perror("receiver: socket"); + return -1; + } + + struct sockaddr_un addr = {}; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1); + + /* Remove any stale socket file */ + unlink(argv[1]); + + if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("receiver: bind"); + return -1; + } + + if (listen(listen_sock, 1) < 0) { + perror("receiver: listen"); + return -1; + } + + RECV_LOG("listening on '%s'\n", argv[1]); + + int conn_sock = accept(listen_sock, NULL, NULL); + if (conn_sock < 0) { + perror("receiver: accept"); + return -1; + } + + RECV_LOG("connection accepted\n"); + + /* Try to receive the fds */ + int fds[MAX_FDS]; + int num_fds = recv_fds(conn_sock, fds); + if (num_fds < 0) + goto out_sock; + + /* Try to use the received fds -- read and print their contents */ + RECV_LOG("attempting to read from received fds...\n"); + int i; + for (i = 0; i < num_fds; ++i) { + char readbuf[256]; + + if (fds[i] < 0) { + RECV_LOG("fd in position %i blocked\n", i); + continue; + } else if (fds[i] == 0) { + RECV_LOG("bad fd in position %i\n", i); + goto out_recv; + } + + ssize_t n = read(fds[i], readbuf, sizeof(readbuf) - 1); + if (n < 0) { + perror("receiver: read from received fd"); + goto out_recv; + } + + readbuf[n] = '\0'; + RECV_LOG("read %zd bytes from fd at position %i: '%s'\n", n, i, readbuf); + } + + RECV_LOG("final result:\n"); + for (int j = 0; j < num_fds; ++j) { + if (fds[j] < 0) { + printf("BLOCKED"); + } else { + printf("PASSED"); + close(fds[j]); + } + putchar(' '); + } + + close(conn_sock); + close(listen_sock); + unlink(argv[1]); + return 0; + +out_recv: + for (int j = 0; j < num_fds; ++j) { + if (fds[j] > 0) + close(fds[j]); + } + +out_sock: + close(conn_sock); + close(listen_sock); + unlink(argv[1]); + return -1; +} diff --git a/tools/testing/selftests/net/af_unix/lsm_blocking/sender.c b/tools/testing/selftests/net/af_unix/lsm_blocking/sender.c new file mode 100644 index 000000000000..b1c76d23b8bd --- /dev/null +++ b/tools/testing/selftests/net/af_unix/lsm_blocking/sender.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * sender.c - Send file descriptors over a Unix domain socket via SCM_RIGHTS + * + * Usage: ./sender <socket_path> <file_to_send> [<file_to_send>...] + * + * Opens the specified files and sends their fds to a receiver connected + * on the given Unix socket path. Used for testing LSM blocking of fd + * passing. + */ + +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> + +#include "helper.h" + +#define SEND_LOG(fmt, ...) fprintf(stdout, "sender: " fmt, ##__VA_ARGS__) +#define SEND_ERR(fmt, ...) fprintf(stderr, "sender: " fmt, ##__VA_ARGS__) + +static int send_fds(int sock, int *fds, int num_fds) +{ + if (num_fds > MAX_FDS) + return -1; + + char buf[1] = { 'X' }; + char ctrl[CMSG_SPACE(MAX_FDS * sizeof(int))] = { 0 }; + + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = ctrl, + .msg_controllen = CMSG_SPACE(num_fds * sizeof(int)), + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(num_fds * sizeof(int)); + memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(int)); + + ssize_t bytes_send = sendmsg(sock, &msg, 0); + if (bytes_send < 0) { + perror("sender: sendmsg"); + return -1; + } + + return 0; +} + +static inline int print_current_label(void) +{ + char label[256]; + if (!read_current_label(label, sizeof(label))) { + SEND_LOG("running with Smack label '%s'\n", label); + return 0; + } + return -1; +} + +int main(int argc, char *argv[]) +{ + if (argc < 3 || argc > 2 + MAX_FDS) { + fprintf(stderr, "Usage: %s <socket_path> <file_to_send> [<file_to_send>...]\\n", + argv[0]); + fprintf(stderr, "Up to a maximum of %d files", MAX_FDS); + return -1; + } + + if (print_current_label()) { + SEND_ERR("cannot read process Smack label"); + return -1; + } + + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("sender: socket"); + return -1; + } + + struct sockaddr_un addr = {}; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("sender: connect"); + goto out_sock; + } + + SEND_LOG("connected to '%s'\n", argv[1]); + + int num_files = argc - 2; + int fds[MAX_FDS]; + int i; + for (i = 0; i < num_files; i++) { + fds[i] = open(argv[2 + i], O_RDONLY); + if (fds[i] < 0) { + perror("sender: open file"); + goto out_opened; + } + SEND_LOG("opened '%s' as fd %d\n", argv[2 + i], fds[i]); + } + + if (send_fds(sock, fds, num_files) < 0) + goto out_opened; + + SEND_LOG("fds successfully sent:"); + for (int j = 0; j < num_files; j++) + printf(" %d", fds[j]); + putchar('\n'); + +out_opened: + for (int j = 0; j < i; j++) + close(fds[j]); +out_sock: + close(sock); + return -1; +} diff --git a/tools/testing/selftests/net/af_unix/lsm_blocking/test_scm_rights_smack.sh b/tools/testing/selftests/net/af_unix/lsm_blocking/test_scm_rights_smack.sh new file mode 100644 index 000000000000..76fcfdd2cd4a --- /dev/null +++ b/tools/testing/selftests/net/af_unix/lsm_blocking/test_scm_rights_smack.sh @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: GPL-2.0 + +# +# test_scm_rights_smack.sh - Test SCM_RIGHTS fd passing using Smack LSM blocking +# +# Must be run as root on a kernel with Smack enabled (security=smack). +# Requires: capsh (libcap), setfattr/getfattr (attr) +# +# We use the following Smack labels: +# "Sender" - label for the sending process +# "Receiver" - label for the receiving process +# "SecretX" - labels for the files being passed +# +# Socket communication (Sender <-> Receiver) is always allowed. +# The test controls whether Receiver can access "SecretX"-labeled fds. +# + + +readonly SOCK="/tmp/scm_test.sock" +readonly TESTFILE1="/tmp/scm_test_secret_1" +readonly TESTFILE2="/tmp/scm_test_secret_2" +readonly SENDER="./sender" +readonly RECEIVER="./receiver" + +set -e + +run_tests() { + + preflight + setup + + run_test "TEST 1" \ + "Receiver should NOT have access to Secret1." \ + "Receiver Secret1 --- +Receiver Secret2 ---" \ + "$TESTFILE1" \ + "BLOCKED" + + run_test "TEST 2" \ + "Receiver should have access to Secret1." \ + "Receiver Secret1 r-- +Receiver Secret2 ---" \ + "$TESTFILE1" \ + "PASSED" + + run_test "TEST 3" \ + "Receiver should have access to Secret2, but NOT Secret1." \ + "Receiver Secret1 --- +Receiver Secret2 r--" \ + "$TESTFILE1 $TESTFILE2" \ + "BLOCKED PASSED" +} + +run_test() { + local name="$1" + local description="$2" + local rules="$3" + local files="$4" + local expected="$5" + + echo "" + echo "$name: $description" + echo "Rules:" + echo "$rules" + echo "Expected: $expected" + echo "" + + while IFS= read -r rule; do + [ -n "$rule" ] && echo "$rule" > /sys/fs/smackfs/load2 + done <<< "$rules" + + local output status last_line + output=$(send_fds "$SOCK" $files) + status=$? + echo "$output" + last_line=$(echo "$output" | tail -n 1 | xargs) + + if [ "$status" -ne 0 ]; then + echo "TEST FAILED: receiver returned $status" + return 1 + fi + + if [[ "$last_line" == "$expected" ]]; then + echo "TEST PASSED: outcome was $expected as expected" + return 0 + else + echo "TEST FAILED: expected $expected, got '$last_line'" + return 1 + fi +} + +setup() { + + printf "Secret 1" > "$TESTFILE1" + printf "Secret 2" > "$TESTFILE2" + + setfattr -n security.SMACK64 -v "Secret1" "$TESTFILE1" + setfattr -n security.SMACK64 -v "Secret2" "$TESTFILE2" + setfattr -n security.SMACK64 -v "Tmp" /tmp + + echo "Sender Receiver -w-" > /sys/fs/smackfs/load2 + echo "Receiver Sender -w-" > /sys/fs/smackfs/load2 + echo "Sender Tmp rwx" > /sys/fs/smackfs/load2 + echo "Receiver Tmp rwx" > /sys/fs/smackfs/load2 + echo "Sender Secret1 r--" > /sys/fs/smackfs/load2 + echo "Sender Secret2 r--" > /sys/fs/smackfs/load2 +} + +send_fds() { + + local sk="$1" + shift + local files="$*" + + ( + echo "Receiver" > /proc/self/attr/current + exec capsh --drop=cap_mac_override,cap_mac_admin -- -c "$RECEIVER $sk" + ) & + local recv_pid=$! + sleep 1 + + ( + echo "Sender" > /proc/self/attr/current + exec capsh --drop=cap_mac_override,cap_mac_admin -- -c "$SENDER $sk $files" + ) || true + + local recv_status=0 + wait "$recv_pid" || recv_status=$? + + if [ "$recv_status" -ne 0 ]; then + echo "receiver exited with $recv_status" + fi + return "$recv_status" +} + +preflight() { + + if [ "$(id -u)" -ne 0 ]; then + echo "ERROR: must be run as root" + exit 1 + fi + + if ! grep -q smack /sys/kernel/security/lsm 2>/dev/null; then + echo "ERROR: Smack is not active" + echo " Check: cat /sys/kernel/security/lsm" + echo " Boot with: security=smack" + exit 1 + fi + + if ! mountpoint -q /sys/fs/smackfs 2>/dev/null; then + echo "Mounting smackfs..." + mount -t smackfs smackfs /sys/fs/smackfs + fi + + if ! command -v capsh &>/dev/null; then + echo "ERROR: capsh not found (install libcap)" + exit 1 + fi + + # Build the test programs if needed + if [ ! -x "$SENDER" ]; then + echo "Building sender..." + gcc -Wall -o sender sender.c + fi + if [ ! -x "$RECEIVER" ]; then + echo "Building receiver..." + gcc -Wall -o receiver receiver.c + fi + +} + +run_tests -- 2.54.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-05-04 17:43 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-28 17:51 [RFC PATCH 0/2] net: af_unix: Useful handling of LSM denials on SCM_RIGHTS Jori Koolstra 2026-04-28 17:51 ` [RFC PATCH 1/2] " Jori Koolstra 2026-04-30 2:04 ` Kuniyuki Iwashima 2026-05-01 15:34 ` Jori Koolstra 2026-05-02 1:24 ` Kuniyuki Iwashima 2026-05-04 17:43 ` Jori Koolstra 2026-04-28 17:51 ` [RFC PATCH 2/2] selftest: Add tests for useful " Jori Koolstra
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox