Module Name:    src
Committed By:   rillig
Date:           Sun Nov  1 14:36:25 UTC 2020

Modified Files:
        src/usr.bin/make: var.c
        src/usr.bin/make/unit-tests: moderrs.exp moderrs.mk varmod-range.exp
            varmod-range.mk varmod-to-separator.exp varmod-to-separator.mk

Log Message:
make(1): treat malformed :range, :ts and :[...] as errors

Before, integer overflow in the :[1..2] modifier had not been detected,
and the actual behavior varied between ILP64 and LP64I32 machines.

Before, the :ts modifier accepted character literals like \012345 and
\x1F600, which don't fit in a single character and were thus truncated.

Before, the :range modifier issued an "Unknown modifier" error message
for :range=x, which was not quite correct.  The error message in this
case is now "Invalid number".


To generate a diff of this commit:
cvs rdiff -u -r1.634 -r1.635 src/usr.bin/make/var.c
cvs rdiff -u -r1.22 -r1.23 src/usr.bin/make/unit-tests/moderrs.exp
cvs rdiff -u -r1.23 -r1.24 src/usr.bin/make/unit-tests/moderrs.mk
cvs rdiff -u -r1.4 -r1.5 src/usr.bin/make/unit-tests/varmod-range.exp \
    src/usr.bin/make/unit-tests/varmod-to-separator.exp
cvs rdiff -u -r1.6 -r1.7 src/usr.bin/make/unit-tests/varmod-range.mk
cvs rdiff -u -r1.5 -r1.6 src/usr.bin/make/unit-tests/varmod-to-separator.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/var.c
diff -u src/usr.bin/make/var.c:1.634 src/usr.bin/make/var.c:1.635
--- src/usr.bin/make/var.c:1.634	Sun Nov  1 13:55:31 2020
+++ src/usr.bin/make/var.c	Sun Nov  1 14:36:25 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: var.c,v 1.634 2020/11/01 13:55:31 rillig Exp $	*/
+/*	$NetBSD: var.c,v 1.635 2020/11/01 14:36:25 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -130,7 +130,7 @@
 #include "metachar.h"
 
 /*	"@(#)var.c	8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: var.c,v 1.634 2020/11/01 13:55:31 rillig Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.635 2020/11/01 14:36:25 rillig Exp $");
 
 #define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1)
 #define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2)
@@ -2006,6 +2006,66 @@ ModMatchEq(const char *mod, const char *
 	   (mod[n] == endc || mod[n] == ':' || mod[n] == '=');
 }
 
+static Boolean
+TryParseIntBase0(const char **pp, int *out_num)
+{
+    char *end;
+    long n;
+
+    errno = 0;
+    n = strtol(*pp, &end, 0);
+    if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE)
+	return FALSE;
+    if (n < INT_MIN || n > INT_MAX)
+	return FALSE;
+
+    *pp = end;
+    *out_num = (int)n;
+    return TRUE;
+}
+
+static Boolean
+TryParseSize(const char **pp, size_t *out_num)
+{
+    char *end;
+    unsigned long n;
+
+    if (!ch_isdigit(**pp))
+	return FALSE;
+
+    errno = 0;
+    n = strtoul(*pp, &end, 10);
+    if (n == ULONG_MAX && errno == ERANGE)
+	return FALSE;
+    if (n > SIZE_MAX)
+	return FALSE;
+
+    *pp = end;
+    *out_num = (size_t)n;
+    return TRUE;
+}
+
+static Boolean
+TryParseChar(const char **pp, int base, char *out_ch)
+{
+    char *end;
+    unsigned long n;
+
+    if (!ch_isalnum(**pp))
+	return FALSE;
+
+    errno = 0;
+    n = strtoul(*pp, &end, base);
+    if (n == ULONG_MAX && errno == ERANGE)
+	return FALSE;
+    if (n > UCHAR_MAX)
+	return FALSE;
+
+    *pp = end;
+    *out_ch = (char)n;
+    return TRUE;
+}
+
 /* :@var@...${var}...@ */
 static ApplyModifierResult
 ApplyModifier_Loop(const char **pp, ApplyModifiersState *st)
