execlp, execvp, execvpe: Don't fall back to sh on ENOEXEC.

Message ID 20201215160521.20719-1-zackw@panix.com
State Changes Requested, archived
Headers
Series execlp, execvp, execvpe: Don't fall back to sh on ENOEXEC. |

Commit Message

Zack Weinberg Dec. 15, 2020, 4:05 p.m. UTC
  When direct execution of a program fails with ENOEXEC, do not attempt
to run that program as if it were a shell script.  The change affects
execlp, execvp, execvpe, and the compatibility versions of posix_spawn
and posix_spawnp (symbol version GLIBC_2_2).  There is no provision
for backward binary compatibility, as I consider the old behavior to
be a bug.  It was inconsistent with the behavior of the rest of the
exec* family of functions, it could cause severe malfunctions of
correct-looking code, and I can even put together a (relatively
far-fetched) scenario where it opens a security hole.

The internal function __execvpex is no longer necessary and is
removed, as is the internal flag SPAWN_XFLAGS_TRY_SHELL.  Tests
posix/tst-{execvp3,execvpe3,spawn4-compat}.c were testing this
misfeature and are removed entirely.  Tests execvpe6.c and vfork3.c
are modified not to rely on the misfeature.

This is an intentional deviation from POSIX, anticipating the change
requested in Austin Group bug #1435.  Austin currently doesn't seem
enthusiastic about the idea.  I think we should make this change *even
if* they reject it.

(This has been discussed before, but it bit me personally yesterday.
I was trying to write a wrapper script to detect when the system could
not execute cross-compiled binaries, and skip them in a test suite.
The code I wrote did not work, spewed garbage on my terminal and
created files with names I couldn't type in the process of not
working, and after several hours of research I determined that the
only practical way to *make* it work was to rewrite the wrapper in C
and reimplement path walking myself.)

(In a couple of places the patch includes unrelated fixes to spelling
and grammar errors in commentary.)

---
 NEWS                             | 13 +++++-
 include/unistd.h                 |  2 -
 posix/Makefile                   |  6 +--
 posix/execvpe.c                  | 67 ++-------------------------
 posix/spawn.c                    | 16 ++-----
 posix/spawn_int.h                |  1 -
 posix/spawnp.c                   | 16 ++-----
 posix/tst-execvp3.c              | 45 -------------------
 posix/tst-execvpe3.c             | 20 ---------
 posix/tst-execvpe6.c             |  4 +-
 posix/tst-spawn4-compat.c        | 77 --------------------------------
 posix/tst-vfork3.c               | 15 ++-----
 sysdeps/posix/spawni.c           | 48 ++++----------------
 sysdeps/unix/sysv/linux/spawni.c | 48 ++++----------------
 14 files changed, 47 insertions(+), 331 deletions(-)
 delete mode 100644 posix/tst-execvp3.c
 delete mode 100644 posix/tst-execvpe3.c
 delete mode 100644 posix/tst-spawn4-compat.c
  

Comments

Paul Eggert Dec. 15, 2020, 8:46 p.m. UTC | #1
On 12/15/20 8:05 AM, Zack Weinberg wrote:
> When direct execution of a program fails with ENOEXEC, do not attempt
> to run that program as if it were a shell script.

A less-intrusive approach would be for execlp etc.'s fallback to look at the 
first 1024 bytes (or whatever) of the file and fail with ENOEXEC if it finds 
a NUL byte, before trying to exec /bin/sh on the file.  This heuristic 
surely would have addressed your problem since machine executables 
invariably contain NUL bytes, and since NUL bytes cannot appear in 
conforming POSIX shell scripts the heuristic would also maintain POSIX 
compatibility. And as a practical matter the heuristic would continue to 
support whatever random application out there is actually relying on this 
longstanding POSIX-required misfeature.
  
Zack Weinberg Dec. 15, 2020, 9:04 p.m. UTC | #2
On Tue, Dec 15, 2020 at 3:46 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> On 12/15/20 8:05 AM, Zack Weinberg wrote:
> > When direct execution of a program fails with ENOEXEC, do not attempt
> > to run that program as if it were a shell script.
>
> A less-intrusive approach would be for execlp etc.'s fallback to look at the
> first 1024 bytes (or whatever) of the file and fail with ENOEXEC if it finds
> a NUL byte, before trying to exec /bin/sh on the file.

