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