diff options
Diffstat (limited to 'src/chroot.c')
-rw-r--r-- | src/chroot.c | 253 |
1 files changed, 195 insertions, 58 deletions
diff --git a/src/chroot.c b/src/chroot.c index 1c1a9769..fff0b533 100644 --- a/src/chroot.c +++ b/src/chroot.c @@ -1,5 +1,5 @@ /* chroot -- run command or shell with special root directory - Copyright (C) 1995-2013 Free Software Foundation, Inc. + Copyright (C) 1995-2014 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 @@ -20,11 +20,15 @@ #include <getopt.h> #include <stdio.h> #include <sys/types.h> +#include <pwd.h> #include <grp.h> #include "system.h" #include "error.h" +#include "ignore-value.h" +#include "mgetgroups.h" #include "quote.h" +#include "root-dev-ino.h" #include "userspec.h" #include "xstrtol.h" @@ -37,6 +41,11 @@ # define MAXGID GID_T_MAX #endif +static inline bool uid_unset (uid_t uid) { return uid == (uid_t) -1; } +static inline bool gid_unset (gid_t gid) { return gid == (gid_t) -1; } +#define uid_set(x) (!uid_unset (x)) +#define gid_set(x) (!gid_unset (x)) + enum { GROUPS = UCHAR_MAX + 1, @@ -53,23 +62,36 @@ static struct option const long_opts[] = }; #if ! HAVE_SETGROUPS -/* At least Interix lacks supplemental group support. Define an - always-successful replacement to avoid checking for setgroups - availability everywhere, just to support broken platforms. */ +/* At least Interix lacks supplemental group support. */ static int -setgroups (size_t size ATTRIBUTE_UNUSED, gid_t const *list ATTRIBUTE_UNUSED) +setgroups (size_t size, gid_t const *list _GL_UNUSED) { - return 0; + if (size == 0) + { + /* Return success when clearing supplemental groups + as ! HAVE_SETGROUPS should only be the case on + platforms that don't support supplemental groups. */ + return 0; + } + else + { + errno = ENOTSUP; + return -1; + } } #endif -/* Call setgroups to set the supplementary groups to those listed in GROUPS. - GROUPS is a comma separated list of supplementary groups (names or numbers). - Parse that list, converting any names to numbers, and call setgroups on the - resulting numbers. Upon any failure give a diagnostic and return nonzero. +/* 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 -set_additional_groups (char const *groups) +parse_additional_groups (char const *groups, GETGROUPS_T **pgids, + size_t *pn_gids, bool show_errors) { GETGROUPS_T *gids = NULL; size_t n_gids_allocated = 0; @@ -84,7 +106,19 @@ set_additional_groups (char const *groups) unsigned long int value; if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID) - g = getgrgid (value); + { + while (isspace (to_uchar (*tmp))) + tmp++; + if (*tmp != '+') + { + /* Handle the case where the name is numeric. */ + g = getgrnam (tmp); + if (g != NULL) + value = g->gr_gid; + } + /* Flag that we've got a group from the number. */ + g = (struct group *) (intptr_t) ! NULL; + } else { g = getgrnam (tmp); @@ -94,9 +128,15 @@ set_additional_groups (char const *groups) if (g == NULL) { - error (0, errno, _("invalid group %s"), quote (tmp)); ret = -1; - continue; + + if (show_errors) + { + error (0, errno, _("invalid group %s"), quote (tmp)); + continue; + } + + break; } if (n_gids == n_gids_allocated) @@ -106,22 +146,36 @@ set_additional_groups (char const *groups) if (ret == 0 && n_gids == 0) { - error (0, 0, _("invalid group list %s"), quote (groups)); + if (show_errors) + error (0, 0, _("invalid group list %s"), quote (groups)); ret = -1; } + *pgids = gids; + if (ret == 0) - { - ret = setgroups (n_gids, gids); - if (ret) - error (0, errno, _("failed to set additional groups")); - } + *pn_gids = n_gids; free (buffer); - free (gids); return ret; } +static bool +is_root (const char* dir) +{ + struct dev_ino root_ino; + if (! get_root_dev_ino (&root_ino)) + error (EXIT_CANCELED, errno, _("failed to get attributes of %s"), + quote ("/")); + + struct stat arg_st; + if (stat (dir, &arg_st) == -1) + error (EXIT_CANCELED, errno, _("failed to get attributes of %s"), + quote (dir)); + + return SAME_INODE (root_ino, arg_st); +} + void usage (int status) { @@ -159,9 +213,18 @@ int main (int argc, char **argv) { int c; - char const *userspec = NULL; + + /* Input user and groups spec. */ + char *userspec = NULL; + char const *username = NULL; char const *groups = NULL; + /* Parsed user and group IDs. */ + uid_t uid = -1; + gid_t gid = -1; + GETGROUPS_T *out_gids = NULL; + size_t n_gids = 0; + initialize_main (&argc, &argv); set_program_name (argv[0]); setlocale (LC_ALL, ""); @@ -176,8 +239,16 @@ main (int argc, char **argv) switch (c) { case USERSPEC: - userspec = optarg; - break; + { + userspec = optarg; + /* 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); + if (userlen && userspec[userlen - 1] == ':') + userspec[userlen - 1] = '\0'; + break; + } case GROUPS: groups = optarg; @@ -198,12 +269,51 @@ main (int argc, char **argv) usage (EXIT_CANCELED); } - if (chroot (argv[optind]) != 0) - error (EXIT_CANCELED, errno, _("cannot change root directory to %s"), - argv[optind]); + /* Only do chroot specific actions if actually changing root. + The main difference here is that we don't change working dir. */ + if (! is_root (argv[optind])) + { + /* 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) + ignore_value (parse_user_spec (userspec, &uid, &gid, NULL, NULL)); + + /* 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))) + { + const struct passwd *pwd; + if ((pwd = getpwuid (uid))) + { + if (gid_unset (gid)) + gid = pwd->pw_gid; + username = pwd->pw_name; + } + } - if (chdir ("/")) - error (EXIT_CANCELED, errno, _("cannot chdir to root directory")); + if (groups && *groups) + ignore_value (parse_additional_groups (groups, &out_gids, &n_gids, + false)); +#if HAVE_SETGROUPS + else if (! groups && gid_set (gid) && username) + { + int ngroups = xgetgroups (username, gid, &out_gids); + if (0 < ngroups) + n_gids = ngroups; + } +#endif + + if (chroot (argv[optind]) != 0) + error (EXIT_CANCELED, errno, _("cannot change root directory to %s"), + argv[optind]); + + if (chdir ("/")) + error (EXIT_CANCELED, errno, _("cannot chdir to root directory")); + } if (argc == optind + 1) { @@ -221,51 +331,78 @@ main (int argc, char **argv) argv += optind + 1; } - bool fail = false; - /* Attempt to set all three: supplementary groups, group ID, user ID. Diagnose any failures. If any have failed, exit before execvp. */ if (userspec) { - uid_t uid = -1; - gid_t gid = -1; - char *user; - char *group; - char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group); + char const *err = parse_user_spec (userspec, &uid, &gid, NULL, NULL); - if (err) + if (err && uid_unset (uid) && gid_unset (gid)) error (EXIT_CANCELED, errno, "%s", err); + } - free (user); - free (group); - - if (groups && set_additional_groups (groups)) - fail = true; - - if (gid != (gid_t) -1 && setgid (gid)) + /* 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))) + { + const struct passwd *pwd; + if ((pwd = getpwuid (uid))) + { + if (gid_unset (gid)) + gid = pwd->pw_gid; + username = pwd->pw_name; + } + else if (gid_unset (gid)) { - error (0, errno, _("failed to set group-ID")); - fail = true; + error (EXIT_CANCELED, errno, + _("no group specified for unknown uid: %d"), (int) uid); } + } - if (uid != (uid_t) -1 && setuid (uid)) + GETGROUPS_T *gids = out_gids; + GETGROUPS_T *in_gids = NULL; + if (groups && *groups) + { + if (parse_additional_groups (groups, &in_gids, &n_gids, !n_gids) != 0) { - error (0, errno, _("failed to set user-ID")); - fail = true; + if (! n_gids) + exit (EXIT_CANCELED); + /* else look-up outside the chroot worked, then go with those. */ } + else + gids = in_gids; } - else +#if HAVE_SETGROUPS + else if (! groups && gid_set (gid) && username) { - /* Yes, this call is identical to the one above. - However, when --userspec and --groups groups are used together, - we don't want to call this function until after parsing USER:GROUP, - and it must be called before setuid. */ - if (groups && set_additional_groups (groups)) - fail = true; + int ngroups = xgetgroups (username, gid, &in_gids); + if (ngroups <= 0) + { + if (! n_gids) + error (EXIT_CANCELED, errno, + _("failed to get supplemental groups")); + /* else look-up outside the chroot worked, then go with those. */ + } + else + { + n_gids = ngroups; + gids = in_gids; + } } +#endif + + if ((uid_set (uid) || groups) && setgroups (n_gids, gids) != 0) + error (EXIT_CANCELED, errno, _("failed to %s supplemental groups"), + gids ? "set" : "clear"); + + free (in_gids); + free (out_gids); + + if (gid_set (gid) && setgid (gid)) + error (EXIT_CANCELED, errno, _("failed to set group-ID")); - if (fail) - exit (EXIT_CANCELED); + if (uid_set (uid) && setuid (uid)) + error (EXIT_CANCELED, errno, _("failed to set user-ID")); /* Execute the given command. */ execvp (argv[0], argv); |