That's a clever idea for preserving compatibility, but to do it we
would have to open and read the file, which could easily cause
problems due to e.g. lack of free file descriptor space.  If we can't
remove this code altogether I'm not sure it's worth changing anything
in libc.

POSIX recommends that the *shell* implement a similar heuristic, and
it appears bash already does.

zw
  
Carlos O'Donell Dec. 15, 2020, 10:17 p.m. UTC | #3
On 12/15/20 4:04 PM, Zack Weinberg wrote:
> On Tue, Dec 15, 2020 at 3:46 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
>> On 12/15/20 8:05 AM, Zack Weinberg wrote:
>>> When direct execution of a program fails with ENOEXEC, do not attempt
>>> to run that program as if it were a shell script.
>>
>> A less-intrusive approach would be for execlp etc.'s fallback to look at the
>> first 1024 bytes (or whatever) of the file and fail with ENOEXEC if it finds
>> a NUL byte, before trying to exec /bin/sh on the file.
> 
> That's a clever idea for preserving compatibility, but to do it we
> would have to open and read the file, which could easily cause
> problems due to e.g. lack of free file descriptor space.  If we can't
> remove this code altogether I'm not sure it's worth changing anything
> in libc.
> 
> POSIX recommends that the *shell* implement a similar heuristic, and
> it appears bash already does.

What shell were you using that didn't implement this?
  
Adhemerval Zanella Dec. 16, 2020, 1:16 p.m. UTC | #4
On 15/12/2020 13:05, Zack Weinberg wrote:
> When direct execution of a program fails with ENOEXEC, do not attempt
> to run that program as if it were a shell script.  The change affects
> execlp, execvp, execvpe, and the compatibility versions of posix_spawn
> and posix_spawnp (symbol version GLIBC_2_2).  There is no provision
> for backward binary compatibility, as I consider the old behavior to
> be a bug.  It was inconsistent with the behavior of the rest of the
> exec* family of functions, it could cause severe malfunctions of
> correct-looking code, and I can even put together a (relatively
> far-fetched) scenario where it opens a security hole.
> 
> The internal function __execvpex is no longer necessary and is
> removed, as is the internal flag SPAWN_XFLAGS_TRY_SHELL.  Tests
> posix/tst-{execvp3,execvpe3,spawn4-compat}.c were testing this
> misfeature and are removed entirely.  Tests execvpe6.c and vfork3.c
> are modified not to rely on the misfeature.
> 
> This is an intentional deviation from POSIX, anticipating the change
> requested in Austin Group bug #1435.  Austin currently doesn't seem
> enthusiastic about the idea.  I think we should make this change *even
> if* they reject it.

Although I tend to agree with the rationale, I think we should follow
current practice of moving legacy semantics to compat symbols, evaluate
the usage by releasing some version with the change, and then check 
if would make sense to also change the compat symbols.

So I would suggest to on 2.33 add new versions for execlp, execvp, execvpe
without the ENOEXEC support, and move the current semantic to compat
support. 

We can then even add the security mitigations suggested by Paul on compat
symbols without the drawbacks raised by you, at least on newer programs
linked to the newer symbols.
  

Patch

diff --git a/NEWS b/NEWS
index 0820984547..73388e838d 100644
--- a/NEWS
+++ b/NEWS
@@ -39,8 +39,7 @@  Deprecated and removed features, and other changes affecting compatibility:
 
 * The deprecated <sys/vtimes.h> header and the function vtimes have been
   removed.  To support old binaries, the vtimes function continues to exist
-  as a compatibility symbol.  Applications should use the getrlimit or
-  prlimit.
+  as a compatibility symbol.  Applications should use getrlimit or prlimit.
 
 * Following a change in the tzdata 2018a release upstream, the zdump
   program is now installed in the /usr/bin subdirectory.  Previously,
@@ -53,6 +52,16 @@  Deprecated and removed features, and other changes affecting compatibility:
   libraries that use this type in their interfaces.  The new definition
   improves consistency with compiler behavior in many scenarios.
 
