net: cleanly handle kernel vs user buffers for ->msg_control
authorChristoph Hellwig <hch@lst.de>
Mon, 11 May 2020 11:59:13 +0000 (13:59 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 11 May 2020 23:59:16 +0000 (16:59 -0700)
The msg_control field in struct msghdr can either contain a user
pointer when used with the recvmsg system call, or a kernel pointer
when used with sendmsg.  To complicate things further kernel_recvmsg
can stuff a kernel pointer in and then use set_fs to make the uaccess
helpers accept it.

Replace it with a union of a kernel pointer msg_control field, and
a user pointer msg_control_user one, and allow kernel_recvmsg operate
on a proper kernel pointer using a bitfield to override the normal
choice of a user pointer for recvmsg.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/socket.h
net/compat.c
net/core/scm.c
net/ipv4/ip_sockglue.c
net/socket.c

index 4cc64d6..04d2bc9 100644 (file)
@@ -50,7 +50,17 @@ struct msghdr {
        void            *msg_name;      /* ptr to socket address structure */
        int             msg_namelen;    /* size of socket address structure */
        struct iov_iter msg_iter;       /* data */
-       void            *msg_control;   /* ancillary data */
+
+       /*
+        * Ancillary data. msg_control_user is the user buffer used for the
+        * recv* side when msg_control_is_user is set, msg_control is the kernel
+        * buffer used for all other cases.
+        */
+       union {
+               void            *msg_control;
+               void __user     *msg_control_user;
+       };
+       bool            msg_control_is_user : 1;
        __kernel_size_t msg_controllen; /* ancillary data buffer length */
        unsigned int    msg_flags;      /* flags on received message */
        struct kiocb    *msg_iocb;      /* ptr to iocb for async requests */
index 4bed96e..69fc6d1 100644 (file)
@@ -56,7 +56,8 @@ int __get_compat_msghdr(struct msghdr *kmsg,
        if (kmsg->msg_namelen > sizeof(struct sockaddr_storage))
                kmsg->msg_namelen = sizeof(struct sockaddr_storage);
 
-       kmsg->msg_control = compat_ptr(msg.msg_control);
+       kmsg->msg_control_is_user = true;
+       kmsg->msg_control_user = compat_ptr(msg.msg_control);
        kmsg->msg_controllen = msg.msg_controllen;
 
        if (save_addr)
@@ -121,7 +122,7 @@ int get_compat_msghdr(struct msghdr *kmsg,
        ((ucmlen) >= sizeof(struct compat_cmsghdr) && \
         (ucmlen) <= (unsigned long) \
         ((mhdr)->msg_controllen - \
-         ((char *)(ucmsg) - (char *)(mhdr)->msg_control)))
+         ((char __user *)(ucmsg) - (char __user *)(mhdr)->msg_control_user)))
 
 static inline struct compat_cmsghdr __user *cmsg_compat_nxthdr(struct msghdr *msg,
                struct compat_cmsghdr __user *cmsg, int cmsg_len)
index 168b006..a75cd63 100644 (file)
@@ -212,16 +212,12 @@ EXPORT_SYMBOL(__scm_send);
 
 int put_cmsg(struct msghdr * msg, int level, int type, int len, void *data)
 {
-       struct cmsghdr __user *cm
-               = (__force struct cmsghdr __user *)msg->msg_control;
-       struct cmsghdr cmhdr;
        int cmlen = CMSG_LEN(len);
-       int err;
 
-       if (MSG_CMSG_COMPAT & msg->msg_flags)
+       if (msg->msg_flags & MSG_CMSG_COMPAT)
                return put_cmsg_compat(msg, level, type, len, data);
 
-       if (cm==NULL || msg->msg_controllen < sizeof(*cm)) {
+       if (!msg->msg_control || msg->msg_controllen < sizeof(struct cmsghdr)) {
                msg->msg_flags |= MSG_CTRUNC;
                return 0; /* XXX: return error? check spec. */
        }
@@ -229,23 +225,30 @@ int put_cmsg(struct msghdr * msg, int level, int type, int len, void *data)
                msg->msg_flags |= MSG_CTRUNC;
                cmlen = msg->msg_controllen;
        }
-       cmhdr.cmsg_level = level;
-       cmhdr.cmsg_type = type;
-       cmhdr.cmsg_len = cmlen;
-
-       err = -EFAULT;
-       if (copy_to_user(cm, &cmhdr, sizeof cmhdr))
-               goto out;
-       if (copy_to_user(CMSG_USER_DATA(cm), data, cmlen - sizeof(*cm)))
-               goto out;
-       cmlen = CMSG_SPACE(len);
-       if (msg->msg_controllen < cmlen)
-               cmlen = msg->msg_controllen;
+
+       if (msg->msg_control_is_user) {
+               struct cmsghdr __user *cm = msg->msg_control_user;
+               struct cmsghdr cmhdr;
+
+               cmhdr.cmsg_level = level;
+               cmhdr.cmsg_type = type;
+               cmhdr.cmsg_len = cmlen;
+               if (copy_to_user(cm, &cmhdr, sizeof cmhdr) ||
+                   copy_to_user(CMSG_USER_DATA(cm), data, cmlen - sizeof(*cm)))
+                       return -EFAULT;
+       } else {
+               struct cmsghdr *cm = msg->msg_control;
+
+               cm->cmsg_level = level;
+               cm->cmsg_type = type;
+               cm->cmsg_len = cmlen;
+               memcpy(CMSG_DATA(cm), data, cmlen - sizeof(*cm));
+       }
+
+       cmlen = min(CMSG_SPACE(len), msg->msg_controllen);
        msg->msg_control += cmlen;
        msg->msg_controllen -= cmlen;
-       err = 0;
-out:
-       return err;
+       return 0;
 }
 EXPORT_SYMBOL(put_cmsg);
 
@@ -328,6 +331,10 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
                return;
        }
 
+       /* no use for FD passing from kernel space callers */
+       if (WARN_ON_ONCE(!msg->msg_control_is_user))
+               return;
+
        for (i = 0; i < fdmax; i++) {
                err = __scm_install_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
                if (err)
index aa3fd61..8206047 100644 (file)
@@ -1492,7 +1492,8 @@ static int do_ip_getsockopt(struct sock *sk, int level, int optname,
                if (sk->sk_type != SOCK_STREAM)
                        return -ENOPROTOOPT;
 
-               msg.msg_control = (__force void *) optval;
+               msg.msg_control_is_user = true;
+               msg.msg_control_user = optval;
                msg.msg_controllen = len;
                msg.msg_flags = flags;
 
index 2dd739f..1c9a726 100644 (file)
@@ -924,14 +924,9 @@ EXPORT_SYMBOL(sock_recvmsg);
 int kernel_recvmsg(struct socket *sock, struct msghdr *msg,
                   struct kvec *vec, size_t num, size_t size, int flags)
 {
-       mm_segment_t oldfs = get_fs();
-       int result;
-
+       msg->msg_control_is_user = false;
        iov_iter_kvec(&msg->msg_iter, READ, vec, num, size);
-       set_fs(KERNEL_DS);
-       result = sock_recvmsg(sock, msg, flags);
-       set_fs(oldfs);
-       return result;
+       return sock_recvmsg(sock, msg, flags);
 }
 EXPORT_SYMBOL(kernel_recvmsg);
 
@@ -2239,7 +2234,8 @@ int __copy_msghdr_from_user(struct msghdr *kmsg,
        if (copy_from_user(&msg, umsg, sizeof(*umsg)))
                return -EFAULT;
 
-       kmsg->msg_control = (void __force *)msg.msg_control;
+       kmsg->msg_control_is_user = true;
+       kmsg->msg_control_user = msg.msg_control;
        kmsg->msg_controllen = msg.msg_controllen;
        kmsg->msg_flags = msg.msg_flags;
 
@@ -2331,16 +2327,10 @@ static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
                                goto out;
                }
                err = -EFAULT;
-               /*
-                * Careful! Before this, msg_sys->msg_control contains a user pointer.
-                * Afterwards, it will be a kernel pointer. Thus the compiler-assisted
-                * checking falls down on this.
-                */
-               if (copy_from_user(ctl_buf,
-                                  (void __user __force *)msg_sys->msg_control,
-                                  ctl_len))
+               if (copy_from_user(ctl_buf, msg_sys->msg_control_user, ctl_len))
                        goto out_freectl;
                msg_sys->msg_control = ctl_buf;
+               msg_sys->msg_control_is_user = false;
        }
        msg_sys->msg_flags = flags;