CVE-2014-0205: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: The futex_wait function in kernel/futex.c in the Linux kernel before 2.6.37 does not properly maintain a certain reference count during requeue operations, which allows local users to cause a denial of service (use-after-free and system crash) or possibly gain privileges via a crafted application that triggers a zero count.
CWE description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
Published: 2014-09-28T19:55Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: kernel/futex.c
Commit date: 2010-10-19 11:41:54 +0200
Commit message: futex: Fix errors in nested key ref-counting

futex_wait() is leaking key references due to futex_wait_setup()
acquiring an additional reference via the queue_lock() routine. The
nested key ref-counting has been masking bugs and complicating code
analysis. queue_lock() is only called with a previously ref-counted
key, so remove the additional ref-counting from the queue_(un)lock()
functions.

Also futex_wait_requeue_pi() drops one key reference too many in
unqueue_me_pi(). Remove the key reference handling from
unqueue_me_pi(). This was paired with a queue_lock() in
futex_lock_pi(), so the count remains unchanged.

Document remaining nested key ref-counting sites.

Signed-off-by: Darren Hart <dvhart@linux.intel.com>
Reported-and-tested-by: Matthieu Fertré<matthieu.fertre@kerlabs.com>
Reported-by: Louis Rilling<louis.rilling@kerlabs.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Eric Dumazet <eric.dumazet@gmail.com>
Cc: John Kacur <jkacur@redhat.com>
Cc: Rusty Russell <rusty@rustcorp.com.au>
LKML-Reference: <4CBB17A8.70401@linux.intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: stable@kernel.org

Diff: diff --git a/kernel/futex.c b/kernel/futex.c
index 6a3a5fa1526d87..e328f574c97c02 100644
--- a/kernel/futex.c
+++ b/kernel/futex.c
@@ -1361,11 +1361,10 @@ static int futex_requeue(u32 __user *uaddr1, int fshared, u32 __user *uaddr2,
 /* The key must be already stored in q->key. */
 static inline struct futex_hash_bucket *queue_lock(struct futex_q *q)
 {
 	struct futex_hash_bucket *hb;
 
-	get_futex_key_refs(&q->key);
 	hb = hash_futex(&q->key);
 	q->lock_ptr = &hb->lock;
 
 	spin_lock(&hb->lock);
 	return hb;
@@ -1373,11 +1372,10 @@ static inline struct futex_hash_bucket *queue_lock(struct futex_q *q)
 
 static inline void
 queue_unlock(struct futex_q *q, struct futex_hash_bucket *hb)
 {
 	spin_unlock(&hb->lock);
-	drop_futex_key_refs(&q->key);
 }
 
 /**
  * queue_me() - Enqueue the futex_q on the futex_hash_bucket
  * @q:	The futex_q to enqueue
@@ -1478,12 +1476,10 @@ static void unqueue_me_pi(struct futex_q *q)
 	BUG_ON(!q->pi_state);
 	free_pi_state(q->pi_state);
 	q->pi_state = NULL;
 
 	spin_unlock(q->lock_ptr);
-
-	drop_futex_key_refs(&q->key);
 }
 
 /*
  * Fixup the pi_state owner with the new owner.
  *
@@ -1810,38 +1806,40 @@ static int futex_wait(u32 __user *uaddr, int fshared,
 		hrtimer_set_expires_range_ns(&to->timer, *abs_time,
 					     current->timer_slack_ns);
 	}
 
 retry:
-	/* Prepare to wait on uaddr. */
+	/*
+	 * Prepare to wait on uaddr. On success, holds hb lock and increments
+	 * q.key refs.
+	 */
 	ret = futex_wait_setup(uaddr, val, fshared, &q, &hb);
 	if (ret)
 		goto out;
 
 	/* queue_me and wait for wakeup, timeout, or a signal. */
 	futex_wait_queue_me(hb, &q, to);
 
 	/* If we were woken (and unqueued), we succeeded, whatever. */
 	ret = 0;
+	/* unqueue_me() drops q.key ref */
 	if (!unqueue_me(&q))
-		goto out_put_key;
+		goto out;
 	ret = -ETIMEDOUT;
 	if (to && !to->task)
-		goto out_put_key;
+		goto out;
 
 	/*
 	 * We expect signal_pending(current), but we might be the
 	 * victim of a spurious wakeup as well.
 	 */
-	if (!signal_pending(current)) {
-		put_futex_key(fshared, &q.key);
+	if (!signal_pending(current))
 		goto retry;
-	}
 
 	ret = -ERESTARTSYS;
 	if (!abs_time)
-		goto out_put_key;
+		goto out;
 
 	restart = &current_thread_info()->restart_block;
 	restart->fn = futex_wait_restart;
 	restart->futex.uaddr = (u32 *)uaddr;
 	restart->futex.val = val;
@@ -1854,12 +1852,10 @@ static int futex_wait(u32 __user *uaddr, int fshared,
 	if (clockrt)
 		restart->futex.flags |= FLAGS_CLOCKRT;
 
 	ret = -ERESTART_RESTARTBLOCK;
 
-out_put_key:
-	put_futex_key(fshared, &q.key);
 out:
 	if (to) {
 		hrtimer_cancel(&to->timer);
 		destroy_hrtimer_on_stack(&to->timer);
 	}
@@ -2234,11 +2230,14 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, int fshared,
 	q.pi_state = NULL;
 	q.bitset = bitset;
 	q.rt_waiter = &rt_waiter;
 	q.requeue_pi_key = &key2;
 
-	/* Prepare to wait on uaddr. */
+	/*
+	 * Prepare to wait on uaddr. On success, increments q.key (key1) ref
+	 * count.
+	 */
 	ret = futex_wait_setup(uaddr, val, fshared, &q, &hb);
 	if (ret)
 		goto out_key2;
 
 	/* Queue the futex_q, drop the hb lock, wait for wakeup. */
@@ -2252,11 +2251,13 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, int fshared,
 
 	/*
 	 * In order for us to be here, we know our q.key == key2, and since
 	 * we took the hb->lock above, we also know that futex_requeue() has
 	 * completed and we no longer have to concern ourselves with a wakeup
-	 * race with the atomic proxy lock acquition by the requeue code.
+	 * race with the atomic proxy lock acquisition by the requeue code. The
+	 * futex_requeue dropped our key1 reference and incremented our key2
+	 * reference count.
 	 */
 
 	/* Check if the requeue code acquired the second futex for us. */
 	if (!q.rt_waiter) {
 		/*

Vulnerable code: 	get_futex_key_refs(&q->key);
	drop_futex_key_refs(&q->key);

	drop_futex_key_refs(&q->key);
	/* Prepare to wait on uaddr. */
		goto out_put_key;
		goto out_put_key;
	if (!signal_pending(current)) {
		put_futex_key(fshared, &q.key);
	}
		goto out_put_key;
out_put_key:
	put_futex_key(fshared, &q.key);
	/* Prepare to wait on uaddr. */
	 * race with the atomic proxy lock acquition by the requeue code.
Fixed code: 	/*
	 * Prepare to wait on uaddr. On success, holds hb lock and increments
	 * q.key refs.
	 */
	/* unqueue_me() drops q.key ref */
		goto out;
		goto out;
	if (!signal_pending(current))
		goto out;
	/*
	 * Prepare to wait on uaddr. On success, increments q.key (key1) ref
	 * count.
	 */
	 * race with the atomic proxy lock acquisition by the requeue code. The
	 * futex_requeue dropped our key1 reference and incremented our key2
	 * reference count.