Bug 8408

Summary: execve() wrongly permits argv and envp to be NULL
Product: Process Management Reporter: Michael Kerrisk (michael.kerrisk)
Component: OtherAssignee: Alan (alan)
Status: REJECTED DOCUMENTED    
Severity: normal CC: akpm, drepper, leonard.r.koenig, mtk.manpages, protasnb
Priority: P2    
Hardware: i386   
OS: Linux   
Kernel Version: 2.6.20 and all earlier kernels Subsystem:
Regression: No Bisected commit-id:

Description Michael Kerrisk 2007-04-30 03:34:47 UTC
Most recent kernel where this bug did *NOT* occur: present in all kernels in
2.6.x and at least as far back as 2.2.
Distribution: all
Hardware Environment: all
Software Environment: all

Problem Description:
The current Linux implementation of execve() permits the argv and envp arguments
to be NULL, with the same meaning as if they were pointers to lists consisting
of a single null pointer.  I.e., the following are equivalent:

char *envp[] = { NULL };
execve(path, argv, envp);

and

exceve(path, argv, NULL);

The second form is non-standard -- SUSv3 requires argv and envp each to be a
NULL-terminate array of pointers to character strings.  On most other Unix
systems (e.g., tested FreBSD 6.1 and Solaris 8), the second of the above forms
will cause execve() to fail with the error EFAULT.  (However, HP-UX 11 behaves
the same as Linux -- but then HP-UX is among the least conformant of the
traditional systems.)  Linux should do the same as FreBSD and Solaris.  

However, changing Linux to conform will result in an ABI change for userland.

The change required is in fs/exec.c, in the function do_execve().  This function
should include a check of the form:

if (argv == NULL || envp == NULL)
    return -EFAULT;


Steps to reproduce:
The following program should fail in execve() with the error EFAULT, but on
Linux it succeeds.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int
main(int argc, char *argv[])
{
    execve("/bin/echo", NULL, NULL);
    perror("execve");   
    exit(EXIT_FAILURE);
}
Comment 1 Natalie Protasevich 2007-08-26 11:59:55 UTC
Probably the best would be to post this bug on LKML, for general discussion on the issue. We definitely need a category for issues of this kind.
Placing it in process management for now.
Thanks.
Comment 2 Alan 2007-09-27 05:37:43 UTC
This isn't a kernel bug. If address zero is mapped then it is valid for it to contain environment or argument. On systems where page zero isn't mapped it'll fault as expected.

NULL is a valid pointer in some cases, it points to a page which contains NULL as its start so happens to be a valid argument vector, by chance.
Comment 3 Michael Kerrisk 2008-06-12 02:41:40 UTC
(In reply to comment #2)
> This isn't a kernel bug. If address zero is mapped then it is valid for it to
> contain environment or argument. On systems where page zero isn't mapped
> it'll
> fault as expected.
> 
> NULL is a valid pointer in some cases, it points to a page which contains
> NULL
> as its start so happens to be a valid argument vector, by chance.
> 

I missed the closure of this bug.

As initially formulated, my bug report was faulty.  Indeed it seems that most systems permit envp to be NULL (I tested FreeBSD and Solaris), with the meaning that the environment list for the new program is empty.

However, my report is correct regarding argv: most other systems (e.g., FreeBSD, Solaris) fail the execve() with EFAULT in the example below.  (The man pages on FreeBSD and Solaris are explicit in specifying that at least one argument must be supplied in argv.)

Alan, your comments about NULL as a pointer to a mapping at page 0 are bogus.  Whether or not there is mapping there does not even come into play, because in fs/exec.c, use is made of this function to check argv and envp:

 static int count(char __user * __user * argv, int max)
 {
         int i = 0;
 
         if (argv != NULL) {
               ...
         }
         return i;
 }

Given a count of 0, copy_strings() then copies no arguments, providing behavior as though (argv != NULL && argv[0] == NULL).

Linux should be giving -EFAULT here, for consistency with other systems.

=====

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int
main()
{

    char *argv[10];

    argv[0] = "date";
    argv[1] = NULL;
    execve("/bin/date", NULL, NULL);
    perror("execve");
    exit(EXIT_FAILURE);
}
Comment 4 Alan 2008-10-02 07:21:53 UTC
Well the spec doesn't say what has to happen if you pass invalid values (-EFAULT is just the correct error if we error it for this).

It is an ABI change and that makes it seem rather excessive for a philosophical point upon which no app will depend on receiving -EFAULT in normal usage, but old Linux code might use NULL NULL

So I still don't think we should fix it.
Comment 5 Michael Kerrisk 2022-01-27 08:14:36 UTC
Add a link, just to track a relevant discussion 14 years later:
https://lore.kernel.org/lkml/20220126043947.10058-1-ariadne@dereferenced.org/
Comment 6 Leonard/Janis Robert König 2022-01-28 15:57:35 UTC
(In reply to Michael Kerrisk from comment #5)

I don't think that's the same issue, isn't it?  This bug report is about disallowing argv == NULL while the linked patch has a stricter requirement, namely that argv[0] != NULL i.e., argc >= 1.  This bug here would still allow for:

    char *argv[] = { NULL };
    execve("/bin/date", argv, NULL);

While the linked patch would require:

    char *argv[] = { "foo", NULL }; // ideally "/bin/date" or suchlike
    execve("/bin/date", argv, NULL);

Note that the former is actually required by POSIX:

> The argument argv is an array of character pointers to null-terminated
> strings. The application shall ensure that the last member of this array is a
> null pointer.

(https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/execve.html)

While the latter is explicitly not part of POSIX (see RATIONALE):

> Early proposals required that the value of argc passed to main() be "one or
> greater". This was driven by the same requirement in drafts of the ISO C
> standard. In fact, historical implementations have passed a value of zero
> when no arguments are supplied to the caller of the exec functions. This
> requirement was removed from the ISO C standard and subsequently removed from
> this volume of POSIX.1-2017 as well.