From c45c51fe97193898f3909dcf5e4c0e117ab239a2 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Fri, 1 May 2009 23:50:11 +0200 Subject: chroot: accept new options --userspec=U:G and --groups=G1,G2,G3 * NEWS: Note chroot's new options. * doc/coreutils.texi: Document them. * src/chroot.c (main): Add support for --userspec and --groups. * tests/Makefile.am (root-tests): Add chroot/credentials. * tests/chroot/credentials: New file. * tests/test-lib.sh: Define NON_ROOT_GROUP to a default value. --- NEWS | 5 ++ doc/coreutils.texi | 23 +++++++-- src/chroot.c | 125 +++++++++++++++++++++++++++++++++++++++++++++-- tests/Makefile.am | 1 + tests/chroot/credentials | 43 ++++++++++++++++ tests/test-lib.sh | 1 + 6 files changed, 192 insertions(+), 6 deletions(-) create mode 100755 tests/chroot/credentials diff --git a/NEWS b/NEWS index 31f1b1a24..3af06e420 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,11 @@ GNU coreutils NEWS -*- outline -*- truncate -s failed to skip all whitespace in the option argument in some locales. +** New features + + chroot now accepts the options --userspec and --groups. + + * Noteworthy changes in release 7.4 (2009-05-07) [stable] ** Bug fixes diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 1a3075ffe..97ea830e1 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -14144,7 +14144,7 @@ underlying function is non-privileged due to lack of support in MS-Windows.} Synopses: @example -chroot @var{newroot} [@var{command} [@var{args}]@dots{}] +chroot @var{option} @var{newroot} [@var{command} [@var{args}]@dots{}] chroot @var{option} @end example @@ -14157,8 +14157,25 @@ variable or @command{/bin/sh} if not set, invoked with the @option{-i} option. @var{command} must not be a special built-in utility (@pxref{Special built-in utilities}). -The only options are @option{--help} and @option{--version}. @xref{Common -options}. Options must precede operands. +The program accepts the following options. Also see @ref{Common options}. +Options must precede operands. + +@table @samp + +@itemx --userspec=@var{user}[:@var{group}] +@opindex --userspec +By default, @var{command} is run with the same credentials +as the invoking process. +Use this option to run it as a different @var{user} and/or with a +different primary @var{group}. + +@itemx --groups=@var{groups} +@opindex --groups +Use this option to specify the supplementary @var{groups} to be +used by the new process. +The items in the list (names or numeric IDs) must be separated by commas. + +@end table Here are a few tips to help avoid common problems in using chroot. To start with a simple example, make @var{command} refer to a statically diff --git a/src/chroot.c b/src/chroot.c index 6d3fddf77..788a1fc41 100644 --- a/src/chroot.c +++ b/src/chroot.c @@ -21,17 +21,88 @@ #include #include #include +#include #include "system.h" #include "error.h" #include "long-options.h" #include "quote.h" +#include "userspec.h" +#include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "chroot" #define AUTHORS proper_name ("Roland McGrath") +#ifndef MAXGID +# define MAXGID GID_T_MAX +#endif + +enum +{ + GROUPS = UCHAR_MAX + 1, + USERSPEC +}; + +static struct option const long_opts[] = +{ + {"groups", required_argument, NULL, GROUPS}, + {"userspec", required_argument, NULL, USERSPEC}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Groups is a comma separated list of additional groups. */ +static int +set_additional_groups (char const *groups) +{ + GETGROUPS_T *gids = NULL; + size_t n_gids_allocated = 0; + size_t n_gids = 0; + char *buffer = xstrdup (groups); + char const *tmp; + int ret; + + for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ",")) + { + struct group *g; + unsigned long int value; + + if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID) + { + g = getgrgid (value); + } + else + { + g = getgrnam (tmp); + if (g != NULL) + value = g->gr_gid; + } + + if (g == NULL) + { + error (0, errno, _("cannot find group %s"), tmp); + free (buffer); + return 1; + } + + if (n_gids == n_gids_allocated) + gids = x2nrealloc (gids, &n_gids_allocated, sizeof *gids); + gids[n_gids++] = value; + } + + free (buffer); + + ret = setgroups (n_gids, gids); + + free (gids); + + return ret; +} + + void usage (int status) { @@ -41,13 +112,20 @@ usage (int status) else { printf (_("\ -Usage: %s NEWROOT [COMMAND [ARG]...]\n\ +Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\ or: %s OPTION\n\ "), program_name, program_name); + fputs (_("\ Run COMMAND with root directory set to NEWROOT.\n\ \n\ "), stdout); + + fputs (_("\ + --userspec=USER:GROUP specify user and group (ID or name) to use\n\ + --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ @@ -62,6 +140,10 @@ If no command is given, run ``${SHELL} -i'' (default: /bin/sh).\n\ int main (int argc, char **argv) { + int c; + char const *userspec = NULL; + char const *groups = NULL; + initialize_main (&argc, &argv); set_program_name (argv[0]); setlocale (LC_ALL, ""); @@ -73,8 +155,21 @@ main (int argc, char **argv) parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version, usage, AUTHORS, (char const *) NULL); - if (getopt_long (argc, argv, "+", NULL, NULL) != -1) - usage (EXIT_FAILURE); + + while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1) + { + switch (c) + { + case USERSPEC: + userspec = optarg; + break; + case GROUPS: + groups = optarg; + break; + default: + usage (EXIT_FAILURE); + } + } if (argc <= optind) { @@ -105,6 +200,30 @@ main (int argc, char **argv) argv += optind + 1; } + if (userspec) + { + uid_t uid; + gid_t gid; + char *user; + char *group; + char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group); + + if (err) + error (EXIT_FAILURE, errno, "%s", err); + + free (user); + free (group); + + if (groups && set_additional_groups (groups)) + error (0, errno, _("failed to set additional groups")); + + if (gid && setgid (gid)) + error (0, errno, _("failed to set group-ID")); + + if (uid && setuid (uid)) + error (0, errno, _("failed to set user-ID")); + } + /* Execute the given command. */ execvp (argv[0], argv); diff --git a/tests/Makefile.am b/tests/Makefile.am index 7fe74c0c3..a0ed986ea 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -24,6 +24,7 @@ root_tests = \ cp/preserve-gid \ cp/special-bits \ cp/cp-mv-enotsup-xattr \ + chroot/credentials \ dd/skip-seek-past-dev \ install/install-C-root \ ls/capability \ diff --git a/tests/chroot/credentials b/tests/chroot/credentials new file mode 100755 index 000000000..23d66bd62 --- /dev/null +++ b/tests/chroot/credentials @@ -0,0 +1,43 @@ +#!/bin/sh +# Verify that the credentials are changed correctly. + +# Copyright (C) 2009 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 . + + +if test "$VERBOSE" = yes; then + set -x + chroot --version +fi + +. $srcdir/test-lib.sh + +require_root_ + +fail=0 + +# Verify that root credentials are kept. +test $(chroot / whoami) = root || fail=1 +test "$(groups)" = "$(chroot / groups)" || fail=1 + +# Verify that credentials are changed correctly. +test "$(chroot --userspec=$NON_ROOT_USERNAME:$NON_ROOT_GROUP / whoami)" != root \ + || fail=1 + +# Verify that there are no additional groups. +test "$(chroot --userspec=$NON_ROOT_USERNAME:$NON_ROOT_GROUP --groups= / id -nG)"\ + = $NON_ROOT_GROUP || fail=1 + +Exit $fail diff --git a/tests/test-lib.sh b/tests/test-lib.sh index 17a35389b..a765bd626 100644 --- a/tests/test-lib.sh +++ b/tests/test-lib.sh @@ -204,6 +204,7 @@ require_root_() { uid_is_privileged_ || skip_test_ "must be run as root" NON_ROOT_USERNAME=${NON_ROOT_USERNAME=nobody} + NON_ROOT_GROUP=${NON_ROOT_GROUP=nogroup} } skip_if_root_() { uid_is_privileged_ && skip_test_ "must be run as non-root"; } -- cgit v1.2.3-54-g00ecf