+* When direct execution of a program fails with ENOEXEC, the functions
+  execlp, execvp, and execvpe will not attempt to run it as a shell
+  script anymore.  The same change has been made to the compatibility
+  versions of posix_spawn and posix_spawnp (symbol version GLIBC_2_2).
+  This is an intentional deviation from POSIX, anticipating the change
+  requested in Austin Group bug #1435; the old behavior could cause
+  severe malfunctions of correct-looking code, and theoretically could
+  open security holes.  Also, it was inconsistent with the behavior of
+  the rest of the exec* family of functions.
+
 Changes to build and runtime requirements:
 
 * On Linux, the system administrator needs to configure /dev/pts with
diff --git a/include/unistd.h b/include/unistd.h
index 54becbc9eb..ed6d8bd2b0 100644
--- a/include/unistd.h
+++ b/include/unistd.h
@@ -83,8 +83,6 @@  libc_hidden_proto (__getcwd)
 extern int __rmdir (const char *__path) attribute_hidden;
 extern int __execvpe (const char *file, char *const argv[],
 		      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
    bytes of BUF.  Returns NULL if the directory couldn't be determined or
diff --git a/posix/Makefile b/posix/Makefile
index 4bfc8d942c..f9208ea72a 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -88,8 +88,8 @@  tests		:= test-errno tstgetopt testfnm runtests runptests \
 		   tst-execvp1 tst-execvp2 tst-execlp1 tst-execlp2 \
 		   tst-execv1 tst-execv2 tst-execl1 tst-execl2 \
 		   tst-execve1 tst-execve2 tst-execle1 tst-execle2 \
-		   tst-execvp3 tst-execvp4 \
-		   tst-execvpe1 tst-execvpe2 tst-execvpe3 tst-execvpe4 \
+		   tst-execvp4 \
+		   tst-execvpe1 tst-execvpe2 tst-execvpe4 \
 		   tst-execvpe5 tst-execvpe6 \
 		   tst-getaddrinfo3 tst-fnmatch2 tst-cpucount tst-cpuset \
 		   bug-getopt1 bug-getopt2 bug-getopt3 bug-getopt4 \
@@ -105,7 +105,7 @@  tests		:= test-errno tstgetopt testfnm runtests runptests \
 		   tst-wordexp-nocmd
 tests-internal	:= bug-regex5 bug-regex20 bug-regex33 \
 		   tst-rfc3484 tst-rfc3484-2 tst-rfc3484-3 \
-		   tst-glob_lstat_compat tst-spawn4-compat
+		   tst-glob_lstat_compat
 tests-container := bug-ga2
 xtests		:= tst-getaddrinfo4 tst-getaddrinfo5
 ifeq (yes,$(build-shared))
diff --git a/posix/execvpe.c b/posix/execvpe.c
index 7ddc9c9d60..540dd817b7 100644
--- a/posix/execvpe.c
+++ b/posix/execvpe.c
@@ -33,43 +33,10 @@ 
 # endif
 #endif
 
-/* The file is accessible but it is not an executable file.  Invoke
-   the shell to interpret it as a script.  */
-static void
-maybe_script_execute (const char *file, char *const argv[], char *const envp[])
-{
-  ptrdiff_t argc;
-  for (argc = 0; argv[argc] != NULL; argc++)
-    {
-      if (argc == INT_MAX - 1)
-	{
-	  errno = E2BIG;
-	  return;
-	}
-    }
-
-  /* Construct an argument list for the shell based on original arguments:
-     1. Empty list (argv = { NULL }, argc = 1 }: new argv will contain 3
-	arguments - default shell, script to execute, and ending NULL.
-     2. Non empty argument list (argc = { ..., NULL }, argc > 1}: new argv
-	will contain also the default shell and the script to execute.  It
-	will also skip the script name in arguments and only copy script
-	arguments.  */
-  char *new_argv[argc > 1 ? 2 + argc : 3];
-  new_argv[0] = (char *) _PATH_BSHELL;
-  new_argv[1] = (char *) file;
-  if (argc > 1)
-    memcpy (new_argv + 2, argv + 1, argc * sizeof (char *));
-  else
-    new_argv[2] = NULL;
-
-  /* Execute the shell.  */
-  __execve (new_argv[0], new_argv, envp);
-}
-
-static int
-__execvpe_common (const char *file, char *const argv[], char *const envp[],
-	          bool exec_script)
+/* 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[])
 {
   /* We check the simple case first. */
   if (*file == '\0')
@@ -82,10 +49,6 @@  __execvpe_common (const char *file, char *const argv[], char *const envp[],
   if (strchr (file, '/') != NULL)
     {
       __execve (file, argv, envp);
-
-      if (errno == ENOEXEC && exec_script)
-        maybe_script_execute (file, argv, envp);
-
       return -1;
     }
 
@@ -135,13 +98,6 @@  __execvpe_common (const char *file, char *const argv[], char *const envp[],
 
       __execve (buffer, argv, envp);
 
-      if (errno == ENOEXEC && exec_script)
-        /* 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
-           substitute argv and reuse it each time through the loop (so it
-           behaves as O(P+C) instead.  */
-        maybe_script_execute (buffer, argv, envp);
-
       switch (errno)
 	{
 	  case EACCES:
@@ -181,19 +137,4 @@  __execvpe_common (const char *file, char *const argv[], char *const envp[],
 
   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)
-
-/* 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);
-}
diff --git a/posix/spawn.c b/posix/spawn.c
index 2c7da407aa..593f9a5c2e 100644
--- a/posix/spawn.c
+++ b/posix/spawn.c
@@ -32,17 +32,9 @@  __posix_spawn (pid_t *pid, const char *path,
 versioned_symbol (libc, __posix_spawn, posix_spawn, GLIBC_2_15);
 libc_hidden_def (__posix_spawn)
 
-
+/* There is no longer any behavior difference between the GLIBC_2_2
+   and GLIBC_2_15 versions of __posix_spawn, but we have to keep both
+   symbols around for compatibility.  */
 #if SHLIB_COMPAT (libc, GLIBC_2_2, GLIBC_2_15)
-int
-attribute_compat_text_section
-__posix_spawn_compat (pid_t *pid, const char *file,
-		      const posix_spawn_file_actions_t *file_actions,
-		      const posix_spawnattr_t *attrp, char *const argv[],
-		      char *const envp[])
-{
-  return __spawni (pid, file, file_actions, attrp, argv, envp,
-		   SPAWN_XFLAGS_TRY_SHELL);
-}
-compat_symbol (libc, __posix_spawn_compat, posix_spawn, GLIBC_2_2);
+compat_symbol (libc, __posix_spawn, posix_spawn, GLIBC_2_2);
 #endif
diff --git a/posix/spawn_int.h b/posix/spawn_int.h
index 9be54c0416..8b3cdadb9b 100644
--- a/posix/spawn_int.h
+++ b/posix/spawn_int.h
@@ -64,7 +64,6 @@  struct __spawn_action
 };
 
 #define SPAWN_XFLAGS_USE_PATH	0x1
-#define SPAWN_XFLAGS_TRY_SHELL	0x2
 
 extern int __posix_spawn_file_actions_realloc (posix_spawn_file_actions_t *
 					       file_actions)
diff --git a/posix/spawnp.c b/posix/spawnp.c
index 344c74d9b0..1ede6fff36 100644
--- a/posix/spawnp.c
+++ b/posix/spawnp.c
@@ -32,17 +32,9 @@  __posix_spawnp (pid_t *pid, const char *file,
 }
 versioned_symbol (libc, __posix_spawnp, posix_spawnp, GLIBC_2_15);
 
-
+/* There is no longer any behavior difference between the GLIBC_2_2
+   and GLIBC_2_15 versions of __posix_spawnp, but we have to keep both
+   symbols around for compatibility.  */
 #if SHLIB_COMPAT (libc, GLIBC_2_2, GLIBC_2_15)
-int
-attribute_compat_text_section
-__posix_spawnp_compat (pid_t *pid, const char *file,
-		       const posix_spawn_file_actions_t *file_actions,
-		       const posix_spawnattr_t *attrp, char *const argv[],
-		       char *const envp[])
-{
-  return __spawni (pid, file, file_actions, attrp, argv, envp,
-		   SPAWN_XFLAGS_USE_PATH | SPAWN_XFLAGS_TRY_SHELL);
-}
-compat_symbol (libc, __posix_spawnp_compat, posix_spawnp, GLIBC_2_2);
+compat_symbol (libc, __posix_spawnp, posix_spawnp, GLIBC_2_2);
 #endif
diff --git a/posix/tst-execvp3.c b/posix/tst-execvp3.c
deleted file mode 100644
index 02a937c2da..0000000000
--- a/posix/tst-execvp3.c
+++ /dev/null
@@ -1,45 +0,0 @@ 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/stat.h>
-
-
-static void do_prepare (void);
-#define PREPARE(argc, argv) do_prepare ()
-static int do_test (void);
-#define TEST_FUNCTION do_test ()
-
-#include "../test-skeleton.c"
-
-#ifndef EXECVP
-# define EXECVP(file, argv)  execvp (file, argv)
-#endif
-
-static char *fname;
-
-static void
-do_prepare (void)
-{
-  int fd = create_temp_file ("testscript", &fname);
-  dprintf (fd, "echo foo\n");
-  fchmod (fd, 0700);
-  close (fd);
-}
-
-
-static int
-do_test (void)
-{
-  if  (setenv ("PATH", test_dir, 1) != 0)
-    {
-      puts ("setenv failed");
-      return 1;
-    }
-
-  char *argv[] = { fname, NULL };
-  EXECVP (basename (fname), argv);
-
-  /* If we come here, the execvp call failed.  */
-  return 1;
-}
diff --git a/posix/tst-execvpe3.c b/posix/tst-execvpe3.c
deleted file mode 100644
index c40b80bfcb..0000000000
--- a/posix/tst-execvpe3.c
+++ /dev/null
@@ -1,20 +0,0 @@ 
-/* Check script execution without shebang for execvpe.
-   Copyright (C) 2016-2020 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
-   <https://www.gnu.org/licenses/>.  */
-
-#define EXECVP(file, argv) execvpe (file, argv, NULL)
-#include <posix/tst-execvp3.c>
diff --git a/posix/tst-execvpe6.c b/posix/tst-execvpe6.c
index 4aa60acf89..b82756db17 100644
--- a/posix/tst-execvpe6.c
+++ b/posix/tst-execvpe6.c
@@ -41,12 +41,12 @@  do_prepare (void)
   close (logfd);
 
   int fd1 = create_temp_file ("testscript", &fname1);
-  dprintf (fd1, "echo foo $1 $2 $3 > %s\n", logname);
+  dprintf (fd1, "#! /bin/sh\necho foo $1 $2 $3 > %s\n", logname);
   fchmod (fd1, 0700);
   close (fd1);
 
   int fd2 = create_temp_file ("testscript", &fname2);
-  dprintf (fd2, "echo foo > %s\n", logname);
+  dprintf (fd2, "#! /bin/sh\necho foo > %s\n", logname);
   fchmod (fd2, 0700);
   close (fd2);
 }
diff --git a/posix/tst-spawn4-compat.c b/posix/tst-spawn4-compat.c
deleted file mode 100644
index 1880ffcb04..0000000000
--- a/posix/tst-spawn4-compat.c
+++ /dev/null
@@ -1,77 +0,0 @@ 
-/* Check if posix_spawn does handle correctly ENOEXEC files.
-   Copyright (C) 2018-2020 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
-   <https://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>
diff --git a/posix/tst-vfork3.c b/posix/tst-vfork3.c
index 8d18f03089..1e5ae9f034 100644
--- a/posix/tst-vfork3.c
+++ b/posix/tst-vfork3.c
@@ -90,9 +90,6 @@  do_test (void)
   char *argv1[] = { (char *) "script1.sh", (char *) "1", NULL };
   run_script ("script1.sh", argv1);
 
-  char *argv2[] = { (char *) "script2.sh", (char *) "2", NULL };
-  run_script ("script2.sh", argv2);
-
   /* Same as before but with execlp.  */
   for (size_t i = 0; i < 5; i++)
     {
@@ -104,7 +101,7 @@  do_test (void)
 	}
       else if (pid == 0)
 	{
-	  execlp ("script2.sh", "script2.sh", "3", NULL);
+	  execlp ("script1.sh", "script1.sh", "2", NULL);
 	  _exit (errno);
 	}
       int status;
@@ -121,8 +118,8 @@  do_test (void)
     }
 
   unsetenv ("PATH");
-  char *argv4[] = { (char *) "echo", (char *) "script 4", NULL };
-  run_script ("echo", argv4);
+  char *argv2[] = { (char *) "echo", (char *) "script 3", NULL };
+  run_script ("echo", argv2);
 
   return 0;
 }
