Copyright 2007 Aleksandr Koltsoff
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.
The following markup list will aid you in understanding the markup used in this document:
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.
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.
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