idr: Fix handling of IDs above INT_MAX
authorMatthew Wilcox <mawilcox@microsoft.com>
Mon, 26 Feb 2018 19:39:30 +0000 (14:39 -0500)
committerMatthew Wilcox <mawilcox@microsoft.com>
Mon, 26 Feb 2018 19:39:30 +0000 (14:39 -0500)
Khalid reported that the kernel selftests are currently failing:

selftests: test_bpf.sh
========================================
test_bpf: [FAIL]
not ok 1..8 selftests:  test_bpf.sh [FAIL]

He bisected it to 6ce711f2750031d12cec91384ac5cfa0a485b60a ("idr: Make
1-based IDRs more efficient").

The root cause is doing a signed comparison in idr_alloc_u32() instead
of an unsigned comparison.  I went looking for any similar problems and
found a couple (which would each result in the failure to warn in two
situations that aren't supposed to happen).

I knocked up a few test-cases to prove that I was right and added them
to the test-suite.

Reported-by: Khalid Aziz <khalid.aziz@oracle.com>
Tested-by: Khalid Aziz <khalid.aziz@oracle.com>
Signed-off-by: Matthew Wilcox <mawilcox@microsoft.com>
lib/idr.c
tools/testing/radix-tree/idr-test.c

index 99ec5bc..823b813 100644 (file)
--- a/lib/idr.c
+++ b/lib/idr.c
@@ -36,8 +36,8 @@ int idr_alloc_u32(struct idr *idr, void *ptr, u32 *nextid,
 {
        struct radix_tree_iter iter;
        void __rcu **slot;
-       int base = idr->idr_base;
-       int id = *nextid;
+       unsigned int base = idr->idr_base;
+       unsigned int id = *nextid;
 
        if (WARN_ON_ONCE(radix_tree_is_internal_node(ptr)))
                return -EINVAL;
@@ -204,10 +204,11 @@ int idr_for_each(const struct idr *idr,
 
        radix_tree_for_each_slot(slot, &idr->idr_rt, &iter, 0) {
                int ret;
+               unsigned long id = iter.index + base;
 
-               if (WARN_ON_ONCE(iter.index > INT_MAX))
+               if (WARN_ON_ONCE(id > INT_MAX))
                        break;
-               ret = fn(iter.index + base, rcu_dereference_raw(*slot), data);
+               ret = fn(id, rcu_dereference_raw(*slot), data);
                if (ret)
                        return ret;
        }
@@ -230,8 +231,8 @@ void *idr_get_next(struct idr *idr, int *nextid)
 {
        struct radix_tree_iter iter;
        void __rcu **slot;
-       int base = idr->idr_base;
-       int id = *nextid;
+       unsigned long base = idr->idr_base;
+       unsigned long id = *nextid;
 
        id = (id < base) ? 0 : id - base;
        slot = radix_tree_iter_find(&idr->idr_rt, &iter, id);
index 44ef9eb..6c645eb 100644 (file)
@@ -178,6 +178,55 @@ void idr_get_next_test(int base)
        idr_destroy(&idr);
 }
 
+int idr_u32_cb(int id, void *ptr, void *data)
+{
+       BUG_ON(id < 0);
+       BUG_ON(ptr != DUMMY_PTR);
+       return 0;
+}
+
+void idr_u32_test1(struct idr *idr, u32 handle)
+{
+       static bool warned = false;
+       u32 id = handle;
+       int sid = 0;
+       void *ptr;
+
+       BUG_ON(idr_alloc_u32(idr, DUMMY_PTR, &id, id, GFP_KERNEL));
+       BUG_ON(id != handle);
+       BUG_ON(idr_alloc_u32(idr, DUMMY_PTR, &id, id, GFP_KERNEL) != -ENOSPC);
+       BUG_ON(id != handle);
+       if (!warned && id > INT_MAX)
+               printk("vvv Ignore these warnings\n");
+       ptr = idr_get_next(idr, &sid);
+       if (id > INT_MAX) {
+               BUG_ON(ptr != NULL);
+               BUG_ON(sid != 0);
+       } else {
+               BUG_ON(ptr != DUMMY_PTR);
+               BUG_ON(sid != id);
+       }
+       idr_for_each(idr, idr_u32_cb, NULL);
+       if (!warned && id > INT_MAX) {
+               printk("^^^ Warnings over\n");
+               warned = true;
+       }
+       BUG_ON(idr_remove(idr, id) != DUMMY_PTR);
+       BUG_ON(!idr_is_empty(idr));
+}
+
+void idr_u32_test(int base)
+{
+       DEFINE_IDR(idr);
+       idr_init_base(&idr, base);
+       idr_u32_test1(&idr, 10);
+       idr_u32_test1(&idr, 0x7fffffff);
+       idr_u32_test1(&idr, 0x80000000);
+       idr_u32_test1(&idr, 0x80000001);
+       idr_u32_test1(&idr, 0xffe00000);
+       idr_u32_test1(&idr, 0xffffffff);
+}
+
 void idr_checks(void)
 {
        unsigned long i;
@@ -248,6 +297,9 @@ void idr_checks(void)
        idr_get_next_test(0);
        idr_get_next_test(1);
        idr_get_next_test(4);
+       idr_u32_test(4);
+       idr_u32_test(1);
+       idr_u32_test(0);
 }
 
 /*