Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a2116ed2 authored by Arnd Bergmann's avatar Arnd Bergmann Committed by David S. Miller
Browse files

net/compat: fix dev_ifsioc emulation corner cases



Handling for SIOCSHWTSTAMP is broken on architectures
with a split user/kernel address space like s390,
because it passes a real user pointer while using
set_fs(KERNEL_DS).
A similar problem might arise the next time somebody
adds code to dev_ifsioc.

Split up dev_ifsioc into three separate functions for
SIOCSHWTSTAMP, SIOC*IFMAP and all other numbers so
we can get rid of set_fs in all potentially affected
cases.

Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
Cc: Patrick Ohly <patrick.ohly@intel.com>
Cc: David S. Miller <davem@davemloft.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent c029f444
Loading
Loading
Loading
Loading
+72 −45
Original line number Diff line number Diff line
@@ -2723,38 +2723,15 @@ static int siocdevprivate_ioctl(struct net *net, unsigned int cmd,
static int dev_ifsioc(struct net *net, struct socket *sock,
			 unsigned int cmd, struct compat_ifreq __user *uifr32)
{
	struct ifreq ifr;
	struct compat_ifmap __user *uifmap32;
	mm_segment_t old_fs;
	struct ifreq __user *uifr;
	int err;

	uifmap32 = &uifr32->ifr_ifru.ifru_map;
	switch (cmd) {
	case SIOCSIFMAP:
		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;
		break;
	case SIOCSHWTSTAMP:
		if (copy_from_user(&ifr, uifr32, sizeof(*uifr32)))
			return -EFAULT;
		ifr.ifr_data = compat_ptr(uifr32->ifr_ifru.ifru_data);
		break;
	default:
		if (copy_from_user(&ifr, uifr32, sizeof(*uifr32)))
	uifr = compat_alloc_user_space(sizeof(*uifr));
	if (copy_in_user(uifr, uifr32, sizeof(*uifr32)))
		return -EFAULT;
		break;
	}
	old_fs = get_fs();
	set_fs (KERNEL_DS);
	err = sock_do_ioctl(net, sock, cmd, (unsigned long)&ifr);
	set_fs (old_fs);

	err = sock_do_ioctl(net, sock, cmd, (unsigned long)uifr);

	if (!err) {
		switch (cmd) {
		case SIOCGIFFLAGS:
@@ -2771,10 +2748,39 @@ static int dev_ifsioc(struct net *net, struct socket *sock,
		case SIOCGIFTXQLEN:
		case SIOCGMIIPHY:
		case SIOCGMIIREG:
			if (copy_to_user(uifr32, &ifr, sizeof(*uifr32)))
				return -EFAULT;
			if (copy_in_user(uifr32, uifr, sizeof(*uifr32)))
				err = -EFAULT;
			break;
		case SIOCGIFMAP:
		}
	}
	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);
@@ -2784,12 +2790,31 @@ static int dev_ifsioc(struct net *net, struct socket *sock,
		err |= __put_user(ifr.ifr_map.port, &uifmap32->port);
		if (err)
			err = -EFAULT;
			break;
		}
	}
	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               */
@@ -3081,6 +3106,9 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock,
		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:
@@ -3095,6 +3123,8 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock,
		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:
@@ -3121,12 +3151,9 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock,
	case SIOCADDMULTI:
	case SIOCDELMULTI:
	case SIOCGIFINDEX:
	case SIOCGIFMAP:
	case SIOCSIFMAP:
	case SIOCGIFADDR:
	case SIOCSIFADDR:
	case SIOCSIFHWBROADCAST:
	case SIOCSHWTSTAMP:
	case SIOCDIFADDR:
	case SIOCGIFBRDADDR:
	case SIOCSIFBRDADDR: