From e25e622dec2ff7579ba561188144d44c3ddad24f Mon Sep 17 00:00:00 2001 From: Tobias Stoeckmann Date: Sun, 26 Oct 2014 13:51:55 +0100 Subject: Buffer overflow on malicious input file There is a hard to reach but possible buffer overflow when using patch with a very large (modified) input file. I doubt you will ever see this with a 64 bit system, but it's possible with 32 bit: $ echo hello > file1 $ echo world > file2 $ diff -Nau file1 file2 > file.diff Nothing fancy so far. Adjust file1 so it contains at least one line that is 2 GB in size. Larger is fine too, but stay below 4 GB. $ tr '\0' c < /dev/zero | dd bs=1K count=2097152 of=file1 Now try to patch it. $ patch -Np0 -i file.diff Segmentation fault The issue is in patch's "plan b" strategy (If your system would still want to use "plan a", force patch to use "plan b" through debug flag). Plan b writes lines into a temporary file, with equally long lines, so it can use a buffer mechanism to access them in a kind of randomly fassion. In order to do that, it retrieves the longest line. In this example, it will encounter the 2 GB line and stores that as the longest one. Afterwards it will adjust the tibufsize variable to be large enough: for (tibufsize = TIBUFSIZE_MINIMUM; tibufsize < maxlen; tibufsize <<= 1) /* do nothing */ ; Due to maxlen's size (2 GB), tibufsize will be SIZE_T_MAX, i.e. 4 GB. A few lines later it allocates space for the tibuf buffers: tibuf[0] = xmalloc (2 * tibufsize); tibuf[1] = tibuf[0] + tibufsize; This will allocate 0 bytes because tibufsize overflowed. The next time patch writes into the buffer, a segmentation fault will occur... Depends on your system how long it takes until that happens. ;) The fix is simple: Bail out on lines that are too long. Patch already does that for files that have too many lines. --- src/inp.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/inp.c b/src/inp.c index 386dc9e..a7ad070 100644 --- a/src/inp.c +++ b/src/inp.c @@ -46,6 +46,7 @@ static bool plan_a (char const *); /* yield false if memory runs out */ static void plan_b (char const *); static void report_revision (bool); static void too_many_lines (char const *) __attribute__((noreturn)); +static void lines_too_long (char const *) __attribute__((noreturn)); /* New patch--prepare to edit another file. */ @@ -128,6 +129,11 @@ too_many_lines (char const *filename) fatal ("File %s has too many lines", quotearg (filename)); } +static void +lines_too_long (char const *filename) +{ + fatal ("Lines in file %s are too long", quotearg (filename)); +} bool get_input_file (char const *filename, char const *outname, mode_t file_type) @@ -367,7 +373,8 @@ plan_b (char const *filename) while ((c = getc (ifp)) != EOF) { - len++; + if (++len > ((size_t) -1) / 2) + lines_too_long (filename); if (c == '\n') { -- cgit v1.2.1