posix: Fix posix_spawnp to not execute invalid binaries in non compat mode (BZ#23264)

Current posix_spawnp implementation wrongly tries to execute invalid
binaries (for instance script without shebang) as a shell script in
non compat mode.  It was a regression introduced by
9ff72da471 when __spawni started to use
__execvpe instead of __execve (glibc __execvpe try to execute ENOEXEC
as shell script regardless).

This patch fixes it by using an internal symbol (__execvpex) with the
faulty semantic (since compat mode is handled by spawni.c itself).

It was reported by Daniel Drake on libc-help [1].

Checked on x86_64-linux-gnu and i686-linux-gnu.

	[BZ #23264]
	* include/unistd.h (__execvpex): New prototype.
	* posix/Makefile (tests): Add tst-spawn4.
	(tests-internal): Add tst-spawn4-compat.
	* posix/execvpe.c (__execvpe_common, __execvpex): New functions.
	* posix/tst-spawn4-compat.c: New file.
	* posix/tst-spawn4.c: Likewise.
	* sysdeps/unix/sysv/linux/spawni.c (__spawni): Do not interpret invalid
	binaries as shell scripts.
	* sysdeps/posix/spawni.c (__spawni): Likewise.

[1] https://sourceware.org/ml/libc-help/2018-06/msg00012.html
This commit is contained in:
Adhemerval Zanella 2018-06-06 14:07:34 -03:00
parent 67c0579669
commit 283d985122
8 changed files with 175 additions and 11 deletions

View File

@ -1,3 +1,16 @@
2018-06-08 Adhemerval Zanella <adhemerval.zanella@linaro.org>
[BZ #23264]
* include/unistd.h (__execvpex): New prototype.
* posix/Makefile (tests): Add tst-spawn4.
(tests-internal): Add tst-spawn4-compat.
* posix/execvpe.c (__execvpe_common, __execvpex): New functions.
* posix/tst-spawn4-compat.c: New file.
* posix/tst-spawn4.c: Likewise.
* sysdeps/unix/sysv/linux/spawni.c (__spawni): Do not interpret invalid
binaries as shell scripts.
* sysdeps/posix/spawni.c (__spawni): Likewise.
2018-06-08 H.J. Lu <hongjiu.lu@intel.com> 2018-06-08 H.J. Lu <hongjiu.lu@intel.com>
[BZ #23145] [BZ #23145]

View File

@ -77,6 +77,8 @@ extern char *__getcwd (char *__buf, size_t __size) attribute_hidden;
extern int __rmdir (const char *__path) attribute_hidden; extern int __rmdir (const char *__path) attribute_hidden;
extern int __execvpe (const char *file, char *const argv[], extern int __execvpe (const char *file, char *const argv[],
char *const envp[]) attribute_hidden; char *const envp[]) attribute_hidden;
extern int __execvpex (const char *file, char *const argv[],
char *const envp[]) attribute_hidden;
/* Get the canonical absolute name of the named directory, and put it in SIZE /* Get the canonical absolute name of the named directory, and put it in SIZE
bytes of BUF. Returns NULL if the directory couldn't be determined or bytes of BUF. Returns NULL if the directory couldn't be determined or

View File

@ -95,10 +95,10 @@ tests := test-errno tstgetopt testfnm runtests runptests \
tst-posix_spawn-fd tst-posix_spawn-setsid \ tst-posix_spawn-fd tst-posix_spawn-setsid \
tst-posix_fadvise tst-posix_fadvise64 \ tst-posix_fadvise tst-posix_fadvise64 \
tst-sysconf-empty-chroot tst-glob_symlinks tst-fexecve \ tst-sysconf-empty-chroot tst-glob_symlinks tst-fexecve \
tst-glob-tilde test-ssize-max tst-glob-tilde test-ssize-max tst-spawn4
tests-internal := bug-regex5 bug-regex20 bug-regex33 \ tests-internal := bug-regex5 bug-regex20 bug-regex33 \
tst-rfc3484 tst-rfc3484-2 tst-rfc3484-3 \ tst-rfc3484 tst-rfc3484-2 tst-rfc3484-3 \
tst-glob_lstat_compat tst-glob_lstat_compat tst-spawn4-compat
xtests := bug-ga2 tst-getaddrinfo4 tst-getaddrinfo5 xtests := bug-ga2 tst-getaddrinfo4 tst-getaddrinfo5
ifeq (yes,$(build-shared)) ifeq (yes,$(build-shared))
test-srcs := globtest test-srcs := globtest

View File

@ -67,11 +67,9 @@ maybe_script_execute (const char *file, char *const argv[], char *const envp[])
__execve (new_argv[0], new_argv, envp); __execve (new_argv[0], new_argv, envp);
} }
static int
/* Execute FILE, searching in the `PATH' environment variable if it contains __execvpe_common (const char *file, char *const argv[], char *const envp[],
no slashes, with arguments ARGV and environment from ENVP. */ bool exec_script)
int
__execvpe (const char *file, char *const argv[], char *const envp[])
{ {
/* We check the simple case first. */ /* We check the simple case first. */
if (*file == '\0') if (*file == '\0')
@ -85,7 +83,7 @@ __execvpe (const char *file, char *const argv[], char *const envp[])
{ {
__execve (file, argv, envp); __execve (file, argv, envp);
if (errno == ENOEXEC) if (errno == ENOEXEC && exec_script)
maybe_script_execute (file, argv, envp); maybe_script_execute (file, argv, envp);
return -1; return -1;
@ -137,7 +135,7 @@ __execvpe (const char *file, char *const argv[], char *const envp[])
__execve (buffer, argv, envp); __execve (buffer, argv, envp);
if (errno == ENOEXEC) if (errno == ENOEXEC && exec_script)
/* This has O(P*C) behavior, where P is the length of the path and C /* This has O(P*C) behavior, where P is the length of the path and C
is the argument count. A better strategy would be allocate the is the argument count. A better strategy would be allocate the
substitute argv and reuse it each time through the loop (so it substitute argv and reuse it each time through the loop (so it
@ -184,4 +182,18 @@ __execvpe (const char *file, char *const argv[], char *const envp[])
return -1; return -1;
} }
/* Execute FILE, searching in the `PATH' environment variable if it contains
no slashes, with arguments ARGV and environment from ENVP. */
int
__execvpe (const char *file, char *const argv[], char *const envp[])
{
return __execvpe_common (file, argv, envp, true);
}
weak_alias (__execvpe, execvpe) weak_alias (__execvpe, execvpe)
/* Same as __EXECVPE, but does not try to execute NOEXEC files. */
int
__execvpex (const char *file, char *const argv[], char *const envp[])
{
return __execvpe_common (file, argv, envp, false);
}

77
posix/tst-spawn4-compat.c Normal file
View File

@ -0,0 +1,77 @@
/* Check if posix_spawn does handle correctly ENOEXEC files.
Copyright (C) 2018 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <spawn.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <support/xunistd.h>
#include <support/check.h>
#include <support/temp_file.h>
#include <shlib-compat.h>
#if TEST_COMPAT (libc, GLIBC_2_0, GLIBC_2_15)
compat_symbol_reference (libc, posix_spawn, posix_spawn, GLIBC_2_2);
compat_symbol_reference (libc, posix_spawnp, posix_spawnp, GLIBC_2_2);
static int
do_test (void)
{
char *scriptname;
int fd = create_temp_file ("tst-spawn4.", &scriptname);
TEST_VERIFY_EXIT (fd >= 0);
const char script[] = "exit 65";
xwrite (fd, script, sizeof (script) - 1);
xclose (fd);
TEST_VERIFY_EXIT (chmod (scriptname, 0x775) == 0);
pid_t pid;
int status;
/* For compat symbol it verifies that trying to issued a shell script
without a shebang is correctly executed. */
status = posix_spawn (&pid, scriptname, NULL, NULL, (char *[]) { 0 },
(char *[]) { 0 });
TEST_VERIFY_EXIT (status == 0);
TEST_VERIFY_EXIT (waitpid (pid, &status, 0) == pid);
TEST_VERIFY_EXIT (WIFEXITED (status) == 1 && WEXITSTATUS (status) == 65);
status = posix_spawnp (&pid, scriptname, NULL, NULL, (char *[]) { 0 },
(char *[]) { 0 });
TEST_VERIFY_EXIT (status == 0);
TEST_VERIFY_EXIT (waitpid (pid, &status, 0) == pid);
TEST_VERIFY_EXIT (WIFEXITED (status) == 1 && WEXITSTATUS (status) == 65);
return 0;
}
#else
static int
do_test (void)
{
return 77;
}
#endif
#include <support/test-driver.c>

56
posix/tst-spawn4.c Normal file
View File

@ -0,0 +1,56 @@
/* Check if posix_spawn does handle correctly ENOEXEC files.
Copyright (C) 2018 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <spawn.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <support/xunistd.h>
#include <support/check.h>
#include <support/temp_file.h>
static int
do_test (void)
{
char *scriptname;
int fd = create_temp_file ("tst-spawn4.", &scriptname);
TEST_VERIFY_EXIT (fd >= 0);
const char script[] = "echo it should not happen";
xwrite (fd, script, sizeof (script) - 1);
xclose (fd);
TEST_VERIFY_EXIT (chmod (scriptname, 0x775) == 0);
pid_t pid;
int status;
/* Check if scripts without shebang are correctly not executed. */
status = posix_spawn (&pid, scriptname, NULL, NULL, (char *[]) { 0 },
(char *[]) { 0 });
TEST_VERIFY_EXIT (status == ENOEXEC);
status = posix_spawnp (&pid, scriptname, NULL, NULL, (char *[]) { 0 },
(char *[]) { 0 });
TEST_VERIFY_EXIT (status == ENOEXEC);
return 0;
}
#include <support/test-driver.c>

View File

@ -310,6 +310,8 @@ __spawni (pid_t * pid, const char *file,
const posix_spawnattr_t * attrp, char *const argv[], const posix_spawnattr_t * attrp, char *const argv[],
char *const envp[], int xflags) char *const envp[], int xflags)
{ {
/* It uses __execvpex to avoid run ENOEXEC in non compatibility mode (it
will be handled by maybe_script_execute). */
return __spawnix (pid, file, acts, attrp, argv, envp, xflags, return __spawnix (pid, file, acts, attrp, argv, envp, xflags,
xflags & SPAWN_XFLAGS_USE_PATH ? __execvpe : __execve); xflags & SPAWN_XFLAGS_USE_PATH ? __execvpex : __execve);
} }

View File

@ -404,6 +404,8 @@ __spawni (pid_t * pid, const char *file,
const posix_spawnattr_t * attrp, char *const argv[], const posix_spawnattr_t * attrp, char *const argv[],
char *const envp[], int xflags) char *const envp[], int xflags)
{ {
/* It uses __execvpex to avoid run ENOEXEC in non compatibility mode (it
will be handled by maybe_script_execute). */
return __spawnix (pid, file, acts, attrp, argv, envp, xflags, return __spawnix (pid, file, acts, attrp, argv, envp, xflags,
xflags & SPAWN_XFLAGS_USE_PATH ? __execvpe : __execve); xflags & SPAWN_XFLAGS_USE_PATH ? __execvpex :__execve);
} }