diff options
Diffstat (limited to 'net/socket.c')
| -rw-r--r-- | net/socket.c | 843 | 
1 files changed, 790 insertions, 53 deletions
diff --git a/net/socket.c b/net/socket.c index 75655365b5fd..b94c3dd71015 100644 --- a/net/socket.c +++ b/net/socket.c @@ -97,6 +97,12 @@  #include <net/sock.h>  #include <linux/netfilter.h> +#include <linux/if_tun.h> +#include <linux/ipv6_route.h> +#include <linux/route.h> +#include <linux/sockios.h> +#include <linux/atalk.h> +  static int sock_no_open(struct inode *irrelevant, struct file *dontcare);  static ssize_t sock_aio_read(struct kiocb *iocb, const struct iovec *iov,  			 unsigned long nr_segs, loff_t pos); @@ -668,10 +674,24 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,  EXPORT_SYMBOL_GPL(__sock_recv_timestamp); -static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock, -				 struct msghdr *msg, size_t size, int flags) +inline void sock_recv_drops(struct msghdr *msg, struct sock *sk, struct sk_buff *skb) +{ +	if (sock_flag(sk, SOCK_RXQ_OVFL) && skb && skb->dropcount) +		put_cmsg(msg, SOL_SOCKET, SO_RXQ_OVFL, +			sizeof(__u32), &skb->dropcount); +} + +void sock_recv_ts_and_drops(struct msghdr *msg, struct sock *sk, +	struct sk_buff *skb) +{ +	sock_recv_timestamp(msg, sk, skb); +	sock_recv_drops(msg, sk, skb); +} +EXPORT_SYMBOL_GPL(sock_recv_ts_and_drops); + +static inline int __sock_recvmsg_nosec(struct kiocb *iocb, struct socket *sock, +				       struct msghdr *msg, size_t size, int flags)  { -	int err;  	struct sock_iocb *si = kiocb_to_siocb(iocb);  	si->sock = sock; @@ -680,13 +700,17 @@ static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock,  	si->size = size;  	si->flags = flags; -	err = security_socket_recvmsg(sock, msg, size, flags); -	if (err) -		return err; -  	return sock->ops->recvmsg(iocb, sock, msg, size, flags);  } +static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock, +				 struct msghdr *msg, size_t size, int flags) +{ +	int err = security_socket_recvmsg(sock, msg, size, flags); + +	return err ?: __sock_recvmsg_nosec(iocb, sock, msg, size, flags); +} +  int sock_recvmsg(struct socket *sock, struct msghdr *msg,  		 size_t size, int flags)  { @@ -702,6 +726,21 @@ int sock_recvmsg(struct socket *sock, struct msghdr *msg,  	return ret;  } +static int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg, +			      size_t size, int flags) +{ +	struct kiocb iocb; +	struct sock_iocb siocb; +	int ret; + +	init_sync_kiocb(&iocb, NULL); +	iocb.private = &siocb; +	ret = __sock_recvmsg_nosec(&iocb, sock, msg, size, flags); +	if (-EIOCBQUEUED == ret) +		ret = wait_on_sync_kiocb(&iocb); +	return ret; +} +  int kernel_recvmsg(struct socket *sock, struct msghdr *msg,  		   struct kvec *vec, size_t num, size_t size, int flags)  { @@ -886,6 +925,24 @@ void dlci_ioctl_set(int (*hook) (unsigned int, void __user *))  EXPORT_SYMBOL(dlci_ioctl_set); +static long sock_do_ioctl(struct net *net, struct socket *sock, +				 unsigned int cmd, unsigned long arg) +{ +	int err; +	void __user *argp = (void __user *)arg; + +	err = sock->ops->ioctl(sock, cmd, arg); + +	/* +	 * If this ioctl is unknown try to hand it down +	 * to the NIC driver. +	 */ +	if (err == -ENOIOCTLCMD) +		err = dev_ioctl(net, cmd, argp); + +	return err; +} +  /*   *	With an ioctl, arg may well be a user mode pointer, but we don't know   *	what to do with it - that's up to the protocol still. @@ -905,11 +962,11 @@ static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)  	if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {  		err = dev_ioctl(net, cmd, argp);  	} else -#ifdef CONFIG_WIRELESS_EXT +#ifdef CONFIG_WEXT_CORE  	if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {  		err = dev_ioctl(net, cmd, argp);  	} else -#endif				/* CONFIG_WIRELESS_EXT */ +#endif  		switch (cmd) {  		case FIOSETOWN:  		case SIOCSPGRP: @@ -959,14 +1016,7 @@ static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)  			mutex_unlock(&dlci_ioctl_mutex);  			break;  		default: -			err = sock->ops->ioctl(sock, cmd, arg); - -			/* -			 * If this ioctl is unknown try to hand it down -			 * to the NIC driver. -			 */ -			if (err == -ENOIOCTLCMD) -				err = dev_ioctl(net, cmd, argp); +			err = sock_do_ioctl(net, sock, cmd, arg);  			break;  		}  	return err; @@ -1100,11 +1150,14 @@ static int sock_fasync(int fd, struct file *filp, int on)  		fna->fa_next = sock->fasync_list;  		write_lock_bh(&sk->sk_callback_lock);  		sock->fasync_list = fna; +		sock_set_flag(sk, SOCK_FASYNC);  		write_unlock_bh(&sk->sk_callback_lock);  	} else {  		if (fa != NULL) {  			write_lock_bh(&sk->sk_callback_lock);  			*prev = fa->fa_next; +			if (!sock->fasync_list) +				sock_reset_flag(sk, SOCK_FASYNC);  			write_unlock_bh(&sk->sk_callback_lock);  			kfree(fa);  		} @@ -1216,7 +1269,7 @@ static int __sock_create(struct net *net, int family, int type, int protocol,  	/* Now protected by module ref count */  	rcu_read_unlock(); -	err = pf->create(net, sock, protocol); +	err = pf->create(net, sock, protocol, kern);  	if (err < 0)  		goto out_module_put; @@ -1965,22 +2018,15 @@ out:  	return err;  } -/* - *	BSD recvmsg interface - */ - -SYSCALL_DEFINE3(recvmsg, int, fd, struct msghdr __user *, msg, -		unsigned int, flags) +static int __sys_recvmsg(struct socket *sock, struct msghdr __user *msg, +			 struct msghdr *msg_sys, unsigned flags, int nosec)  {  	struct compat_msghdr __user *msg_compat =  	    (struct compat_msghdr __user *)msg; -	struct socket *sock;  	struct iovec iovstack[UIO_FASTIOV];  	struct iovec *iov = iovstack; -	struct msghdr msg_sys;  	unsigned long cmsg_ptr;  	int err, iov_size, total_len, len; -	int fput_needed;  	/* kernel mode address */  	struct sockaddr_storage addr; @@ -1990,27 +2036,23 @@ SYSCALL_DEFINE3(recvmsg, int, fd, struct msghdr __user *, msg,  	int __user *uaddr_len;  	if (MSG_CMSG_COMPAT & flags) { -		if (get_compat_msghdr(&msg_sys, msg_compat)) +		if (get_compat_msghdr(msg_sys, msg_compat))  			return -EFAULT;  	} -	else if (copy_from_user(&msg_sys, msg, sizeof(struct msghdr))) +	else if (copy_from_user(msg_sys, msg, sizeof(struct msghdr)))  		return -EFAULT; -	sock = sockfd_lookup_light(fd, &err, &fput_needed); -	if (!sock) -		goto out; -  	err = -EMSGSIZE; -	if (msg_sys.msg_iovlen > UIO_MAXIOV) -		goto out_put; +	if (msg_sys->msg_iovlen > UIO_MAXIOV) +		goto out;  	/* Check whether to allocate the iovec area */  	err = -ENOMEM; -	iov_size = msg_sys.msg_iovlen * sizeof(struct iovec); -	if (msg_sys.msg_iovlen > UIO_FASTIOV) { +	iov_size = msg_sys->msg_iovlen * sizeof(struct iovec); +	if (msg_sys->msg_iovlen > UIO_FASTIOV) {  		iov = sock_kmalloc(sock->sk, iov_size, GFP_KERNEL);  		if (!iov) -			goto out_put; +			goto out;  	}  	/* @@ -2018,46 +2060,47 @@ SYSCALL_DEFINE3(recvmsg, int, fd, struct msghdr __user *, msg,  	 *      kernel msghdr to use the kernel address space)  	 */ -	uaddr = (__force void __user *)msg_sys.msg_name; +	uaddr = (__force void __user *)msg_sys->msg_name;  	uaddr_len = COMPAT_NAMELEN(msg);  	if (MSG_CMSG_COMPAT & flags) { -		err = verify_compat_iovec(&msg_sys, iov, +		err = verify_compat_iovec(msg_sys, iov,  					  (struct sockaddr *)&addr,  					  VERIFY_WRITE);  	} else -		err = verify_iovec(&msg_sys, iov, +		err = verify_iovec(msg_sys, iov,  				   (struct sockaddr *)&addr,  				   VERIFY_WRITE);  	if (err < 0)  		goto out_freeiov;  	total_len = err; -	cmsg_ptr = (unsigned long)msg_sys.msg_control; -	msg_sys.msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT); +	cmsg_ptr = (unsigned long)msg_sys->msg_control; +	msg_sys->msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);  	if (sock->file->f_flags & O_NONBLOCK)  		flags |= MSG_DONTWAIT; -	err = sock_recvmsg(sock, &msg_sys, total_len, flags); +	err = (nosec ? sock_recvmsg_nosec : sock_recvmsg)(sock, msg_sys, +							  total_len, flags);  	if (err < 0)  		goto out_freeiov;  	len = err;  	if (uaddr != NULL) {  		err = move_addr_to_user((struct sockaddr *)&addr, -					msg_sys.msg_namelen, uaddr, +					msg_sys->msg_namelen, uaddr,  					uaddr_len);  		if (err < 0)  			goto out_freeiov;  	} -	err = __put_user((msg_sys.msg_flags & ~MSG_CMSG_COMPAT), +	err = __put_user((msg_sys->msg_flags & ~MSG_CMSG_COMPAT),  			 COMPAT_FLAGS(msg));  	if (err)  		goto out_freeiov;  	if (MSG_CMSG_COMPAT & flags) -		err = __put_user((unsigned long)msg_sys.msg_control - cmsg_ptr, +		err = __put_user((unsigned long)msg_sys->msg_control - cmsg_ptr,  				 &msg_compat->msg_controllen);  	else -		err = __put_user((unsigned long)msg_sys.msg_control - cmsg_ptr, +		err = __put_user((unsigned long)msg_sys->msg_control - cmsg_ptr,  				 &msg->msg_controllen);  	if (err)  		goto out_freeiov; @@ -2066,21 +2109,162 @@ SYSCALL_DEFINE3(recvmsg, int, fd, struct msghdr __user *, msg,  out_freeiov:  	if (iov != iovstack)  		sock_kfree_s(sock->sk, iov, iov_size); -out_put: +out: +	return err; +} + +/* + *	BSD recvmsg interface + */ + +SYSCALL_DEFINE3(recvmsg, int, fd, struct msghdr __user *, msg, +		unsigned int, flags) +{ +	int fput_needed, err; +	struct msghdr msg_sys; +	struct socket *sock = sockfd_lookup_light(fd, &err, &fput_needed); + +	if (!sock) +		goto out; + +	err = __sys_recvmsg(sock, msg, &msg_sys, flags, 0); +  	fput_light(sock->file, fput_needed);  out:  	return err;  } -#ifdef __ARCH_WANT_SYS_SOCKETCALL +/* + *     Linux recvmmsg interface + */ + +int __sys_recvmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen, +		   unsigned int flags, struct timespec *timeout) +{ +	int fput_needed, err, datagrams; +	struct socket *sock; +	struct mmsghdr __user *entry; +	struct compat_mmsghdr __user *compat_entry; +	struct msghdr msg_sys; +	struct timespec end_time; + +	if (timeout && +	    poll_select_set_timeout(&end_time, timeout->tv_sec, +				    timeout->tv_nsec)) +		return -EINVAL; +	datagrams = 0; + +	sock = sockfd_lookup_light(fd, &err, &fput_needed); +	if (!sock) +		return err; + +	err = sock_error(sock->sk); +	if (err) +		goto out_put; + +	entry = mmsg; +	compat_entry = (struct compat_mmsghdr __user *)mmsg; + +	while (datagrams < vlen) { +		/* +		 * No need to ask LSM for more than the first datagram. +		 */ +		if (MSG_CMSG_COMPAT & flags) { +			err = __sys_recvmsg(sock, (struct msghdr __user *)compat_entry, +					    &msg_sys, flags, datagrams); +			if (err < 0) +				break; +			err = __put_user(err, &compat_entry->msg_len); +			++compat_entry; +		} else { +			err = __sys_recvmsg(sock, (struct msghdr __user *)entry, +					    &msg_sys, flags, datagrams); +			if (err < 0) +				break; +			err = put_user(err, &entry->msg_len); +			++entry; +		} + +		if (err) +			break; +		++datagrams; + +		if (timeout) { +			ktime_get_ts(timeout); +			*timeout = timespec_sub(end_time, *timeout); +			if (timeout->tv_sec < 0) { +				timeout->tv_sec = timeout->tv_nsec = 0; +				break; +			} + +			/* Timeout, return less than vlen datagrams */ +			if (timeout->tv_nsec == 0 && timeout->tv_sec == 0) +				break; +		} + +		/* Out of band data, return right away */ +		if (msg_sys.msg_flags & MSG_OOB) +			break; +	} + +out_put: +	fput_light(sock->file, fput_needed); + +	if (err == 0) +		return datagrams; + +	if (datagrams != 0) { +		/* +		 * We may return less entries than requested (vlen) if the +		 * sock is non block and there aren't enough datagrams... +		 */ +		if (err != -EAGAIN) { +			/* +			 * ... or  if recvmsg returns an error after we +			 * received some datagrams, where we record the +			 * error to return on the next call or if the +			 * app asks about it using getsockopt(SO_ERROR). +			 */ +			sock->sk->sk_err = -err; +		} + +		return datagrams; +	} + +	return err; +} + +SYSCALL_DEFINE5(recvmmsg, int, fd, struct mmsghdr __user *, mmsg, +		unsigned int, vlen, unsigned int, flags, +		struct timespec __user *, timeout) +{ +	int datagrams; +	struct timespec timeout_sys; + +	if (!timeout) +		return __sys_recvmmsg(fd, mmsg, vlen, flags, NULL); + +	if (copy_from_user(&timeout_sys, timeout, sizeof(timeout_sys))) +		return -EFAULT; + +	datagrams = __sys_recvmmsg(fd, mmsg, vlen, flags, &timeout_sys); + +	if (datagrams > 0 && +	    copy_to_user(timeout, &timeout_sys, sizeof(timeout_sys))) +		datagrams = -EFAULT; + +	return datagrams; +} + +#ifdef __ARCH_WANT_SYS_SOCKETCALL  /* Argument list sizes for sys_socketcall */  #define AL(x) ((x) * sizeof(unsigned long)) -static const unsigned char nargs[19]={ +static const unsigned char nargs[20] = {  	AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),  	AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),  	AL(6),AL(2),AL(5),AL(5),AL(3),AL(3), -	AL(4) +	AL(4),AL(5)  };  #undef AL @@ -2100,7 +2284,7 @@ SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)  	int err;  	unsigned int len; -	if (call < 1 || call > SYS_ACCEPT4) +	if (call < 1 || call > SYS_RECVMMSG)  		return -EINVAL;  	len = nargs[call]; @@ -2178,6 +2362,10 @@ SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)  	case SYS_RECVMSG:  		err = sys_recvmsg(a0, (struct msghdr __user *)a1, a[2]);  		break; +	case SYS_RECVMMSG: +		err = sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], +				   (struct timespec __user *)a[4]); +		break;  	case SYS_ACCEPT4:  		err = sys_accept4(a0, (struct sockaddr __user *)a1,  				  (int __user *)a[2], a[3]); @@ -2300,6 +2488,552 @@ void socket_seq_show(struct seq_file *seq)  #endif				/* CONFIG_PROC_FS */  #ifdef CONFIG_COMPAT +static int do_siocgstamp(struct net *net, struct socket *sock, +			 unsigned int cmd, struct compat_timeval __user *up) +{ +	mm_segment_t old_fs = get_fs(); +	struct timeval ktv; +	int err; + +	set_fs(KERNEL_DS); +	err = sock_do_ioctl(net, sock, cmd, (unsigned long)&ktv); +	set_fs(old_fs); +	if (!err) { +		err = put_user(ktv.tv_sec, &up->tv_sec); +		err |= __put_user(ktv.tv_usec, &up->tv_usec); +	} +	return err; +} + +static int do_siocgstampns(struct net *net, struct socket *sock, +			 unsigned int cmd, struct compat_timespec __user *up) +{ +	mm_segment_t old_fs = get_fs(); +	struct timespec kts; +	int err; + +	set_fs(KERNEL_DS); +	err = sock_do_ioctl(net, sock, cmd, (unsigned long)&kts); +	set_fs(old_fs); +	if (!err) { +		err = put_user(kts.tv_sec, &up->tv_sec); +		err |= __put_user(kts.tv_nsec, &up->tv_nsec); +	} +	return err; +} + +static int dev_ifname32(struct net *net, struct compat_ifreq __user *uifr32) +{ +	struct ifreq __user *uifr; +	int err; + +	uifr = compat_alloc_user_space(sizeof(struct ifreq)); +	if (copy_in_user(uifr, uifr32, sizeof(struct compat_ifreq))) +		return -EFAULT; + +	err = dev_ioctl(net, SIOCGIFNAME, uifr); +	if (err) +		return err; + +	if (copy_in_user(uifr32, uifr, sizeof(struct compat_ifreq))) +		return -EFAULT; + +	return 0; +} + +static int dev_ifconf(struct net *net, struct compat_ifconf __user *uifc32) +{ +	struct compat_ifconf ifc32; +	struct ifconf ifc; +	struct ifconf __user *uifc; +	struct compat_ifreq __user *ifr32; +	struct ifreq __user *ifr; +	unsigned int i, j; +	int err; + +	if (copy_from_user(&ifc32, uifc32, sizeof(struct compat_ifconf))) +		return -EFAULT; + +	if (ifc32.ifcbuf == 0) { +		ifc32.ifc_len = 0; +		ifc.ifc_len = 0; +		ifc.ifc_req = NULL; +		uifc = compat_alloc_user_space(sizeof(struct ifconf)); +	} else { +		size_t len =((ifc32.ifc_len / sizeof (struct compat_ifreq)) + 1) * +			sizeof (struct ifreq); +		uifc = compat_alloc_user_space(sizeof(struct ifconf) + len); +		ifc.ifc_len = len; +		ifr = ifc.ifc_req = (void __user *)(uifc + 1); +		ifr32 = compat_ptr(ifc32.ifcbuf); +		for (i = 0; i < ifc32.ifc_len; i += sizeof (struct compat_ifreq)) { +			if (copy_in_user(ifr, ifr32, sizeof(struct compat_ifreq))) +				return -EFAULT; +			ifr++; +			ifr32++; +		} +	} +	if (copy_to_user(uifc, &ifc, sizeof(struct ifconf))) +		return -EFAULT; + +	err = dev_ioctl(net, SIOCGIFCONF, uifc); +	if (err) +		return err; + +	if (copy_from_user(&ifc, uifc, sizeof(struct ifconf))) +		return -EFAULT; + +	ifr = ifc.ifc_req; +	ifr32 = compat_ptr(ifc32.ifcbuf); +	for (i = 0, j = 0; +             i + sizeof (struct compat_ifreq) <= ifc32.ifc_len && j < ifc.ifc_len; +	     i += sizeof (struct compat_ifreq), j += sizeof (struct ifreq)) { +		if (copy_in_user(ifr32, ifr, sizeof (struct compat_ifreq))) +			return -EFAULT; +		ifr32++; +		ifr++; +	} + +	if (ifc32.ifcbuf == 0) { +		/* Translate from 64-bit structure multiple to +		 * a 32-bit one. +		 */ +		i = ifc.ifc_len; +		i = ((i / sizeof(struct ifreq)) * sizeof(struct compat_ifreq)); +		ifc32.ifc_len = i; +	} else { +		ifc32.ifc_len = i; +	} +	if (copy_to_user(uifc32, &ifc32, sizeof(struct compat_ifconf))) +		return -EFAULT; + +	return 0; +} + +static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32) +{ +	struct ifreq __user *ifr; +	u32 data; +	void __user *datap; + +	ifr = compat_alloc_user_space(sizeof(*ifr)); + +	if (copy_in_user(&ifr->ifr_name, &ifr32->ifr_name, IFNAMSIZ)) +		return -EFAULT; + +	if (get_user(data, &ifr32->ifr_ifru.ifru_data)) +		return -EFAULT; + +	datap = compat_ptr(data); +	if (put_user(datap, &ifr->ifr_ifru.ifru_data)) +		return -EFAULT; + +	return dev_ioctl(net, SIOCETHTOOL, ifr); +} + +static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32) +{ +	void __user *uptr; +	compat_uptr_t uptr32; +	struct ifreq __user *uifr; + +	uifr = compat_alloc_user_space(sizeof (*uifr)); +	if (copy_in_user(uifr, uifr32, sizeof(struct compat_ifreq))) +		return -EFAULT; + +	if (get_user(uptr32, &uifr32->ifr_settings.ifs_ifsu)) +		return -EFAULT; + +	uptr = compat_ptr(uptr32); + +	if (put_user(uptr, &uifr->ifr_settings.ifs_ifsu.raw_hdlc)) +		return -EFAULT; + +	return dev_ioctl(net, SIOCWANDEV, uifr); +} + +static int bond_ioctl(struct net *net, unsigned int cmd, +			 struct compat_ifreq __user *ifr32) +{ +	struct ifreq kifr; +	struct ifreq __user *uifr; +	mm_segment_t old_fs; +	int err; +	u32 data; +	void __user *datap; + +	switch (cmd) { +	case SIOCBONDENSLAVE: +	case SIOCBONDRELEASE: +	case SIOCBONDSETHWADDR: +	case SIOCBONDCHANGEACTIVE: +		if (copy_from_user(&kifr, ifr32, sizeof(struct compat_ifreq))) +			return -EFAULT; + +		old_fs = get_fs(); +		set_fs (KERNEL_DS); +		err = dev_ioctl(net, cmd, &kifr); +		set_fs (old_fs); + +		return err; +	case SIOCBONDSLAVEINFOQUERY: +	case SIOCBONDINFOQUERY: +		uifr = compat_alloc_user_space(sizeof(*uifr)); +		if (copy_in_user(&uifr->ifr_name, &ifr32->ifr_name, IFNAMSIZ)) +			return -EFAULT; + +		if (get_user(data, &ifr32->ifr_ifru.ifru_data)) +			return -EFAULT; + +		datap = compat_ptr(data); +		if (put_user(datap, &uifr->ifr_ifru.ifru_data)) +			return -EFAULT; + +		return dev_ioctl(net, cmd, uifr); +	default: +		return -EINVAL; +	}; +} + +static int siocdevprivate_ioctl(struct net *net, unsigned int cmd, +				 struct compat_ifreq __user *u_ifreq32) +{ +	struct ifreq __user *u_ifreq64; +	char tmp_buf[IFNAMSIZ]; +	void __user *data64; +	u32 data32; + +	if (copy_from_user(&tmp_buf[0], &(u_ifreq32->ifr_ifrn.ifrn_name[0]), +			   IFNAMSIZ)) +		return -EFAULT; +	if (__get_user(data32, &u_ifreq32->ifr_ifru.ifru_data)) +		return -EFAULT; +	data64 = compat_ptr(data32); + +	u_ifreq64 = compat_alloc_user_space(sizeof(*u_ifreq64)); + +	/* Don't check these user accesses, just let that get trapped +	 * in the ioctl handler instead. +	 */ +	if (copy_to_user(&u_ifreq64->ifr_ifrn.ifrn_name[0], &tmp_buf[0], +			 IFNAMSIZ)) +		return -EFAULT; +	if (__put_user(data64, &u_ifreq64->ifr_ifru.ifru_data)) +		return -EFAULT; + +	return dev_ioctl(net, cmd, u_ifreq64); +} + +static int dev_ifsioc(struct net *net, struct socket *sock, +			 unsigned int cmd, struct compat_ifreq __user *uifr32) +{ +	struct ifreq __user *uifr; +	int err; + +	uifr = compat_alloc_user_space(sizeof(*uifr)); +	if (copy_in_user(uifr, uifr32, sizeof(*uifr32))) +		return -EFAULT; + +	err = sock_do_ioctl(net, sock, cmd, (unsigned long)uifr); + +	if (!err) { +		switch (cmd) { +		case SIOCGIFFLAGS: +		case SIOCGIFMETRIC: +		case SIOCGIFMTU: +		case SIOCGIFMEM: +		case SIOCGIFHWADDR: +		case SIOCGIFINDEX: +		case SIOCGIFADDR: +		case SIOCGIFBRDADDR: +		case SIOCGIFDSTADDR: +		case SIOCGIFNETMASK: +		case SIOCGIFPFLAGS: +		case SIOCGIFTXQLEN: +		case SIOCGMIIPHY: +		case SIOCGMIIREG: +			if (copy_in_user(uifr32, uifr, sizeof(*uifr32))) +				err = -EFAULT; +			break; +		} +	} +	return err; +} + +static int compat_sioc_ifmap(struct net *net, unsigned int cmd, +			struct compat_ifreq __user *uifr32) +{ +	struct ifreq ifr; +	struct compat_ifmap __user *uifmap32; +	mm_segment_t old_fs; +	int err; + +	uifmap32 = &uifr32->ifr_ifru.ifru_map; +	err = copy_from_user(&ifr, uifr32, sizeof(ifr.ifr_name)); +	err |= __get_user(ifr.ifr_map.mem_start, &uifmap32->mem_start); +	err |= __get_user(ifr.ifr_map.mem_end, &uifmap32->mem_end); +	err |= __get_user(ifr.ifr_map.base_addr, &uifmap32->base_addr); +	err |= __get_user(ifr.ifr_map.irq, &uifmap32->irq); +	err |= __get_user(ifr.ifr_map.dma, &uifmap32->dma); +	err |= __get_user(ifr.ifr_map.port, &uifmap32->port); +	if (err) +		return -EFAULT; + +	old_fs = get_fs(); +	set_fs (KERNEL_DS); +	err = dev_ioctl(net, cmd, (void __user *)&ifr); +	set_fs (old_fs); + +	if (cmd == SIOCGIFMAP && !err) { +		err = copy_to_user(uifr32, &ifr, sizeof(ifr.ifr_name)); +		err |= __put_user(ifr.ifr_map.mem_start, &uifmap32->mem_start); +		err |= __put_user(ifr.ifr_map.mem_end, &uifmap32->mem_end); +		err |= __put_user(ifr.ifr_map.base_addr, &uifmap32->base_addr); +		err |= __put_user(ifr.ifr_map.irq, &uifmap32->irq); +		err |= __put_user(ifr.ifr_map.dma, &uifmap32->dma); +		err |= __put_user(ifr.ifr_map.port, &uifmap32->port); +		if (err) +			err = -EFAULT; +	} +	return err; +} + +static int compat_siocshwtstamp(struct net *net, struct compat_ifreq __user *uifr32) +{ +	void __user *uptr; +	compat_uptr_t uptr32; +	struct ifreq __user *uifr; + +	uifr = compat_alloc_user_space(sizeof (*uifr)); +	if (copy_in_user(uifr, uifr32, sizeof(struct compat_ifreq))) +		return -EFAULT; + +	if (get_user(uptr32, &uifr32->ifr_data)) +		return -EFAULT; + +	uptr = compat_ptr(uptr32); + +	if (put_user(uptr, &uifr->ifr_data)) +		return -EFAULT; + +	return dev_ioctl(net, SIOCSHWTSTAMP, uifr); +} + +struct rtentry32 { +	u32   		rt_pad1; +	struct sockaddr rt_dst;         /* target address               */ +	struct sockaddr rt_gateway;     /* gateway addr (RTF_GATEWAY)   */ +	struct sockaddr rt_genmask;     /* target network mask (IP)     */ +	unsigned short  rt_flags; +	short           rt_pad2; +	u32   		rt_pad3; +	unsigned char   rt_tos; +	unsigned char   rt_class; +	short           rt_pad4; +	short           rt_metric;      /* +1 for binary compatibility! */ +	/* char * */ u32 rt_dev;        /* forcing the device at add    */ +	u32   		rt_mtu;         /* per route MTU/Window         */ +	u32   		rt_window;      /* Window clamping              */ +	unsigned short  rt_irtt;        /* Initial RTT                  */ +}; + +struct in6_rtmsg32 { +	struct in6_addr		rtmsg_dst; +	struct in6_addr		rtmsg_src; +	struct in6_addr		rtmsg_gateway; +	u32			rtmsg_type; +	u16			rtmsg_dst_len; +	u16			rtmsg_src_len; +	u32			rtmsg_metric; +	u32			rtmsg_info; +	u32			rtmsg_flags; +	s32			rtmsg_ifindex; +}; + +static int routing_ioctl(struct net *net, struct socket *sock, +			 unsigned int cmd, void __user *argp) +{ +	int ret; +	void *r = NULL; +	struct in6_rtmsg r6; +	struct rtentry r4; +	char devname[16]; +	u32 rtdev; +	mm_segment_t old_fs = get_fs(); + +	if (sock && sock->sk && sock->sk->sk_family == AF_INET6) { /* ipv6 */ +		struct in6_rtmsg32 __user *ur6 = argp; +		ret = copy_from_user (&r6.rtmsg_dst, &(ur6->rtmsg_dst), +			3 * sizeof(struct in6_addr)); +		ret |= __get_user (r6.rtmsg_type, &(ur6->rtmsg_type)); +		ret |= __get_user (r6.rtmsg_dst_len, &(ur6->rtmsg_dst_len)); +		ret |= __get_user (r6.rtmsg_src_len, &(ur6->rtmsg_src_len)); +		ret |= __get_user (r6.rtmsg_metric, &(ur6->rtmsg_metric)); +		ret |= __get_user (r6.rtmsg_info, &(ur6->rtmsg_info)); +		ret |= __get_user (r6.rtmsg_flags, &(ur6->rtmsg_flags)); +		ret |= __get_user (r6.rtmsg_ifindex, &(ur6->rtmsg_ifindex)); + +		r = (void *) &r6; +	} else { /* ipv4 */ +		struct rtentry32 __user *ur4 = argp; +		ret = copy_from_user (&r4.rt_dst, &(ur4->rt_dst), +					3 * sizeof(struct sockaddr)); +		ret |= __get_user (r4.rt_flags, &(ur4->rt_flags)); +		ret |= __get_user (r4.rt_metric, &(ur4->rt_metric)); +		ret |= __get_user (r4.rt_mtu, &(ur4->rt_mtu)); +		ret |= __get_user (r4.rt_window, &(ur4->rt_window)); +		ret |= __get_user (r4.rt_irtt, &(ur4->rt_irtt)); +		ret |= __get_user (rtdev, &(ur4->rt_dev)); +		if (rtdev) { +			ret |= copy_from_user (devname, compat_ptr(rtdev), 15); +			r4.rt_dev = devname; devname[15] = 0; +		} else +			r4.rt_dev = NULL; + +		r = (void *) &r4; +	} + +	if (ret) { +		ret = -EFAULT; +		goto out; +	} + +	set_fs (KERNEL_DS); +	ret = sock_do_ioctl(net, sock, cmd, (unsigned long) r); +	set_fs (old_fs); + +out: +	return ret; +} + +/* Since old style bridge ioctl's endup using SIOCDEVPRIVATE + * for some operations; this forces use of the newer bridge-utils that + * use compatiable ioctls + */ +static int old_bridge_ioctl(compat_ulong_t __user *argp) +{ +	compat_ulong_t tmp; + +	if (get_user(tmp, argp)) +		return -EFAULT; +	if (tmp == BRCTL_GET_VERSION) +		return BRCTL_VERSION + 1; +	return -EINVAL; +} + +static int compat_sock_ioctl_trans(struct file *file, struct socket *sock, +			 unsigned int cmd, unsigned long arg) +{ +	void __user *argp = compat_ptr(arg); +	struct sock *sk = sock->sk; +	struct net *net = sock_net(sk); + +	if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) +		return siocdevprivate_ioctl(net, cmd, argp); + +	switch (cmd) { +	case SIOCSIFBR: +	case SIOCGIFBR: +		return old_bridge_ioctl(argp); +	case SIOCGIFNAME: +		return dev_ifname32(net, argp); +	case SIOCGIFCONF: +		return dev_ifconf(net, argp); +	case SIOCETHTOOL: +		return ethtool_ioctl(net, argp); +	case SIOCWANDEV: +		return compat_siocwandev(net, argp); +	case SIOCGIFMAP: +	case SIOCSIFMAP: +		return compat_sioc_ifmap(net, cmd, argp); +	case SIOCBONDENSLAVE: +	case SIOCBONDRELEASE: +	case SIOCBONDSETHWADDR: +	case SIOCBONDSLAVEINFOQUERY: +	case SIOCBONDINFOQUERY: +	case SIOCBONDCHANGEACTIVE: +		return bond_ioctl(net, cmd, argp); +	case SIOCADDRT: +	case SIOCDELRT: +		return routing_ioctl(net, sock, cmd, argp); +	case SIOCGSTAMP: +		return do_siocgstamp(net, sock, cmd, argp); +	case SIOCGSTAMPNS: +		return do_siocgstampns(net, sock, cmd, argp); +	case SIOCSHWTSTAMP: +		return compat_siocshwtstamp(net, argp); + +	case FIOSETOWN: +	case SIOCSPGRP: +	case FIOGETOWN: +	case SIOCGPGRP: +	case SIOCBRADDBR: +	case SIOCBRDELBR: +	case SIOCGIFVLAN: +	case SIOCSIFVLAN: +	case SIOCADDDLCI: +	case SIOCDELDLCI: +		return sock_ioctl(file, cmd, arg); + +	case SIOCGIFFLAGS: +	case SIOCSIFFLAGS: +	case SIOCGIFMETRIC: +	case SIOCSIFMETRIC: +	case SIOCGIFMTU: +	case SIOCSIFMTU: +	case SIOCGIFMEM: +	case SIOCSIFMEM: +	case SIOCGIFHWADDR: +	case SIOCSIFHWADDR: +	case SIOCADDMULTI: +	case SIOCDELMULTI: +	case SIOCGIFINDEX: +	case SIOCGIFADDR: +	case SIOCSIFADDR: +	case SIOCSIFHWBROADCAST: +	case SIOCDIFADDR: +	case SIOCGIFBRDADDR: +	case SIOCSIFBRDADDR: +	case SIOCGIFDSTADDR: +	case SIOCSIFDSTADDR: +	case SIOCGIFNETMASK: +	case SIOCSIFNETMASK: +	case SIOCSIFPFLAGS: +	case SIOCGIFPFLAGS: +	case SIOCGIFTXQLEN: +	case SIOCSIFTXQLEN: +	case SIOCBRADDIF: +	case SIOCBRDELIF: +	case SIOCSIFNAME: +	case SIOCGMIIPHY: +	case SIOCGMIIREG: +	case SIOCSMIIREG: +		return dev_ifsioc(net, sock, cmd, argp); + +	case SIOCSARP: +	case SIOCGARP: +	case SIOCDARP: +	case SIOCATMARK: +		return sock_do_ioctl(net, sock, cmd, arg); +	} + +	/* Prevent warning from compat_sys_ioctl, these always +	 * result in -EINVAL in the native case anyway. */ +	switch (cmd) { +	case SIOCRTMSG: +	case SIOCGIFCOUNT: +	case SIOCSRARP: +	case SIOCGRARP: +	case SIOCDRARP: +	case SIOCSIFLINK: +	case SIOCGIFSLAVE: +	case SIOCSIFSLAVE: +		return -EINVAL; +	} + +	return -ENOIOCTLCMD; +} +  static long compat_sock_ioctl(struct file *file, unsigned cmd,  			      unsigned long arg)  { @@ -2318,6 +3052,9 @@ static long compat_sock_ioctl(struct file *file, unsigned cmd,  	    (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST))  		ret = compat_wext_handle_ioctl(net, cmd, arg); +	if (ret == -ENOIOCTLCMD) +		ret = compat_sock_ioctl_trans(file, sock, cmd, arg); +  	return ret;  }  #endif  | 
