[PATCH] fix sysctl_nr_open bugs
authorAl Viro <viro@zeniv.linux.org.uk>
Mon, 28 Apr 2008 00:04:15 +0000 (20:04 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Thu, 1 May 2008 17:08:57 +0000 (13:08 -0400)
* if luser with root sets it to something that is not a multiple of
  BITS_PER_LONG, the system is screwed.
* if it gets decreased at the wrong time, we can get expand_files()
  returning success and _not_ increasing the size of table as asked.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/file.c

index f6fbcb4..4c6f0ea 100644 (file)
--- a/fs/file.c
+++ b/fs/file.c
@@ -150,8 +150,16 @@ static struct fdtable * alloc_fdtable(unsigned int nr)
        nr /= (1024 / sizeof(struct file *));
        nr = roundup_pow_of_two(nr + 1);
        nr *= (1024 / sizeof(struct file *));
-       if (nr > sysctl_nr_open)
-               nr = sysctl_nr_open;
+       /*
+        * Note that this can drive nr *below* what we had passed if sysctl_nr_open
+        * had been set lower between the check in expand_files() and here.  Deal
+        * with that in caller, it's cheaper that way.
+        *
+        * We make sure that nr remains a multiple of BITS_PER_LONG - otherwise
+        * bitmaps handling below becomes unpleasant, to put it mildly...
+        */
+       if (unlikely(nr > sysctl_nr_open))
+               nr = ((sysctl_nr_open - 1) | (BITS_PER_LONG - 1)) + 1;
 
        fdt = kmalloc(sizeof(struct fdtable), GFP_KERNEL);
        if (!fdt)
@@ -200,6 +208,16 @@ static int expand_fdtable(struct files_struct *files, int nr)
        if (!new_fdt)
                return -ENOMEM;
        /*
+        * extremely unlikely race - sysctl_nr_open decreased between the check in
+        * caller and alloc_fdtable().  Cheaper to catch it here...
+        */
+       if (unlikely(new_fdt->max_fds <= nr)) {
+               free_fdarr(new_fdt);
+               free_fdset(new_fdt);
+               kfree(new_fdt);
+               return -EMFILE;
+       }
+       /*
         * Check again since another task may have expanded the fd table while
         * we dropped the lock
         */