diff --git a/lib/libc/sys/execve.2 b/lib/libc/sys/execve.2 --- a/lib/libc/sys/execve.2 +++ b/lib/libc/sys/execve.2 @@ -28,7 +28,7 @@ .\" @(#)execve.2 8.5 (Berkeley) 6/1/94 .\" $FreeBSD$ .\" -.Dd March 30, 2020 +.Dd January 25, 2022 .Dt EXECVE 2 .Os .Sh NAME @@ -273,6 +273,9 @@ The new process file is not an ordinary file. .It Bq Er EACCES The new process file mode denies execute permission. +.It Bq Er EINVAL +.Fa argv +did not contain at least one element. .It Bq Er ENOEXEC The new process file has the appropriate access permission, but has an invalid magic number in its header. diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c --- a/sys/kern/kern_exec.c +++ b/sys/kern/kern_exec.c @@ -356,6 +356,15 @@ exec_args_get_begin_envv(args) - args->begin_argv); AUDIT_ARG_ENVV(exec_args_get_begin_envv(args), args->envc, args->endp - exec_args_get_begin_envv(args)); + + /* + * Must have at least one argument. Bail out after auditing, rather + * than before, in case empty argv is significant to some audit-based + * IDS. Probably not a big deal right now, given that this is the only + * EINVAL documented for execve(2). + */ + if (args->argc == 0) + return (EINVAL); return (do_execve(td, args, mac_p, oldvmspace)); } diff --git a/tests/sys/kern/execve/Makefile b/tests/sys/kern/execve/Makefile --- a/tests/sys/kern/execve/Makefile +++ b/tests/sys/kern/execve/Makefile @@ -10,6 +10,7 @@ PROGS+= good_aout PROGS+= execve_helper +PROGS+= execve_argc_helper LDFLAGS.goodaout+= -static diff --git a/tests/sys/kern/execve/execve_argc_helper.c b/tests/sys/kern/execve/execve_argc_helper.c new file mode 100644 --- /dev/null +++ b/tests/sys/kern/execve/execve_argc_helper.c @@ -0,0 +1,12 @@ +/* + * This file is in the public domain. + */ + +#include + +int +main(int argc, char **argv __unused) +{ + + return (argc); +} diff --git a/tests/sys/kern/execve/execve_helper.c b/tests/sys/kern/execve/execve_helper.c --- a/tests/sys/kern/execve/execve_helper.c +++ b/tests/sys/kern/execve/execve_helper.c @@ -38,17 +38,25 @@ #include #include #include +#include #include +/* Passing -n == null_argv */ +static char * const null_argv[] = { NULL }; + int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "usage: %s \n", argv[0]); + if ((argc != 2 && argc != 3) || + (argc == 3 && strcmp(argv[1], "-n") != 0)) { + fprintf(stderr, "usage: %s [-n] \n", argv[0]); exit(2); } - execve(argv[1], &argv[1], NULL); + if (argc == 2) + execve(argv[1], &argv[1], NULL); + else + execve(argv[2], null_argv, NULL); err(1, "execve failed"); } diff --git a/tests/sys/kern/execve/execve_test.sh b/tests/sys/kern/execve/execve_test.sh --- a/tests/sys/kern/execve/execve_test.sh +++ b/tests/sys/kern/execve/execve_test.sh @@ -99,6 +99,25 @@ -x "cd $(atf_get_srcdir) && ./execve_helper trunc_aout" } +empty_args_head() +{ + atf_set "descr" "Empty argv behavior" +} +empty_args_body() +{ + # Exit status misleading, if stderr is empty then it's OK and the exit + # status is argc. + atf_check -s exit:1 \ + -x "cd $(atf_get_srcdir) && ./execve_helper execve_argc_helper" + + # Historically we allowed argc == 0, while execve(2) claimed we didn't. + # execve() should kick back an EINVAL now. We verified the helper was + # there/working in the check just above. + atf_check -s exit:1 \ + -e inline:"execve_helper: execve failed: Invalid argument\n" \ + -x "cd $(atf_get_srcdir) && ./execve_helper -n execve_argc_helper" +} + atf_init_test_cases() { atf_add_test_case bad_interp_len @@ -111,5 +130,6 @@ atf_add_test_case script_arg_nospace atf_add_test_case sparse_aout atf_add_test_case trunc_aout + atf_add_test_case empty_args }