diff --git a/configure.ac b/configure.ac index 162c0cf3c..749fa84f0 100644 --- a/configure.ac +++ b/configure.ac @@ -627,6 +627,7 @@ AC_ARG_ENABLE(pciaccess, AS_HELP_STRING([--enable-pciaccess], [Build Xorg with p AC_ARG_ENABLE(linux_acpi, AS_HELP_STRING([--disable-linux-acpi], [Disable building ACPI support on Linux (if available).]), [enable_linux_acpi=$enableval], [enable_linux_acpi=yes]) AC_ARG_ENABLE(linux_apm, AS_HELP_STRING([--disable-linux-apm], [Disable building APM support on Linux (if available).]), [enable_linux_apm=$enableval], [enable_linux_apm=yes]) AC_ARG_ENABLE(systemd-logind, AC_HELP_STRING([--enable-systemd-logind], [Build systemd-logind support (default: auto)]), [SYSTEMD_LOGIND=$enableval], [SYSTEMD_LOGIND=auto]) +AC_ARG_ENABLE(suid-wrapper, AC_HELP_STRING([--enable-suid-wrapper], [Build suid-root wrapper for legacy driver support on rootless xserver systems (default: no)]), [SUID_WRAPPER=$enableval], [SUID_WRAPPER=no]) dnl DDXes. AC_ARG_ENABLE(xorg, AS_HELP_STRING([--enable-xorg], [Build Xorg server (default: auto)]), [XORG=$enableval], [XORG=auto]) @@ -924,6 +925,16 @@ if test "x$SYSTEMD_LOGIND" = xyes; then fi AM_CONDITIONAL(SYSTEMD_LOGIND, [test "x$SYSTEMD_LOGIND" = xyes]) +if test "x$SUID_WRAPPER" = xyes; then + dnl The wrapper uses libdrm headers, so ensure they are available + PKG_CHECK_MODULES([LIBDRM], $LIBDRM) + dnl This is a define so that if some platforms want to put the wrapper + dnl somewhere else this can be easily changed + AC_DEFINE_DIR(SUID_WRAPPER_DIR, libexecdir, [Where to install Xorg.bin and Xorg.wrap]) + SETUID="no" +fi +AM_CONDITIONAL(SUID_WRAPPER, [test "x$SUID_WRAPPER" = xyes]) + if test "x$NEED_DBUS" = xyes; then AC_DEFINE(NEED_DBUS, 1, [Enable D-Bus core]) fi @@ -2116,7 +2127,6 @@ fi AC_MSG_RESULT([$XWIN]) if test "x$XWIN" = xyes; then - AC_DEFINE_DIR(SYSCONFDIR, sysconfdir, [Location of system.XWinrc]) AC_DEFINE_DIR(DEFAULT_LOGDIR, logdir, [Default log location]) AC_DEFINE_UNQUOTED(XORG_VERSION_CURRENT, [$VENDOR_RELEASE], [Current Xorg version]) AC_DEFINE_UNQUOTED(__VENDORDWEBSUPPORT__, ["$VENDOR_WEB"], [Vendor web address for support]) @@ -2440,6 +2450,7 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ [ Enable GNU and other extensions to the C environment for glibc])]) AC_DEFINE_DIR(PROJECTROOT, prefix, [Overall prefix]) +AC_DEFINE_DIR(SYSCONFDIR, sysconfdir, [sysconfdir]) AC_SUBST([RELEASE_DATE]) BUILD_DATE="`date +'%Y%m%d'`" @@ -2499,6 +2510,7 @@ dri3/Makefile present/Makefile hw/Makefile hw/xfree86/Makefile +hw/xfree86/Xorg.sh hw/xfree86/common/Makefile hw/xfree86/common/xf86Build.h hw/xfree86/ddc/Makefile diff --git a/hw/xfree86/Makefile.am b/hw/xfree86/Makefile.am index 73e1b4c1f..a315bbc17 100644 --- a/hw/xfree86/Makefile.am +++ b/hw/xfree86/Makefile.am @@ -81,9 +81,15 @@ Xorg_DEPENDENCIES = $(LOCAL_LIBS) Xorg_LDFLAGS = $(LD_EXPORT_SYMBOLS_FLAG) +if SUID_WRAPPER +wrapdir = $(SUID_WRAPPER_DIR) +wrap_PROGRAMS = Xorg.wrap +Xorg_wrap_SOURCES = xorg-wrapper.c +endif + BUILT_SOURCES = xorg.conf.example DISTCLEANFILES = xorg.conf.example -EXTRA_DIST = xorgconf.cpp +EXTRA_DIST = xorgconf.cpp Xorg.sh.in # Without logdir, X will post an error on the terminal and will not start install-data-local: @@ -98,6 +104,11 @@ if INSTALL_SETUID chown root $(DESTDIR)$(bindir)/Xorg chmod u+s $(DESTDIR)$(bindir)/Xorg endif +if SUID_WRAPPER + mv $(DESTDIR)$(bindir)/Xorg $(DESTDIR)$(SUID_WRAPPER_DIR)/Xorg.bin + ${INSTALL} -m 755 Xorg.sh $(DESTDIR)$(bindir)/Xorg + -chown root $(DESTDIR)$(SUID_WRAPPER_DIR)/Xorg.wrap && chmod u+s $(DESTDIR)$(SUID_WRAPPER_DIR)/Xorg.wrap +endif uninstall-local: if CYGWIN @@ -119,7 +130,7 @@ xorg.conf.example: xorgconf.cpp relink: $(AM_V_at)rm -f Xorg$(EXEEXT) && $(MAKE) Xorg$(EXEEXT) -CLEANFILES = sdksyms.c sdksyms.dep +CLEANFILES = sdksyms.c sdksyms.dep Xorg.sh EXTRA_DIST += sdksyms.sh sdksyms.dep sdksyms.c: sdksyms.sh diff --git a/hw/xfree86/Xorg.sh.in b/hw/xfree86/Xorg.sh.in new file mode 100644 index 000000000..cef4859c8 --- /dev/null +++ b/hw/xfree86/Xorg.sh.in @@ -0,0 +1,11 @@ +#!/bin/sh +# +# Execute Xorg.wrap if it exists otherwise execute Xorg.bin directly. +# This allows distros to put the suid wrapper in a separate package. + +basedir=@SUID_WRAPPER_DIR@ +if [ -x "$basedir"/Xorg.wrap ]; then + exec "$basedir"/Xorg.wrap "$@" +else + exec "$basedir"/Xorg.bin "$@" +fi diff --git a/hw/xfree86/man/Makefile.am b/hw/xfree86/man/Makefile.am index 80e22cbab..f41d26c4e 100644 --- a/hw/xfree86/man/Makefile.am +++ b/hw/xfree86/man/Makefile.am @@ -1,3 +1,8 @@ include $(top_srcdir)/manpages.am appman_PRE = Xorg.man fileman_PRE = xorg.conf.man xorg.conf.d.man + +if SUID_WRAPPER +appman_PRE += Xorg.wrap.man +fileman_PRE += Xwrapper.config.man +endif diff --git a/hw/xfree86/man/Xorg.wrap.man b/hw/xfree86/man/Xorg.wrap.man new file mode 100644 index 000000000..f2153e35b --- /dev/null +++ b/hw/xfree86/man/Xorg.wrap.man @@ -0,0 +1,67 @@ +.\" Xwrapper.wrap.1 +.\" +.\" Copyright 2014 Red Hat, Inc. +.\" +.\" Permission to use, copy, modify, distribute, and sell this software and its +.\" documentation for any purpose is hereby granted without fee, provided that +.\" the above copyright notice appear in all copies and that both that +.\" copyright notice and this permission notice appear in supporting +.\" documentation. +.\" +.\" The above copyright notice and this permission notice shall be included +.\" in all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +.\" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +.\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +.\" IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR +.\" OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +.\" ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +.\" OTHER DEALINGS IN THE SOFTWARE. +.\" +.\" Except as contained in this notice, the name of The Open Group shall +.\" not be used in advertising or otherwise to promote the sale, use or +.\" other dealings in this Software without prior written authorization +.\" from The Open Group. +.\" +.\" shorthand for double quote that works everywhere. +.ds q \N'34' +.TH Xorg.wrap 1 __xorgversion__ +.SH NAME +Xorg.wrap \- Xorg X server binary wrapper +.SH DESCRIPTION +The Xorg X server may need root rights to function properly. To start the +Xorg X server with these rights your system is using a suid root wrapper +installed as __suid_wrapper_dir__/Xorg.wrap which will execute the real +X server which is installed as __suid_wrapper_dir__/Xorg.bin . +.PP +By default Xorg.wrap will autodetect if root rights are necessary, and +if not it will drop its elevated rights before starting the real X server. +By default Xorg.wrap will only allow executing the real X server from login +sessions on a physical console. + +.SH CONFIG FILE +Xorg.wrap's default behavior can be overridden from the +\fI__sysconfdir__/X11/Xwrapper.config\fP config file. Lines starting with a +\fB#\fP in Xwrapper.config are considered comments and will be ignored. Any +other non empty lines must take the form of \fBkey\fP = \fIvalue\fP. +.TP 8 +\fBallowed_users\fP = \fIrootonly\fP|\fIconsole\fP|\fIanybody\fP +Specify which users may start the X server through the wrapper. Use +\fIrootonly\fP to only allow root, use \fIconsole\fP to only allow users +logged into a physical console, and use \fIanybody\fP to allow anybody. +The default is \fIconsole\fP. +.TP 8 +\fBneeds_root_rights\fP = \fIyes\fP|\fIno\fP|\fIauto\fP +Configure if the wrapper should drop its elevated (root) rights before starting +the X server. Use \fIyes\fP to force execution as root, \fIno\fP to force +execution with all suid rights dropped, and \fIauto\fP to letter the wrapper +auto-detect. The default is \fIauto\fP. +.PP +When auto-detecting the wrapper will drop rights if kms graphics are available +and not drop them if no kms graphics are detected. If a system has multiple +graphics cards and some are not kms capable auto-detection may fail, +in this case manual configuration should be used. + +.SH "SEE ALSO" +Xorg X server information: \fIXorg\fP(1) diff --git a/hw/xfree86/man/Xwrapper.config.man b/hw/xfree86/man/Xwrapper.config.man new file mode 100644 index 000000000..800947c55 --- /dev/null +++ b/hw/xfree86/man/Xwrapper.config.man @@ -0,0 +1 @@ +.so man1/Xorg.wrap.1 diff --git a/hw/xfree86/xorg-wrapper.c b/hw/xfree86/xorg-wrapper.c new file mode 100644 index 000000000..90c8c11ef --- /dev/null +++ b/hw/xfree86/xorg-wrapper.c @@ -0,0 +1,231 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Hans de Goede + */ + +#include "dix-config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For DRM_DEV_NAME */ + +#define CONFIG_FILE SYSCONFDIR "/X11/Xwrapper.config" + +enum { ROOT_ONLY, CONSOLE_ONLY, ANYBODY }; + +/* KISS non locale / LANG parsing isspace version */ +static int is_space(char c) +{ + return c == ' ' || c == '\t' || c == '\n'; +} + +static char *strip(char *s) +{ + int i; + + /* Strip leading whitespace */ + while (s[0] && is_space(s[0])) + s++; + + /* Strip trailing whitespace */ + i = strlen(s) - 1; + while (i >= 0 && is_space(s[i])) { + s[i] = 0; + i--; + } + + return s; +} + +static void parse_config(int *allowed, int *needs_root_rights) +{ + FILE *f; + char buf[1024]; + char *stripped, *equals, *key, *value; + int line = 0; + + f = fopen(CONFIG_FILE, "r"); + if (!f) + return; + + while (fgets(buf, sizeof(buf), f)) { + line++; + + /* Skip comments and empty lines */ + stripped = strip(buf); + if (stripped[0] == '#' || stripped[0] == 0) + continue; + + /* Split in a key + value pair */ + equals = strchr(stripped, '='); + if (!equals) { + fprintf(stderr, "Syntax error at %s line %d\n", CONFIG_FILE, line); + exit(1); + } + *equals = 0; + key = strip(stripped); /* To remove trailing whitespace from key */ + value = strip(equals + 1); /* To remove leading whitespace from val */ + if (!key[0]) { + fprintf(stderr, "Missing key at %s line %d\n", CONFIG_FILE, line); + exit(1); + } + if (!value[0]) { + fprintf(stderr, "Missing value at %s line %d\n", CONFIG_FILE, line); + exit(1); + } + + /* And finally process */ + if (strcmp(key, "allowed_users") == 0) { + if (strcmp(value, "rootonly") == 0) + *allowed = ROOT_ONLY; + else if (strcmp(value, "console") == 0) + *allowed = CONSOLE_ONLY; + else if (strcmp(value, "anybody") == 0) + *allowed = ANYBODY; + else { + fprintf(stderr, + "Invalid value '%s' for 'allowed_users' at %s line %d\n", + value, CONFIG_FILE, line); + exit(1); + } + } + else if (strcmp(key, "needs_root_rights") == 0) { + if (strcmp(value, "yes") == 0) + *needs_root_rights = 1; + else if (strcmp(value, "no") == 0) + *needs_root_rights = 0; + else if (strcmp(value, "auto") == 0) + *needs_root_rights = -1; + else { + fprintf(stderr, + "Invalid value '%s' for 'needs_root_rights' at %s line %d\n", + value, CONFIG_FILE, line); + exit(1); + } + } + else if (strcmp(key, "nice_value") == 0) { + /* Backward compatibility with older Debian Xwrapper, ignore */ + } + else { + fprintf(stderr, "Invalid key '%s' at %s line %d\n", key, + CONFIG_FILE, line); + exit(1); + } + } + fclose(f); +} + +int main(int argc, char *argv[]) +{ + struct drm_mode_card_res res; + struct stat st; + char buf[PATH_MAX]; + int i, r, fd; + int kms_cards = 0; + int total_cards = 0; + int allowed = CONSOLE_ONLY; + int needs_root_rights = -1; + + parse_config(&allowed, &needs_root_rights); + + /* For non root users check if they are allowed to run the X server */ + if (getuid() != 0) { + switch (allowed) { + case ROOT_ONLY: + /* Already checked above */ + fprintf(stderr, "%s: Only root is allowed to run the X server\n", argv[0]); + exit(1); + break; + case CONSOLE_ONLY: + /* Some of stdin / stdout / stderr maybe redirected to a file */ + for (i = STDIN_FILENO; i <= STDERR_FILENO; i++) { + r = fstat(i, &st); + if (r == 0 && S_ISCHR(st.st_mode) && major(st.st_rdev) == 4) + break; + } + if (i > STDERR_FILENO) { + fprintf(stderr, "%s: Only console users are allowed to run the X server\n", argv[0]); + exit(1); + } + break; + case ANYBODY: + break; + } + } + + /* Detect if we need root rights, except when overriden by the config */ + if (needs_root_rights == -1) { + for (i = 0; i < 16; i++) { + snprintf(buf, sizeof(buf), DRM_DEV_NAME, DRM_DIR_NAME, i); + fd = open(buf, O_RDWR); + if (fd == -1) + continue; + + total_cards++; + + memset(&res, 0, sizeof(struct drm_mode_card_res)); + r = ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res); + if (r == 0 && res.count_connectors > 0) + kms_cards++; + + close(fd); + } + } + + /* If we've found cards, and all cards support kms, drop root rights */ + if (needs_root_rights == 0 || (total_cards && kms_cards == total_cards)) { + gid_t realgid = getgid(); + uid_t realuid = getuid(); + + if (setresgid(-1, realgid, realgid) != 0) { + perror("Could not drop setgid privileges"); + exit(1); + } + if (setresuid(-1, realuid, realuid) != 0) { + perror("Could not drop setuid privileges"); + exit(1); + } + } + + snprintf(buf, sizeof(buf), "%s/Xorg.bin", SUID_WRAPPER_DIR); + + /* Check if the server is executable by our real uid */ + if (access(buf, X_OK) != 0) { + perror("Missing execute permissions for " SUID_WRAPPER_DIR "Xorg.bin"); + exit(1); + } + + argv[0] = buf; + (void) execv(argv[0], argv); + perror("Failed to execute " SUID_WRAPPER_DIR "/Xorg.bin"); + exit(1); +} diff --git a/include/dix-config.h.in b/include/dix-config.h.in index 06455a837..f980a3d37 100644 --- a/include/dix-config.h.in +++ b/include/dix-config.h.in @@ -288,9 +288,15 @@ /* Support SHAPE extension */ #undef SHAPE +/* Where to install Xorg.bin and Xorg.wrap */ +#undef SUID_WRAPPER_DIR + /* Define to 1 on systems derived from System V Release 4 */ #undef SVR4 +/* sysconfdir */ +#undef SYSCONFDIR + /* Support TCP socket connections */ #undef TCPCONN diff --git a/include/xwin-config.h.in b/include/xwin-config.h.in index c5119f268..176c01980 100644 --- a/include/xwin-config.h.in +++ b/include/xwin-config.h.in @@ -26,9 +26,6 @@ /* Vendor web address for support */ #undef __VENDORDWEBSUPPORT__ -/* Location of system.XWinrc */ -#undef SYSCONFDIR - /* Default log location */ #undef DEFAULT_LOGDIR diff --git a/manpages.am b/manpages.am index dfd671915..648210b4e 100644 --- a/manpages.am +++ b/manpages.am @@ -31,6 +31,7 @@ MAN_SUBSTS += -e 's|__logdir__|$(logdir)|g' \ -e 's|__XKB_DFLT_OPTIONS__|$(XKB_DFLT_OPTIONS)|g' \ -e 's|__bundle_id_prefix__|$(BUNDLE_ID_PREFIX)|g' \ -e 's|__modulepath__|$(DEFAULT_MODULE_PATH)|g' \ + -e 's|__suid_wrapper_dir__|$(SUID_WRAPPER_DIR)|g' \ -e 's|__default_font_path__|$(COMPILEDDEFAULTFONTPATH)|g' \ -e '\|$(COMPILEDDEFAULTFONTPATH)| s|/,|/, |g'