From a2116ed223c88b6c424f42398e54d1607dc785ba Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 11 Nov 2009 03:39:40 +0000 Subject: [PATCH] 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 Cc: Patrick Ohly Cc: David S. Miller Signed-off-by: David S. Miller --- net/socket.c | 117 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/net/socket.c b/net/socket.c index befd9f5b1620..05c482848a67 100644 --- a/net/socket.c +++ b/net/socket.c @@ -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: