Hello,
After some investigation I have found that this bug is caused by the blocking of copy_in_user function in the path of SIOCGIFCONF ioctl handler (dev_ifconf in fs/compat_ioctl.c). As solving this is a little bit over my head, I have forwarded information to the [EMAIL PROTECTED] mailing list [0], hopefully someone there will be able to offer some advice. A possible way to solve this problem is to revert the patch which introduced the offending code [1]. The patch reverting this change is attached and also available at [2]. I have built a kernel with it applied and the system no longer lock-ups. The fixed kernel is available at [3], please test it to confirm that it indeed solves it.
[0] http://marc.theaimsgroup.com/?t=110780063700005&r=1&w=2 [1] http://linux.bkbits.net:8080/linux-2.6/diffs/fs/[EMAIL PROTECTED]|src/|src/fs|hist/fs/compat_ioctl.c [2] http://www.wooyd.org/debian/patches/2.6.8-dev_ifconf.patch [3] http://www.wooyd.org/debian/bug288180
Best regards,
Jurij Smakov [EMAIL PROTECTED] Key: http://www.wooyd.org/pgpkey/ KeyID: C99E03CC
--- a/fs/compat_ioctl.c 2004-08-14 01:38:04.000000000 -0400 +++ b/fs/compat_ioctl.c 2005-02-07 12:33:18.000000000 -0500 @@ -487,71 +487,75 @@ { struct ifconf32 ifc32; struct ifconf ifc; - struct ifconf __user *uifc; - struct ifreq32 __user *ifr32; - struct ifreq __user *ifr; + struct ifreq32 *ifr32; + struct ifreq *ifr; + mm_segment_t old_fs; unsigned int i, j; int err; if (copy_from_user(&ifc32, compat_ptr(arg), sizeof(struct ifconf32))) return -EFAULT; - if (ifc32.ifcbuf == 0) { + if(ifc32.ifcbuf == 0) { ifc32.ifc_len = 0; ifc.ifc_len = 0; - ifc.ifc_req = NULL; - uifc = compat_alloc_user_space(sizeof(struct ifconf)); + ifc.ifc_buf = NULL; } else { - size_t len =((ifc32.ifc_len / sizeof (struct ifreq32)) + 1) * + ifc.ifc_len = ((ifc32.ifc_len / sizeof (struct ifreq32)) + 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 ifreq32)) { - if (copy_in_user(ifr, ifr32, sizeof(struct ifreq32))) - return -EFAULT; - ifr++; - ifr32++; - } + /* should the size be limited? -arnd */ + ifc.ifc_buf = kmalloc (ifc.ifc_len, GFP_KERNEL); + if (!ifc.ifc_buf) + return -ENOMEM; } - if (copy_to_user(uifc, &ifc, sizeof(struct ifconf))) - return -EFAULT; - - err = sys_ioctl (fd, SIOCGIFCONF, (unsigned long)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 < ifc32.ifc_len && j < ifc.ifc_len; - i += sizeof (struct ifreq32), j += sizeof (struct ifreq)) { - if (copy_in_user(ifr32, ifr, sizeof (struct ifreq32))) + for (i = 0; i < ifc32.ifc_len; i += sizeof (struct ifreq32)) { + if (copy_from_user(ifr, ifr32, sizeof (struct ifreq32))) { + kfree (ifc.ifc_buf); return -EFAULT; - ifr32++; + } ifr++; + ifr32++; } - - 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 ifreq32)); - ifc32.ifc_len = i; - } else { - if (i <= ifc32.ifc_len) - ifc32.ifc_len = i; - else - ifc32.ifc_len = i - sizeof (struct ifreq32); + old_fs = get_fs(); set_fs (KERNEL_DS); + err = sys_ioctl (fd, SIOCGIFCONF, (unsigned long)&ifc); + set_fs (old_fs); + if (!err) { + ifr = ifc.ifc_req; + ifr32 = compat_ptr(ifc32.ifcbuf); + for (i = 0, j = 0; i < ifc32.ifc_len && j < ifc.ifc_len; + i += sizeof (struct ifreq32), j += sizeof (struct ifreq)) { + int k = copy_to_user(ifr32, ifr, sizeof (struct ifreq32)); + ifr32++; + ifr++; + if (k) { + err = -EFAULT; + break; + } + + } + if (!err) { + 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 ifreq32)); + ifc32.ifc_len = i; + } else { + if (i <= ifc32.ifc_len) + ifc32.ifc_len = i; + else + ifc32.ifc_len = i - sizeof (struct ifreq32); + } + if (copy_to_user(compat_ptr(arg), &ifc32, sizeof(struct ifconf32))) + err = -EFAULT; + } } - if (copy_to_user(compat_ptr(arg), &ifc32, sizeof(struct ifconf32))) - return -EFAULT; - - return 0; + if(ifc.ifc_buf != NULL) + kfree (ifc.ifc_buf); + return err; } static int ethtool_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)