mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 03:35:32 -04:00
net: af_unix: useful handling of LSM denials on SCM_RIGHTS
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 a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful handling of LSM denials when receiving SCM_RIGHTS messages: instead of truncating the message at the first blocked fd, keep every fd slot and store the LSM errno in the blocked slot. The socket option is inherited by the child accept() socket if set on the listen() socket. [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org> Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl> Link: https://patch.msgid.link/20260813162818.149248-4-jkoolstra@xs4all.nl Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
48b84acc5e
commit
fd8756fa14
@@ -155,6 +155,8 @@
|
||||
#define SO_INQ 84
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 85
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64
|
||||
|
||||
@@ -166,6 +166,8 @@
|
||||
#define SO_INQ 84
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 85
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64
|
||||
|
||||
@@ -147,6 +147,8 @@
|
||||
#define SO_INQ 0x4052
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 0x4053
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
#define SO_INQ 0x005d
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 0x005e
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ struct unix_sock {
|
||||
struct scm_stat scm_stat;
|
||||
int inq_len;
|
||||
bool recvmsg_inq;
|
||||
bool scm_rights_notrunc;
|
||||
#if IS_ENABLED(CONFIG_AF_UNIX_OOB)
|
||||
struct sk_buff *oob_skb;
|
||||
#endif
|
||||
|
||||
@@ -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, bool notrunc);
|
||||
void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
|
||||
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);
|
||||
@@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,
|
||||
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)
|
||||
{
|
||||
if (!ufd)
|
||||
return -EFAULT;
|
||||
return receive_fd(f, ufd, flags);
|
||||
}
|
||||
int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
|
||||
bool notrunc);
|
||||
|
||||
#endif /* __LINUX_NET_SCM_H */
|
||||
|
||||
|
||||
@@ -150,6 +150,8 @@
|
||||
#define SO_INQ 84
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 85
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
|
||||
|
||||
@@ -286,7 +286,7 @@ 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, bool notrunc)
|
||||
{
|
||||
struct compat_cmsghdr __user *cm =
|
||||
(struct compat_cmsghdr __user *)msg->msg_control_user;
|
||||
@@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
|
||||
int err = 0, i;
|
||||
|
||||
for (i = 0; i < fdmax; i++) {
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -351,7 +351,31 @@ 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)
|
||||
int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
|
||||
bool notrunc)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!ufd)
|
||||
return -EFAULT;
|
||||
|
||||
error = security_file_receive(f);
|
||||
if (error)
|
||||
return notrunc ? put_user(error, ufd) : error;
|
||||
|
||||
FD_PREPARE(fdf, flags, get_file(f));
|
||||
if (fdf.err)
|
||||
return fdf.err;
|
||||
|
||||
error = put_user(fd_prepare_fd(fdf), ufd);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
__receive_sock(fd_prepare_file(fdf));
|
||||
return fd_publish(fdf);
|
||||
}
|
||||
|
||||
void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)
|
||||
{
|
||||
struct cmsghdr __user *cm =
|
||||
(__force struct cmsghdr __user *)msg->msg_control_user;
|
||||
@@ -365,12 +389,12 @@ 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, notrunc);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < fdmax; i++) {
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
@@ -542,8 +566,12 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
|
||||
if (!__scm_recv_common(sock->sk, msg, scm, flags))
|
||||
return;
|
||||
|
||||
if (scm->fp)
|
||||
scm_detach_fds(msg, scm);
|
||||
if (scm->fp) {
|
||||
struct unix_sock *u;
|
||||
|
||||
u = unix_sk(sock->sk);
|
||||
scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_notrunc));
|
||||
}
|
||||
|
||||
if (sock->sk->sk_scm_pidfd)
|
||||
scm_pidfd_recv(msg, scm);
|
||||
|
||||
@@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname)
|
||||
{
|
||||
switch (optname) {
|
||||
case SO_INQ:
|
||||
case SO_RIGHTS_NOTRUNC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -957,6 +958,14 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
|
||||
|
||||
WRITE_ONCE(u->recvmsg_inq, val);
|
||||
break;
|
||||
|
||||
case SO_RIGHTS_NOTRUNC:
|
||||
if (val > 1 || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
WRITE_ONCE(u->scm_rights_notrunc, val);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
@@ -1746,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
|
||||
init_peercred(newsk, &peercred);
|
||||
|
||||
newu = unix_sk(newsk);
|
||||
newu->listener = other;
|
||||
RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
|
||||
otheru = unix_sk(other);
|
||||
newu->listener = other;
|
||||
newu->scm_rights_notrunc = READ_ONCE(otheru->scm_rights_notrunc);
|
||||
RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
|
||||
|
||||
/* copy address information from listening to new sock
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user