CVE-2012-0879: Uncontrolled Resource Consumption (CWE-400)
value: The I/O implementation for block devices in the Linux kernel before 2.6.33 does not properly handle the CLONE_IO feature, which allows local users to cause a denial of service (I/O instability) by starting multiple processes that share an I/O context.
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.
CVSS v3 base score: 5.5
Published: 2012-05-17T11:00Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: block/blk-ioc.c, include/linux/iocontext.h, kernel/exit.c, kernel/fork.c
Commit date: 2009-12-04 16:36:18 +0100
Commit message: block: Fix io_context leak after failure of clone with CLONE_IO

With CLONE_IO, parent's io_context->nr_tasks is incremented, but never
decremented whenever copy_process() fails afterwards, which prevents
exit_io_context() from calling IO schedulers exit functions.

Give a task_struct to exit_io_context(), and call exit_io_context() instead of
put_io_context() in copy_process() cleanup path.

Signed-off-by: Louis Rilling <louis.rilling@kerlabs.com>
Signed-off-by: Jens Axboe <jens.axboe@oracle.com>

Diff: diff --git a/block/blk-ioc.c b/block/blk-ioc.c
index dcd041290b28b..cbdabb0dd6d77 100644
--- a/block/blk-ioc.c
+++ b/block/blk-ioc.c
@@ -64,18 +64,18 @@ static void cfq_exit(struct io_context *ioc)
 	}
 	rcu_read_unlock();
 }
 
 /* Called by the exitting task */
-void exit_io_context(void)
+void exit_io_context(struct task_struct *task)
 {
 	struct io_context *ioc;
 
-	task_lock(current);
-	ioc = current->io_context;
-	current->io_context = NULL;
-	task_unlock(current);
+	task_lock(task);
+	ioc = task->io_context;
+	task->io_context = NULL;
+	task_unlock(task);
 
 	if (atomic_dec_and_test(&ioc->nr_tasks)) {
 		if (ioc->aic && ioc->aic->exit)
 			ioc->aic->exit(ioc->aic);
 		cfq_exit(ioc);
diff --git a/include/linux/iocontext.h b/include/linux/iocontext.h
index d61b0b8b5cd19..a63235996309c 100644
--- a/include/linux/iocontext.h
+++ b/include/linux/iocontext.h
@@ -96,18 +96,19 @@ static inline struct io_context *ioc_task_link(struct io_context *ioc)
 	}
 
 	return NULL;
 }
 
+struct task_struct;
 #ifdef CONFIG_BLOCK
 int put_io_context(struct io_context *ioc);
-void exit_io_context(void);
+void exit_io_context(struct task_struct *task);
 struct io_context *get_io_context(gfp_t gfp_flags, int node);
 struct io_context *alloc_io_context(gfp_t gfp_flags, int node);
 void copy_io_context(struct io_context **pdst, struct io_context **psrc);
 #else
-static inline void exit_io_context(void)
+static inline void exit_io_context(struct task_struct *task)
 {
 }
 
 struct io_context;
 static inline int put_io_context(struct io_context *ioc)
diff --git a/kernel/exit.c b/kernel/exit.c
index f7864ac2ecc1a..2544000125d99 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1002,11 +1002,11 @@ NORET_TYPE void do_exit(long code)
 	 * or not. In the worst case it loops once more.
 	 */
 	tsk->flags |= PF_EXITPIDONE;
 
 	if (tsk->io_context)
-		exit_io_context();
+		exit_io_context(tsk);
 
 	if (tsk->splice_pipe)
 		__free_pipe_info(tsk->splice_pipe);
 
 	validate_creds_for_do_exit(tsk);
diff --git a/kernel/fork.c b/kernel/fork.c
index 166b8c49257c5..607353425bb0d 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1308,11 +1308,12 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 
 bad_fork_free_pid:
 	if (pid != &init_struct_pid)
 		free_pid(pid);
 bad_fork_cleanup_io:
-	put_io_context(p->io_context);
+	if (p->io_context)
+		exit_io_context(p);
 bad_fork_cleanup_namespaces:
 	exit_task_namespaces(p);
 bad_fork_cleanup_mm:
 	if (p->mm)
 		mmput(p->mm);

Vulnerable code: void exit_io_context(void)
	task_lock(current);
	ioc = current->io_context;
	current->io_context = NULL;
	task_unlock(current);
void exit_io_context(void);
static inline void exit_io_context(void)
		exit_io_context();
	put_io_context(p->io_context);
Fixed code: void exit_io_context(struct task_struct *task)
	task_lock(task);
	ioc = task->io_context;
	task->io_context = NULL;
	task_unlock(task);
struct task_struct;
void exit_io_context(struct task_struct *task);
static inline void exit_io_context(struct task_struct *task)
		exit_io_context(tsk);
	if (p->io_context)
		exit_io_context(p);