diff mbox series

exec: Force single empty string when argv is empty

Message ID 20220201000947.2453721-1-keescook@chromium.org
State Accepted
Commit dcd46d897adb70d63e025f175a00a89797d31a43
Headers show
Series exec: Force single empty string when argv is empty | expand

Commit Message

Kees Cook Feb. 1, 2022, 12:09 a.m. UTC
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/20220127000724.15106-1-ariadne@dereferenced.org/
[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/

Reported-by: Ariadne Conill <ariadne@dereferenced.org>
Reported-by: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Rich Felker <dalias@libc.org>
Cc: Eric Biederman <ebiederm@xmission.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: linux-fsdevel@vger.kernel.org
Cc: stable@vger.kernel.org
Signed-off-by: Kees Cook <keescook@chromium.org>
---
 fs/exec.c | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

Comments

Ariadne Conill Feb. 1, 2022, 1 a.m. UTC | #1
Hi,

On Mon, 31 Jan 2022, Kees Cook wrote:

> 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.

Yes, it's a shame this is the case, but we do what we have to do, I guess 
:)

>
> 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/20220127000724.15106-1-ariadne@dereferenced.org/
> [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/
>
> Reported-by: Ariadne Conill <ariadne@dereferenced.org>
> Reported-by: Michael Kerrisk <mtk.manpages@gmail.com>
> Cc: Matthew Wilcox <willy@infradead.org>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Rich Felker <dalias@libc.org>
> Cc: Eric Biederman <ebiederm@xmission.com>
> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> Cc: linux-fsdevel@vger.kernel.org
> Cc: stable@vger.kernel.org
> Signed-off-by: Kees Cook <keescook@chromium.org>

In terms of going with this approach as an alternative verses my original 
patch,

Acked-by: Ariadne Conill <ariadne@dereferenced.org>

Ariadne
Andy Lutomirski Feb. 1, 2022, 2 a.m. UTC | #2
On 1/31/22 16:09, Kees Cook wrote:
> 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/20220127000724.15106-1-ariadne@dereferenced.org/
> [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/

Acked-by: Andy Lutomirski <luto@kernel.org>

and cc-ing linux-api.

I agree that this should be done regardless of any security context change.

> 
> Reported-by: Ariadne Conill <ariadne@dereferenced.org>
> Reported-by: Michael Kerrisk <mtk.manpages@gmail.com>
> Cc: Matthew Wilcox <willy@infradead.org>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Rich Felker <dalias@libc.org>
> Cc: Eric Biederman <ebiederm@xmission.com>
> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> Cc: linux-fsdevel@vger.kernel.org
> Cc: stable@vger.kernel.org
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
>   fs/exec.c | 26 +++++++++++++++++++++++++-
>   1 file changed, 25 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/exec.c b/fs/exec.c
> index 79f2c9483302..bbf3aadf7ce1 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -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 = (min(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;
David Laight Feb. 1, 2022, 9:17 a.m. UTC | #3
From: Kees Cook
> Sent: 01 February 2022 00:10
...
> 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.

Has anyone considered using the pathname for argv[0]?
So converting:
	execl(path, NULL);
into:
	execl(path, path, NULL);

I've not spotted any such suggestion.

	David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Christian Brauner Feb. 1, 2022, 1:22 p.m. UTC | #4
On Mon, Jan 31, 2022 at 04:09:47PM -0800, Kees Cook wrote:
> 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/20220127000724.15106-1-ariadne@dereferenced.org/
> [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/
> 
> Reported-by: Ariadne Conill <ariadne@dereferenced.org>
> Reported-by: Michael Kerrisk <mtk.manpages@gmail.com>
> Cc: Matthew Wilcox <willy@infradead.org>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Rich Felker <dalias@libc.org>
> Cc: Eric Biederman <ebiederm@xmission.com>
> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> Cc: linux-fsdevel@vger.kernel.org
> Cc: stable@vger.kernel.org
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---

Looks good,
Acked-by: Christian Brauner <brauner@kernel.org>
Rich Felker Feb. 1, 2022, 2:53 p.m. UTC | #5
On Mon, Jan 31, 2022 at 04:09:47PM -0800, Kees Cook wrote:
> 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/20220127000724.15106-1-ariadne@dereferenced.org/
> [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/
> 
> Reported-by: Ariadne Conill <ariadne@dereferenced.org>
> Reported-by: Michael Kerrisk <mtk.manpages@gmail.com>
> Cc: Matthew Wilcox <willy@infradead.org>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Rich Felker <dalias@libc.org>
> Cc: Eric Biederman <ebiederm@xmission.com>
> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> Cc: linux-fsdevel@vger.kernel.org
> Cc: stable@vger.kernel.org
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
>  fs/exec.c | 26 +++++++++++++++++++++++++-
>  1 file changed, 25 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/exec.c b/fs/exec.c
> index 79f2c9483302..bbf3aadf7ce1 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -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 = (min(bprm->argc, 1) + bprm->envc) * sizeof(void *);
Kees Cook Feb. 2, 2022, 3:50 p.m. UTC | #6
On February 1, 2022 6:53:25 AM PST, Rich Felker <dalias@libc.org> wrote:
>On Mon, Jan 31, 2022 at 04:09:47PM -0800, Kees Cook wrote:
>> 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/20220127000724.15106-1-ariadne@dereferenced.org/
>> [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/
>> 
>> Reported-by: Ariadne Conill <ariadne@dereferenced.org>
>> Reported-by: Michael Kerrisk <mtk.manpages@gmail.com>
>> Cc: Matthew Wilcox <willy@infradead.org>
>> Cc: Christian Brauner <brauner@kernel.org>
>> Cc: Rich Felker <dalias@libc.org>
>> Cc: Eric Biederman <ebiederm@xmission.com>
>> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
>> Cc: linux-fsdevel@vger.kernel.org
>> Cc: stable@vger.kernel.org
>> Signed-off-by: Kees Cook <keescook@chromium.org>
>> ---
>>  fs/exec.c | 26 +++++++++++++++++++++++++-
>>  1 file changed, 25 insertions(+), 1 deletion(-)
>> 
>> diff --git a/fs/exec.c b/fs/exec.c
>> index 79f2c9483302..bbf3aadf7ce1 100644
>> --- a/fs/exec.c
>> +++ b/fs/exec.c
>> @@ -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 = (min(bprm->argc, 1) + bprm->envc) * sizeof(void *);
>
>From #musl:
>
><mixi> kees: shouldn't the min(bprm->argc, 1) be max(...) in your patch?

Fix has already been sent, yup.

>I'm pretty sure without fixing that, you're introducing a giant vuln
>here.

I wouldn't say "giant", but yes, it weakened a defense in depth for avoiding high stack utilization.

> I believe this is the second time a patch attempting to fix this
>non-vuln has proposed adding a new vuln...

Mistakes happen, and that's why there is review and testing. Thank you for being part of the review process! :)

-Kees
Rich Felker Feb. 2, 2022, 5:12 p.m. UTC | #7
On Wed, Feb 02, 2022 at 07:50:42AM -0800, Kees Cook wrote:
> 
> 
> On February 1, 2022 6:53:25 AM PST, Rich Felker <dalias@libc.org> wrote:
> >On Mon, Jan 31, 2022 at 04:09:47PM -0800, Kees Cook wrote:
> >> 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/20220127000724.15106-1-ariadne@dereferenced.org/
> >> [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/
> >> 
> >> Reported-by: Ariadne Conill <ariadne@dereferenced.org>
> >> Reported-by: Michael Kerrisk <mtk.manpages@gmail.com>
> >> Cc: Matthew Wilcox <willy@infradead.org>
> >> Cc: Christian Brauner <brauner@kernel.org>
> >> Cc: Rich Felker <dalias@libc.org>
> >> Cc: Eric Biederman <ebiederm@xmission.com>
> >> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> >> Cc: linux-fsdevel@vger.kernel.org
> >> Cc: stable@vger.kernel.org
> >> Signed-off-by: Kees Cook <keescook@chromium.org>
> >> ---
> >>  fs/exec.c | 26 +++++++++++++++++++++++++-
> >>  1 file changed, 25 insertions(+), 1 deletion(-)
> >> 
> >> diff --git a/fs/exec.c b/fs/exec.c
> >> index 79f2c9483302..bbf3aadf7ce1 100644
> >> --- a/fs/exec.c
> >> +++ b/fs/exec.c
> >> @@ -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 = (min(bprm->argc, 1) + bprm->envc) * sizeof(void *);
> >
> >From #musl:
> >
> ><mixi> kees: shouldn't the min(bprm->argc, 1) be max(...) in your patch?
> 
> Fix has already been sent, yup.
> 
> >I'm pretty sure without fixing that, you're introducing a giant vuln
> >here.
> 
> I wouldn't say "giant", but yes, it weakened a defense in depth for
> avoiding high stack utilization.

I thought it was deciding the amount of memory to allocate/reserve for
the arg slots, but based on the comment it looks like it's just a way
to fail early rather than making the new process image fault later if
they don't fit.

> > I believe this is the second time a patch attempting to fix this
> >non-vuln has proposed adding a new vuln...
> 
> Mistakes happen, and that's why there is review and testing. Thank
> you for being part of the review process! :)

I know, and I'm sorry for being a bit hostile over it, and for jumping
the gun about the severity. I just get frustrated when I see a rush to
make changes over an incidental part of a popularized vuln, with
disproportionate weight on "doing something" and not enough on being
careful.

Rich
Kees Cook Feb. 2, 2022, 7:03 p.m. UTC | #8
On Wed, Feb 02, 2022 at 12:12:19PM -0500, Rich Felker wrote:
> On Wed, Feb 02, 2022 at 07:50:42AM -0800, Kees Cook wrote:
> > On February 1, 2022 6:53:25 AM PST, Rich Felker <dalias@libc.org> wrote:
> > >From #musl:
> > >
> > ><mixi> kees: shouldn't the min(bprm->argc, 1) be max(...) in your patch?
> > 
> > Fix has already been sent, yup.
> > 
> > >I'm pretty sure without fixing that, you're introducing a giant vuln
> > >here.
> > 
> > I wouldn't say "giant", but yes, it weakened a defense in depth for
> > avoiding high stack utilization.
> 
> I thought it was deciding the amount of memory to allocate/reserve for
> the arg slots, but based on the comment it looks like it's just a way
> to fail early rather than making the new process image fault later if
> they don't fit.

Right.

> > > I believe this is the second time a patch attempting to fix this
> > >non-vuln has proposed adding a new vuln...
> > 
> > Mistakes happen, and that's why there is review and testing. Thank
> > you for being part of the review process! :)
> 
> I know, and I'm sorry for being a bit hostile over it, and for jumping
> the gun about the severity. I just get frustrated when I see a rush to
> make changes over an incidental part of a popularized vuln, with
> disproportionate weight on "doing something" and not enough on being
> careful.

Sure, I can see it looks that way. My sense of urgency on this in
particular is that we're early in the development cycle, and it's an
ABI-breaking change, so I want to maximize how much time it has to get
tested out in real workloads. (i.e. we've now seen that just rejecting
NULL argv is likely too painful, etc.)

All that said, I regularly bemoan the lack of sufficient regression
tests for execve() and the binfmt_*.c loaders. I've written a couple,
and so have others, but what I really want is a library of "binary that
got broken by exec change" for doing regression testing against. That
gets hampered by both size, redistribution issues, etc, so I've wanted
to have minimal reproducers for each, but creating those take time, etc,
etc.

(And you'll note I wrote[1] a test for this particular behavior, because
I'm trying to avoid falling further behind in test coverage.)

I would _love_ it if someone had the time and attention to go through
all the past binaries and make a regression test series. :)

-Kees

[1] https://lore.kernel.org/linux-hardening/20220201011637.2457646-1-keescook@chromium.org/
Kees Cook Feb. 2, 2022, 8:31 p.m. UTC | #9
On Tue, Feb 01, 2022 at 09:17:47AM +0000, David Laight wrote:
> From: Kees Cook
> > Sent: 01 February 2022 00:10
> ...
> > 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.
> 
> Has anyone considered using the pathname for argv[0]?
> So converting:
> 	execl(path, NULL);
> into:
> 	execl(path, path, NULL);
> 
> I've not spotted any such suggestion.

It came up on some IRC discussions at some point. I'm personally not a
fan of this because it creates a bit of "new" ABI that has a lot of
variability (depending on "" is one thing, but depending on a "missing"
argv matching the exec path is very different). I think there were also
concerns about dealing with fd-based exec ("what is the 'right' name"),
etc.

I'd prefer we stay as simple as possible for this change.
diff mbox series

Patch

diff --git a/fs/exec.c b/fs/exec.c
index 79f2c9483302..bbf3aadf7ce1 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -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 = (min(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;