cpumask: fix compat getaffinity
KOSAKI Motohiro [Wed, 19 May 2010 00:37:41 +0000 (09:37 +0900)]
Commit a45185d2d "cpumask: convert kernel/compat.c" broke libnuma, which
abuses sched_getaffinity to find out NR_CPUS in order to parse
/sys/devices/system/node/node*/cpumap.

On NUMA systems with less than 32 possibly CPUs, the current
compat_sys_sched_getaffinity now returns '4' instead of the actual
NR_CPUS/8, which makes libnuma bail out when parsing the cpumap.

The libnuma call sched_getaffinity(0, bitmap, 4096) at first.  It mean
the libnuma expect the return value of sched_getaffinity() is either len
argument or NR_CPUS.  But it doesn't expect to return nr_cpu_ids.

Strictly speaking, userland requirement are

1) Glibc assume the return value mean the lengh of initialized
   of mask argument. E.g. if sched_getaffinity(1024) return 128,
   glibc make zero fill rest 896 byte.
2) Libnuma assume the return value can be used to guess NR_CPUS
   in kernel. It assume len-arg<NR_CPUS makes -EINVAL. But
   it try len=4096 at first and 4096 is always bigger than
   NR_CPUS. Then, if we remove strange min_length normalization,
   we never hit -EINVAL case.

sched_getaffinity() already solved this issue.  This patch adapts
compat_sys_sched_getaffinity() to match the non-compat case.

Signed-off-by: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Acked-by: Rusty Russell <rusty@rustcorp.com.au>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Reported-by: Ken Werner <ken.werner@web.de>
Cc: stable@kernel.org
Cc: Andi Kleen <andi@firstfloor.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

kernel/compat.c

index 7f40e92..5adab05 100644 (file)
@@ -495,29 +495,26 @@ asmlinkage long compat_sys_sched_getaffinity(compat_pid_t pid, unsigned int len,
 {
        int ret;
        cpumask_var_t mask;
-       unsigned long *k;
-       unsigned int min_length = cpumask_size();
-
-       if (nr_cpu_ids <= BITS_PER_COMPAT_LONG)
-               min_length = sizeof(compat_ulong_t);
 
-       if (len < min_length)
+       if ((len * BITS_PER_BYTE) < nr_cpu_ids)
+               return -EINVAL;
+       if (len & (sizeof(compat_ulong_t)-1))
                return -EINVAL;
 
        if (!alloc_cpumask_var(&mask, GFP_KERNEL))
                return -ENOMEM;
 
        ret = sched_getaffinity(pid, mask);
-       if (ret < 0)
-               goto out;
+       if (ret == 0) {
+               size_t retlen = min_t(size_t, len, cpumask_size());
 
-       k = cpumask_bits(mask);
-       ret = compat_put_bitmap(user_mask_ptr, k, min_length * 8);
-       if (ret == 0)
-               ret = min_length;
-
-out:
+               if (compat_put_bitmap(user_mask_ptr, cpumask_bits(mask), retlen * 8))
+                       ret = -EFAULT;
+               else
+                       ret = retlen;
+       }
        free_cpumask_var(mask);
+
        return ret;
 }