CVE-2011-2491: Uncontrolled Resource Consumption (CWE-400)
value: The Network Lock Manager (NLM) protocol implementation in the NFS client functionality in the Linux kernel before 3.0 allows local users to cause a denial of service (system hang) via a LOCK_UN flock system call.
CWE description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Published: 2013-03-01T12:37Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: fs/lockd/clntproc.c, include/linux/sunrpc/sched.h, net/sunrpc/clnt.c, net/sunrpc/sched.c
Commit date: 2011-06-15 11:24:27 -0400
Commit message: NLM: Don't hang forever on NLM unlock requests

If the NLM daemon is killed on the NFS server, we can currently end up
hanging forever on an 'unlock' request, instead of aborting. Basically,
if the rpcbind request fails, or the server keeps returning garbage, we
really want to quit instead of retrying.

Tested-by: Vasily Averin <vvs@sw.ru>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Cc: stable@kernel.org

Diff: diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c
index adb45ec9038c..e374050a911c 100644
--- a/fs/lockd/clntproc.c
+++ b/fs/lockd/clntproc.c
@@ -706,11 +706,17 @@ static void nlmclnt_unlock_callback(struct rpc_task *task, void *data)
 	if (RPC_ASSASSINATED(task))
 		goto die;
 
 	if (task->tk_status < 0) {
 		dprintk("lockd: unlock failed (err = %d)\n", -task->tk_status);
-		goto retry_rebind;
+		switch (task->tk_status) {
+		case -EACCES:
+		case -EIO:
+			goto die;
+		default:
+			goto retry_rebind;
+		}
 	}
 	if (status == NLM_LCK_DENIED_GRACE_PERIOD) {
 		rpc_delay(task, NLMCLNT_GRACE_WAIT);
 		goto retry_unlock;
 	}
diff --git a/include/linux/sunrpc/sched.h b/include/linux/sunrpc/sched.h
index f73c482ec9c6..fe2d8e6b923b 100644
--- a/include/linux/sunrpc/sched.h
+++ b/include/linux/sunrpc/sched.h
@@ -82,11 +82,12 @@ struct rpc_task {
 #ifdef RPC_DEBUG
 	unsigned short		tk_pid;		/* debugging aid */
 #endif
 	unsigned char		tk_priority : 2,/* Task priority */
 				tk_garb_retry : 2,
-				tk_cred_retry : 2;
+				tk_cred_retry : 2,
+				tk_rebind_retry : 2;
 };
 #define tk_xprt			tk_client->cl_xprt
 
 /* support walking a list of tasks on a wait queue */
 #define	task_for_each(task, pos, head) \
diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
index b84d7395535e..566bcfd067f6 100644
--- a/net/sunrpc/clnt.c
+++ b/net/sunrpc/clnt.c
@@ -1173,10 +1173,13 @@ call_bind_status(struct rpc_task *task)
 		/* fail immediately if this is an RPC ping */
 		if (task->tk_msg.rpc_proc->p_proc == 0) {
 			status = -EOPNOTSUPP;
 			break;
 		}
+		if (task->tk_rebind_retry == 0)
+			break;
+		task->tk_rebind_retry--;
 		rpc_delay(task, 3*HZ);
 		goto retry_timeout;
 	case -ETIMEDOUT:
 		dprintk("RPC: %5u rpcbind request timed out\n",
 				task->tk_pid);
diff --git a/net/sunrpc/sched.c b/net/sunrpc/sched.c
index 6b43ee7221d5..a27406b1654f 100644
--- a/net/sunrpc/sched.c
+++ b/net/sunrpc/sched.c
@@ -790,10 +790,11 @@ static void rpc_init_task(struct rpc_task *task, const struct rpc_task_setup *ta
 	INIT_LIST_HEAD(&task->tk_task);
 
 	/* Initialize retry counters */
 	task->tk_garb_retry = 2;
 	task->tk_cred_retry = 2;
+	task->tk_rebind_retry = 2;
 
 	task->tk_priority = task_setup_data->priority - RPC_PRIORITY_LOW;
 	task->tk_owner = current->tgid;
 
 	/* Initialize workqueue for async tasks */

Vulnerable code: 		goto retry_rebind;
				tk_cred_retry : 2;
Fixed code: 		switch (task->tk_status) {
		case -EACCES:
		case -EIO:
			goto die;
		default:
			goto retry_rebind;
		}
				tk_cred_retry : 2,
				tk_rebind_retry : 2;
		if (task->tk_rebind_retry == 0)
			break;
		task->tk_rebind_retry--;
	task->tk_rebind_retry = 2;