Module Name:    src
Committed By:   rillig
Date:           Tue Dec 22 08:05:08 UTC 2020

Modified Files:
        src/usr.bin/make: parse.c
        src/usr.bin/make/unit-tests: opt-file.exp opt-file.mk

Log Message:
make(1): fix assertion failure for files without trailing newline

Previously, mmapped files didn't always have the final newline added.
Only those that ended at a page boundary did.

This confused ParseRawLine, which assumed (and since parse.c 1.510 from
moments ago also asserted) that every line ends with a newline, which
allows the code to assume that after a backslash, there is at least one
other character in the buffer, thereby preventing an out-of-bounds read.

This bug had been there at least since parse.c 1.170 from 2010-12-25
04:57:07, maybe even earlier, I didn't check.

Now line_end always points to the trailing newline, which allows
ParseGetLine to overwrite that character to end the string.


To generate a diff of this commit:
cvs rdiff -u -r1.510 -r1.511 src/usr.bin/make/parse.c
cvs rdiff -u -r1.5 -r1.6 src/usr.bin/make/unit-tests/opt-file.exp
cvs rdiff -u -r1.7 -r1.8 src/usr.bin/make/unit-tests/opt-file.mk

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.bin/make/parse.c
diff -u src/usr.bin/make/parse.c:1.510 src/usr.bin/make/parse.c:1.511
--- src/usr.bin/make/parse.c:1.510	Tue Dec 22 06:48:33 2020
+++ src/usr.bin/make/parse.c	Tue Dec 22 08:05:08 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: parse.c,v 1.510 2020/12/22 06:48:33 rillig Exp $	*/
+/*	$NetBSD: parse.c,v 1.511 2020/12/22 08:05:08 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -117,7 +117,7 @@
 #include "pathnames.h"
 
 /*	"@(#)parse.c	8.3 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: parse.c,v 1.510 2020/12/22 06:48:33 rillig Exp $");
+MAKE_RCSID("$NetBSD: parse.c,v 1.511 2020/12/22 08:05:08 rillig Exp $");
 
 /* types and constants */
 
@@ -466,13 +466,14 @@ loadedfile_mmap(struct loadedfile *lf, i
 	if (lf->buf == MAP_FAILED)
 		return FALSE;
 
-	if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') {
-		char *b = bmake_malloc(lf->len + 1);
-		b[lf->len] = '\n';
-		memcpy(b, lf->buf, lf->len++);
-		munmap(lf->buf, lf->maplen);
-		lf->maplen = 0;
-		lf->buf = b;
+	if (lf->len > 0 && lf->buf[lf->len - 1] != '\n') {
+		if (lf->len == lf->maplen) {
+			char *b = bmake_malloc(lf->len + 1);
+			memcpy(b, lf->buf, lf->len);
+			munmap(lf->buf, lf->maplen);
+			lf->maplen = 0;
+		}
+		lf->buf[lf->len++] = '\n';
 	}
 
 	return TRUE;
@@ -2687,19 +2688,15 @@ ParseRawLine(IFile *curFile, char **out_
 		if (ch == '\\') {
 			if (firstBackslash == NULL)
 				firstBackslash = p;
-			/*
-			 * FIXME: In opt-file.mk, this command succeeds:
-			 *	printf '%s' 'V=v\' | make -r -f -
-			 * Using an intermediate file fails though:
-			 *	printf '%s' 'V=v\' > backslash
-			 *	make -r -f backslash
-			 *
-			 * In loadedfile_mmap, the trailing newline is not
-			 * added in every case, only if the file ends at a
-			 * page boundary.
-			 */
-			if (p[1] == '\n')
+			if (p[1] == '\n') {
 				curFile->lineno++;
+				if (p + 2 == curFile->buf_end) {
+					line_end = p;
+					*line_end = '\n';
+					p += 2;
+					continue;
+				}
+			}
 			p += 2;
 			line_end = p;
 			assert(p <= curFile->buf_end);
@@ -2843,12 +2840,8 @@ ParseGetLine(GetLineMode mode)
 		}
 
 		/* We now have a line of data */
-		/*
-		 * FIXME: undefined behavior since line_end points right
-		 * after the allocated buffer. This becomes apparent when
-		 * using a strict malloc implementation that adds canaries
-		 * before and after the allocated space.
-		 */
+		/* TODO: Remove line_end, it's not necessary here. */
+		assert(*line_end == '\n');
 		*line_end = '\0';
 
 		if (mode == GLM_FOR_BODY)

Index: src/usr.bin/make/unit-tests/opt-file.exp
diff -u src/usr.bin/make/unit-tests/opt-file.exp:1.5 src/usr.bin/make/unit-tests/opt-file.exp:1.6
--- src/usr.bin/make/unit-tests/opt-file.exp:1.5	Mon Dec  7 00:53:30 2020
+++ src/usr.bin/make/unit-tests/opt-file.exp	Tue Dec 22 08:05:08 2020
@@ -1,4 +1,5 @@
 value
+value
 make: "(stdin)" line 1: Zero byte read from file
 make: Fatal errors encountered -- cannot continue
 make: stopped in unit-tests

Index: src/usr.bin/make/unit-tests/opt-file.mk
diff -u src/usr.bin/make/unit-tests/opt-file.mk:1.7 src/usr.bin/make/unit-tests/opt-file.mk:1.8
--- src/usr.bin/make/unit-tests/opt-file.mk:1.7	Sun Dec  6 20:55:30 2020
+++ src/usr.bin/make/unit-tests/opt-file.mk	Tue Dec 22 08:05:08 2020
@@ -1,4 +1,4 @@
-# $NetBSD: opt-file.mk,v 1.7 2020/12/06 20:55:30 rillig Exp $
+# $NetBSD: opt-file.mk,v 1.8 2020/12/22 08:05:08 rillig Exp $
 #
 # Tests for the -f command line option.
 
@@ -6,6 +6,7 @@
 
 all: .PHONY
 all: file-ending-in-backslash
+all: file-ending-in-backslash-mmap
 all: file-containing-null-byte
 
 # Passing '-' as the filename reads from stdin.  This is unusual but possible.
@@ -35,6 +36,14 @@ file-ending-in-backslash: .PHONY
 	@printf '%s' 'VAR=value\' \
 	| ${MAKE} -r -f - -V VAR
 
+# Between parse.c 1.170 from 2010-12-25 and parse.c 1.511 from 2020-12-22,
+# there was an out-of-bounds write in ParseGetLine, where line_end pointed at
+# the end of the allocated buffer, in the special case where loadedfile_mmap
+# had not added the final newline character.
+file-ending-in-backslash-mmap: .PHONY
+	@printf '%s' 'VAR=value\' > opt-file-backslash
+	@${MAKE} -r -f opt-file-backslash -V VAR
+
 # If a file contains null bytes, the rest of the line is skipped, and parsing
 # continues in the next line.  Throughout the history of make, the behavior
 # has changed several times, sometimes knowingly, sometimes by accident.

Reply via email to