FAILED: patch "[PATCH] exec: Force single empty string when argv is empty" failed to apply to 4.19-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 4.19-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From dcd46d897adb70d63e025f175a00a89797d31a43 Mon Sep 17 00:00:00 2001
From: Kees Cook <[email protected]>
Date: Mon, 31 Jan 2022 16:09:47 -0800
Subject: [PATCH] exec: Force single empty string when argv is empty
Quoting[1] Ariadne Conill:
"In several other operating systems, it is a hard requirement that the second argument to execve(2) be the name of a program, thus prohibiting a scenario where argc < 1. POSIX 2017 also recommends this behaviour, but it is not an explicit requirement[2]:
The argument arg0 should point to a filename string that is associated with the process being started by one of the exec functions.
...
Interestingly, Michael Kerrisk opened an issue about this in 2008[3], but there was no consensus to support fixing this issue then. Hopefully now that CVE-2021-4034 shows practical exploitative use[4] of this bug in a shellcode, we can reconsider.
This issue is being tracked in the KSPP issue tracker[5]."
While the initial code searches[6][7] turned up what appeared to be mostly corner case tests, trying to that just reject argv == NULL (or an immediately terminated pointer list) quickly started tripping[8] existing userspace programs.
The next best approach is forcing a single empty string into argv and adjusting argc to match. The number of programs depending on argc == 0 seems a smaller set than those calling execve with a NULL argv.
Account for the additional stack space in bprm_stack_limits(). Inject an empty string when argc == 0 (and set argc = 1). Warn about the case so userspace has some notice about the change:
process './argc0' launched './argc0' with NULL argv: empty string added
Additionally WARN() and reject NULL argv usage for kernel threads.
[1] https://lore.kernel.org/lkml/[email protected]/
[2] https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
[3] https://bugzilla.kernel.org/show_bug.cgi?id=8408
[4] https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt
[5] https://github.com/KSPP/linux/issues/176
[6] https://codesearch.debian.net/search?q=execve%5C+*%5C%28%5B%5E%2C%5D%2B%2C+*NULL&literal=0
[7] https://codesearch.debian.net/search?q=execlp%3F%5Cs*%5C%28%5B%5E%2C%5D%2B%2C%5Cs*NULL&literal=0
[8] https://lore.kernel.org/lkml/20220131144352.GE16385@xsang-OptiPlex-9020/
fs/exec.c +25 -1
@@ -495,8 +495,14 @@ static int bprm_stack_limits(struct linux_binprm *bprm)* the stack. They aren't stored until much later when we can't * signal to the parent that the child has run out of stack space. * Instead, calculate it here so it's possible to fail gracefully. +* +* In the case of argc = 0, make sure there is space for adding a +* empty string (which will bump argc to 1), to ensure confused +* userspace programs don't start processing from argv[1], thinking +* argc can never be 0, to keep them from walking envp by accident. +* See do_execveat_common(). */ - ptr_size = (bprm->argc + bprm->envc) *sizeof (void *); + ptr_size = (max (bprm->argc,1 ) + bprm->envc) *sizeof (void *);if (limit <= ptr_size)return -E2BIG ; limit -= ptr_size;
@@ -1897,6 +1903,9 @@ static int do_execveat_common(int fd, struct filename *filename, } retval =count (argv,MAX_ARG_STRINGS ); +if (retval ==0 ) +pr_warn_once ("process '%s' launched '%s' with NULL argv: empty string added\n" , + current->comm, bprm->filename);if (retval <0 ) goto out_free; bprm->argc = retval;
@@ -1923,6 +1932,19 @@ static int do_execveat_common(int fd, struct filename *filename,if (retval <0 ) goto out_free; +/* +* When argv is empty, add an empty string ("") as argv[0] to +* ensure confused userspace programs that start processing +* from argv[1] won't end up walking envp. See also +* bprm_stack_limits(). +*/ +if (bprm->argc ==0 ) { + retval =copy_string_kernel ("" , bprm); +if (retval <0 ) + goto out_free; + bprm->argc =1 ; + } + retval =bprm_execve (bprm, fd, filename, flags); out_free:free_bprm (bprm);
@@ -1951,6 +1973,8 @@ int kernel_execve(const char *kernel_filename, } retval =count_strings_kernel (argv); +if (WARN_ON_ONCE (retval ==0 )) + retval = -EINVAL ;if (retval <0 ) goto out_free; bprm->argc = retval;
PATCH 1/1 exec: Force single empty string when argv is empty
From: Kees Cook <[email protected]>
commit dcd46d897adb70d63e025f175a00a89797d31a43 upstream.
Quoting[1] Ariadne Conill:
"In several other operating systems, it is a hard requirement that the second argument to execve(2) be the name of a program, thus prohibiting a scenario where argc < 1. POSIX 2017 also recommends this behaviour, but it is not an explicit requirement[2]:
The argument arg0 should point to a filename string that is associated with the process being started by one of the exec functions.
...
Interestingly, Michael Kerrisk opened an issue about this in 2008[3], but there was no consensus to support fixing this issue then. Hopefully now that CVE-2021-4034 shows practical exploitative use[4] of this bug in a shellcode, we can reconsider.
This issue is being tracked in the KSPP issue tracker[5]."
While the initial code searches[6][7] turned up what appeared to be mostly corner case tests, trying to that just reject argv == NULL (or an immediately terminated pointer list) quickly started tripping[8] existing userspace programs.
The next best approach is forcing a single empty string into argv and adjusting argc to match. The number of programs depending on argc == 0 seems a smaller set than those calling execve with a NULL argv.
Account for the additional stack space in bprm_stack_limits(). Inject an empty string when argc == 0 (and set argc = 1). Warn about the case so userspace has some notice about the change:
process './argc0' launched './argc0' with NULL argv: empty string added
Additionally WARN() and reject NULL argv usage for kernel threads.
[1] https://lore.kernel.org/lkml/[email protected]/
[2] https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
[3] https://bugzilla.kernel.org/show_bug.cgi?id=8408
[4] https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt
[5] https://github.com/KSPP/linux/issues/176
[6] https://codesearch.debian.net/search?q=execve%5C+*%5C%28%5B%5E%2C%5D%2B%2C+*NULL&literal=0
[7] https://codesearch.debian.net/search?q=execlp%3F%5Cs*%5C%28%5B%5E%2C%5D%2B%2C%5Cs*NULL&literal=0
[8] https://lore.kernel.org/lkml/20220131144352.GE16385@xsang-OptiPlex-9020/
[vegard: fixed conflicts due to missing
886d7de631da71e30909980fdbf318f7caade262^- and
3950e975431bc914f7e81b8f2a2dbdf2064acb0f^- and
655c16a8ce9c15842547f40ce23fd148aeccc074]
--- fs/exec.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
This has been tested in both argc == 0 and argc >= 1 cases, but I would still appreciate a review given the differences with mainline. If it's considered too risky I'm also fine with dropping it -- just wanted to make sure this didn't fall through the cracks, as it does block a real (albeit old by now) exploit.
fs/exec.c +17 -0
@@ -1805,6 +1805,9 @@ static int __do_execve_file(int fd, struct filename *filename, goto out_unmark; bprm->argc =count (argv,MAX_ARG_STRINGS ); +if (bprm->argc ==0 ) +pr_warn_once ("process '%s' launched '%s' with NULL argv: empty string added\n" , + current->comm, bprm->filename);if ((retval = bprm->argc) <0 ) goto out;
@@ -1829,6 +1832,20 @@ static int __do_execve_file(int fd, struct filename *filename,if (retval <0 ) goto out; +/* +* When argv is empty, add an empty string ("") as argv[0] to +* ensure confused userspace programs that start processing +* from argv[1] won't end up walking envp. See also +* bprm_stack_limits(). +*/ +if (bprm->argc ==0 ) { +const char *argv[] = {"" ,NULL }; + retval =copy_strings_kernel (1 , argv, bprm); +if (retval <0 ) + goto out; + bprm->argc =1 ; + } + retval =exec_binprm (bprm);if (retval <0 ) goto out;
Signature · 1 line
2.35.1.46.g38062e73e0