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