@@ -2263,9 +2323,12 @@ ApplyModifier_Range(const char **pp, App
 	return AMR_UNKNOWN;
 
     if (mod[5] == '=') {
-	char *ep;
-	n = (size_t)strtoul(mod + 6, &ep, 10);
-	*pp = ep;
+	const char *p = mod + 6;
+	if (!TryParseSize(&p, &n)) {
+	    Parse_Error(PARSE_FATAL, "Invalid number: %s\n", mod + 6);
+	    return AMR_CLEANUP;
+	}
+	*pp = p;
     } else {
 	n = 0;
 	*pp = mod + 5;
@@ -2562,24 +2625,27 @@ ApplyModifier_ToSep(const char **pp, App
 
     /* ":ts\x40" or ":ts\100" */
     {
-	const char *numStart = sep + 1;
+	const char *p = sep + 1;
 	int base = 8;		/* assume octal */
-	char *end;
 
 	if (sep[1] == 'x') {
 	    base = 16;
-	    numStart++;
+	    p++;
 	} else if (!ch_isdigit(sep[1])) {
 	    (*pp)++;		/* just for backwards compatibility */
 	    return AMR_BAD;	/* ":ts<backslash><unrecognised>". */
 	}
 
-	st->sep = (char)strtoul(numStart, &end, base);
-	if (*end != ':' && *end != st->endc) {
+	if (!TryParseChar(&p, base, &st->sep)) {
+	    Parse_Error(PARSE_FATAL, "Invalid character number: %s\n", p);
+	    return AMR_CLEANUP;
+	}
+	if (*p != ':' && *p != st->endc) {
 	    (*pp)++;		/* just for backwards compatibility */
 	    return AMR_BAD;
 	}
-	*pp = end;
+
+	*pp = p;
     }
 
 ok:
@@ -2653,9 +2719,9 @@ static ApplyModifierResult
 ApplyModifier_Words(const char **pp, ApplyModifiersState *st)
 {
     char *estr;
-    char *ep;
     int first, last;
     VarParseResult res;
+    const char *p;
 
     (*pp)++;			/* skip the '[' */
     res = ParseModifierPart(pp, ']', st->eflags, st,
@@ -2705,18 +2771,17 @@ ApplyModifier_Words(const char **pp, App
      * We expect estr to contain a single integer for :[N], or two integers
      * separated by ".." for :[start..end].
      */
-    first = (int)strtol(estr, &ep, 0);
-    if (ep == estr)		/* Found junk instead of a number */
-	goto bad_modifier;
+    p = estr;
+    if (!TryParseIntBase0(&p, &first))
+	goto bad_modifier;	/* Found junk instead of a number */
 
-    if (ep[0] == '\0') {	/* Found only one integer in :[N] */
+    if (p[0] == '\0') {	/* Found only one integer in :[N] */
 	last = first;
-    } else if (ep[0] == '.' && ep[1] == '.' && ep[2] != '\0') {
+    } else if (p[0] == '.' && p[1] == '.' && p[2] != '\0') {
 	/* Expecting another integer after ".." */
-	ep += 2;
-	last = (int)strtol(ep, &ep, 0);
-	if (ep[0] != '\0')	/* Found junk after ".." */
-	    goto bad_modifier;
+	p += 2;
+	if (!TryParseIntBase0(&p, &last) || *p != '\0')
+	    goto bad_modifier;	/* Found junk after ".." */
     } else
 	goto bad_modifier;	/* Found junk instead of ".." */
 

Index: src/usr.bin/make/unit-tests/moderrs.exp
diff -u src/usr.bin/make/unit-tests/moderrs.exp:1.22 src/usr.bin/make/unit-tests/moderrs.exp:1.23
--- src/usr.bin/make/unit-tests/moderrs.exp:1.22	Sun Nov  1 10:56:08 2020
+++ src/usr.bin/make/unit-tests/moderrs.exp	Sun Nov  1 14:36:25 2020
@@ -45,7 +45,8 @@ want: Unfinished modifier for UNDEF (']'
 make: Unfinished modifier for UNDEF (']' missing)
 
 13=
-12345=ok
+make: Bad modifier `:[123451234512345123451234512345]' for UNDEF
+12345=S,^ok,:S,^3ok,}
 
 exclam:
 want: Unfinished modifier for VARNAME ('!' missing)

Index: src/usr.bin/make/unit-tests/moderrs.mk
diff -u src/usr.bin/make/unit-tests/moderrs.mk:1.23 src/usr.bin/make/unit-tests/moderrs.mk:1.24
--- src/usr.bin/make/unit-tests/moderrs.mk:1.23	Sun Nov  1 10:56:08 2020
+++ src/usr.bin/make/unit-tests/moderrs.mk	Sun Nov  1 14:36:25 2020
@@ -1,4 +1,4 @@
-# $NetBSD: moderrs.mk,v 1.23 2020/11/01 10:56:08 rillig Exp $
+# $NetBSD: moderrs.mk,v 1.24 2020/11/01 14:36:25 rillig Exp $
 #
 # various modifier error tests
 
@@ -74,15 +74,21 @@ words: print-header print-footer
 
 	# Word index out of bounds.
 	#
-	# On LP64I32, strtol returns LONG_MAX,
-	# which is then truncated to int (undefined behavior),
-	# typically resulting in -1.
-	# This -1 is interpreted as "the last word".
+	# Until 2020-11-01, the behavior in this case depended upon the size
+	# of unsigned long.
 	#
-	# On ILP32, strtol returns LONG_MAX,
-	# which is a large number.
-	# This results in a range from LONG_MAX - 1 to 3,
-	# which is empty.
+	# On LP64I32, strtol returns LONG_MAX, which was then truncated to
+	# int (undefined behavior), typically resulting in -1.  This -1 was
+	# interpreted as "the last word".
+	#
+	# On ILP32, strtol returns LONG_MAX, which is a large number.  This
+	# resulted in a range from LONG_MAX - 1 to 3, which was empty.
+	#
+	# Since 2020-11-01, the numeric overflow is detected and generates an
+	# error.  In the remainder of the text, the '$,' is no longer parsed
+	# as part of a variable modifier, where it would have been interpreted
+	# as an anchor to the :S modifier, but as a normal variable named ','.
+	# That variable is undefined, resulting in an empty string.
 	@echo 12345=${UNDEF:U1 2 3:[123451234512345123451234512345]:S,^$,ok,:S,^3$,ok,}
 
 exclam: print-header print-footer

Index: src/usr.bin/make/unit-tests/varmod-range.exp
diff -u src/usr.bin/make/unit-tests/varmod-range.exp:1.4 src/usr.bin/make/unit-tests/varmod-range.exp:1.5
--- src/usr.bin/make/unit-tests/varmod-range.exp:1.4	Sun Nov  1 13:55:31 2020
+++ src/usr.bin/make/unit-tests/varmod-range.exp	Sun Nov  1 14:36:25 2020
@@ -1,13 +1,14 @@
+make: "varmod-range.mk" line 53: Invalid number: x}Rest" != "Rest"
+
+make: "varmod-range.mk" line 53: Malformed conditional ("${:U:range=x}Rest" != "Rest")
 make: Unknown modifier 'x'
-make: "varmod-range.mk" line 49: Malformed conditional ("${:U:range=x}Rest" != "Rest")
-make: Unknown modifier 'x'
-make: "varmod-range.mk" line 58: Malformed conditional ("${:U:range=0x0}Rest" != "Rest")
+make: "varmod-range.mk" line 62: Malformed conditional ("${:U:range=0x0}Rest" != "Rest")
 make: Unknown modifier 'r'
-make: "varmod-range.mk" line 74: Malformed conditional ("${a b c:L:rang}Rest" != "Rest")
+make: "varmod-range.mk" line 78: Malformed conditional ("${a b c:L:rang}Rest" != "Rest")
 make: Unknown modifier 'r'
-make: "varmod-range.mk" line 81: Malformed conditional ("${a b c:L:rango}Rest" != "Rest")
+make: "varmod-range.mk" line 85: Malformed conditional ("${a b c:L:rango}Rest" != "Rest")
 make: Unknown modifier 'r'
-make: "varmod-range.mk" line 88: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest")
+make: "varmod-range.mk" line 92: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest")
 make: Fatal errors encountered -- cannot continue
 make: stopped in unit-tests
 exit status 1
Index: src/usr.bin/make/unit-tests/varmod-to-separator.exp
diff -u src/usr.bin/make/unit-tests/varmod-to-separator.exp:1.4 src/usr.bin/make/unit-tests/varmod-to-separator.exp:1.5
--- src/usr.bin/make/unit-tests/varmod-to-separator.exp:1.4	Sun Nov  1 13:28:50 2020
+++ src/usr.bin/make/unit-tests/varmod-to-separator.exp	Sun Nov  1 14:36:25 2020
@@ -1,17 +1,21 @@
-make: "varmod-to-separator.mk" line 106: warning: The separator \400 is accepted even though it is out of bounds.
-make: "varmod-to-separator.mk" line 118: warning: The separator \x100 is accepted even though it is out of bounds.
+make: "varmod-to-separator.mk" line 107: Invalid character number: 400:tu}
+
+make: "varmod-to-separator.mk" line 107: Malformed conditional (${WORDS:[1..3]:ts\400:tu})
+make: "varmod-to-separator.mk" line 121: Invalid character number: 100:tu}
+
+make: "varmod-to-separator.mk" line 121: Malformed conditional (${WORDS:[1..3]:ts\x100:tu})
 make: Bad modifier `:ts\-300' for WORDS
-make: "varmod-to-separator.mk" line 124: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
+make: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
 make: Bad modifier `:ts\8' for 1 2 3
-make: "varmod-to-separator.mk" line 132: Malformed conditional (${1 2 3:L:ts\8:tu})
+make: "varmod-to-separator.mk" line 136: Malformed conditional (${1 2 3:L:ts\8:tu})
 make: Bad modifier `:ts\100L' for 1 2 3
-make: "varmod-to-separator.mk" line 139: Malformed conditional (${1 2 3:L:ts\100L})
+make: "varmod-to-separator.mk" line 143: Malformed conditional (${1 2 3:L:ts\100L})
 make: Bad modifier `:ts\x40g' for 1 2 3
-make: "varmod-to-separator.mk" line 146: Malformed conditional (${1 2 3:L:ts\x40g})
+make: "varmod-to-separator.mk" line 150: Malformed conditional (${1 2 3:L:ts\x40g})
 make: Bad modifier `:tx' for WORDS
-make: "varmod-to-separator.mk" line 154: Malformed conditional (${WORDS:tx} != "anything")
+make: "varmod-to-separator.mk" line 158: Malformed conditional (${WORDS:tx} != "anything")
 make: Bad modifier `:t\X' for WORDS
-make: "varmod-to-separator.mk" line 161: Malformed conditional (${WORDS:t\X} != "anything")
+make: "varmod-to-separator.mk" line 165: Malformed conditional (${WORDS:t\X} != "anything")
 make: Fatal errors encountered -- cannot continue
 make: stopped in unit-tests
 exit status 1

Index: src/usr.bin/make/unit-tests/varmod-range.mk
diff -u src/usr.bin/make/unit-tests/varmod-range.mk:1.6 src/usr.bin/make/unit-tests/varmod-range.mk:1.7
--- src/usr.bin/make/unit-tests/varmod-range.mk:1.6	Sun Nov  1 13:55:31 2020
+++ src/usr.bin/make/unit-tests/varmod-range.mk	Sun Nov  1 14:36:25 2020
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-range.mk,v 1.6 2020/11/01 13:55:31 rillig Exp $
+# $NetBSD: varmod-range.mk,v 1.7 2020/11/01 14:36:25 rillig Exp $
 #
 # Tests for the :range variable modifier, which generates sequences
 # of integers from the given range.
@@ -43,9 +43,13 @@
 #.endif
 
 # The :range modifier requires a number as parameter.
-# As of 2020-11-01, the parser tries to read the 'x' as a number, fails and
-# stops there.  It then tries to parse the next modifier at that point,
-# which fails with the message "Unknown modifier".
+#
+# Until 2020-11-01, the parser tried to read the 'x' as a number, failed and
+# stopped there.  It then tried to parse the next modifier at that point,
+# which failed with the message "Unknown modifier".
+#
+# Since 2020-11-01, the parser issues a more precise "Invalid number" error
+# instead.
 .if "${:U:range=x}Rest" != "Rest"
 .  error
 .else

Index: src/usr.bin/make/unit-tests/varmod-to-separator.mk
diff -u src/usr.bin/make/unit-tests/varmod-to-separator.mk:1.5 src/usr.bin/make/unit-tests/varmod-to-separator.mk:1.6
--- src/usr.bin/make/unit-tests/varmod-to-separator.mk:1.5	Sun Nov  1 13:28:50 2020
+++ src/usr.bin/make/unit-tests/varmod-to-separator.mk	Sun Nov  1 14:36:25 2020
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-to-separator.mk,v 1.5 2020/11/01 13:28:50 rillig Exp $
+# $NetBSD: varmod-to-separator.mk,v 1.6 2020/11/01 14:36:25 rillig Exp $
 #
 # Tests for the :ts variable modifier, which joins the words of the variable
 # using an arbitrary character as word separator.
@@ -102,6 +102,8 @@ WORDS=	one two three four five six
 
 # The value of the separator character must not be outside the value space
 # for an unsigned character though.
+#
+# Since 2020-11-01, these out-of-bounds values are rejected.
 .if ${WORDS:[1..3]:ts\400:tu}
 .  warning The separator \400 is accepted even though it is out of bounds.
 .else
@@ -114,6 +116,8 @@ WORDS=	one two three four five six
 .endif
 
 # The hexadecimal number must be in the range of an unsigned char.
+#
+# Since 2020-11-01, these out-of-bounds values are rejected.
 .if ${WORDS:[1..3]:ts\x100:tu}
 .  warning The separator \x100 is accepted even though it is out of bounds.
 .else

Reply via email to