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: Arnd Bergmann <arnd@arndb.de>
Cc: Patrick Ohly <patrick.ohly@intel.com>
Cc: David S. Miller <davem@davemloft.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Arnd Bergmann 2009-11-11 03:39:40 +00:00 committed by David S. Miller
parent c029f4440f
commit a2116ed223

View File

@ -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)))
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);
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:
@ -2771,18 +2748,7 @@ 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;
break;
case SIOCGIFMAP:
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)
if (copy_in_user(uifr32, uifr, sizeof(*uifr32)))
err = -EFAULT;
break;
}
@ -2790,6 +2756,65 @@ static int dev_ifsioc(struct net *net, struct socket *sock,
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 */
@ -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: