27ddac6
From 20ae2081584450e552735a3df968ce5b5946a607 Mon Sep 17 00:00:00 2001
27ddac6
From: Kees Cook <keescook@chromium.org>
27ddac6
Date: Mon, 26 Nov 2012 08:56:37 -0500
27ddac6
Subject: [PATCH 1/2] exec: do not leave bprm->interp on stack
27ddac6
27ddac6
If a series of scripts are executed, each triggering module loading via
27ddac6
unprintable bytes in the script header, kernel stack contents can leak
27ddac6
into the command line.
27ddac6
27ddac6
Normally execution of binfmt_script and binfmt_misc happens recursively.
27ddac6
However, when modules are enabled, and unprintable bytes exist in the
27ddac6
bprm->buf, execution will restart after attempting to load matching binfmt
27ddac6
modules.  Unfortunately, the logic in binfmt_script and binfmt_misc does
27ddac6
not expect to get restarted.  They leave bprm->interp pointing to their
27ddac6
local stack.  This means on restart bprm->interp is left pointing into
27ddac6
unused stack memory which can then be copied into the userspace argv
27ddac6
areas.
27ddac6
27ddac6
After additional study, it seems that both recursion and restart remains
27ddac6
the desirable way to handle exec with scripts, misc, and modules.  As
27ddac6
such, we need to protect the changes to interp.
27ddac6
27ddac6
This changes the logic to require allocation for any changes to the
27ddac6
bprm->interp.  To avoid adding a new kmalloc to every exec, the default
27ddac6
value is left as-is.  Only when passing through binfmt_script or
27ddac6
binfmt_misc does an allocation take place.
27ddac6
27ddac6
For a proof of concept, see DoTest.sh from:
27ddac6
http://www.halfdog.net/Security/2012/LinuxKernelBinfmtScriptStackDataDisclosure/
27ddac6
27ddac6
Signed-off-by: Kees Cook <keescook@chromium.org>
27ddac6
Cc: halfdog <me@halfdog.net>
27ddac6
Cc: P J P <ppandit@redhat.com>
27ddac6
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
27ddac6
Cc: <stable@vger.kernel.org>
27ddac6
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
27ddac6
---
27ddac6
 fs/binfmt_misc.c        |  5 ++++-
27ddac6
 fs/binfmt_script.c      |  4 +++-
27ddac6
 fs/exec.c               | 15 +++++++++++++++
27ddac6
 include/linux/binfmts.h |  1 +
27ddac6
 4 files changed, 23 insertions(+), 2 deletions(-)
27ddac6
27ddac6
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
27ddac6
index 790b3cd..772428d 100644
27ddac6
--- a/fs/binfmt_misc.c
27ddac6
+++ b/fs/binfmt_misc.c
27ddac6
@@ -176,7 +176,10 @@ static int load_misc_binary(struct linux_binprm *bprm, struct pt_regs *regs)
27ddac6
 		goto _error;
27ddac6
 	bprm->argc ++;
27ddac6
 
27ddac6
-	bprm->interp = iname;	/* for binfmt_script */
27ddac6
+	/* Update interp in case binfmt_script needs it. */
27ddac6
+	retval = bprm_change_interp(iname, bprm);
27ddac6
+	if (retval < 0)
27ddac6
+		goto _error;
27ddac6
 
27ddac6
 	interp_file = open_exec (iname);
27ddac6
 	retval = PTR_ERR (interp_file);
27ddac6
diff --git a/fs/binfmt_script.c b/fs/binfmt_script.c
27ddac6
index d3b8c1f..df49d48 100644
27ddac6
--- a/fs/binfmt_script.c
27ddac6
+++ b/fs/binfmt_script.c
27ddac6
@@ -82,7 +82,9 @@ static int load_script(struct linux_binprm *bprm,struct pt_regs *regs)
27ddac6
 	retval = copy_strings_kernel(1, &i_name, bprm);
27ddac6
 	if (retval) return retval; 
27ddac6
 	bprm->argc++;
27ddac6
-	bprm->interp = interp;
27ddac6
+	retval = bprm_change_interp(interp, bprm);
27ddac6
+	if (retval < 0)
27ddac6
+		return retval;
27ddac6
 
27ddac6
 	/*
27ddac6
 	 * OK, now restart the process with the interpreter's dentry.
27ddac6
diff --git a/fs/exec.c b/fs/exec.c
27ddac6
index fab2c6d..59896ae 100644
27ddac6
--- a/fs/exec.c
27ddac6
+++ b/fs/exec.c
27ddac6
@@ -1202,9 +1202,24 @@ void free_bprm(struct linux_binprm *bprm)
27ddac6
 		mutex_unlock(&current->signal->cred_guard_mutex);
27ddac6
 		abort_creds(bprm->cred);
27ddac6
 	}
27ddac6
+	/* If a binfmt changed the interp, free it. */
27ddac6
+	if (bprm->interp != bprm->filename)
27ddac6
+		kfree(bprm->interp);
27ddac6
 	kfree(bprm);
27ddac6
 }
27ddac6
 
27ddac6
+int bprm_change_interp(char *interp, struct linux_binprm *bprm)
27ddac6
+{
27ddac6
+	/* If a binfmt changed the interp, free it first. */
27ddac6
+	if (bprm->interp != bprm->filename)
27ddac6
+		kfree(bprm->interp);
27ddac6
+	bprm->interp = kstrdup(interp, GFP_KERNEL);
27ddac6
+	if (!bprm->interp)
27ddac6
+		return -ENOMEM;
27ddac6
+	return 0;
27ddac6
+}
27ddac6
+EXPORT_SYMBOL(bprm_change_interp);
27ddac6
+
27ddac6
 /*
27ddac6
  * install the new credentials for this executable
27ddac6
  */
27ddac6
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
27ddac6
index 366422b..eb53e15 100644
27ddac6
--- a/include/linux/binfmts.h
27ddac6
+++ b/include/linux/binfmts.h
27ddac6
@@ -128,6 +128,7 @@ extern int setup_arg_pages(struct linux_binprm * bprm,
27ddac6
 			   unsigned long stack_top,
27ddac6
 			   int executable_stack);
27ddac6
 extern int bprm_mm_init(struct linux_binprm *bprm);
27ddac6
+extern int bprm_change_interp(char *interp, struct linux_binprm *bprm);
27ddac6
 extern int copy_strings_kernel(int argc, const char *const *argv,
27ddac6
 			       struct linux_binprm *bprm);
27ddac6
 extern int prepare_bprm_creds(struct linux_binprm *bprm);
27ddac6
-- 
27ddac6
1.8.0
27ddac6