Skip to content

Commit b5372fe

Browse files
keestorvalds
authored andcommitted
exec: load_script: Do not exec truncated interpreter path
Commit 8099b04 ("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>
1 parent 301e361 commit b5372fe

File tree

1 file changed

+48
-9
lines changed

1 file changed

+48
-9
lines changed

fs/binfmt_script.c

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,30 @@
1414
#include <linux/err.h>
1515
#include <linux/fs.h>
1616

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+
1733
static int load_script(struct linux_binprm *bprm)
1834
{
1935
const char *i_arg, *i_name;
20-
char *cp;
36+
char *cp, *buf_end;
2137
struct file *file;
2238
int retval;
2339

40+
/* Not ours to exec if we don't start with "#!". */
2441
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
2542
return -ENOEXEC;
2643

@@ -33,18 +50,40 @@ static int load_script(struct linux_binprm *bprm)
3350
if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE)
3451
return -ENOENT;
3552

36-
/*
37-
* This section does the #! interpretation.
38-
* Sorta complicated, but hopefully it will work. -TYT
39-
*/
40-
53+
/* Release since we are not mapping a binary into memory. */
4154
allow_write_access(bprm->file);
4255
fput(bprm->file);
4356
bprm->file = NULL;
4457

45-
bprm->buf[BINPRM_BUF_SIZE - 1] = '\0';
46-
if ((cp = strchr(bprm->buf, '\n')) == NULL)
47-
cp = bprm->buf+BINPRM_BUF_SIZE-1;
58+
/*
59+
* This section handles parsing the #! line into separate
60+
* interpreter path and argument strings. We must be careful
61+
* because bprm->buf is not yet guaranteed to be NUL-terminated
62+
* (though the buffer will have trailing NUL padding when the
63+
* file size was smaller than the buffer size).
64+
*
65+
* We do not want to exec a truncated interpreter path, so either
66+
* we find a newline (which indicates nothing is truncated), or
67+
* we find a space/tab/NUL after the interpreter path (which
68+
* itself may be preceded by spaces/tabs). Truncating the
69+
* arguments is fine: the interpreter can re-read the script to
70+
* parse them on its own.
71+
*/
72+
buf_end = bprm->buf + sizeof(bprm->buf) - 1;
73+
cp = strnchr(bprm->buf, sizeof(bprm->buf), '\n');
74+
if (!cp) {
75+
cp = next_non_spacetab(bprm->buf + 2, buf_end);
76+
if (!cp)
77+
return -ENOEXEC; /* Entire buf is spaces/tabs */
78+
/*
79+
* If there is no later space/tab/NUL we must assume the
80+
* interpreter path is truncated.
81+
*/
82+
if (!next_terminator(cp, buf_end))
83+
return -ENOEXEC;
84+
cp = buf_end;
85+
}
86+
/* NUL-terminate the buffer and any trailing spaces/tabs. */
4887
*cp = '\0';
4988
while (cp > bprm->buf) {
5089
cp--;

0 commit comments

Comments
 (0)