summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugh Dickins <hugh@veritas.com>2003-09-05 22:28:20 -0700
committerLinus Torvalds <torvalds@home.osdl.org>2003-09-05 22:28:20 -0700
commitf5ecfe8f690037ce631b2289f7f02ed699d2b230 (patch)
tree35807376f1322023d67099f21c2a85d7a9c9c907
parentfaff7e92c34815dbd7e90f86f9259ad18b01f181 (diff)
[PATCH] Fix futex hashing bugs
This fixes two buts that the glibc NPTL verification tests found, one new and one old. The new bug is that "offset" has been declared as an alternative in the union, instead of as an element in the structures comprising it, effectively eliminating it from the key: keys match which should not. The old bug is that if futex_requeue were called with identical key1 and key2 (sensible? tended to happen given the first bug), it was liable to loop for a long time holding futex_lock: guard against that, still respecting the semantics of futex_requeue. While here, please let's also fix the get_futex_key VM_NONLINEAR case, which was returning the 1 from get_user_pages, taken as an error by its callers. And save a few bytes and improve debuggability by uninlining the top-level futex_wake, futex_requeue, futex_wait.
-rw-r--r--kernel/futex.c24
1 files changed, 15 insertions, 9 deletions
diff --git a/kernel/futex.c b/kernel/futex.c
index a4feceee661a..695e20eb222f 100644
--- a/kernel/futex.c
+++ b/kernel/futex.c
@@ -49,16 +49,18 @@ union futex_key {
struct {
unsigned long pgoff;
struct inode *inode;
+ int offset;
} shared;
struct {
unsigned long uaddr;
struct mm_struct *mm;
+ int offset;
} private;
struct {
unsigned long word;
void *ptr;
+ int offset;
} both;
- int offset;
};
/*
@@ -91,7 +93,7 @@ static inline struct list_head *hash_futex(union futex_key *key)
{
return &futex_queues[hash_long(key->both.word
+ (unsigned long) key->both.ptr
- + key->offset, FUTEX_HASHBITS)];
+ + key->both.offset, FUTEX_HASHBITS)];
}
/*
@@ -101,7 +103,7 @@ static inline int match_futex(union futex_key *key1, union futex_key *key2)
{
return (key1->both.word == key2->both.word
&& key1->both.ptr == key2->both.ptr
- && key1->offset == key2->offset);
+ && key1->both.offset == key2->both.offset);
}
/*
@@ -127,10 +129,10 @@ static int get_futex_key(unsigned long uaddr, union futex_key *key)
/*
* The futex address must be "naturally" aligned.
*/
- key->offset = uaddr % PAGE_SIZE;
- if (unlikely((key->offset % sizeof(u32)) != 0))
+ key->both.offset = uaddr % PAGE_SIZE;
+ if (unlikely((key->both.offset % sizeof(u32)) != 0))
return -EINVAL;
- uaddr -= key->offset;
+ uaddr -= key->both.offset;
/*
* The futex is hashed differently depending on whether
@@ -199,6 +201,7 @@ static int get_futex_key(unsigned long uaddr, union futex_key *key)
key->shared.pgoff =
page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);
put_page(page);
+ return 0;
}
return err;
}
@@ -208,7 +211,7 @@ static int get_futex_key(unsigned long uaddr, union futex_key *key)
* Wake up all waiters hashed on the physical page that is mapped
* to this virtual address:
*/
-static inline int futex_wake(unsigned long uaddr, int num)
+static int futex_wake(unsigned long uaddr, int num)
{
struct list_head *i, *next, *head;
union futex_key key;
@@ -247,7 +250,7 @@ out:
* Requeue all waiters hashed on one physical page to another
* physical page.
*/
-static inline int futex_requeue(unsigned long uaddr1, unsigned long uaddr2,
+static int futex_requeue(unsigned long uaddr1, unsigned long uaddr2,
int nr_wake, int nr_requeue)
{
struct list_head *i, *next, *head1, *head2;
@@ -282,6 +285,9 @@ static inline int futex_requeue(unsigned long uaddr1, unsigned long uaddr2,
this->key = key2;
if (ret - nr_wake >= nr_requeue)
break;
+ /* Make sure to stop if key1 == key2 */
+ if (head1 == head2 && head1 != next)
+ head1 = i;
}
}
}
@@ -320,7 +326,7 @@ static inline int unqueue_me(struct futex_q *q)
return ret;
}
-static inline int futex_wait(unsigned long uaddr, int val, unsigned long time)
+static int futex_wait(unsigned long uaddr, int val, unsigned long time)
{
DECLARE_WAITQUEUE(wait, current);
int ret, curval;