UserCHRoot - Switch root, change directory, drop privileges and exec

Copyright 2007 Aleksandr Koltsoff

Introduction

When dealing with chroot environments, it's sometimes useful to have a tool with which the chroot will be done, but the target executable will not be run using root privileges. It is also sometimes useful to switch the current working directory for the target executable just before it starts. userchroot is a small program intended to be run as suid (u+s root) in these cases. It tries to be as careful as possible in order not to run the target executable as root user. It does not support arbitrary user specification, but instead will drop back to normal user privileges of the calling user (undoing what suid-bit has done basically). If dropping root privilege fails (or actually resetting the effective credentials to the normal ones), the target executable will not be executed.

userchroot has been tested on Linux, implemented using C and licensed under GNU General Public License (v2). The tool is provided without a warranty of any kind, in the understanding that software bugs do exist and bad things can happen.

Table of contents

Document conventions

The following markup list will aid you in understanding the markup used in this document:

Building and installing

Using suid bits with programs that you randomly download off the Internet is not a good security practice. Because of this reason, building and installing the software is not automated at all (no autotools, no Makefile).

In order to build you will only need regular gcc and the standard C library headers. You will probably need to run chmod, chown and cp in order to install the program usefully.

The vagueness of this section is intentional.

Obtaining the source code

For release tar-balls please consult the release directory. The most recent changelog is also included there.

If you're bothered by the amount of comments in the code, feel free to remove them from your own copy. The comments exist in order for the source code to be of some educative value. Same probably goes with this page, although I will be positively surprised if someone finds the software useful.

Source code for userchroot

Feel free to browse the source code and even click on the links. The code presented here is the same as in the latest release (unless the changes are typographical).

Please notice that even if the source files are placed under GPL, it doesn't mean that this specific page is.

  1 /**
  2  * A simple program to aid in launching chroot/--rbound programs
  3  * using the original user credentials. This program is supposed to
  4  * be used with suid-bit set (chmod u+s).
  5  *
  6  * Copyright 2007 Aleksandr Koltsoff (czr@iki.fi)
  7  * Released under the GNU General Public License version 2 (GPL).
  8  * Please see provided COPYING file or http://www.gnu.org/licenses/gpl.txt
  9  *
 10  *
 11  * The objective of this program is to:
 12  * - chroot (argv[1])
 13  * - change working directory to (argv[2])
 14  * - switch user credentials back to original (since this was
 15  *   run via suid-bit)
 16  * - exec the target program (argv[3]) (we use direct exec)
 17  *
 18  * rest of the argv is passed to the target program directly.
 19  * environment is passed to the target program (without modifications)
 20  *
 21  * Parameters:
 22  * - argv[1]: new-root (must start with abs-path)
 23  * - argv[2]: new working dir, relative to post-chroot
 24  * - argv[3]: executable name (related to new working directory) to execve
 25  * - argv[4+ if any]: additional parameters passed to target executable
 26  *
 27  * Never returns success exit code (0), but will return error codes
 28  * if checks will fail before getting to the execve-part. In that case
 29  * errors will be print out on stderr.
 30  *
 31  * Exit codes:
 32  * 1: problem with command line arguments (usage printed)
 33  * 2: no root-privs (won't even attempt to chroot)
 34  * 3: chroot target does not exist or cannot be accessed with real
 35  *    UID/GID (non-root) (tested with access(2))
 36  * 4: chroot-syscall fails
 37  * 5: cd / fails (post chroot)
 38  * 6: dropping privileges failed
 39  * 7: no X_OK on the target (just before execve)
 40  * 8: execve failed
 41  * 9: somehow execve returned even if it didn't fail (impossible)
 42  */
 43 
 44 #include <unistd.h> // {g,s}et{e,}{u,g}id(), execve and other friends
 45 #include <sys/types.h>
 46 #include <stdio.h>
 47 
 48 #define PROGNAME "userchroot"
 49 
 50 #define DEBUG (0)
 51 
 52 // we don't modify our environment on the way to destination program
 53 extern char** environ;
 54 
 55 int main(int argc, char** argv) {
 56 
 57   // we'll use these to check whether dropping privs succeeds
 58   gid_t origGid;
 59   uid_t origUid;
 60 
 61   if (DEBUG) {
 62     printf(PROGNAME ": starting (pid=%u)\n", (unsigned)getpid());
 63   }
 64 
 65   // check for params
 66   if (argc < 4) {
 67     fprintf(stderr, PROGNAME
 68       ": USAGE: new-root new-cwd exec-name [exec-params]\n");
 69     return 1;
 70   }
 71 
 72   if (DEBUG) {
 73     // euid should be root, uid should be the original (assuming u+s)
 74     printf("uid=%u euid=%u\n",
 75       (unsigned)getuid(), (unsigned)geteuid());
 76     // if egid != gid, we restore it as well (later)
 77     printf("gid=%u egid=%u\n",
 78       (unsigned)getgid(), (unsigned)getegid());
 79   }
 80 
 81   // check that we have the proper creds (need root for chroot)
 82   if ((geteuid() != 0) && (getegid() != 0)) {
 83     fprintf(stderr, PROGNAME ": no root privs (suid missing?)\n");
 84     return 2;
 85   }
 86   // check for X_OK for target dir (blah, this uses real UID/GID,
 87   // not effective. but we can live with it
 88   if (access(argv[1], X_OK) != 0) {
 89     perror(PROGNAME ": real UID/GID cannot access new root");
 90     return 3;
 91   }
 92   // do the chroot
 93   if (chroot(argv[1]) != 0) {
 94     perror(PROGNAME ": failed to chroot");
 95     return 4;
 96   }
 97   // change working directory to /
 98   if (chdir(argv[2]) != 0) {
 99     perror(PROGNAME ": failed to switch working directory");
100     return 5;
101   }
102   if (DEBUG) printf("Restoring privileges\n");
103   // restore privileges (drop root)
104   origUid = getuid();
105   origGid = getgid();
106   setegid(origGid);
107   seteuid(origUid);
108   // check that the switch was ok
109   // we do not allow programs to run without the drop being
110   // successful as this would possibly run the program
111   // using root-privs, when that is not what we want
112   if ((getegid() != origGid) || (geteuid() != origUid)) {
113     fprintf(stderr, PROGNAME ": Failed to drop privileges, aborting\n");
114     return 6;
115   }
116 
117   // aids in debugging problematic cases
118   if (DEBUG) {
119     printf("uid=%u euid=%u\n", (unsigned)getuid(), (unsigned)geteuid());
120     // if egid != gid, we restore it as well
121     printf("gid=%u egid=%u\n", (unsigned)getgid(), (unsigned)getegid());
122   }
123 
124   // verify that it's ok for us to X_OK the target executable
125   if (access(argv[3], X_OK) != 0) {
126     perror(PROGNAME ": target missing?");
127     return 7;
128   }
129 
130   // the command line that the target will get is argv[3] >.
131   // we also use argv[3] as the executable name to launch.
132   if (execve(argv[3], &argv[3], environ) != 0) {
133     perror(PROGNAME ": failed to execve");
134     return 8;
135   }
136 
137   // we never should get here.
138   return 9;
139 }

Listing 1: Source of temp/userchroot.c