/*++ Copyright (c) 2014 Minoca Corp. All Rights Reserved Module Name: chroot.c Abstract: This module implements the chroot utility, which runs a command line jailed to a specific region of the file system. Author: Evan Green 22-Oct-2014 Environment: POSIX --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include "swlib.h" // // ---------------------------------------------------------------- Definitions // #define CHROOT_VERSION_MAJOR 1 #define CHROOT_VERSION_MINOR 0 #define CHROOT_USAGE \ "usage: chroot [options] new_root [command [arguments]]\n" \ " chroot options\n" \ "The chroot utility runs the given command or interactive shell jailed \n" \ "to a specific region of the file system. If no command is given, \n" \ "${SHELL} -i is run (with a default of /bin/sh). Options are\n" \ " -e, --escape -- Attempt to escape the current root.\n" \ " -u, --userspec=user:group -- Specifies the user and group (ID or \n" \ " name) to change to before executing the command.\n" \ " -G, --groups=groups -- Specifies the supplementary groups the \n" \ " process will become a member of before executing the command.\n" \ " --help -- Show this help text and exit.\n" \ " --version -- Print the application version information and exit.\n" #define CHROOT_OPTIONS_STRING "eu:G:h" #define CHROOT_SHELL_VARIABLE "SHELL" #define CHROOT_DEFAULT_SHELL "/bin/sh" // // Set this option to attempt to escape the current chroot. // #define CHROOT_OPTION_ESCAPE 0x00000001 // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // // // -------------------------------------------------------------------- Globals // struct option ChrootLongOptions[] = { {"escape", no_argument, 0, 'e'}, {"userspec", required_argument, 0, 'u'}, {"groups", required_argument, 0, 'g'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {NULL, 0, 0, 0}, }; // // ------------------------------------------------------------------ Functions // INT ChrootMain ( INT ArgumentCount, CHAR **Arguments ) /*++ Routine Description: This routine is the main entry point for the chroot utility, which changes the root directory and runs a command. Arguments: ArgumentCount - Supplies the number of command line arguments the program was invoked with. Arguments - Supplies a tokenized array of command line arguments. Return Value: Returns an integer exit code. 0 for success, nonzero otherwise. --*/ { PSTR Argument; ULONG ArgumentIndex; id_t Group; size_t GroupCount; gid_t *Groups; gid_t NewGroup; uid_t NewUser; INT Option; ULONG Options; PSTR ShellArguments[3]; int Status; uid_t User; Groups = NULL; GroupCount = 0; Options = 0; User = SwGetRealUserId(); Group = SwGetRealGroupId(); NewUser = User; NewGroup = Group; // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, CHROOT_OPTIONS_STRING, ChrootLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { Status = 1; goto MainEnd; } switch (Option) { case 'e': Options |= CHROOT_OPTION_ESCAPE; break; case 'u': Status = SwParseUserAndGroupString(optarg, &NewUser, &NewGroup); if (Status != 0) { if (Status == EINVAL) { SwPrintError(0, optarg, "Invalid user/group string"); } else { SwPrintError(Status, optarg, "Invalid user/group string"); } goto MainEnd; } if (NewUser == (uid_t)-1) { NewUser = User; } if (NewGroup == (gid_t)-1) { NewGroup = Group; } break; case 'g': Status = SwParseGroupList(optarg, &Groups, &GroupCount); if (Status != 0) { SwPrintError(Status, optarg, "Invalid group list"); goto MainEnd; } break; case 'V': SwPrintVersion(CHROOT_VERSION_MAJOR, CHROOT_VERSION_MINOR); return 1; case 'h': printf(CHROOT_USAGE); return 1; default: assert(FALSE); Status = 1; goto MainEnd; } } ArgumentIndex = optind; if (ArgumentIndex > ArgumentCount) { ArgumentIndex = ArgumentCount; } // // If the user wants to escape, try to leave the root by passing in NULL. // if ((Options & CHROOT_OPTION_ESCAPE) != 0) { Status = SwChroot(NULL); if (Status != 0) { SwPrintError(Status, NULL, "Failed to change root"); goto MainEnd; } // // Change the root. Change the current directory too. // } else { if (ArgumentIndex == ArgumentCount) { SwPrintError(0, NULL, "New root directory expected"); Status = EINVAL; goto MainEnd; } Argument = Arguments[ArgumentIndex]; ArgumentIndex += 1; Status = chdir(Argument); if (Status != 0) { Status = errno; SwPrintError(Status, Argument, "Failed to change directory"); goto MainEnd; } Status = SwChroot("."); if (Status != 0) { SwPrintError(Status, Argument, "Failed to change root"); goto MainEnd; } } // // Change the user, group, and groups if needed. // if (Groups != NULL) { Status = SwSetGroups(GroupCount, Groups); if (Status != 0) { SwPrintError(Status, NULL, "Failed to set supplementary groups"); goto MainEnd; } } if (NewGroup != Group) { Status = SwSetRealGroupId(NewGroup); if (Status != 0) { SwPrintError(Status, NULL, "Failed to set group ID"); goto MainEnd; } } if (NewUser != User) { Status = SwSetRealUserId(NewUser); if (Status != 0) { SwPrintError(Status, NULL, "Failed to set user ID"); goto MainEnd; } } if (ArgumentIndex == ArgumentCount) { ShellArguments[0] = getenv("SHELL"); if (ShellArguments[0] == NULL) { ShellArguments[0] = CHROOT_DEFAULT_SHELL; } ShellArguments[1] = "-i"; ShellArguments[2] = NULL; Argument = ShellArguments[0]; Status = SwExec(ShellArguments[0], ShellArguments, 2); } else { Argument = Arguments[ArgumentIndex]; Status = SwExec(Arguments[ArgumentIndex], &(Arguments[ArgumentIndex]), ArgumentCount - ArgumentIndex); } SwPrintError(Status, Argument, "Failed to execute"); MainEnd: if (Groups != NULL) { free(Groups); } return Status; } // // --------------------------------------------------------- Internal Functions //