i3-nagbar: take our terminal execution kludge to the next level

Please read commit 2bf80528bd first.
Then read the comment within the code of this commit.
Then run in circles and cry loudly.

fixes #1002
fixes #1026
This commit is contained in:
Michael Stapelberg 2013-06-10 22:55:39 +02:00
parent a99fc537fc
commit d51173b2ba
3 changed files with 113 additions and 14 deletions

View File

@ -164,15 +164,18 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
/* Also closes fd */ /* Also closes fd */
fclose(script); fclose(script);
char *link_path;
sasprintf(&link_path, "%s.nagbar_cmd", script_path);
symlink(get_exe_path(argv0), link_path);
char *terminal_cmd; 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("argv0 = %s\n", argv0);
printf("terminal_cmd = %s\n", terminal_cmd); printf("terminal_cmd = %s\n", terminal_cmd);
setenv("_I3_NAGBAR_CMD", script_path, 1);
start_application(terminal_cmd); start_application(terminal_cmd);
unsetenv("_I3_NAGBAR_CMD");
free(link_path);
free(terminal_cmd); free(terminal_cmd);
free(script_path); 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[]) { int main(int argc, char *argv[]) {
/* The following lines are a horrible kludge. Because terminal emulators /* The following lines are a terribly horrible kludge. Because terminal
* have different ways of interpreting the -e command line argument (some * emulators have different ways of interpreting the -e command line
* need -e "less /etc/fstab", others need -e less /etc/fstab), we need to * argument (some need -e "less /etc/fstab", others need -e less
* write commands to a script and then just run that script. However, since * /etc/fstab), we need to write commands to a script and then just run
* on some machines, $XDG_RUNTIME_DIR and $TMPDIR are mounted with noexec, * that script. However, since on some machines, $XDG_RUNTIME_DIR and
* we cannot directly execute the script either. * $TMPDIR are mounted with noexec, we cannot directly execute the script
* either.
* *
* Therefore, we run i3-nagbar instead and pass the path to the script in * Initially, we tried to pass the command via the environment variable
* the environment variable $_I3_NAGBAR_CMD. i3-nagbar then execs /bin/sh * _I3_NAGBAR_CMD. But turns out that some terminal emulators such as
* with that path in order to run that script. * 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 symlink i3-nagbar (which MUST reside on an executable
* filesystem) with a special name and run that symlink. When i3-nagbar
* recognizes its 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 * 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 * certain circumstances. This should not open any new security issues, I
* hope. */ * hope. */
char *cmd = NULL; char *cmd = NULL;
if ((cmd = getenv("_I3_NAGBAR_CMD")) != NULL) { const size_t argv0_len = strlen(argv[0]);
unsetenv("_I3_NAGBAR_CMD"); 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); execl("/bin/sh", "/bin/sh", cmd, NULL);
err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd); err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
} }

View File

@ -364,4 +364,12 @@ bool is_debug_build() __attribute__((const));
*/ */
char *get_process_filename(const char *prefix); 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 #endif

76
libi3/get_exe_path.c Normal file
View File

@ -0,0 +1,76 @@
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#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 its in /usr/bin? */
return "/usr/bin/i3bar";
}