Skip to content

Commit

Permalink
idr: Fix handling of IDs above INT_MAX
Browse files Browse the repository at this point in the history
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 6ce711f ("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 <[email protected]>
Tested-by: Khalid Aziz <[email protected]>
Signed-off-by: Matthew Wilcox <[email protected]>
  • Loading branch information
Matthew Wilcox committed Feb 26, 2018
1 parent 3d4d5d6 commit 4b0ad07
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 6 deletions.
13 changes: 7 additions & 6 deletions lib/idr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
Expand Down
52 changes: 52 additions & 0 deletions tools/testing/radix-tree/idr-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

/*
Expand Down

0 comments on commit 4b0ad07

Please sign in to comment.