1 /* Test execveat at the various corner cases.
2    Copyright (C) 2021-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <fcntl.h>
20 #include <errno.h>
21 #include <stdlib.h>
22 #include <sys/types.h>
23 #include <dirent.h>
24 #include <support/check.h>
25 #include <support/support.h>
26 #include <support/temp_file.h>
27 #include <support/xdlfcn.h>
28 #include <support/xstdio.h>
29 #include <support/xunistd.h>
30 #include <wait.h>
31 #include <support/test-driver.h>
32 
33 int
call_execveat(int fd,const char * pathname,int flags,int expected_fail,int num)34 call_execveat (int fd, const char *pathname, int flags, int expected_fail,
35                int num)
36 {
37   char *envp[] = { (char *) "FOO=3", NULL };
38   char *argv[] = { (char *) "sh", (char *) "-c", (char *) "exit $FOO", NULL };
39   pid_t pid;
40   int status;
41 
42   if (test_verbose > 0)
43     printf ("call line number: %d\n", num);
44 
45   pid = xfork ();
46   if (pid == 0)
47     {
48       TEST_COMPARE (execveat (fd, pathname, argv, envp, flags), -1);
49       if (errno == ENOSYS)
50 	exit (EXIT_UNSUPPORTED);
51       else if (errno == expected_fail)
52         {
53           if (test_verbose > 0)
54             printf ("expected fail: errno %d\n", errno);
55           _exit (0);
56         }
57       else
58         FAIL_EXIT1 ("execveat failed: %m (%d)", errno);
59     }
60   xwaitpid (pid, &status, 0);
61 
62   if (!WIFEXITED (status))
63     FAIL_RET ("child hasn't exited normally");
64 
65   if (WIFEXITED (status))
66     {
67       if (WEXITSTATUS (status) == EXIT_UNSUPPORTED)
68         FAIL_UNSUPPORTED ("execveat is unimplemented");
69       else if (expected_fail != 0)
70         TEST_COMPARE (WEXITSTATUS (status), 0);
71       else
72         TEST_COMPARE (WEXITSTATUS (status), 3);
73     }
74   return 0;
75 }
76 
77 static int
do_test(void)78 do_test (void)
79 {
80   DIR *dirp;
81   int fd;
82 #ifdef O_PATH
83   int fd_out;
84   char *tmp_dir, *symlink_name, *tmp_sh;
85   struct stat64 st;
86 #endif
87 
88   dirp = opendir ("/bin");
89   if (dirp == NULL)
90     FAIL_EXIT1 ("failed to open /bin");
91   fd = dirfd (dirp);
92 
93   /* Call execveat for various fd/pathname combinations.  */
94 
95   /* Check the pathname relative to a valid dirfd.  */
96   call_execveat (fd, "sh", 0, 0, __LINE__);
97   xchdir ("/bin");
98   /* Use the special value AT_FDCWD as dirfd. Quoting open(2):
99      If pathname is relative and dirfd is the special value AT_FDCWD, then
100      pathname is interpreted relative to the current working directory of
101      the calling process.  */
102   call_execveat (AT_FDCWD, "sh", 0, 0, __LINE__);
103   xclose (fd);
104 #ifdef O_PATH
105   /* Check the pathname relative to a valid dirfd with O_PATH.  */
106   fd = xopen ("/bin", O_PATH | O_DIRECTORY, O_RDONLY);
107   call_execveat (fd, "sh", 0, 0, __LINE__);
108   xclose (fd);
109 
110   /* Check absolute pathname, dirfd should be ignored.  */
111   call_execveat (AT_FDCWD, "/bin/sh", 0, 0, __LINE__);
112   fd = xopen ("/usr", O_PATH | O_DIRECTORY, 0);
113   /* Same check for absolute pathname, but with input file descriptor
114      openend with different flags.  The dirfd should be ignored.  */
115   call_execveat (fd, "/bin/sh", 0, 0, __LINE__);
116   xclose (fd);
117 #endif
118 
119   fd = xopen ("/usr", O_RDONLY, 0);
120   /* Same check for absolute pathname, but with input file descriptor
121      openend with different flags.  The dirfd should be ignored.  */
122   call_execveat (fd, "/bin/sh", 0, 0, __LINE__);
123   xclose (fd);
124 
125   fd = xopen ("/bin/sh", O_RDONLY, 0);
126   /* Check relative pathname, where dirfd does not point to a directory.  */
127   call_execveat (fd, "sh", 0, ENOTDIR, __LINE__);
128   /* Check absolute pathname, but dirfd is a regular file.  The dirfd
129      should be ignored.  */
130   call_execveat (fd, "/bin/sh", 0, 0, __LINE__);
131   xclose (fd);
132 
133 #ifdef O_PATH
134   /* Quoting open(2): O_PATH
135      Obtain a file descriptor that can be used for two purposes: to
136      indicate a location in the filesystem tree and to perform
137      operations that act purely at the file descriptor level.  */
138   fd = xopen ("/bin/sh", O_PATH, 0);
139   /* Check the empty pathname.  Dirfd is a regular file with O_PATH.  */
140   call_execveat (fd, "", 0, ENOENT, __LINE__);
141   /* Same check for an empty pathname, but with AT_EMPTY_PATH flag.
142      Quoting open(2):
143      If oldpath is an empty string, create a link to the file referenced
144      by olddirfd (which may have been obtained using the open(2) O_PATH flag. */
145   call_execveat (fd, "", AT_EMPTY_PATH, 0, __LINE__);
146   call_execveat (fd, "", AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, 0, __LINE__);
147   xclose (fd);
148 
149   /* Create a temporary directory "tmp_dir" and create a symbolik link tmp_sh
150      pointing to /bin/sh inside the tmp_dir. Open dirfd as a symbolic link.  */
151   tmp_dir = support_create_temp_directory ("tst-execveat_dir");
152   symlink_name = xasprintf ("%s/symlink", tmp_dir);
153   xsymlink ("tmp_sh", symlink_name);
154   add_temp_file (symlink_name);
155   tmp_sh = xasprintf ("%s/tmp_sh", tmp_dir);
156   add_temp_file (tmp_sh);
157   fd_out = xopen (symlink_name, O_CREAT | O_WRONLY, 0);
158   xstat ("/bin/sh", &st);
159   fd = xopen ("/bin/sh", O_RDONLY, 0);
160   xcopy_file_range (fd, 0, fd_out, 0, st.st_size, 0);
161   xfchmod (fd_out, 0700);
162   xclose (fd);
163   xclose (fd_out);
164   fd_out = xopen (symlink_name, O_PATH, 0);
165 
166  /* Check the empty pathname. Dirfd is a symbolic link.  */
167   call_execveat (fd_out, "", 0, ENOENT, __LINE__);
168   call_execveat (fd_out, "", AT_EMPTY_PATH, 0, __LINE__);
169   call_execveat (fd_out, "", AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, 0,
170                  __LINE__);
171   xclose (fd_out);
172   free (symlink_name);
173   free (tmp_sh);
174   free (tmp_dir);
175 #endif
176 
177   /* Call execveat with closed fd, we expect this to fail with EBADF.  */
178   call_execveat (fd, "sh", 0, EBADF, __LINE__);
179   /* Call execveat with closed fd, we expect this to pass because the pathname is
180      absolute.  */
181   call_execveat (fd, "/bin/sh", 0, 0, __LINE__);
182 
183   return 0;
184 }
185 
186 #include <support/test-driver.c>
187