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