| From f1380967d3c4843872393a9126c919280b45c71c Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Mon, 18 Feb 2019 16:36:48 -0800 |
| Subject: exec: load_script: Do not exec truncated interpreter path |
| |
| From: Kees Cook <keescook@chromium.org> |
| |
| [ Upstream commit b5372fe5dc84235dbe04998efdede3c4daa866a9 ] |
| |
| 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> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/binfmt_script.c | 57 ++++++++++++++++++++++++++++++++++++++-------- |
| 1 file changed, 48 insertions(+), 9 deletions(-) |
| |
| diff --git a/fs/binfmt_script.c b/fs/binfmt_script.c |
| index 7cde3f46ad263..e996174cbfc02 100644 |
| --- a/fs/binfmt_script.c |
| +++ b/fs/binfmt_script.c |
| @@ -14,13 +14,30 @@ |
| #include <linux/err.h> |
| #include <linux/fs.h> |
| |
| +static inline bool spacetab(char c) { return c == ' ' || c == '\t'; } |
| +static inline char *next_non_spacetab(char *first, const char *last) |
| +{ |
| + for (; first <= last; first++) |
| + if (!spacetab(*first)) |
| + return first; |
| + return NULL; |
| +} |
| +static inline char *next_terminator(char *first, const char *last) |
| +{ |
| + for (; first <= last; first++) |
| + if (spacetab(*first) || !*first) |
| + return first; |
| + return NULL; |
| +} |
| + |
| static int load_script(struct linux_binprm *bprm) |
| { |
| const char *i_arg, *i_name; |
| - char *cp; |
| + char *cp, *buf_end; |
| struct file *file; |
| int retval; |
| |
| + /* Not ours to exec if we don't start with "#!". */ |
| if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!')) |
| return -ENOEXEC; |
| |
| @@ -33,18 +50,40 @@ static int load_script(struct linux_binprm *bprm) |
| if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) |
| return -ENOENT; |
| |
| - /* |
| - * This section does the #! interpretation. |
| - * Sorta complicated, but hopefully it will work. -TYT |
| - */ |
| - |
| + /* Release since we are not mapping a binary into memory. */ |
| allow_write_access(bprm->file); |
| fput(bprm->file); |
| bprm->file = NULL; |
| |
| - bprm->buf[BINPRM_BUF_SIZE - 1] = '\0'; |
| - if ((cp = strchr(bprm->buf, '\n')) == NULL) |
| - cp = bprm->buf+BINPRM_BUF_SIZE-1; |
| + /* |
| + * This section handles parsing the #! line into separate |
| + * interpreter path and argument strings. We must be careful |
| + * because bprm->buf is not yet guaranteed to be NUL-terminated |
| + * (though the buffer will have trailing NUL padding when the |
| + * file size was smaller than the buffer size). |
| + * |
| + * We do not want to exec a truncated interpreter path, so either |
| + * we find a newline (which indicates nothing is truncated), or |
| + * we find a space/tab/NUL after the interpreter path (which |
| + * itself may be preceded by spaces/tabs). Truncating the |
| + * arguments is fine: the interpreter can re-read the script to |
| + * parse them on its own. |
| + */ |
| + buf_end = bprm->buf + sizeof(bprm->buf) - 1; |
| + cp = strnchr(bprm->buf, sizeof(bprm->buf), '\n'); |
| + if (!cp) { |
| + cp = next_non_spacetab(bprm->buf + 2, buf_end); |
| + if (!cp) |
| + return -ENOEXEC; /* Entire buf is spaces/tabs */ |
| + /* |
| + * If there is no later space/tab/NUL we must assume the |
| + * interpreter path is truncated. |
| + */ |
| + if (!next_terminator(cp, buf_end)) |
| + return -ENOEXEC; |
| + cp = buf_end; |
| + } |
| + /* NUL-terminate the buffer and any trailing spaces/tabs. */ |
| *cp = '\0'; |
| while (cp > bprm->buf) { |
| cp--; |
| -- |
| 2.20.1 |
| |