@@ -153,23 +150,17 @@  do_prepare (void)
 
   char script0[len + sizeof "/script0.sh"];
   char script1[len + sizeof "/script1.sh"];
-  char script2[len + sizeof "/script2.sh"];
 
   strcpy (stpcpy (script0, tmpdirname), "/script0.sh");
   strcpy (stpcpy (script1, tmpdirname), "/script1.sh");
-  strcpy (stpcpy (script2, tmpdirname), "/script2.sh");
 
   add_temp_file (tmpdirname);
   add_temp_file (script0);
   add_temp_file (script1);
-  add_temp_file (script2);
 
   const char content0[] = "#!/bin/sh\necho empty\n";
   create_script (script0, content0, sizeof content0);
 
   const char content1[] = "#!/bin/sh\necho script $1\n";
   create_script (script1, content1, sizeof content1);
-
-  const char content2[] = "echo script $1\n";
-  create_script (script2, content2, sizeof content2);
 }
diff --git a/sysdeps/posix/spawni.c b/sysdeps/posix/spawni.c
index 943a598771..17ad56f213 100644
--- a/sysdeps/posix/spawni.c
+++ b/sysdeps/posix/spawni.c
@@ -27,7 +27,6 @@ 
 #include <sys/mman.h>
 #include <not-cancel.h>
 #include <local-setxid.h>
