/* xserver-wrapper.c - a simple wrapper for X servers that decides whether to * let them be run. * * By Stephen Early * * Stephen Early: modified to use /etc/X11/X symlink with security level of * 'Console' if /etc/X11/Xserver does not exist * Mark W. Eichin: permit non-privileged -showconfig (6 May 1997) * Mark W. Eichin: fix sense of error check for -showconfig (11 May 1997) * Mark W. Eichin: drop privileges on alternate -config, even if we do pass the * security check, to prevent using the error handling to read * the first line of any protected file (19 Sep 1997) * Erik Troan: prevent buffer overruns (25 Mar 1998) * Topi Miettinen: plug file descriptor leak (26 Apr 1998) * Branden Robinson: only fclose() if file was opened (3 May 1999) * Colin Phipps: minor device number check should be < 64, not < 128, or we * catch serial terminals (26 Feb 2000) * Branden Robinson: ensure sanity of X server socket directory (13 Jun 2000) * Branden Robinson: make all paths #defines * more helpful socket dir error messages (29 Jun 2000) * Branden Robinson: bail out if the config file contains only the silly * default X server name (XF86_NONE) (30 Jul 2000) * Branden Robinson: increase verbosity when wrapper config not found * (2 Oct 2000) * Branden Robinson: * - new configuration file, Xwrapper.config, with different format * (name=value) * - now just exec's /etc/X11/X; whatever this symlink points * to will be used as the X server * - config file specifies allowed user types as before (root only, * console users, anyone) * - config file specifies nice value to use for server * (17 Nov 2000) * Branden Robinson: now accepts hyphens in variable contents (24 Nov 2000) * Branden Robinson: fix dumb errors left over from debugging (3 Dec 2000) * Branden Robinson: let root start the server even if he isn't on a * console, and the security level is console (11 Dec 2000) * Branden Robinson: check out the X server symlink with readlink; abort if * it's not a symlink, or if it points back to this wrapper * (24 Feb 2001) * Branden Robinson: whoops; readlink() doesn't null-terminate the target * string (27 Feb 20001) * Branden Robinson: add more info to "suspicious" error messages (16 Mar 2001) * Branden Robinson: also allow unprivileged use of "-version" option * (13 Jul 2001) * Branden Robinson: check mode of DRI device directory, if it exists, and warn * if it is weird (28 Aug 2001) * Branden Robinson: skip lines in Xwrapper.config that don't match expected * format (9 Dec 2001) * Branden Robinson: fix logic that was supposed to also allow unprivileged use * of "-version" option but which actually forbade both * "-showconfig" and "-verbose"; also let unprivileged users * specify "-help" option to get a usage message (26 Dec 2001) * Branden Robinson: change nice() usage to fit SuSv2 semantics; see Debian Bug * #140012 (2 Apr 2002) * Branden Robinson: *sigh* Ben Collins changed our FROZEN C library back to * pre-SuSv2 nice() semantics, so rewrote the nice() error * handling; also correct limits on legal nice values from * -20 <= x <= 20 to -20 <= x <= 19 (29 Apr 2002) * Branden Robinson: make the nice() error handling switchable with a #define * between SuSv2 semantics and old-style semantics * (16 Oct 2002) * Branden Robinson: stop using the GNU extension strnlen() to appease the * Debian GNU/NetBSD geeks, who are using BSD's C library * (16 Oct 2002) * Branden Robinson: chdir() to the directory where the X server symlink is kept * before executing its target, so that relative symlinks work * (1 Aug 2003) * Guillem Jover: add console detection support for GNU/kFreeBSD, and some * messages at build and run time to allow the user to know * what failed on unsupported systems * (30 Mar 2007) * Brice Goglin: drop privileges on alternate config file given with * -xf86config (14 Jun 2007) * Tollef Fog Heen: stop handling -config specifically, since newer * Xorg does that check automatically and only allows * non-root users to specify configuration files * relative to /etc/X11 (10 Aug 2007) * Loïc Minier: on Linux, also consider alternate tty devices (major 5 and * minor < 64) as consoles (24 Sep 2008) * Julien Cristau: remove the nice_value option * Julien Cristau: recognize /usr/bin/X as a path to this wrapper (6 Jun 2009) * Julien Cristau: don't print an error message if Xwrapper.config doesn't exist * (11 Aug 2009) * Julien Cristau: allow unprivileged -showDefaultModulePath and * -showDefaultLibPath options (11 Aug 2009) * Julien Cristau: don't check the mode of the DRI device directory * (11 Aug 2009) * Julien Cristau: also drop group privileges (1 Nov 2011) * Julien Cristau: disallow major 5 again for consoles (15 Dec 2011) * * This is free software; you may redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2, * or (at your option) any later version. * * This 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 with * the Debian operating system, in /usr/share/common-licenses/GPL; if * not, write to the Free Software Foundation, Inc., 59 Temple Place, * Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include #if defined(__linux__) #define TTY_MAJOR_DEV 4 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include #endif #define X_WRAPPER_CONFIG_FILE "/etc/X11/Xwrapper.config" #define X_SERVER_SYMLINK_DIR "/etc/X11" #define X_SERVER_SYMLINK "/etc/X11/X" #define X_SOCKET_DIR "/tmp/.X11-unix" #define X_SOCKET_DIR_MODE (S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif typedef enum { RootOnly, Console, Anybody } SecurityLevel; static SecurityLevel getSecLevel(char *security) { char *c; for (c = security; *c; c++) *c = toupper(*c); if (strncmp(security,"ROOTONLY",8) == 0) return RootOnly; if (strncmp(security,"CONSOLE",7) == 0) return Console; if (strncmp(security,"ANYBODY",7) == 0) return Anybody; return RootOnly; } static int onConsole() { #if defined(__linux__) struct stat s; /* see if stdin is a virtual console device */ if (fstat(0, &s) != 0) { (void) fprintf(stderr, "X: cannot stat stdin\n"); return FALSE; } if (S_ISCHR(s.st_mode) && (major(s.st_rdev) == TTY_MAJOR_DEV && minor(s.st_rdev) < 64)) { return TRUE; } #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) int idx; if (ioctl(0, VT_GETINDEX, &idx) != -1) return TRUE; #else #warning This program needs porting to your kernel. (void) fprintf(stderr, "X: unable to determine if running on a console\n"); #endif return FALSE; } static int checkSecLevel(SecurityLevel level) { switch (level) { case RootOnly: if (getuid() == 0) { /* real uid is root */ return TRUE; } else { return FALSE; } break; case Console: if (getuid() == 0) return TRUE; /* root */ return onConsole(); break; case Anybody: return TRUE; } return FALSE; } int main(int argc, char **argv) { FILE *cf; struct stat statbuf; char xserver[1025]; char line[1024]; char var[65]; char value[257]; int length; int i; char *val; mode_t mask; SecurityLevel level = RootOnly; /* attempt to use our config file */ cf = fopen(X_WRAPPER_CONFIG_FILE, "r"); if (cf) { /* parse it */ val = fgets(line, 1024, cf); while (val != NULL) { var[0] = '\0'; value[0] = '\0'; if (sscanf(line, " %64[A-Za-z0-9_] = %256[A-Za-z0-9_ -] ", var, value) > 0) { /* truncate extra spaces at end of value */ length = strlen(value); if (length > 256) { length = 256; } for (i = (length - 1); (value[i] == ' '); i--) { value[i] = '\0'; } /* DEBUG (void) fprintf(stderr, "var: %s, value: %s.\n", var, value); */ if (strncasecmp(var, "allowed_users", 64) == 0) { level = getSecLevel(value); /* DEBUG (void) fprintf(stderr, "security level set to %d\n", level); */ } } val = fgets(line, 1024, cf); } (void) fclose(cf); } else { /* DEBUG (void) fprintf(stderr, "X: unable to open wrapper config file %s\n", X_WRAPPER_CONFIG_FILE); */ } if (lstat(X_SERVER_SYMLINK, &statbuf)) { (void) fprintf(stderr, "X: cannot stat %s (%s), aborting.\n", X_SERVER_SYMLINK, strerror(errno)); exit(1); } i = readlink(X_SERVER_SYMLINK, xserver, 1024); if (i < 0) { (void) fprintf(stderr, "X: cannot read %s symbolic link (%s), aborting.\n", X_SERVER_SYMLINK, strerror(errno)); exit(1); } xserver[i] = '\0'; /* readlink() does not null-terminate the string */ if ((strcmp(xserver, "/usr/bin/X11/X") == 0) || (strcmp(xserver, "/usr/X11R6/bin/X") == 0) || (strcmp(xserver, "/usr/bin/X") == 0)) { (void) fprintf(stderr, "X: %s points back to X wrapper executable, " "aborting.\n", X_SERVER_SYMLINK); exit(1); } if (access(X_SERVER_SYMLINK, X_OK)) { /* access() uses real uid */ (void) fprintf(stderr, "%s is not executable\n", X_SERVER_SYMLINK); exit(1); } /* do we have permission to run the X server? */ if (checkSecLevel(level)) { /* check for a sane server socket dir */ mask = umask(0); /* some stupid kernels can't set the sticky bit during a mkdir() */ if (!(mkdir(X_SOCKET_DIR, X_SOCKET_DIR_MODE))) { (void) chmod(X_SOCKET_DIR, X_SOCKET_DIR_MODE); } (void) umask(mask); /* do paranoid checks on the directory where the X server creates its socket */ if (lstat(X_SOCKET_DIR, &statbuf)) { (void) fprintf(stderr, "X: cannot stat %s (%s), aborting.\n", X_SOCKET_DIR, strerror(errno)); exit(1); } if ((statbuf.st_uid != 0) || (statbuf.st_gid != 0)) { (void) fprintf(stderr, "X: %s has suspicious ownership (not root:root), " "aborting.\n", X_SOCKET_DIR); exit(1); } if (statbuf.st_mode != (S_IFDIR | X_SOCKET_DIR_MODE)) { (void) fprintf(stderr, "X: %s has suspicious mode (not %o) or is not a " "directory, aborting.\n", X_SOCKET_DIR, X_SOCKET_DIR_MODE); exit(1); } for (i = 1; i < argc; i++) { if (strlen(argv[i]) > 256) { if (setgid(getgid()) || setuid(getuid())) { perror("X unable to drop setuid privileges for suspiciously long " "argument"); exit(1); } } } /* run the X server */ seteuid(0); /* DEBUG exit(0); */ /* * change to the directory where the X server symlink is so that a relative * symlink will work and execute the X server */ if (chdir(X_SERVER_SYMLINK_DIR)) { (void) fprintf(stderr, "X: cannot chdir() to %s (%s), aborting.\n", X_SERVER_SYMLINK_DIR, strerror(errno)); exit(1); } (void) execv(xserver, argv); (void) fprintf(stderr, "X: exec of %s failed\n", xserver); exit(1); } else { /* DEBUG fprintf(stderr, "argc = %d, argv[1] = \"%s\"\n", argc, argv[1]); */ /* DEBUG fprintf(stderr, "strcmp(argv[1], \"-showconfig\") = %d, strcmp(argv[1], \"-version\" = %d\n", (strcmp(argv[1], "-showconfig")), (strcmp(argv[1], "-version"))); */ if (argc == 2 && ( (strcmp(argv[1], "-help") == 0) || (strcmp(argv[1], "-showconfig") == 0) || (strcmp(argv[1], "-version") == 0) || (strcmp(argv[1], "-showDefaultModulePath") == 0) || (strcmp(argv[1], "-showDefaultLibPath") == 0) ) ) { if (setgid(getgid()) || setuid(getuid())) { perror("X unable to drop setuid privileges"); exit(1); } execv(xserver,argv); (void) fprintf(stderr, "X: unprivileged exec of %s failed, " "aborting.\n", xserver); exit(1); } else { (void) fprintf(stderr, "X: user not authorized to run the X " "server, aborting.\n"); exit(1); } } (void) fprintf(stderr, "X: Impossible! Unreachable statement reached!\n"); exit(1); } /* * vim:set cindent et fo=tcroq sts=2 sw=2 tw=80: */