Josh Boyer fee5e57
From ba1b23d05259e31d30a78017cdfbc010dcb08aa6 Mon Sep 17 00:00:00 2001
Josh Boyer fee5e57
From: Kees Cook <keescook@chromium.org>
Josh Boyer fee5e57
Date: Mon, 26 Nov 2012 09:02:11 -0500
Josh Boyer fee5e57
Subject: [PATCH 2/2] exec: use -ELOOP for max recursion depth
Josh Boyer fee5e57
Josh Boyer fee5e57
To avoid an explosion of request_module calls on a chain of abusive
Josh Boyer fee5e57
scripts, fail maximum recursion with -ELOOP instead of -ENOEXEC. As soon
Josh Boyer fee5e57
as maximum recursion depth is hit, the error will fail all the way back
Josh Boyer fee5e57
up the chain, aborting immediately.
Josh Boyer fee5e57
Josh Boyer fee5e57
This also has the side-effect of stopping the user's shell from attempting
Josh Boyer fee5e57
to reexecute the top-level file as a shell script. As seen in the
Josh Boyer fee5e57
dash source:
Josh Boyer fee5e57
Josh Boyer fee5e57
        if (cmd != path_bshell && errno == ENOEXEC) {
Josh Boyer fee5e57
                *argv-- = cmd;
Josh Boyer fee5e57
                *argv = cmd = path_bshell;
Josh Boyer fee5e57
                goto repeat;
Josh Boyer fee5e57
        }
Josh Boyer fee5e57
Josh Boyer fee5e57
The above logic was designed for running scripts automatically that lacked
Josh Boyer fee5e57
the "#!" header, not to re-try failed recursion. On a legitimate -ENOEXEC,
Josh Boyer fee5e57
things continue to behave as the shell expects.
Josh Boyer fee5e57
Josh Boyer fee5e57
Additionally, when tracking recursion, the binfmt handlers should not be
Josh Boyer fee5e57
involved. The recursion being tracked is the depth of calls through
Josh Boyer fee5e57
search_binary_handler(), so that function should be exclusively responsible
Josh Boyer fee5e57
for tracking the depth.
Josh Boyer fee5e57
Josh Boyer fee5e57
Signed-off-by: Kees Cook <keescook@chromium.org>
Josh Boyer fee5e57
Cc: halfdog <me@halfdog.net>
Josh Boyer fee5e57
Cc: P J P <ppandit@redhat.com>
Josh Boyer fee5e57
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Josh Boyer fee5e57
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Josh Boyer fee5e57
---
Josh Boyer fee5e57
 fs/binfmt_em86.c        |  1 -
Josh Boyer fee5e57
 fs/binfmt_misc.c        |  6 ------
Josh Boyer fee5e57
 fs/binfmt_script.c      |  4 +---
Josh Boyer fee5e57
 fs/exec.c               | 10 +++++-----
Josh Boyer fee5e57
 include/linux/binfmts.h |  2 --
Josh Boyer fee5e57
 5 files changed, 6 insertions(+), 17 deletions(-)
Josh Boyer fee5e57
Josh Boyer fee5e57
diff --git a/fs/binfmt_em86.c b/fs/binfmt_em86.c
Josh Boyer fee5e57
index 2790c7e..575796a 100644
Josh Boyer fee5e57
--- a/fs/binfmt_em86.c
Josh Boyer fee5e57
+++ b/fs/binfmt_em86.c
Josh Boyer fee5e57
@@ -42,7 +42,6 @@ static int load_em86(struct linux_binprm *bprm,struct pt_regs *regs)
Josh Boyer fee5e57
 			return -ENOEXEC;
Josh Boyer fee5e57
 	}
Josh Boyer fee5e57
 
Josh Boyer fee5e57
-	bprm->recursion_depth++; /* Well, the bang-shell is implicit... */
Josh Boyer fee5e57
 	allow_write_access(bprm->file);
Josh Boyer fee5e57
 	fput(bprm->file);
Josh Boyer fee5e57
 	bprm->file = NULL;
Josh Boyer fee5e57
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
Josh Boyer fee5e57
index 772428d..f0f1a06 100644
Josh Boyer fee5e57
--- a/fs/binfmt_misc.c
Josh Boyer fee5e57
+++ b/fs/binfmt_misc.c
Josh Boyer fee5e57
@@ -117,10 +117,6 @@ static int load_misc_binary(struct linux_binprm *bprm, struct pt_regs *regs)
Josh Boyer fee5e57
 	if (!enabled)
Josh Boyer fee5e57
 		goto _ret;
Josh Boyer fee5e57
 
Josh Boyer fee5e57
-	retval = -ENOEXEC;
Josh Boyer fee5e57
-	if (bprm->recursion_depth > BINPRM_MAX_RECURSION)
Josh Boyer fee5e57
-		goto _ret;
Josh Boyer fee5e57
-
Josh Boyer fee5e57
 	/* to keep locking time low, we copy the interpreter string */
Josh Boyer fee5e57
 	read_lock(&entries_lock);
Josh Boyer fee5e57
 	fmt = check_file(bprm);
