mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02: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
					
				
					 5 changed files with 49 additions and 40 deletions
				
			
		| 
						 | 
					@ -50,7 +50,17 @@ struct msghdr {
 | 
				
			||||||
	void		*msg_name;	/* ptr to socket address structure */
 | 
						void		*msg_name;	/* ptr to socket address structure */
 | 
				
			||||||
	int		msg_namelen;	/* size of socket address structure */
 | 
						int		msg_namelen;	/* size of socket address structure */
 | 
				
			||||||
	struct iov_iter	msg_iter;	/* data */
 | 
						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 */
 | 
						__kernel_size_t	msg_controllen;	/* ancillary data buffer length */
 | 
				
			||||||
	unsigned int	msg_flags;	/* flags on received message */
 | 
						unsigned int	msg_flags;	/* flags on received message */
 | 
				
			||||||
	struct kiocb	*msg_iocb;	/* ptr to iocb for async requests */
 | 
						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))
 | 
						if (kmsg->msg_namelen > sizeof(struct sockaddr_storage))
 | 
				
			||||||
		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;
 | 
						kmsg->msg_controllen = msg.msg_controllen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (save_addr)
 | 
						if (save_addr)
 | 
				
			||||||
| 
						 | 
					@ -121,7 +122,7 @@ int get_compat_msghdr(struct msghdr *kmsg,
 | 
				
			||||||
	((ucmlen) >= sizeof(struct compat_cmsghdr) && \
 | 
						((ucmlen) >= sizeof(struct compat_cmsghdr) && \
 | 
				
			||||||
	 (ucmlen) <= (unsigned long) \
 | 
						 (ucmlen) <= (unsigned long) \
 | 
				
			||||||
	 ((mhdr)->msg_controllen - \
 | 
						 ((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,
 | 
					static inline struct compat_cmsghdr __user *cmsg_compat_nxthdr(struct msghdr *msg,
 | 
				
			||||||
		struct compat_cmsghdr __user *cmsg, int cmsg_len)
 | 
							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)
 | 
					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 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);
 | 
							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;
 | 
							msg->msg_flags |= MSG_CTRUNC;
 | 
				
			||||||
		return 0; /* XXX: return error? check spec. */
 | 
							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;
 | 
							msg->msg_flags |= MSG_CTRUNC;
 | 
				
			||||||
		cmlen = msg->msg_controllen;
 | 
							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_level = level;
 | 
				
			||||||
		cmhdr.cmsg_type = type;
 | 
							cmhdr.cmsg_type = type;
 | 
				
			||||||
		cmhdr.cmsg_len = cmlen;
 | 
							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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = -EFAULT;
 | 
							cm->cmsg_level = level;
 | 
				
			||||||
	if (copy_to_user(cm, &cmhdr, sizeof cmhdr))
 | 
							cm->cmsg_type = type;
 | 
				
			||||||
		goto out;
 | 
							cm->cmsg_len = cmlen;
 | 
				
			||||||
	if (copy_to_user(CMSG_USER_DATA(cm), data, cmlen - sizeof(*cm)))
 | 
							memcpy(CMSG_DATA(cm), data, cmlen - sizeof(*cm));
 | 
				
			||||||
		goto out;
 | 
						}
 | 
				
			||||||
	cmlen = CMSG_SPACE(len);
 | 
					
 | 
				
			||||||
	if (msg->msg_controllen < cmlen)
 | 
						cmlen = min(CMSG_SPACE(len), msg->msg_controllen);
 | 
				
			||||||
		cmlen = msg->msg_controllen;
 | 
					 | 
				
			||||||
	msg->msg_control += cmlen;
 | 
						msg->msg_control += cmlen;
 | 
				
			||||||
	msg->msg_controllen -= cmlen;
 | 
						msg->msg_controllen -= cmlen;
 | 
				
			||||||
	err = 0;
 | 
						return 0;
 | 
				
			||||||
out:
 | 
					 | 
				
			||||||
	return err;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL(put_cmsg);
 | 
					EXPORT_SYMBOL(put_cmsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -328,6 +331,10 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
 | 
				
			||||||
		return;
 | 
							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++) {
 | 
						for (i = 0; i < fdmax; i++) {
 | 
				
			||||||
		err = __scm_install_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
 | 
							err = __scm_install_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
 | 
				
			||||||
		if (err)
 | 
							if (err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1492,7 +1492,8 @@ static int do_ip_getsockopt(struct sock *sk, int level, int optname,
 | 
				
			||||||
		if (sk->sk_type != SOCK_STREAM)
 | 
							if (sk->sk_type != SOCK_STREAM)
 | 
				
			||||||
			return -ENOPROTOOPT;
 | 
								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_controllen = len;
 | 
				
			||||||
		msg.msg_flags = flags;
 | 
							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,
 | 
					int kernel_recvmsg(struct socket *sock, struct msghdr *msg,
 | 
				
			||||||
		   struct kvec *vec, size_t num, size_t size, int flags)
 | 
							   struct kvec *vec, size_t num, size_t size, int flags)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	mm_segment_t oldfs = get_fs();
 | 
						msg->msg_control_is_user = false;
 | 
				
			||||||
	int result;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	iov_iter_kvec(&msg->msg_iter, READ, vec, num, size);
 | 
						iov_iter_kvec(&msg->msg_iter, READ, vec, num, size);
 | 
				
			||||||
	set_fs(KERNEL_DS);
 | 
						return sock_recvmsg(sock, msg, flags);
 | 
				
			||||||
	result = sock_recvmsg(sock, msg, flags);
 | 
					 | 
				
			||||||
	set_fs(oldfs);
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL(kernel_recvmsg);
 | 
					EXPORT_SYMBOL(kernel_recvmsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2239,7 +2234,8 @@ int __copy_msghdr_from_user(struct msghdr *kmsg,
 | 
				
			||||||
	if (copy_from_user(&msg, umsg, sizeof(*umsg)))
 | 
						if (copy_from_user(&msg, umsg, sizeof(*umsg)))
 | 
				
			||||||
		return -EFAULT;
 | 
							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_controllen = msg.msg_controllen;
 | 
				
			||||||
	kmsg->msg_flags = msg.msg_flags;
 | 
						kmsg->msg_flags = msg.msg_flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2331,16 +2327,10 @@ static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
 | 
				
			||||||
				goto out;
 | 
									goto out;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		err = -EFAULT;
 | 
							err = -EFAULT;
 | 
				
			||||||
		/*
 | 
							if (copy_from_user(ctl_buf, msg_sys->msg_control_user, ctl_len))
 | 
				
			||||||
		 * 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))
 | 
					 | 
				
			||||||
			goto out_freectl;
 | 
								goto out_freectl;
 | 
				
			||||||
		msg_sys->msg_control = ctl_buf;
 | 
							msg_sys->msg_control = ctl_buf;
 | 
				
			||||||
 | 
							msg_sys->msg_control_is_user = false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	msg_sys->msg_flags = flags;
 | 
						msg_sys->msg_flags = flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue