1 /* Tests for posix_spawn signal handling.
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    <http://www.gnu.org/licenses/>.  */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <getopt.h>
22 #include <spawn.h>
23 #include <fcntl.h>
24 #include <sys/wait.h>
25 #include <dirent.h>
26 #include <stdbool.h>
27 #include <errno.h>
28 #include <limits.h>
29 
30 #include <support/check.h>
31 #include <support/xunistd.h>
32 #include <support/support.h>
33 
34 #include <arch-fd_to_filename.h>
35 #include <array_length.h>
36 
37 /* Nonzero if the program gets called via `exec'.  */
38 static int restart;
39 
40 /* Hold the four initial argument used to respawn the process, plus
41    the extra '--direct' and '--restart', and a final NULL.  */
42 static char *initial_argv[7];
43 static int initial_argv_count;
44 
45 #define CMDLINE_OPTIONS \
46   { "restart", no_argument, &restart, 1 },
47 
48 #define NFDS 100
49 
50 static int
parse_fd(const char * str)51 parse_fd (const char *str)
52 {
53   char *endptr;
54   long unsigned int fd = strtoul (str, &endptr, 10);
55   if (*endptr != '\0' || fd > INT_MAX)
56     FAIL_EXIT1 ("invalid file descriptor value: %s", str);
57   return fd;
58 }
59 
60 /* Called on process re-execution.  The arguments are the expected opened
61    file descriptors.  */
62 _Noreturn static void
handle_restart(int argc,char * argv[])63 handle_restart (int argc, char *argv[])
64 {
65   TEST_VERIFY (argc > 0);
66   int lowfd = parse_fd (argv[0]);
67 
68   size_t nfds = argc > 1 ? argc - 1 : 0;
69   struct fd_t
70   {
71     int fd;
72     _Bool found;
73   } *fds = xmalloc (sizeof (struct fd_t) * nfds);
74   for (int i = 0; i < nfds; i++)
75     {
76       fds[i].fd = parse_fd (argv[i + 1]);
77       fds[i].found = false;
78     }
79 
80   DIR *dirp = opendir (FD_TO_FILENAME_PREFIX);
81   if (dirp == NULL)
82     FAIL_EXIT1 ("opendir (\"" FD_TO_FILENAME_PREFIX "\"): %m");
83 
84   while (true)
85     {
86       errno = 0;
87       struct dirent64 *e = readdir64 (dirp);
88       if (e == NULL)
89         {
90           if (errno != 0)
91             FAIL_EXIT1 ("readdir: %m");
92           break;
93         }
94 
95       if (e->d_name[0] == '.')
96         continue;
97 
98       char *endptr;
99       long int fd = strtol (e->d_name, &endptr, 10);
100       if (*endptr != '\0' || fd < 0 || fd > INT_MAX)
101         FAIL_EXIT1 ("readdir: invalid file descriptor name: /proc/self/fd/%s",
102                     e->d_name);
103 
104       /* Ignore the descriptors not in the range of the opened files.  */
105       if (fd < lowfd || fd == dirfd (dirp))
106 	continue;
107 
108       bool found = false;
109       for (int i = 0; i < nfds; i++)
110 	if (fds[i].fd == fd)
111 	  fds[i].found = found = true;
112 
113       if (!found)
114 	{
115 	  char *path = xasprintf ("/proc/self/fd/%s", e->d_name);
116 	  char *resolved = xreadlink (path);
117 	  FAIL_EXIT1 ("unexpected open file descriptor %ld: %s", fd, resolved);
118 	}
119     }
120   closedir (dirp);
121 
122   for (int i = 0; i < nfds; i++)
123     if (!fds[i].found)
124       FAIL_EXIT1 ("file descriptor %d not opened", fds[i].fd);
125 
126   free (fds);
127 
128   exit (EXIT_SUCCESS);
129 }
130 
131 static void
spawn_closefrom_test(posix_spawn_file_actions_t * fa,int lowfd,int highfd,int * extrafds,size_t nextrafds)132 spawn_closefrom_test (posix_spawn_file_actions_t *fa, int lowfd, int highfd,
133 		      int *extrafds, size_t nextrafds)
134 {
135   /* 3 or 7 elements from initial_argv:
136        + path to ld.so          optional
137        + --library-path         optional
138        + the library path       optional
139        + application name
140        + --direct
141        + --restart
142        + lowest opened file descriptor
143        + up to 2 * maximum_fd arguments (the expected open file descriptors),
144 	 plus NULL.  */
145 
146   int argv_size = initial_argv_count + 2 * NFDS + 1;
147   char *args[argv_size];
148   int argc = 0;
149 
150   for (char **arg = initial_argv; *arg != NULL; arg++)
151     args[argc++] = *arg;
152 
153   args[argc++] = xasprintf ("%d", lowfd);
154 
155   for (int i = lowfd; i < highfd; i++)
156     args[argc++] = xasprintf ("%d", i);
157 
158   for (int i = 0; i < nextrafds; i++)
159     args[argc++] = xasprintf ("%d", extrafds[i]);
160 
161   args[argc] = NULL;
162   TEST_VERIFY (argc < argv_size);
163 
164   pid_t pid;
165   int status;
166 
167   TEST_COMPARE (posix_spawn (&pid, args[0], fa, NULL, args, environ), 0);
168   TEST_COMPARE (xwaitpid (pid, &status, 0), pid);
169   TEST_VERIFY (WIFEXITED (status));
170   TEST_VERIFY (!WIFSIGNALED (status));
171   TEST_COMPARE (WEXITSTATUS (status), 0);
172 }
173 
174 static void
do_test_closefrom(void)175 do_test_closefrom (void)
176 {
177   int lowfd = support_open_dev_null_range (NFDS, O_RDONLY, 0600);
178   const int half_fd = lowfd + NFDS / 2;
179 
180   /* Close half of the descriptors and check result.  */
181   {
182     posix_spawn_file_actions_t fa;
183     TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
184 
185     int ret = posix_spawn_file_actions_addclosefrom_np (&fa, half_fd);
186     if (ret == EINVAL)
187       /* Hurd currently does not support closefrom fileaction.  */
188       FAIL_UNSUPPORTED ("posix_spawn_file_actions_addclosefrom_np unsupported");
189     TEST_COMPARE (ret, 0);
190 
191     spawn_closefrom_test (&fa, lowfd, half_fd, NULL, 0);
192 
193     TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
194   }
195 
196   /* Create some gaps, close up to a threshold, and check result.  */
197   xclose (lowfd + 57);
198   xclose (lowfd + 78);
199   xclose (lowfd + 81);
200   xclose (lowfd + 82);
201   xclose (lowfd + 84);
202   xclose (lowfd + 90);
203 
204   {
205     posix_spawn_file_actions_t fa;
206     TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
207 
208     TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, half_fd), 0);
209 
210     spawn_closefrom_test (&fa, lowfd, half_fd, NULL, 0);
211 
212     TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
213   }
214 
215   /* Close the remaining but the last one.  */
216   {
217     posix_spawn_file_actions_t fa;
218     TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
219 
220     TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd + 1), 0);
221 
222     spawn_closefrom_test (&fa, lowfd, lowfd + 1, NULL, 0);
223 
224     TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
225   }
226 
227   /* Close everything.  */
228   {
229     posix_spawn_file_actions_t fa;
230     TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
231 
232     TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd), 0);
233 
234     spawn_closefrom_test (&fa, lowfd, lowfd, NULL, 0);
235 
236     TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
237   }
238 
239   /* Close a range and add some file actions.  */
240   {
241     posix_spawn_file_actions_t fa;
242     TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
243 
244     TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd + 1), 0);
245     TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, lowfd, "/dev/null",
246 						    0666, O_RDONLY), 0);
247     TEST_COMPARE (posix_spawn_file_actions_adddup2 (&fa, lowfd, lowfd + 1), 0);
248     TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, lowfd, "/dev/null",
249 						    0666, O_RDONLY), 0);
250 
251     spawn_closefrom_test (&fa, lowfd, lowfd, (int[]){lowfd, lowfd + 1}, 2);
252 
253     TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
254   }
255 }
256 
257 static int
do_test(int argc,char * argv[])258 do_test (int argc, char *argv[])
259 {
260   /* We must have either:
261 
262      - one or four parameters if called initially:
263        + argv[1]: path for ld.so        optional
264        + argv[2]: "--library-path"      optional
265        + argv[3]: the library path      optional
266        + argv[4]: the application name
267 
268      - six parameters left if called through re-execution:
269        + argv[1]: the application name
270        + argv[2]: the lowest file descriptor expected
271        + argv[3]: first expected open file descriptor   optional
272        + argv[n]: last expected open file descritptor   optional
273 
274      * When built with --enable-hardcoded-path-in-tests or issued without
275        using the loader directly.  */
276 
277   if (restart)
278     /* Ignore the application name. */
279     handle_restart (argc - 1, &argv[1]);
280 
281   TEST_VERIFY_EXIT (argc == 2 || argc == 5);
282 
283   int i;
284 
285   for (i = 0; i < argc - 1; i++)
286     initial_argv[i] = argv[i + 1];
287   initial_argv[i++] = (char *) "--direct";
288   initial_argv[i++] = (char *) "--restart";
289 
290   initial_argv_count = i;
291 
292   do_test_closefrom ();
293 
294   return 0;
295 }
296 
297 #define TEST_FUNCTION_ARGV do_test
298 #include <support/test-driver.c>
299