Josh Boyer fee5e57
@@ -200,8 +196,6 @@ static int load_misc_binary(struct linux_binprm *bprm, struct pt_regs *regs)
Josh Boyer fee5e57
 	if (retval < 0)
Josh Boyer fee5e57
 		goto _error;
Josh Boyer fee5e57
 
Josh Boyer fee5e57
-	bprm->recursion_depth++;
Josh Boyer fee5e57
-
Josh Boyer fee5e57
 	retval = search_binary_handler (bprm, regs);
Josh Boyer fee5e57
 	if (retval < 0)
Josh Boyer fee5e57
 		goto _error;
Josh Boyer fee5e57
diff --git a/fs/binfmt_script.c b/fs/binfmt_script.c
Josh Boyer fee5e57
index df49d48..8ae4be1 100644
Josh Boyer fee5e57
--- a/fs/binfmt_script.c
Josh Boyer fee5e57
+++ b/fs/binfmt_script.c
Josh Boyer fee5e57
@@ -22,15 +22,13 @@ static int load_script(struct linux_binprm *bprm,struct pt_regs *regs)
Josh Boyer fee5e57
 	char interp[BINPRM_BUF_SIZE];
Josh Boyer fee5e57
 	int retval;
Josh Boyer fee5e57
 
Josh Boyer fee5e57
-	if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!') ||
Josh Boyer fee5e57
-	    (bprm->recursion_depth > BINPRM_MAX_RECURSION))
Josh Boyer fee5e57
+	if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
Josh Boyer fee5e57
 		return -ENOEXEC;
Josh Boyer fee5e57
 	/*
Josh Boyer fee5e57
 	 * This section does the #! interpretation.
Josh Boyer fee5e57
 	 * Sorta complicated, but hopefully it will work.  -TYT
Josh Boyer fee5e57
 	 */
Josh Boyer fee5e57
 
Josh Boyer fee5e57
-	bprm->recursion_depth++;
Josh Boyer fee5e57
 	allow_write_access(bprm->file);
Josh Boyer fee5e57
 	fput(bprm->file);
Josh Boyer fee5e57
 	bprm->file = NULL;
Josh Boyer fee5e57
diff --git a/fs/exec.c b/fs/exec.c
Josh Boyer fee5e57
index c6e6de4..85c1f9e 100644
Josh Boyer fee5e57
--- a/fs/exec.c
Josh Boyer fee5e57
+++ b/fs/exec.c
Josh Boyer fee5e57
@@ -1371,6 +1371,10 @@ int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
Josh Boyer fee5e57
 	struct linux_binfmt *fmt;
Josh Boyer fee5e57
 	pid_t old_pid, old_vpid;
Josh Boyer fee5e57
 
Josh Boyer fee5e57
+	/* This allows 4 levels of binfmt rewrites before failing hard. */
Josh Boyer fee5e57
+	if (depth > 5)
Josh Boyer fee5e57
+		return -ELOOP;
Josh Boyer fee5e57
+
Josh Boyer fee5e57
 	retval = security_bprm_check(bprm);
Josh Boyer fee5e57
 	if (retval)
Josh Boyer fee5e57
 		return retval;
Josh Boyer fee5e57
@@ -1395,12 +1399,8 @@ int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
Josh Boyer fee5e57
 			if (!try_module_get(fmt->module))
Josh Boyer fee5e57
 				continue;
Josh Boyer fee5e57
 			read_unlock(&binfmt_lock);
Josh Boyer fee5e57
+			bprm->recursion_depth = depth + 1;
Josh Boyer fee5e57
 			retval = fn(bprm, regs);
Josh Boyer fee5e57
-			/*
Josh Boyer fee5e57
-			 * Restore the depth counter to its starting value
Josh Boyer fee5e57
-			 * in this call, so we don't have to rely on every
Josh Boyer fee5e57
-			 * load_binary function to restore it on return.
Josh Boyer fee5e57
-			 */
Josh Boyer fee5e57
 			bprm->recursion_depth = depth;
Josh Boyer fee5e57
 			if (retval >= 0) {
Josh Boyer fee5e57
 				if (depth == 0) {
Josh Boyer fee5e57
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
Josh Boyer fee5e57
index de0628e..54135f6 100644
Josh Boyer fee5e57
--- a/include/linux/binfmts.h
Josh Boyer fee5e57
+++ b/include/linux/binfmts.h
Josh Boyer fee5e57
@@ -54,8 +54,6 @@ struct linux_binprm {
Josh Boyer fee5e57
 #define BINPRM_FLAGS_EXECFD_BIT 1
Josh Boyer fee5e57
 #define BINPRM_FLAGS_EXECFD (1 << BINPRM_FLAGS_EXECFD_BIT)
Josh Boyer fee5e57
 
Josh Boyer fee5e57
-#define BINPRM_MAX_RECURSION 4
Josh Boyer fee5e57
-
Josh Boyer fee5e57
 /* Function parameter for binfmt->coredump */
Josh Boyer fee5e57
 struct coredump_params {
Josh Boyer fee5e57
 	siginfo_t *siginfo;
Josh Boyer fee5e57
-- 
Josh Boyer fee5e57
1.8.0
Josh Boyer fee5e57