diff --git a/Makefile b/Makefile index 54e36d99..56dea3f1 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,9 @@ selinux_policies ::= qubes-gui-daemon.pp all_targets := gui-daemon/qubes-guid gui-daemon/qubes-guid.1 \ shmoverride/shmoverride.so \ - shmoverride/X-wrapper-qubes pulse/pacat-simple-vchan \ + shmoverride/X-wrapper-qubes \ + shmoverride/Xwayland-wrapper \ + pulse/pacat-simple-vchan \ screen-layout-handler/watch-screen-layout-changes @@ -52,6 +54,9 @@ shmoverride/shmoverride.so: shmoverride/X-wrapper-qubes: (cd shmoverride; $(MAKE) X-wrapper-qubes) +shmoverride/Xwayland-wrapper: + (cd shmoverride; $(MAKE) Xwayland-wrapper) + pulse/pacat-simple-vchan: $(MAKE) -C pulse pacat-simple-vchan @@ -67,6 +72,7 @@ install: install -D pulse/pacat-simple-vchan $(DESTDIR)/usr/bin/pacat-simple-vchan install -D shmoverride/X-wrapper-qubes $(DESTDIR)/usr/bin/X-wrapper-qubes install -D shmoverride/shmoverride.so $(DESTDIR)$(LIBDIR)/qubes-gui-daemon/shmoverride.so + install -D shmoverride/Xwayland-wrapper $(DESTDIR)/usr/libexec/qubes/wrappers/Xwayland install -D -m 0644 gui-daemon/guid.conf $(DESTDIR)/etc/qubes/guid.conf install -D gui-daemon/qubes-localgroup.sh $(DESTDIR)/etc/X11/xinit/xinitrc.d/qubes-localgroup.sh install -D -m 0644 common/90-default-gui-daemon.policy $(DESTDIR)/etc/qubes/policy.d/90-default-gui-daemon.policy diff --git a/debian/qubes-gui-daemon.install b/debian/qubes-gui-daemon.install index ddf9ceb0..049ee252 100644 --- a/debian/qubes-gui-daemon.install +++ b/debian/qubes-gui-daemon.install @@ -1,6 +1,7 @@ usr/bin/X.qubes usr/bin/qubes-guid usr/libexec/qubes/watch-screen-layout-changes +usr/libexec/qubes/wrappers/Xwayland usr/share/man/man1/qubes-guid.1 usr/lib/*/qubes-gui-daemon/shmoverride.so usr/lib/qubes/icon-receiver diff --git a/rpm_spec/gui-daemon.spec.in b/rpm_spec/gui-daemon.spec.in index 961a0880..6e0f2e4a 100644 --- a/rpm_spec/gui-daemon.spec.in +++ b/rpm_spec/gui-daemon.spec.in @@ -178,6 +178,7 @@ rm -f %{name}-%{version} /etc/xdg/autostart/qubes-icon-receiver.desktop /etc/X11/xinit/xinitrc.d/qubes-localgroup.sh /usr/libexec/qubes/watch-screen-layout-changes +/usr/libexec/qubes/wrappers/Xwayland /usr/lib/qubes/icon-receiver %config %{_sysconfdir}/qubes-rpc/qubes.WindowIconUpdater %config %{_sysconfdir}/qubes/rpc-config/qubes.WindowIconUpdater diff --git a/shmoverride/.gitignore b/shmoverride/.gitignore index 979bacea..9352006f 100644 --- a/shmoverride/.gitignore +++ b/shmoverride/.gitignore @@ -1,3 +1,3 @@ X_wrapper_qubes shmoverride.so - +Xwayland-wrapper diff --git a/shmoverride/Makefile b/shmoverride/Makefile index 854742fb..a75441b1 100644 --- a/shmoverride/Makefile +++ b/shmoverride/Makefile @@ -33,7 +33,7 @@ extra_cflags := -g -O2 -I../include/ -fPIC -Wall -Wextra -Werror \ -I../include -fvisibility=hidden -pthread CC=gcc -all: shmoverride.so X-wrapper-qubes +all: shmoverride.so X-wrapper-qubes Xwayland-wrapper shmoverride.so: shmoverride.o ./list.o $(CC) $(CFLAGS) $(extra_cflags) -shared -o shmoverride.so \ @@ -43,8 +43,11 @@ vpath %.c ../common X-wrapper-qubes: X-wrapper-qubes.o +Xwayland-wrapper: Xwayland-wrapper.in + sed -e "s,@SHMOVERRIDE_LIB_PATH@,$(LIBDIR)/qubes-gui-daemon/shmoverride.so," < $< > $@ + clean: - rm -f ./*~ ./*.o shmoverride.so X-wrapper-qubes + rm -f ./*~ ./*.o shmoverride.so X-wrapper-qubes Xwayland-wrapper %.o: %.c Makefile $(CC) -MD -MP -MF $@.dep -c -o $@ $(extra_cflags) $(CFLAGS) $< diff --git a/shmoverride/README b/shmoverride/README index 04f8cf30..5ea08fdc 100644 --- a/shmoverride/README +++ b/shmoverride/README @@ -20,3 +20,10 @@ it uses cmd_pages to determine which frames or grant pages should be mapped and from which domain. The munmap() implementation checks if the address is one that shmoverride.so had previously mapped, and if so, calls the appropriate Xen API functions to release the memory. + + +The LD_PRELOAD can be used in two ways: +1. Set directly for the Xorg/Xwayland process. For this, the package provides convenient wrappers: + - /usr/bin/X for Xorg + - /usr/libexec/qubes/wrappers/Xwayland for Xwayland (can be used by prepending /usr/libexec/qubes/wrappers to PATH for the wayland compositor process) +2. Set for some parent process and use SHMOVERRIDE_PROGLIST="Xorg Xwayland" (space separated list of program basenames) to select which process should be affected. diff --git a/shmoverride/Xwayland-wrapper.in b/shmoverride/Xwayland-wrapper.in new file mode 100755 index 00000000..e9139e00 --- /dev/null +++ b/shmoverride/Xwayland-wrapper.in @@ -0,0 +1,4 @@ +#!/bin/sh + +export PATH="${PATH#*/wrappers:}" +exec env LD_PRELOAD="@SHMOVERRIDE_LIB_PATH@" Xwayland "$@" diff --git a/shmoverride/shmoverride.c b/shmoverride/shmoverride.c index d399905d..722ad0d0 100644 --- a/shmoverride/shmoverride.c +++ b/shmoverride/shmoverride.c @@ -72,6 +72,8 @@ static int (*real_munmap) (void *shmaddr, size_t len); static int (*real_fstat64) (VER_ARG int fd, struct stat64 *buf); static int (*real_fstat)(VER_ARG int fd, struct stat *buf); +static int try_init(void); + static struct stat global_buf; static int gntdev_fd = -1; @@ -84,7 +86,7 @@ static int xc_hnd; static xengnttab_handle *xgt; static char __shmid_filename[SHMID_FILENAME_LEN]; static char *shmid_filename = NULL; -static int idfd = -1, display = -1; +static int idfd = -1, display = -1, init_called = 0; static uint8_t *mmap_mfns(struct shm_args_hdr *shm_args) { uint8_t *map; @@ -151,6 +153,8 @@ ASM_DEF(void *, mmap, real_fstat = FSTAT; } + try_init(); + #if defined MAP_ANON && defined MAP_ANONYMOUS && (MAP_ANONYMOUS) != (MAP_ANON) # error header bug (def mismatch) #endif @@ -217,6 +221,9 @@ ASM_DEF(int, munmap, void *addr, size_t len) { if (len > SIZE_MAX - XC_PAGE_SIZE) abort(); + + try_init(); + const uintptr_t addr_int = (uintptr_t)addr; const uintptr_t rounded_addr = addr_int & ~(uintptr_t)(XC_PAGE_SIZE - 1); return real_munmap((void *)rounded_addr, len + (addr_int - rounded_addr)); @@ -350,7 +357,7 @@ static bool parse_display_name(const char *const ptr, int *display_num) return true; } -static int get_display(void) +static int get_display(const char *progname_allowlist) { ssize_t res; int fd, rc = -1, display = 0; @@ -369,6 +376,7 @@ static int get_display(void) return rc; } + bool argv0 = progname_allowlist != NULL; bool skip = true; /* Skip argv[0] (the program name) */ while(true) { errno = 0; @@ -382,6 +390,35 @@ static int get_display(void) size_t length = (size_t)res; assert(ptr && ptr[length] == '\0'); + if (argv0) { + char *current_item, *next_item; + char *allowlist_copy = strdup(progname_allowlist); + char *progname = strrchr(ptr, '/'); + + if (!allowlist_copy) { + perror("allowlist copy"); + goto cleanup; + } + + if (progname) + /* skip '/' */ + progname++; + else + progname = ptr; + + next_item = allowlist_copy; + while ((current_item = strsep(&next_item, " ")) != NULL) { + if (strcmp(current_item, progname) == 0) + break; + } + free(allowlist_copy); + /* abort if not found */ + if (!current_item) { + fprintf(stderr, "skipping shmoverride in %s\n", progname); + goto cleanup; + } + } + /* * Skip option arguments. Some options take more than one argument, * but the extra arguments are always optional and display names @@ -438,6 +475,7 @@ static int assign_off(off_t *off) { #define STAT(id) \ ASM_DEF(int, f ## id, int filedes, struct id *buf) { \ + try_init(); \ int res = real_f ## id(VER filedes, buf); \ if (res || \ !S_ISCHR(buf->st_mode) || \ @@ -454,6 +492,7 @@ STAT(stat64) #ifdef _STAT_VER #define STAT(id) \ ASM_DEF(int, __fx ## id, int ver, int filedes, struct id *buf) { \ + try_init(); \ if (ver != _STAT_VER) { \ fprintf(stderr, \ "Wrong _STAT_VER: got %d, expected %d, libc has incompatibly changed\n", \ @@ -467,9 +506,13 @@ STAT(stat64) #undef STAT #endif -int __attribute__ ((constructor)) initfunc(void) +static int try_init(void) { - unsetenv("LD_PRELOAD"); + // Ideally it is being called in constructor, if something is calling this before + // constructor - we're assuming it is not multi-threaded code. + if (__builtin_expect(init_called, 1)) return 0; + init_called = 1; + fprintf(stderr, "shmoverride constructor running\n"); dlerror(); if (!(real_mmap = dlsym(RTLD_NEXT, "mmap64"))) { @@ -484,7 +527,14 @@ int __attribute__ ((constructor)) initfunc(void) } else if (!(real_munmap = dlsym(RTLD_NEXT, "munmap"))) { fprintf(stderr, "shmoverride: no munmap?: %s\n", dlerror()); abort(); - } else if ((gntdev_fd = open("/dev/xen/gntdev", O_PATH | O_CLOEXEC | O_NOCTTY)) == -1) { + } + + if ((display = get_display(getenv("SHMOVERRIDE_PROGLIST"))) < 0) + goto cleanup; + + unsetenv("LD_PRELOAD"); + + if ((gntdev_fd = open("/dev/xen/gntdev", O_PATH | O_CLOEXEC | O_NOCTTY)) == -1) { perror("open /dev/xen/gntdev"); goto cleanup; } else if (real_fstat(VER gntdev_fd, &global_buf)) { @@ -494,6 +544,7 @@ int __attribute__ ((constructor)) initfunc(void) fprintf(stderr, "/dev/xen/gntdev is not a character special file"); goto cleanup; } + #ifdef XENCTRL_HAS_XC_INTERFACE xc_hnd = xc_interface_open(NULL, NULL, 0); if (!xc_hnd) { @@ -511,32 +562,31 @@ int __attribute__ ((constructor)) initfunc(void) goto cleanup; // Allow it to run when not under Xen. } - if ((display = get_display()) < 0) - goto cleanup; - if ((unsigned int)snprintf(__shmid_filename, sizeof __shmid_filename, SHMID_FILENAME_PREFIX "%d", display) >= sizeof __shmid_filename) { fputs("snprintf() failed!\n", stderr); abort(); } - shmid_filename = __shmid_filename; - fprintf(stderr, "shmoverride: running with shm file %s\n", shmid_filename); + fprintf(stderr, "shmoverride: running with shm file %s\n", __shmid_filename); /* Try to lock the shm.id file (don't rely on whether it exists, a previous * process might have crashed). */ - idfd = open(shmid_filename, O_RDWR | O_CLOEXEC | O_CREAT | O_NOCTTY, 0600); + idfd = open(__shmid_filename, O_RDWR | O_CLOEXEC | O_CREAT | O_NOCTTY, 0600); if (idfd < 0) { fprintf(stderr, "shmoverride opening %s: %s\n", - shmid_filename, strerror(errno)); + __shmid_filename, strerror(errno)); goto cleanup; } if (flock(idfd, LOCK_EX | LOCK_NB) < 0) { fprintf(stderr, "shmoverride flock %s: %s\n", - shmid_filename, strerror(errno)); + __shmid_filename, strerror(errno)); /* There is probably an alive process holding the file, give up. */ goto cleanup; } + /* Save shmid file for cleanup only after taking the lock */ + shmid_filename = __shmid_filename; + if (ftruncate(idfd, SHM_ARGS_SIZE) < 0) { perror("shmoverride ftruncate"); goto cleanup; @@ -581,6 +631,10 @@ int __attribute__ ((constructor)) initfunc(void) shm_args = NULL; return 0; } +int __attribute__ ((constructor)) initfunc(void) +{ + return try_init(); +} int __attribute__ ((destructor)) descfunc(void) {