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