-#include <shlib-compat.h>
 #include <nptl/pthreadP.h>
 #include <dl-sysdep.h>
 #include <libc-pointer-arith.h>
@@ -56,31 +55,6 @@  struct posix_spawn_args
   int pipe[2];
 };
 
-/* Older version requires that shell script without shebang definition
-   to be called explicitly using /bin/sh (_PATH_BSHELL).  */
-static void
-maybe_script_execute (struct posix_spawn_args *args)
-{
-  if (SHLIB_COMPAT (libc, GLIBC_2_2, GLIBC_2_15)
-      && (args->xflags & SPAWN_XFLAGS_TRY_SHELL) && errno == ENOEXEC)
-    {
-      char *const *argv = args->argv;
-      ptrdiff_t argc = args->argc;
-
-      /* Construct an argument list for the shell.  */
-      char *new_argv[argc + 2];
-      new_argv[0] = (char *) _PATH_BSHELL;
-      new_argv[1] = (char *) args->file;
-      if (argc > 1)
-	memcpy (new_argv + 2, argv + 1, argc * sizeof (char *));
-      else
-	new_argv[2] = NULL;
-
-      /* Execute the shell.  */
-      args->exec (new_argv[0], new_argv, args->envp);
-    }
-}
-
 /* Function used in the clone call to setup the signals mask, posix_spawn
    attributes, and file actions.  */
 static int
