[v2] exec: load_script: Allow interpreter argument truncation
diff mbox series

Message ID 20190214164331.GA33450@beast
State In Next
Commit 76b21f3b9c4d038dca146804ea08c8a4b2e9fb72
Headers show
Series
  • [v2] exec: load_script: Allow interpreter argument truncation
Related show

Commit Message

Kees Cook Feb. 14, 2019, 4:43 p.m. UTC
While we want to make sure the kernel doesn't attempt to execute a
truncated interpreter path, we must allow the interpreter arguments to
be truncated. Perl, for example, will re-read the script itself to parse
arguments correctly.

This documents the parsing steps, and will fail to exec if the string was
truncated with neither an end-of-line nor any trailing whitespace.

Reported-and-Tested-by: Samuel Dionne-Riel <samuel@dionne-riel.com>
Fixes: 8099b047ecc4 ("exec: load_script: don't blindly truncate shebang string")
Signed-off-by: Kees Cook <keescook@chromium.org>
---
v2:
 - fix 1-byte-too-early-bail-out in truncation detection (Oleg)
 - add Samuel's "tested" tag
---
 fs/binfmt_script.c | 37 +++++++++++++++++++++++++++++++------
 1 file changed, 31 insertions(+), 6 deletions(-)

Comments

Oleg Nesterov Feb. 14, 2019, 5:46 p.m. UTC | #1
On 02/14, Kees Cook wrote:
>
> v2:
>  - fix 1-byte-too-early-bail-out in truncation detection (Oleg)
>  - add Samuel's "tested" tag

Looks correct...

but you know, I'll try to read this patch again tomorrow after sleep.

And I can't believe this code can't be simplified... but let me repeat
that I tried and failed.

Oleg.
Linus Torvalds Feb. 14, 2019, 5:59 p.m. UTC | #2
On Thu, Feb 14, 2019 at 8:43 AM Kees Cook <keescook@chromium.org> wrote:
>
> This documents the parsing steps, and will fail to exec if the string was
> truncated with neither an end-of-line nor any trailing whitespace.

Is there any reason why we don't just revert 8099b047ecc4 ("exec:
load_script: don't blindly truncate shebang string").

That revert patch would seem to be much simpler than this rather
complicated thing.

                    Linus
Kees Cook Feb. 14, 2019, 6:10 p.m. UTC | #3
On Thu, Feb 14, 2019 at 9:59 AM Linus Torvalds
<torvalds@linux-foundation.org> wrote:
>
> On Thu, Feb 14, 2019 at 8:43 AM Kees Cook <keescook@chromium.org> wrote:
> >
> > This documents the parsing steps, and will fail to exec if the string was
> > truncated with neither an end-of-line nor any trailing whitespace.
>
> Is there any reason why we don't just revert 8099b047ecc4 ("exec:
> load_script: don't blindly truncate shebang string").
>
> That revert patch would seem to be much simpler than this rather
> complicated thing.

We certainly can, but we'll still need to fix the "don't exec a
truncated interpreter path" problem (which this fixes).

I'm happy to do it as two steps instead: revert, then a separate fix
to land in the merge window?
Linus Torvalds Feb. 14, 2019, 11:07 p.m. UTC | #4
On Thu, Feb 14, 2019 at 10:10 AM Kees Cook <keescook@chromium.org> wrote:>
> We certainly can, but we'll still need to fix the "don't exec a
> truncated interpreter path" problem (which this fixes).
>
> I'm happy to do it as two steps instead: revert, then a separate fix
> to land in the merge window?

I think that's better. It should also be fairly easy: just look for
whitespace in the truncated output (in the place where we already
check for the "oh, we truncated")

It's even possible that somebody depends on a truncated interpreter
executable name. Unlikely, but possiible. So I'd rather just revert,
and re-try the smaller approach for 5.1.

               Linus

Patch
diff mbox series

diff --git a/fs/binfmt_script.c b/fs/binfmt_script.c
index d0078cbb718b..fedcbf3e1f1c 100644
--- a/fs/binfmt_script.c
+++ b/fs/binfmt_script.c
@@ -20,6 +20,7 @@  static int load_script(struct linux_binprm *bprm)
 	char *cp;
 	struct file *file;
 	int retval;
+	bool truncated = false, end_of_interp = false;
 
 	if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
 		return -ENOEXEC;
@@ -42,32 +43,56 @@  static int load_script(struct linux_binprm *bprm)
 	fput(bprm->file);
 	bprm->file = NULL;
 
+	/*
+	 * Truncating interpreter arguments is okay: the interpreter
+	 * can re-read the script to parse them on its own. Truncating
+	 * the interpreter path itself, though, is bad. Note truncation
+	 * here, and check for either newline or start of arguments
+	 * below.
+	 */
 	for (cp = bprm->buf+2;; cp++) {
-		if (cp >= bprm->buf + BINPRM_BUF_SIZE)
-			return -ENOEXEC;
-		if (!*cp || (*cp == '\n'))
+		if (!*cp || (*cp == '\n')) {
+			end_of_interp = true;
 			break;
+		}
+		if (cp == bprm->buf + BINPRM_BUF_SIZE - 1) {
+			truncated = true;
+			break;
+		}
 	}
 	*cp = '\0';
 
+	/* Truncate trailing whitespace */
 	while (cp > bprm->buf) {
 		cp--;
-		if ((*cp == ' ') || (*cp == '\t'))
+		if ((*cp == ' ') || (*cp == '\t')) {
+			end_of_interp = true;
 			*cp = '\0';
-		else
+		} else
 			break;
 	}
+	/* Skip leading whitespace */
 	for (cp = bprm->buf+2; (*cp == ' ') || (*cp == '\t'); cp++);
 	if (*cp == '\0')
 		return -ENOEXEC; /* No interpreter name found */
 	i_name = cp;
 	i_arg = NULL;
+	/*
+	 * Skip until end of string or finding whitespace which
+	 * signals the start of interpreter arguments.
+	 */
 	for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++)
 		/* nothing */ ;
-	while ((*cp == ' ') || (*cp == '\t'))
+	/* Truncate and skip any whitespace in front of arguments */
+	while ((*cp == ' ') || (*cp == '\t')) {
+		end_of_interp = true;
 		*cp++ = '\0';
+	}
 	if (*cp)
 		i_arg = cp;
+	/* Fail exec if the name of the interpreter was cut off. */
+	if (truncated && !end_of_interp)
+		return -ENOEXEC;
 	/*
 	 * OK, we've parsed out the interpreter name and
 	 * (optional) argument.