|
| 1 | +#include <dlfcn.h> |
| 2 | +#include <libgen.h> |
| 3 | +#include <stdio.h> |
| 4 | +#include <stdlib.h> |
| 5 | +#include <string.h> |
| 6 | +#include <unistd.h> |
| 7 | + |
| 8 | +const char* termux_rewrite_executable(const char* filename, char* buffer, int buffer_len) |
| 9 | +{ |
| 10 | + strcpy(buffer, "/data/data/com.termux/files/usr/bin/"); |
| 11 | + char* bin_match = strstr(filename, "/bin/"); |
| 12 | + if (bin_match == filename || bin_match == (filename + 4)) { |
| 13 | + // We have either found "/bin/" at the start of the string or at |
| 14 | + // "/xxx/bin/". Take the path after that. |
| 15 | + strncpy(buffer + 36, bin_match + 5, buffer_len - 37); |
| 16 | + filename = buffer; |
| 17 | + } |
| 18 | + return filename; |
| 19 | +} |
| 20 | + |
| 21 | +// Examples: |
| 22 | +// [1] "#!/bin/sh" should exec "$PREFIX" without argument. |
| 23 | +// [2] "#! /bin/sh" should exec "$PREFIX" without argument. |
| 24 | +// [3] "#!/bin/sh a" should exec "$PREFIX" with single argument "a" |
| 25 | +// [4] "#! /bin/sh a " should exec "$PREFIX" with single argument "a" |
| 26 | +// [5] "#! /bin/sh a b " should exec "$PREFIX" with single argument "a b" |
| 27 | +int execve(const char* filename, char* const* argv, char *const envp[]) |
| 28 | +{ |
| 29 | + int fd = -1; |
| 30 | + |
| 31 | + char filename_buffer[512]; |
| 32 | + filename = termux_rewrite_executable(filename, filename_buffer, sizeof(filename_buffer)); |
| 33 | + |
| 34 | + fd = open(filename, O_RDONLY); |
| 35 | + if (fd == -1) goto final; |
| 36 | + |
| 37 | + // execve(2): "A maximum line length of 127 characters is allowed |
| 38 | + // for the first line in a #! executable shell script." |
| 39 | + char shebang[128]; |
| 40 | + ssize_t read_bytes = read(fd, shebang, sizeof(shebang) - 1); |
| 41 | + if (read_bytes < 5 || !(shebang[0] == '#' && shebang[1] == '!')) goto final; |
| 42 | + |
| 43 | + shebang[read_bytes] = 0; |
| 44 | + char* newline_location = strchr(shebang, '\n'); |
| 45 | + if (newline_location == NULL) goto final; |
| 46 | + |
| 47 | + // Strip whitespace at end of shebang: |
| 48 | + while (*(newline_location - 1) == ' ') newline_location--; |
| 49 | + |
| 50 | + // Null-terminate the shebang line: |
| 51 | + *newline_location = 0; |
| 52 | + |
| 53 | + // Skip whitespace to find interpreter start: |
| 54 | + char* interpreter = shebang + 2; |
| 55 | + while (*interpreter == ' ') interpreter++; |
| 56 | + if (interpreter == newline_location) goto final; |
| 57 | + |
| 58 | + char* arg = NULL; |
| 59 | + char* whitespace_pos = strchr(interpreter, ' '); |
| 60 | + if (whitespace_pos != NULL) { |
| 61 | + // Null-terminate the interpreter string. |
| 62 | + *whitespace_pos = 0; |
| 63 | + |
| 64 | + // Find start of argument: |
| 65 | + arg = whitespace_pos + 1;; |
| 66 | + while (*arg != 0 && *arg == ' ') arg++; |
| 67 | + if (arg == newline_location) { |
| 68 | + // Only whitespace after interpreter. |
| 69 | + arg = NULL; |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + const char* new_argv[4]; |
| 74 | + new_argv[0] = basename(interpreter); |
| 75 | + |
| 76 | + char interp_buf[512]; |
| 77 | + const char* new_interpreter = termux_rewrite_executable(interpreter, interp_buf, sizeof(interp_buf)); |
| 78 | + |
| 79 | + if (arg) { |
| 80 | + new_argv[1] = arg; |
| 81 | + new_argv[2] = filename; |
| 82 | + new_argv[3] = NULL; |
| 83 | + } else { |
| 84 | + new_argv[1] = filename; |
| 85 | + new_argv[2] = NULL; |
| 86 | + } |
| 87 | + |
| 88 | + filename = new_interpreter; |
| 89 | + argv = (char**) new_argv; |
| 90 | + |
| 91 | +final: |
| 92 | + if (fd != -1) close(fd); |
| 93 | + int (*real_execve)(const char*, char *const[], char *const[]) = dlsym(RTLD_NEXT, "execve"); |
| 94 | + return real_execve(filename, argv, envp); |
| 95 | +} |
0 commit comments