ash: expand: Ensure result is escaped in cvtnum

Upstream commit:

    Date: Fri, 1 Jun 2018 18:25:29 +0800
    expand: Ensure result is escaped in cvtnum

    The minus sign generated from arithmetic expansion is currently
    unquoted which causes anomalies when the result is used in where
    the quoting matters.

    This patch fixes it by explicitly calling memtodest on the result
    in cvtnum.

    Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/ash.c b/shell/ash.c
index 8d18228..005d87e 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -6071,26 +6071,6 @@
 static struct arglist exparg;
 
 /*
- * Our own itoa().
- * cvtnum() is used even if math support is off (to prepare $? values and such).
- */
-static int
-cvtnum(arith_t num)
-{
-	int len;
-
-	/* 32-bit and wider ints require buffer size of bytes*3 (or less) */
-	len = sizeof(arith_t) * 3;
-	/* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
-	if (sizeof(arith_t) < 4) len += 2;
-
-	expdest = makestrspace(len, expdest);
-	len = fmtstr(expdest, len, ARITH_FMT, num);
-	STADJUST(len, expdest);
-	return len;
-}
-
-/*
  * Break the argument string into pieces based upon IFS and add the
  * strings to the argument list.  The regions of the string to be
  * searched for IFS characters have been stored by recordregion.
@@ -6347,16 +6327,18 @@
 /*
  * Put a string on the stack.
  */
-static void
+static size_t
 memtodest(const char *p, size_t len, int flags)
 {
 	int syntax = flags & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
 	char *q;
+	char *s;
 
 	if (!len)
-		return;
+		return 0;
 
 	q = makestrspace(len * 2, expdest);
+	s = q;
 
 	do {
 		unsigned char c = *p++;
@@ -6375,6 +6357,7 @@
 	} while (--len);
 
 	expdest = q;
+	return q - s;
 }
 
 static size_t
@@ -6386,6 +6369,22 @@
 }
 
 /*
+ * Our own itoa().
+ * cvtnum() is used even if math support is off (to prepare $? values and such).
+ */
+static int
+cvtnum(arith_t num, int flags)
+{
+	/* 32-bit and wider ints require buffer size of bytes*3 (or less) */
+	/* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
+	int len = (sizeof(arith_t) >= 4) ? sizeof(arith_t) * 3 : sizeof(arith_t) * 3 + 2;
+	char buf[len];
+
+	len = fmtstr(buf, len, ARITH_FMT, num);
+	return memtodest(buf, len, flags);
+}
+
+/*
  * Record the fact that we have to scan this region of the
  * string for IFS characters.
  */
@@ -6683,7 +6682,7 @@
 	if (flag & QUOTES_ESC)
 		rmescapes(p + 1, 0, NULL);
 
-	len = cvtnum(ash_arith(p + 1));
+	len = cvtnum(ash_arith(p + 1), flag);
 
 	if (!(flag & EXP_QUOTED))
 		recordregion(begoff, begoff + len, 0);
@@ -7328,7 +7327,7 @@
 		if (num == 0)
 			return -1;
  numvar:
-		len = cvtnum(num);
+		len = cvtnum(num, flags);
 		goto check_1char_name;
 	case '-':
 		expdest = makestrspace(NOPTS, expdest);
@@ -7494,7 +7493,7 @@
 		varunset(p, var, 0, 0);
 
 	if (subtype == VSLENGTH) {
-		cvtnum(varlen > 0 ? varlen : 0);
+		cvtnum(varlen > 0 ? varlen : 0, flag);
 		goto record;
 	}
 
diff --git a/shell/ash_test/ash-quoting/negative_arith.right b/shell/ash_test/ash-quoting/negative_arith.right
new file mode 100644
index 0000000..e7e51ee
--- /dev/null
+++ b/shell/ash_test/ash-quoting/negative_arith.right
@@ -0,0 +1,2 @@
+tempfile0.tmp tempfile9.tmp
+tempfile0.tmp tempfile1.tmp tempfile9.tmp
diff --git a/shell/ash_test/ash-quoting/negative_arith.tests b/shell/ash_test/ash-quoting/negative_arith.tests
new file mode 100755
index 0000000..8e47ca1
--- /dev/null
+++ b/shell/ash_test/ash-quoting/negative_arith.tests
@@ -0,0 +1,8 @@
+>tempfile0.tmp
+>tempfile1.tmp
+>tempfile9.tmp
+# The [...] is interpreted as: "any of the chars 0, -, and 9"
+echo tempfile[0"$((-9))"].tmp
+# The [...] is [0-9], interpreted as: "any digit"
+echo tempfile[0$((-9))].tmp
+rm tempfile?.tmp
diff --git a/shell/hush_test/hush-quoting/negative_arith.right b/shell/hush_test/hush-quoting/negative_arith.right
new file mode 100644
index 0000000..e7e51ee
--- /dev/null
+++ b/shell/hush_test/hush-quoting/negative_arith.right
@@ -0,0 +1,2 @@
+tempfile0.tmp tempfile9.tmp
+tempfile0.tmp tempfile1.tmp tempfile9.tmp
diff --git a/shell/hush_test/hush-quoting/negative_arith.tests b/shell/hush_test/hush-quoting/negative_arith.tests
new file mode 100755
index 0000000..8e47ca1
--- /dev/null
+++ b/shell/hush_test/hush-quoting/negative_arith.tests
@@ -0,0 +1,8 @@
+>tempfile0.tmp
+>tempfile1.tmp
+>tempfile9.tmp
+# The [...] is interpreted as: "any of the chars 0, -, and 9"
+echo tempfile[0"$((-9))"].tmp
+# The [...] is [0-9], interpreted as: "any digit"
+echo tempfile[0$((-9))].tmp
+rm tempfile?.tmp