Skip to content

Commit

Permalink
lib/idr.c: fix rcu related race with idr_find
Browse files Browse the repository at this point in the history
2nd part of the fixes needed for
http://bugzilla.kernel.org/show_bug.cgi?id=11796.

When the idr tree is either grown or shrunk, then the update to the number
of layers and the top pointer were not atomic.  This race caused crashes.

The attached patch fixes that by replicating the layers counter in each
layer, thus idr_find doesn't need idp->layers anymore.

Signed-off-by: Manfred Spraul <[email protected]>
Cc: Clement Calmels <[email protected]>
Cc: Nadia Derbey <[email protected]>
Cc: Pierre Peiffer <[email protected]>
Cc: <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
manfred-colorfu authored and torvalds committed Dec 2, 2008
1 parent 1d678f3 commit 6ff2d39
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 3 deletions.
3 changes: 2 additions & 1 deletion include/linux/idr.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ struct idr_layer {
unsigned long bitmap; /* A zero bit means "space here" */
struct idr_layer *ary[1<<IDR_BITS];
int count; /* When zero, we can release it */
int layer; /* distance from leaf */
struct rcu_head rcu_head;
};

struct idr {
struct idr_layer *top;
struct idr_layer *id_free;
int layers;
int layers; /* only valid without concurrent changes */
int id_free_cnt;
spinlock_t lock;
};
Expand Down
14 changes: 12 additions & 2 deletions lib/idr.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ static int sub_alloc(struct idr *idp, int *starting_id, struct idr_layer **pa)
new = get_from_free_list(idp);
if (!new)
return -1;
new->layer = l-1;
rcu_assign_pointer(p->ary[m], new);
p->count++;
}
Expand All @@ -210,6 +211,7 @@ static int idr_get_empty_slot(struct idr *idp, int starting_id,
if (unlikely(!p)) {
if (!(p = get_from_free_list(idp)))
return -1;
p->layer = 0;
layers = 1;
}
/*
Expand Down Expand Up @@ -237,6 +239,7 @@ static int idr_get_empty_slot(struct idr *idp, int starting_id,
}
new->ary[0] = p;
new->count = 1;
new->layer = layers-1;
if (p->bitmap == IDR_FULL)
__set_bit(0, &new->bitmap);
p = new;
Expand Down Expand Up @@ -493,17 +496,21 @@ void *idr_find(struct idr *idp, int id)
int n;
struct idr_layer *p;

n = idp->layers * IDR_BITS;
p = rcu_dereference(idp->top);
if (!p)
return NULL;
n = (p->layer+1) * IDR_BITS;

/* Mask off upper bits we don't use for the search. */
id &= MAX_ID_MASK;

if (id >= (1 << n))
return NULL;
BUG_ON(n == 0);

while (n > 0 && p) {
n -= IDR_BITS;
BUG_ON(n != p->layer*IDR_BITS);
p = rcu_dereference(p->ary[(id >> n) & IDR_MASK]);
}
return((void *)p);
Expand Down Expand Up @@ -582,8 +589,11 @@ void *idr_replace(struct idr *idp, void *ptr, int id)
int n;
struct idr_layer *p, *old_p;

n = idp->layers * IDR_BITS;
p = idp->top;
if (!p)
return ERR_PTR(-EINVAL);

n = (p->layer+1) * IDR_BITS;

id &= MAX_ID_MASK;

Expand Down

0 comments on commit 6ff2d39

Please sign in to comment.