Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

exec: load_script: Do not exec truncated interpreter path

Commit 8099b047ecc4 ("exec: load_script: don't blindly truncate
shebang string") was trying to protect against a confused exec of a
truncated interpreter path. However, it was overeager and also refused
to truncate arguments as well, which broke userspace, and it was
reverted. This attempts the protection again, but allows arguments to
remain truncated. In an effort to improve readability, helper functions
and comments have been added.

Co-developed-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Samuel Dionne-Riel <samuel@dionne-riel.com>
Cc: Richard Weinberger <richard.weinberger@gmail.com>
Cc: Graham Christensen <graham@grahamc.com>
Cc: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Kees Cook and committed by
Linus Torvalds
b5372fe5 301e3610

+48 -9
+48 -9
fs/binfmt_script.c
··· 14 14 #include <linux/err.h> 15 15 #include <linux/fs.h> 16 16 17 + static inline bool spacetab(char c) { return c == ' ' || c == '\t'; } 18 + static inline char *next_non_spacetab(char *first, const char *last) 19 + { 20 + for (; first <= last; first++) 21 + if (!spacetab(*first)) 22 + return first; 23 + return NULL; 24 + } 25 + static inline char *next_terminator(char *first, const char *last) 26 + { 27 + for (; first <= last; first++) 28 + if (spacetab(*first) || !*first) 29 + return first; 30 + return NULL; 31 + } 32 + 17 33 static int load_script(struct linux_binprm *bprm) 18 34 { 19 35 const char *i_arg, *i_name; 20 - char *cp; 36 + char *cp, *buf_end; 21 37 struct file *file; 22 38 int retval; 23 39 40 + /* Not ours to exec if we don't start with "#!". */ 24 41 if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!')) 25 42 return -ENOEXEC; 26 43 ··· 50 33 if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) 51 34 return -ENOENT; 52 35 53 - /* 54 - * This section does the #! interpretation. 55 - * Sorta complicated, but hopefully it will work. -TYT 56 - */ 57 - 36 + /* Release since we are not mapping a binary into memory. */ 58 37 allow_write_access(bprm->file); 59 38 fput(bprm->file); 60 39 bprm->file = NULL; 61 40 62 - bprm->buf[BINPRM_BUF_SIZE - 1] = '\0'; 63 - if ((cp = strchr(bprm->buf, '\n')) == NULL) 64 - cp = bprm->buf+BINPRM_BUF_SIZE-1; 41 + /* 42 + * This section handles parsing the #! line into separate 43 + * interpreter path and argument strings. We must be careful 44 + * because bprm->buf is not yet guaranteed to be NUL-terminated 45 + * (though the buffer will have trailing NUL padding when the 46 + * file size was smaller than the buffer size). 47 + * 48 + * We do not want to exec a truncated interpreter path, so either 49 + * we find a newline (which indicates nothing is truncated), or 50 + * we find a space/tab/NUL after the interpreter path (which 51 + * itself may be preceded by spaces/tabs). Truncating the 52 + * arguments is fine: the interpreter can re-read the script to 53 + * parse them on its own. 54 + */ 55 + buf_end = bprm->buf + sizeof(bprm->buf) - 1; 56 + cp = strnchr(bprm->buf, sizeof(bprm->buf), '\n'); 57 + if (!cp) { 58 + cp = next_non_spacetab(bprm->buf + 2, buf_end); 59 + if (!cp) 60 + return -ENOEXEC; /* Entire buf is spaces/tabs */ 61 + /* 62 + * If there is no later space/tab/NUL we must assume the 63 + * interpreter path is truncated. 64 + */ 65 + if (!next_terminator(cp, buf_end)) 66 + return -ENOEXEC; 67 + cp = buf_end; 68 + } 69 + /* NUL-terminate the buffer and any trailing spaces/tabs. */ 65 70 *cp = '\0'; 66 71 while (cp > bprm->buf) { 67 72 cp--;