@@ -174,12 +148,13 @@  __spawni_child (void *arguments)
 
 	    case spawn_do_open:
 	      {
-		/* POSIX states that if fildes was already an open file descriptor,
-		   it shall be closed before the new file is opened.  This avoid
-		   pontential issues when posix_spawn plus addopen action is called
-		   with the process already at maximum number of file descriptor
-		   opened and also for multiple actions on single-open special
-		   paths (like /dev/watchdog).  */
+		/* If fildes was already an open file descriptor,
+		   POSIX specifies we should close it before opening
+		   the new file.  This avoids potential issues when
+		   posix_spawn is called with the process already
+		   close to its maximum number of open file
+		   descriptors, and also with special paths that can
+		   only be opened once (like /dev/watchdog).  */
 		__close_nocancel (action->action.open_action.fd);
 
 		int new_fd = __open_nocancel (action->action.open_action.path,
@@ -242,11 +217,6 @@  __spawni_child (void *arguments)
 
   args->exec (args->file, args->argv, args->envp);
 
-  /* This is compatibility function required to enable posix_spawn run
-     script without shebang definition for older posix_spawn versions
-     (2.15).  */
-  maybe_script_execute (args);
-
 fail:
   /* errno should have an appropriate non-zero value; otherwise,
      there's a bug in glibc or the kernel.  For lack of an error code
@@ -332,8 +302,6 @@  __spawni (pid_t * pid, const char *file,
 	  const posix_spawnattr_t * attrp, char *const argv[],
 	  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,
-		    xflags & SPAWN_XFLAGS_USE_PATH ? __execvpex : __execve);
+		    xflags & SPAWN_XFLAGS_USE_PATH ? __execvpe : __execve);
 }
diff --git a/sysdeps/unix/sysv/linux/spawni.c b/sysdeps/unix/sysv/linux/spawni.c
index f157bfffd2..61016d6f5b 100644
--- a/sysdeps/unix/sysv/linux/spawni.c
+++ b/sysdeps/unix/sysv/linux/spawni.c
@@ -26,7 +26,6 @@ 
 #include <sys/mman.h>
 #include <not-cancel.h>
 #include <local-setxid.h>
-#include <shlib-compat.h>
 #include <nptl/pthreadP.h>
 #include <dl-sysdep.h>
 #include <libc-pointer-arith.h>
@@ -89,31 +88,6 @@  struct posix_spawn_args
   int err;
 };
 
-/* Older version requires that shell script without shebang definition
-   to be called explicitly using /bin/sh (_PATH_BSHELL).  */
-static void
-maybe_script_execute (struct posix_spawn_args *args)
-{
-  if (SHLIB_COMPAT (libc, GLIBC_2_2, GLIBC_2_15)
-      && (args->xflags & SPAWN_XFLAGS_TRY_SHELL) && errno == ENOEXEC)
-    {
-      char *const *argv = args->argv;
-      ptrdiff_t argc = args->argc;
-
-      /* Construct an argument list for the shell.  */
-      char *new_argv[argc + 2];
-      new_argv[0] = (char *) _PATH_BSHELL;
-      new_argv[1] = (char *) args->file;
-      if (argc > 1)
-	memcpy (new_argv + 2, argv + 1, argc * sizeof (char *));
-      else
-	new_argv[2] = NULL;
-
-      /* Execute the shell.  */
-      args->exec (new_argv[0], new_argv, args->envp);
-    }
-}
-
 /* Function used in the clone call to setup the signals mask, posix_spawn
    attributes, and file actions.  It run on its own stack (provided by the
    posix_spawn call).  */
@@ -221,12 +195,13 @@  __spawni_child (void *arguments)
 
 	    case spawn_do_open:
 	      {
-		/* POSIX states that if fildes was already an open file descriptor,
-		   it shall be closed before the new file is opened.  This avoid
-		   pontential issues when posix_spawn plus addopen action is called
-		   with the process already at maximum number of file descriptor
-		   opened and also for multiple actions on single-open special
-		   paths (like /dev/watchdog).  */
+		/* If fildes was already an open file descriptor,
+		   POSIX specifies we should close it before opening
+		   the new file.  This avoids potential issues when
+		   posix_spawn is called with the process already
+		   close to its maximum number of open file
+		   descriptors, and also with special paths that can
+		   only be opened once (like /dev/watchdog).  */
 		__close_nocancel (action->action.open_action.fd);
 
 		int ret = __open_nocancel (action->action.open_action.path,
@@ -291,11 +266,6 @@  __spawni_child (void *arguments)
 
   args->exec (args->file, args->argv, args->envp);
 
-  /* This is compatibility function required to enable posix_spawn run
-     script without shebang definition for older posix_spawn versions
-     (2.15).  */
-  maybe_script_execute (args);
-
 fail:
   /* errno should have an appropriate non-zero value; otherwise,
      there's a bug in glibc or the kernel.  For lack of an error code
@@ -426,8 +396,6 @@  __spawni (pid_t * pid, const char *file,
 	  const posix_spawnattr_t * attrp, char *const argv[],
 	  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,
-		    xflags & SPAWN_XFLAGS_USE_PATH ? __execvpex :__execve);
+		    xflags & SPAWN_XFLAGS_USE_PATH ? __execvpe : __execve);
 }