mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 17:25:38 +00:00
net: cleanly handle kernel vs user buffers for ->msg_control
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>
This commit is contained in:
parent
2618d530dd
commit
1f466e1f15
@ -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 */
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
22
net/socket.c
22
net/socket.c
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user