build: ensure sys/select.h is included
[coreutils.git] / src / chroot.c
blob42ea5fa927c82c4a3a83028a5640c5f3ccdde738
1 /* chroot -- run command or shell with special root directory
2 Copyright (C) 1995-2019 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Roland McGrath. */
19 #include <config.h>
20 #include <getopt.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <pwd.h>
24 #include <grp.h>
26 #include "system.h"
27 #include "die.h"
28 #include "error.h"
29 #include "ignore-value.h"
30 #include "mgetgroups.h"
31 #include "quote.h"
32 #include "root-dev-ino.h"
33 #include "userspec.h"
34 #include "xstrtol.h"
36 /* The official name of this program (e.g., no 'g' prefix). */
37 #define PROGRAM_NAME "chroot"
39 #define AUTHORS proper_name ("Roland McGrath")
41 #ifndef MAXGID
42 # define MAXGID GID_T_MAX
43 #endif
45 static inline bool uid_unset (uid_t uid) { return uid == (uid_t) -1; }
46 static inline bool gid_unset (gid_t gid) { return gid == (gid_t) -1; }
47 #define uid_set(x) (!uid_unset (x))
48 #define gid_set(x) (!gid_unset (x))
50 enum
52 GROUPS = UCHAR_MAX + 1,
53 USERSPEC,
54 SKIP_CHDIR
57 static struct option const long_opts[] =
59 {"groups", required_argument, NULL, GROUPS},
60 {"userspec", required_argument, NULL, USERSPEC},
61 {"skip-chdir", no_argument, NULL, SKIP_CHDIR},
62 {GETOPT_HELP_OPTION_DECL},
63 {GETOPT_VERSION_OPTION_DECL},
64 {NULL, 0, NULL, 0}
67 #if ! HAVE_SETGROUPS
68 /* At least Interix lacks supplemental group support. */
69 static int
70 setgroups (size_t size, gid_t const *list _GL_UNUSED)
72 if (size == 0)
74 /* Return success when clearing supplemental groups
75 as ! HAVE_SETGROUPS should only be the case on
76 platforms that don't support supplemental groups. */
77 return 0;
79 else
81 errno = ENOTSUP;
82 return -1;
85 #endif
87 /* Determine the group IDs for the specified supplementary GROUPS,
88 which is a comma separated list of supplementary groups (names or numbers).
89 Allocate an array for the parsed IDs and store it in PGIDS,
90 which may be allocated even on parse failure.
91 Update the number of parsed groups in PN_GIDS on success.
92 Upon any failure return nonzero, and issue diagnostic if SHOW_ERRORS is true.
93 Otherwise return zero. */
95 static int
96 parse_additional_groups (char const *groups, GETGROUPS_T **pgids,
97 size_t *pn_gids, bool show_errors)
99 GETGROUPS_T *gids = NULL;
100 size_t n_gids_allocated = 0;
101 size_t n_gids = 0;
102 char *buffer = xstrdup (groups);
103 char const *tmp;
104 int ret = 0;
106 for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
108 struct group *g;
109 unsigned long int value;
111 if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
113 while (isspace (to_uchar (*tmp)))
114 tmp++;
115 if (*tmp != '+')
117 /* Handle the case where the name is numeric. */
118 g = getgrnam (tmp);
119 if (g != NULL)
120 value = g->gr_gid;
122 /* Flag that we've got a group from the number. */
123 g = (struct group *) (intptr_t) ! NULL;
125 else
127 g = getgrnam (tmp);
128 if (g != NULL)
129 value = g->gr_gid;
132 if (g == NULL)
134 ret = -1;
136 if (show_errors)
138 error (0, errno, _("invalid group %s"), quote (tmp));
139 continue;
142 break;
145 if (n_gids == n_gids_allocated)
146 gids = X2NREALLOC (gids, &n_gids_allocated);
147 gids[n_gids++] = value;
150 if (ret == 0 && n_gids == 0)
152 if (show_errors)
153 error (0, 0, _("invalid group list %s"), quote (groups));
154 ret = -1;
157 *pgids = gids;
159 if (ret == 0)
160 *pn_gids = n_gids;
162 free (buffer);
163 return ret;
166 /* Return whether the passed path is equivalent to "/".
167 Note we don't compare against get_root_dev_ino() as "/"
168 could be bind mounted to a separate location. */
170 static bool
171 is_root (const char* dir)
173 char *resolved = canonicalize_file_name (dir);
174 bool is_res_root = resolved && STREQ ("/", resolved);
175 free (resolved);
176 return is_res_root;
179 void
180 usage (int status)
182 if (status != EXIT_SUCCESS)
183 emit_try_help ();
184 else
186 printf (_("\
187 Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
188 or: %s OPTION\n\
189 "), program_name, program_name);
191 fputs (_("\
192 Run COMMAND with root directory set to NEWROOT.\n\
194 "), stdout);
196 fputs (_("\
197 --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\
198 "), stdout);
199 fputs (_("\
200 --userspec=USER:GROUP specify user and group (ID or name) to use\n\
201 "), stdout);
202 printf (_("\
203 --skip-chdir do not change working directory to %s\n\
204 "), quoteaf ("/"));
206 fputs (HELP_OPTION_DESCRIPTION, stdout);
207 fputs (VERSION_OPTION_DESCRIPTION, stdout);
208 fputs (_("\
210 If no command is given, run '\"$SHELL\" -i' (default: '/bin/sh -i').\n\
211 "), stdout);
212 emit_ancillary_info (PROGRAM_NAME);
214 exit (status);
218 main (int argc, char **argv)
220 int c;
222 /* Input user and groups spec. */
223 char *userspec = NULL;
224 char const *username = NULL;
225 char const *groups = NULL;
226 bool skip_chdir = false;
228 /* Parsed user and group IDs. */
229 uid_t uid = -1;
230 gid_t gid = -1;
231 GETGROUPS_T *out_gids = NULL;
232 size_t n_gids = 0;
234 initialize_main (&argc, &argv);
235 set_program_name (argv[0]);
236 setlocale (LC_ALL, "");
237 bindtextdomain (PACKAGE, LOCALEDIR);
238 textdomain (PACKAGE);
240 initialize_exit_failure (EXIT_CANCELED);
241 atexit (close_stdout);
243 while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
245 switch (c)
247 case USERSPEC:
249 userspec = optarg;
250 /* Treat 'user:' just like 'user'
251 as we lookup the primary group by default
252 (and support doing so for UIDs as well as names. */
253 size_t userlen = strlen (userspec);
254 if (userlen && userspec[userlen - 1] == ':')
255 userspec[userlen - 1] = '\0';
256 break;
259 case GROUPS:
260 groups = optarg;
261 break;
263 case SKIP_CHDIR:
264 skip_chdir = true;
265 break;
267 case_GETOPT_HELP_CHAR;
269 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
271 default:
272 usage (EXIT_CANCELED);
276 if (argc <= optind)
278 error (0, 0, _("missing operand"));
279 usage (EXIT_CANCELED);
282 char const *newroot = argv[optind];
283 bool is_oldroot = is_root (newroot);
285 if (! is_oldroot && skip_chdir)
287 error (0, 0, _("option --skip-chdir only permitted if NEWROOT is old %s"),
288 quoteaf ("/"));
289 usage (EXIT_CANCELED);
292 if (! is_oldroot)
294 /* We have to look up users and groups twice.
295 - First, outside the chroot to load potentially necessary passwd/group
296 parsing plugins (e.g. NSS);
297 - Second, inside chroot to redo parsing in case IDs are different.
298 Within chroot lookup is the main justification for having
299 the --user option supported by the chroot command itself. */
300 if (userspec)
301 ignore_value (parse_user_spec (userspec, &uid, &gid, NULL, NULL));
303 /* If no gid is supplied or looked up, do so now.
304 Also lookup the username for use with getgroups. */
305 if (uid_set (uid) && (! groups || gid_unset (gid)))
307 const struct passwd *pwd;
308 if ((pwd = getpwuid (uid)))
310 if (gid_unset (gid))
311 gid = pwd->pw_gid;
312 username = pwd->pw_name;
316 if (groups && *groups)
317 ignore_value (parse_additional_groups (groups, &out_gids, &n_gids,
318 false));
319 #if HAVE_SETGROUPS
320 else if (! groups && gid_set (gid) && username)
322 int ngroups = xgetgroups (username, gid, &out_gids);
323 if (0 < ngroups)
324 n_gids = ngroups;
326 #endif
329 if (chroot (newroot) != 0)
330 die (EXIT_CANCELED, errno, _("cannot change root directory to %s"),
331 quoteaf (newroot));
333 if (! skip_chdir && chdir ("/"))
334 die (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
336 if (argc == optind + 1)
338 /* No command. Run an interactive shell. */
339 char *shell = getenv ("SHELL");
340 if (shell == NULL)
341 shell = bad_cast ("/bin/sh");
342 argv[0] = shell;
343 argv[1] = bad_cast ("-i");
344 argv[2] = NULL;
346 else
348 /* The following arguments give the command. */
349 argv += optind + 1;
352 /* Attempt to set all three: supplementary groups, group ID, user ID.
353 Diagnose any failures. If any have failed, exit before execvp. */
354 if (userspec)
356 char const *err = parse_user_spec (userspec, &uid, &gid, NULL, NULL);
358 if (err && uid_unset (uid) && gid_unset (gid))
359 die (EXIT_CANCELED, errno, "%s", (err));
362 /* If no gid is supplied or looked up, do so now.
363 Also lookup the username for use with getgroups. */
364 if (uid_set (uid) && (! groups || gid_unset (gid)))
366 const struct passwd *pwd;
367 if ((pwd = getpwuid (uid)))
369 if (gid_unset (gid))
370 gid = pwd->pw_gid;
371 username = pwd->pw_name;
373 else if (gid_unset (gid))
375 die (EXIT_CANCELED, errno,
376 _("no group specified for unknown uid: %d"), (int) uid);
380 GETGROUPS_T *gids = out_gids;
381 GETGROUPS_T *in_gids = NULL;
382 if (groups && *groups)
384 if (parse_additional_groups (groups, &in_gids, &n_gids, !n_gids) != 0)
386 if (! n_gids)
387 return EXIT_CANCELED;
388 /* else look-up outside the chroot worked, then go with those. */
390 else
391 gids = in_gids;
393 #if HAVE_SETGROUPS
394 else if (! groups && gid_set (gid) && username)
396 int ngroups = xgetgroups (username, gid, &in_gids);
397 if (ngroups <= 0)
399 if (! n_gids)
400 die (EXIT_CANCELED, errno,
401 _("failed to get supplemental groups"));
402 /* else look-up outside the chroot worked, then go with those. */
404 else
406 n_gids = ngroups;
407 gids = in_gids;
410 #endif
412 if ((uid_set (uid) || groups) && setgroups (n_gids, gids) != 0)
413 die (EXIT_CANCELED, errno, _("failed to set supplemental groups"));
415 free (in_gids);
416 free (out_gids);
418 if (gid_set (gid) && setgid (gid))
419 die (EXIT_CANCELED, errno, _("failed to set group-ID"));
421 if (uid_set (uid) && setuid (uid))
422 die (EXIT_CANCELED, errno, _("failed to set user-ID"));
424 /* Execute the given command. */
425 execvp (argv[0], argv);
427 int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
428 error (0, errno, _("failed to run command %s"), quote (argv[0]));
429 return exit_status;