From d51173b2baa6eb41a512b990112bec5deaa6f900 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Jun 2013 22:55:39 +0200 Subject: [PATCH] i3-nagbar: take our terminal execution kludge to the next level Please read commit 2bf80528bdb2814331f9149289a0dd1e3422282b first. Then read the comment within the code of this commit. Then run in circles and cry loudly. fixes #1002 fixes #1026 --- i3-nagbar/main.c | 43 +++++++++++++++++-------- include/libi3.h | 8 +++++ libi3/get_exe_path.c | 76 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 libi3/get_exe_path.c diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 2243aa72..8f28de16 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -164,15 +164,18 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve /* Also closes fd */ fclose(script); + char *link_path; + sasprintf(&link_path, "%s.nagbar_cmd", script_path); + symlink(get_exe_path(argv0), link_path); + char *terminal_cmd; - sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", argv0); + sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); printf("argv0 = %s\n", argv0); printf("terminal_cmd = %s\n", terminal_cmd); - setenv("_I3_NAGBAR_CMD", script_path, 1); start_application(terminal_cmd); - unsetenv("_I3_NAGBAR_CMD"); + free(link_path); free(terminal_cmd); free(script_path); @@ -275,23 +278,35 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { } int main(int argc, char *argv[]) { - /* The following lines are a horrible kludge. Because terminal emulators - * have different ways of interpreting the -e command line argument (some - * need -e "less /etc/fstab", others need -e less /etc/fstab), we need to - * write commands to a script and then just run that script. However, since - * on some machines, $XDG_RUNTIME_DIR and $TMPDIR are mounted with noexec, - * we cannot directly execute the script either. + /* The following lines are a terribly horrible kludge. Because terminal + * emulators have different ways of interpreting the -e command line + * argument (some need -e "less /etc/fstab", others need -e less + * /etc/fstab), we need to write commands to a script and then just run + * that script. However, since on some machines, $XDG_RUNTIME_DIR and + * $TMPDIR are mounted with noexec, we cannot directly execute the script + * either. + * + * Initially, we tried to pass the command via the environment variable + * _I3_NAGBAR_CMD. But turns out that some terminal emulators such as + * xfce4-terminal run all windows from a single master process and only + * pass on the command (not the environment) to that master process. * - * Therefore, we run i3-nagbar instead and pass the path to the script in - * the environment variable $_I3_NAGBAR_CMD. i3-nagbar then execs /bin/sh - * with that path in order to run that script. + * Therefore, we symlink i3-nagbar (which MUST reside on an executable + * filesystem) with a special name and run that symlink. When i3-nagbar + * recognizes it’s started as a binary ending in .nagbar_cmd, it strips off + * the .nagbar_cmd suffix and runs /bin/sh on argv[0]. That way, we can run + * a shell script on a noexec filesystem. * * From a security point of view, i3-nagbar is just an alias to /bin/sh in * certain circumstances. This should not open any new security issues, I * hope. */ char *cmd = NULL; - if ((cmd = getenv("_I3_NAGBAR_CMD")) != NULL) { - unsetenv("_I3_NAGBAR_CMD"); + const size_t argv0_len = strlen(argv[0]); + if (argv0_len > strlen(".nagbar_cmd") && + strcmp(argv[0] + argv0_len - strlen(".nagbar_cmd"), ".nagbar_cmd") == 0) { + unlink(argv[0]); + cmd = strdup(argv[0]); + *(cmd + argv0_len - strlen(".nagbar_cmd")) = '\0'; execl("/bin/sh", "/bin/sh", cmd, NULL); err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd); } diff --git a/include/libi3.h b/include/libi3.h index 53f3383d..b0141f1d 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -364,4 +364,12 @@ bool is_debug_build() __attribute__((const)); */ char *get_process_filename(const char *prefix); +/** + * This function returns the absolute path to the executable it is running in. + * + * The implementation follows http://stackoverflow.com/a/933996/712014 + * + */ +const char *get_exe_path(const char *argv0); + #endif diff --git a/libi3/get_exe_path.c b/libi3/get_exe_path.c new file mode 100644 index 00000000..8176aa76 --- /dev/null +++ b/libi3/get_exe_path.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include "libi3.h" + +/* + * This function returns the absolute path to the executable it is running in. + * + * The implementation follows http://stackoverflow.com/a/933996/712014 + * + */ +const char *get_exe_path(const char *argv0) { + static char destpath[PATH_MAX]; + char tmp[PATH_MAX]; + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + /* Linux and Debian/kFreeBSD provide /proc/self/exe */ +#if defined(__linux__) || defined(__FreeBSD_kernel__) + const char *exepath = "/proc/self/exe"; +#elif defined(__FreeBSD__) + const char *exepath = "/proc/curproc/file"; +#endif + ssize_t linksize; + + if ((linksize = readlink(exepath, destpath, sizeof(destpath))) != -1) { + /* readlink() does not NULL-terminate strings, so we have to. */ + destpath[linksize] = '\0'; + + return destpath; + } +#endif + + /* argv[0] is most likely a full path if it starts with a slash. */ + if (argv0[0] == '/') + return argv0; + + /* if argv[0] contains a /, prepend the working directory */ + if (strchr(argv0, '/') != NULL && + getcwd(tmp, sizeof(tmp)) != NULL) { + snprintf(destpath, sizeof(destpath), "%s/%s", tmp, argv0); + return destpath; + } + + /* Fall back to searching $PATH (or _CS_PATH in absence of $PATH). */ + char *path = getenv("PATH"); + size_t pathlen; + if (path == NULL) { + /* _CS_PATH is typically something like "/bin:/usr/bin" */ + pathlen = confstr(_CS_PATH, tmp, sizeof(tmp)); + sasprintf(&path, ":%s", tmp); + } else { + pathlen = strlen(path); + path = strdup(path); + } + const char *component; + char *str = path; + while (1) { + if ((component = strtok(str, ":")) == NULL) + break; + str = NULL; + snprintf(destpath, sizeof(destpath), "%s/%s", component, argv0); + /* Of course this is not 100% equivalent to actually exec()ing the + * binary, but meh. */ + if (access(destpath, X_OK) == 0) { + free(path); + return destpath; + } + } + free(path); + + /* Last resort: maybe it’s in /usr/bin? */ + return "/usr/bin/i3bar"; +} -- 2.39.5