From 7ea72c40db03659a9d468f000d59ff133aadf47e Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Wed, 5 Jun 2013 01:40:56 +0200 Subject: [PATCH] Hook execve() and the other exec*() functions. Prevents programs from removing our environment variables during the exec. Also reduces unnecessary calls to update_environment() by calling it only once before the exec. --- .gitignore | 2 + src/coloredstderr.c | 127 ++++++++++++++++- src/trackfds.h | 36 +++-- tests/Makefile.am | 4 +- tests/example_exec.c | 272 ++++++++++++++++++++++++++++++++++++ tests/example_exec.expected | 104 ++++++++++++++ tests/run.sh | 2 + 7 files changed, 529 insertions(+), 18 deletions(-) create mode 100644 tests/example_exec.c create mode 100644 tests/example_exec.expected diff --git a/.gitignore b/.gitignore index bab1c5a..098d918 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ /tests/Makefile.in /tests/example /tests/example.o +/tests/example_exec +/tests/example_exec.o /tests/example_vfork /tests/example_vfork.o /tests/run.sh.log diff --git a/src/coloredstderr.c b/src/coloredstderr.c index a55ce38..6a10654 100644 --- a/src/coloredstderr.c +++ b/src/coloredstderr.c @@ -92,14 +92,11 @@ static void dup_fd(int oldfd, int newfd) { if (tracked_fds_find(oldfd)) { if (!tracked_fds_find(newfd)) { tracked_fds_add(newfd); - update_environment(); } /* We are not tracking this file descriptor, remove newfd from the list * (if present). */ } else { - if (tracked_fds_remove(newfd)) { - update_environment(); - } + tracked_fds_remove(newfd); } } @@ -347,3 +344,125 @@ pid_t vfork(void) { return fork(); } #endif + + +/* Hook execve() and the other exec*() functions. Some shells use exec*() with + * a custom environment which doesn't necessarily contain our updates to + * ENV_NAME_FDS. It's also faster to update the environment only when + * necessary, right before the exec() to pass it to the new process. */ + +static int (*real_execve)(const char *filename, char *const argv[], char *const env[]); +int execve(const char *filename, char *const argv[], char *const env[]) { + DLSYM_FUNCTION(real_execve, "execve"); + + int found = 0; + size_t index = 0; + + /* Count arguments and search for existing ENV_NAME_FDS environment + * variable. */ + size_t count = 0; + char * const *x = env; + while (*x) { + if (!strncmp(*x, ENV_NAME_FDS "=", strlen(ENV_NAME_FDS) + 1)) { + found = 1; + index = count; + } + + x++; + count++; + } + /* Terminating NULL. */ + count++; + + char *env_copy[count + 1 /* space for our new entry if necessary */]; + memcpy(env_copy, env, count * sizeof(char *)); + + /* Make sure the information from the environment is loaded. We can't just + * do nothing (like update_environment()) because the caller might pass a + * different environment which doesn't include any of our settings. */ + if (!initialized) { + init_from_environment(); + } + + char fds_env[strlen(ENV_NAME_FDS) + 1 + update_environment_buffer_size()]; + strcpy(fds_env, ENV_NAME_FDS "="); + update_environment_buffer(fds_env + strlen(ENV_NAME_FDS) + 1); + + if (found) { + env_copy[index] = fds_env; + } else { + /* If the process removed ENV_NAME_FDS from the environment, re-add + * it. */ + env_copy[count-1] = fds_env; + env_copy[count] = NULL; + } + + return real_execve(filename, argv, env_copy); +} + +#define EXECL_COPY_VARARGS_START(args) \ + va_list ap; \ + char *x; \ + \ + /* Count arguments. */ \ + size_t count = 1; /* arg */ \ + va_start(ap, arg); \ + while (va_arg(ap, const char *)) { \ + count++; \ + } \ + va_end(ap); \ + \ + /* Copy varargs. */ \ + char *args[count + 1 /* terminating NULL */]; \ + args[0] = (char *)arg; \ + \ + size_t i = 1; \ + va_start(ap, arg); \ + while ((x = va_arg(ap, char *))) { \ + args[i++] = x; \ + } \ + args[i] = NULL; +#define EXECL_COPY_VARARGS_END(args) \ + va_end(ap); +#define EXECL_COPY_VARARGS(args) \ + EXECL_COPY_VARARGS_START(args); \ + EXECL_COPY_VARARGS_END(args); + +int execl(const char *path, const char *arg, ...) { + EXECL_COPY_VARARGS(args); + + update_environment(); + return execv(path, args); +} + +int execlp(const char *file, const char *arg, ...) { + EXECL_COPY_VARARGS(args); + + update_environment(); + return execvp(file, args); +} + +int execle(const char *path, const char *arg, ... /*, char *const envp[] */) { + EXECL_COPY_VARARGS_START(args); + /* Get envp[] located after arguments. */ + char * const *envp = va_arg(ap, char * const *); + EXECL_COPY_VARARGS_END(args); + + return execve(path, args, envp); +} + +static int (*real_execv)(const char *path, char *const argv[]); +int execv(const char *path, char *const argv[]) { + DLSYM_FUNCTION(real_execv, "execv"); + + update_environment(); + return real_execv(path, argv); +} + +static int (*real_execvp)(const char *path, char *const argv[]); +int execvp(const char *path, char *const argv[]) { + DLSYM_FUNCTION(real_execvp, "execvp"); + + update_environment(); + return real_execvp(path, argv); +} diff --git a/src/trackfds.h b/src/trackfds.h index 4760938..74d27aa 100644 --- a/src/trackfds.h +++ b/src/trackfds.h @@ -121,19 +121,7 @@ static void init_from_environment(void) { #endif } -static void update_environment(void) { -#ifdef DEBUG - debug("update_environment()\t\t[%d]\n", getpid()); -#endif - - /* An integer (32-bit) has at most 10 digits, + 1 for the comma after each - * number. Bigger file descriptors (which shouldn't occur in reality) are - * skipped. */ - char env[tracked_fds_count * (10 + 1) + 1 /* to fit '\0' */ ]; - env[0] = 0; - - char *x = env; - +static void update_environment_buffer(char *x) { size_t i; for (i = 0; i < tracked_fds_count; i++) { int length = snprintf(x, 10 + 1, "%d", tracked_fds[i]); @@ -148,6 +136,28 @@ static void update_environment(void) { /* Make sure the string is always zero terminated. */ *x = 0; } +} +inline static size_t update_environment_buffer_size(void) { + /* An integer (32-bit) has at most 10 digits, + 1 for the comma after each + * number. Bigger file descriptors (which shouldn't occur in reality) are + * skipped. */ + return tracked_fds_count * (10 + 1) + 1 /* to fit '\0' */; +} +static void update_environment(void) { +#ifdef DEBUG + debug("update_environment()\t\t[%d]\n", getpid()); +#endif + + /* If we haven't parsed the environment we also haven't modified it - so + * nothing to do. */ + if (!initialized) { + return; + } + + char env[update_environment_buffer_size()]; + env[0] = 0; + + update_environment_buffer(env); #if 0 debug(" setenv('%s', '%s', 1)\n", ENV_NAME_FDS, env); diff --git a/tests/Makefile.am b/tests/Makefile.am index bfaaea2..025fa8a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,10 +1,12 @@ TESTS = run.sh -check_PROGRAMS = example example_vfork +check_PROGRAMS = example example_exec example_vfork example_SOURCES = example.c +example_exec_SOURCES = example_exec.c example_vfork_SOURCES = example_vfork.c dist_check_SCRIPTS = run.sh lib.sh dist_check_DATA = example.expected \ + example_exec.expected \ example_vfork.expected \ example-noforce.sh \ example-noforce.sh.expected \ diff --git a/tests/example_exec.c b/tests/example_exec.c new file mode 100644 index 0000000..3a18567 --- /dev/null +++ b/tests/example_exec.c @@ -0,0 +1,272 @@ +/* + * Test execve() and exec*() functions. + * + * Copyright (C) 2013 Simon Ruderich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 + * along with this program. If not, see . + */ + +#include +#include +#include +#include + + +#define MAGIC "@RUN_" +#define MAGIC_LENGTH (strlen(MAGIC)) + +#define SETUP_RUN(number) \ + sprintf(argv0, "%s" MAGIC "%03d", argv[0], (number)) + + +extern char **environ; + + +static int find_magic_run_number(char *argv0, int *number) { + size_t length = strlen(argv0); + if (length <= MAGIC_LENGTH + 3) { + return 0; + } + /* Check if the string ends with "@RUN_YYY" where YYY should be a number + * (not checked). */ + argv0 += length - MAGIC_LENGTH - 3; + if (strncmp(argv0, MAGIC, MAGIC_LENGTH)) { + return 0; + } + *number = atoi(argv0 + MAGIC_LENGTH); + + /* Strip off "@RUN_YYY". */ + argv0[0] = 0; + return 1; +} + +static void dump(char *argv[]) { + size_t i; + + i = 0; + while (*argv) { + printf("argv[%zu] = |%s|\n", i++, *argv++); + } + i = 0; + char **env = environ; + while (*env) { + /* Skip LD_PRELOAD which contains system dependent values. */ + if (strncmp(*env, "LD_PRELOAD=", 11)) { + printf("environ[%zu] = |%s|\n", i, *env); + } + i++; + env++; + } + printf("\n"); + /* When not writing to stdout (e.g. redirection while testing), glibc is + * buffering heavily. Flush to display the output before the exec*(). */ + fflush(stdout); +} + + +int main(int argc, char **argv) { + char argv0[strlen(argv[0]) + MAGIC_LENGTH + 3 + 1]; + + char *old_ldpreload = getenv("LD_PRELOAD"); + size_t ldpreload_length = strlen("LD_PRELOAD=") + 1; + if (old_ldpreload) { + ldpreload_length += strlen(old_ldpreload); + } + char ldpreload[ldpreload_length]; + strcpy(ldpreload, "LD_PRELOAD="); + if (old_ldpreload) { + strcat(ldpreload, old_ldpreload); + } + + int run; + + /* First call. */ + if (!find_magic_run_number(argv[0], &run)) { + SETUP_RUN(1); + + char *args[] = { argv0, NULL }; + char *envp[] = { ldpreload, NULL }; + + execve(argv[0], args, envp); + return EXIT_FAILURE; + } + + /* Following calls, each testing an exec*() feature. */ + + dump(argv); + SETUP_RUN(run + 1); + + int skip = run - 1; + + + /* Check that we update/insert the environment correctly. */ + + if (!skip--) { + printf("\nCHECKING COLORING.\n\n"); + fflush(stdout); + + dup2(2, 3); + + char *args[] = { argv0, NULL }; + char *envp[] = { ldpreload, NULL }; + + execve(argv[0], args, envp); + return EXIT_FAILURE; + } else if (!skip--) { + dup2(2, 4); + + execl(argv[0], argv0, NULL); + return EXIT_FAILURE; + } else if (!skip--) { + dup2(2, 5); + + execlp(argv[0], argv0, NULL); + return EXIT_FAILURE; + } else if (!skip--) { + dup2(2, 6); + + char *envp[] = { ldpreload, NULL }; + + execle(argv[0], argv0, NULL, envp); + return EXIT_FAILURE; + } else if (!skip--) { + dup2(2, 7); + + /* Test closing a few descriptors. */ + close(5); + close(6); + + char *args[] = { argv0, NULL }; + + execv(argv[0], args); + return EXIT_FAILURE; + } else if (!skip--) { + dup2(2, 8); + + /* And a few more. */ + close(7); + close(4); + + char *args[] = { argv0, NULL }; + + execvp(argv[0], args); + return EXIT_FAILURE; + } else { + close(3); + close(8); + } + + + /* And make sure our hooks don't change the behaviour. */ + + /* execve(2) */ + if (!skip--) { + printf("\nCHECKING TRANSPARENCY.\n\n"); + fflush(stdout); + + char *args[] = { argv0, NULL }; + char *envp[] = { ldpreload, NULL }; + + execve(argv[0], args, envp); + return EXIT_FAILURE; + } else if (!skip--) { + char *args[] = { argv0, NULL }; + char *envp[] = { "TEST=42", ldpreload, NULL }; + + execve(argv[0], args, envp); + return EXIT_FAILURE; + } else if (!skip--) { + char *args[] = { argv0, "foo", "bar", NULL }; + char *envp[] = { "TEST=43", "FOO=", ldpreload, NULL }; + + execve(argv[0], args, envp); + return EXIT_FAILURE; + + /* execl(3) */ + } else if (!skip--) { + setenv("TEST", "44", 1); + + execl(argv[0], argv0, NULL); + return EXIT_FAILURE; + } else if (!skip--) { + setenv("TEST", "45", 1); + + execl(argv[0], argv0, "foo", "bar", NULL); + return EXIT_FAILURE; + + /* execp(3), but not testing the p(ath) part */ + } else if (!skip--) { + setenv("TEST", "46", 1); + + execlp(argv[0], argv0, NULL); + return EXIT_FAILURE; + } else if (!skip--) { + setenv("TEST", "47", 1); + + execlp(argv[0], argv0, "foo", "bar", NULL); + return EXIT_FAILURE; + + /* execle(3) */ + } else if (!skip--) { + char *envp[] = { ldpreload, NULL }; + + execle(argv[0], argv0, NULL, envp); + return EXIT_FAILURE; + } else if (!skip--) { + char *envp[] = { "TEST=48", ldpreload, NULL }; + + execle(argv[0], argv0, NULL, envp); + return EXIT_FAILURE; + } else if (!skip--) { + char *envp[] = { "TEST=49", "FOO=", ldpreload, NULL }; + + execle(argv[0], argv0, "foo", "bar", NULL, envp); + return EXIT_FAILURE; + + /* execv(3) */ + } else if (!skip--) { + setenv("TEST", "50", 1); + + char *args[] = { argv0, NULL }; + + execv(argv[0], args); + return EXIT_FAILURE; + } else if (!skip--) { + setenv("TEST", "51", 1); + + char *args[] = { argv0, "foo", "bar", NULL }; + + execv(argv[0], args); + return EXIT_FAILURE; + + /* execvp(3), but not testing the p(ath) part */ + } else if (!skip--) { + setenv("TEST", "52", 1); + + char *args[] = { argv0, NULL }; + + execvp(argv[0], args); + return EXIT_FAILURE; + } else if (!skip--) { + setenv("TEST", "53", 1); + + char *args[] = { argv0, "foo", "bar", NULL }; + + execvp(argv[0], args); + return EXIT_FAILURE; + } + + printf("Done.\n"); + return EXIT_SUCCESS; +} diff --git a/tests/example_exec.expected b/tests/example_exec.expected new file mode 100644 index 0000000..9a8a6ce --- /dev/null +++ b/tests/example_exec.expected @@ -0,0 +1,104 @@ +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,| + + +CHECKING COLORING. + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,3,| + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,3,4,| + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,3,4,5,| + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,3,4,5,6,| + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,3,4,7,| + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,3,8,| + + +CHECKING TRANSPARENCY. + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +environ[0] = |TEST=42| +environ[2] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +argv[1] = |foo| +argv[2] = |bar| +environ[0] = |TEST=43| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +environ[0] = |TEST=44| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +argv[1] = |foo| +argv[2] = |bar| +environ[0] = |TEST=45| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +environ[0] = |TEST=46| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +argv[1] = |foo| +argv[2] = |bar| +environ[0] = |TEST=47| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +environ[1] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +environ[0] = |TEST=48| +environ[2] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +argv[1] = |foo| +argv[2] = |bar| +environ[0] = |TEST=49| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +environ[0] = |TEST=50| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +argv[1] = |foo| +argv[2] = |bar| +environ[0] = |TEST=51| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +environ[0] = |TEST=52| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +argv[0] = |./example_exec| +argv[1] = |foo| +argv[2] = |bar| +environ[0] = |TEST=53| +environ[1] = |FOO=| +environ[3] = |COLORED_STDERR_FDS=2,| + +Done. diff --git a/tests/run.sh b/tests/run.sh index 56ea950..0106d45 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -33,9 +33,11 @@ force_write=1 test_script example-simple.sh test_script example-redirects.sh test_program example +test_program example_exec test_program example_vfork test_script_subshell example-simple.sh test_script_subshell example-redirects.sh test_program_subshell example +test_program_subshell example_exec test_program_subshell example_vfork -- 2.43.2