apply: avoid out-of-bounds access in fuzzy_matchlines()

fuzzy_matchlines() uses a pointers to the first and last characters of two lines to keep track while matching them. This makes it impossible to deal with empty strings. It accesses characters before the start of empty lines. It can also access characters after the end when checking for trailing whitespace in the main loop. Avoid that by using pointers to the first character and the one *after* the last one. This is well-defined as long as the latter is not dereferenced. Basically rewrite the function based on that premise; it becomes much simpler as a result. There is no need to check for leading whitespace outside of the main loop anymore. Reported-by: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Signed-off-by: Rene Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Nov 11, 2017 at 15:10 UTC 6ce15ce576afb0510e9d6189ff3780369fdc5b2b
1 file changed +20 -39
apply.c
+20 -39
@@ -303,52 +303,33 @@ static uint32_t hash_line(const char *cp, size_t len)
303 static int fuzzy_matchlines(const char *s1, size_t n1,
304 const char *s2, size_t n2)
305 {
306 - const char *last1 = s1 + n1 - 1;
307 - const char *last2 = s2 + n2 - 1;
308 - int result = 0;
306 + const char *end1 = s1 + n1;
307 + const char *end2 = s2 + n2;
308
309 /* ignore line endings */
311 - while ((*last1 == '\r') || (*last1 == '\n'))
312 - last1--;
313 - while ((*last2 == '\r') || (*last2 == '\n'))
314 - last2--;
315 -
316 - /* skip leading whitespaces, if both begin with whitespace */
317 - if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) {
318 - while (isspace(*s1) && (s1 <= last1))
319 - s1++;
320 - while (isspace(*s2) && (s2 <= last2))
321 - s2++;
322 - }
323 - /* early return if both lines are empty */
324 - if ((s1 > last1) && (s2 > last2))
325 - return 1;
326 - while (!result) {
327 - result = *s1++ - *s2++;
328 - /*
329 - * Skip whitespace inside. We check for whitespace on
330 - * both buffers because we don't want "a b" to match
331 - * "ab"
332 - */
333 - if (isspace(*s1) && isspace(*s2)) {
334 - while (isspace(*s1) && s1 <= last1)
310 + while (s1 < end1 && (end1[-1] == '\r' || end1[-1] == '\n'))
311 + end1--;
312 + while (s2 < end2 && (end2[-1] == '\r' || end2[-1] == '\n'))
313 + end2--;
314 +
315 + while (s1 < end1 && s2 < end2) {
316 + if (isspace(*s1)) {
317 + /*
318 + * Skip whitespace. We check on both buffers
319 + * because we don't want "a b" to match "ab".
320 + */
321 + if (!isspace(*s2))
322 + return 0;
323 + while (s1 < end1 && isspace(*s1))
324 s1++;
336 - while (isspace(*s2) && s2 <= last2)
325 + while (s2 < end2 && isspace(*s2))
326 s2++;
338 - }
339 - /*
340 - * If we reached the end on one side only,
341 - * lines don't match
342 - */
343 - if (
344 - ((s2 > last2) && (s1 <= last1)) ||
345 - ((s1 > last1) && (s2 <= last2)))
327 + } else if (*s1++ != *s2++)
328 return 0;
347 - if ((s1 > last1) && (s2 > last2))
348 - break;
329 }
330
351 - return !result;
331 + /* If we reached the end on one side only, lines don't match. */
332 + return s1 == end1 && s2 == end2;
333 }
334
335 static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)