forked from mirrors/linux
		
	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; | ||||||
| 	} | 	} | ||||||
| 	cmhdr.cmsg_level = level; |  | ||||||
| 	cmhdr.cmsg_type = type; |  | ||||||
| 	cmhdr.cmsg_len = cmlen; |  | ||||||
| 
 | 
 | ||||||
| 	err = -EFAULT; | 	if (msg->msg_control_is_user) { | ||||||
| 	if (copy_to_user(cm, &cmhdr, sizeof cmhdr)) | 		struct cmsghdr __user *cm = msg->msg_control_user; | ||||||
| 		goto out; | 		struct cmsghdr cmhdr; | ||||||
| 	if (copy_to_user(CMSG_USER_DATA(cm), data, cmlen - sizeof(*cm))) | 
 | ||||||
| 		goto out; | 		cmhdr.cmsg_level = level; | ||||||
| 	cmlen = CMSG_SPACE(len); | 		cmhdr.cmsg_type = type; | ||||||
| 	if (msg->msg_controllen < cmlen) | 		cmhdr.cmsg_len = cmlen; | ||||||
| 		cmlen = msg->msg_controllen; | 		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_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
	
	 Christoph Hellwig
						Christoph Hellwig