[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#987975: marked as done (unblock: mksh/59c-6)



Your message dated Tue, 04 May 2021 20:07:44 +0000
with message-id <E1le1Ka-0001Kf-C4@respighi.debian.org>
and subject line unblock mksh
has caused the Debian Bug report #987975,
regarding unblock: mksh/59c-6
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
987975: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=987975
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock
X-Debbugs-Cc: tg@mirbsd.de

Please unblock package mksh

Give it a few days in sid first, please, but I’m filing the
unblock request right now already so it has more time for review.

[ Reason ]
These are cherry-picks, reduced to the minimum necessary (e.g.
less RCS ID churn, some changes that aren’t _that_ crucial removed
or modified in a way to make the diff smaller), that address the
following things:
• some memory leaks
• harden conversion of imported variables to integer, similar to
  Perl’s “taint”; this is basically what ksh93 had as CVE-2019-14868
• fix lexer token state corruption when switching back and forth
  between command line editing and lexing (could be triggered by an
  editing command reliably)
• fix escaping (typeset -p; ${var@Q}) and globbing (tab completion,
  pattern expansion) for pathnames which contain escaped or unescaped
  curly braces, tilde, \x02
Furthermore because this is a new sourceful upload the buildds will
rebuild this against latest linux-libc-dev and klibc (I checked, the
unblock on the latter was already granted) which is a good thing,
especially given klibc’s malloc fixes.

The attached diff is a debdiff in which the single-debian-patch
was replaced by a diff between the patched extracted trees to
increase legibility, as usual. To aid in mapping the patch hunks
to the topics above I will comment on them here:

 mksh-59c/debian/changelog |   25 +++++++++++++

- change documentation

 mksh_59c-6/check.t        |   37 +++++++++++++++++++

- changed version date to 2021/05/03
- new test for the “taint” checks

 mksh_59c-6/edit.c         |   73 ++++++++++++++++++++++++++-------------

- only escaping-related changes

 mksh_59c-6/eval.c         |    6 +--

- memory leak fixes: s/global(arrayname(x))/arraybase(x)/

 mksh_59c-6/misc.c         |    1 

- another memory leak, in command line option -T processing

 mksh_59c-6/mksh.1         |    4 +-

- documentation for the escaping-related changes

 mksh_59c-6/mksh.faq       |   18 ++++++++-

- remove note about evaluate-region being broken (lexer state thing)
- documentation for the “taint” thing

 mksh_59c-6/sh.h           |   14 +++----

- version number
- escaping-related changes
- arrayname ⇒ arraybase (prototype)

 mksh_59c-6/shf.c          |    2 -

- escaping-related

 mksh_59c-6/syn.c          |    4 +-

- lexer state issue

 mksh_59c-6/var.c          |   86 ++++++++++++++++++++++++++++++----------------

- the @@ -938,7 +944,7 @@ hunk and the last one are memory
  leak-related (arrayname ⇒ arraybase; arraybase implementation
  free()s the temporary variable after calling global() properly
  and uses a stack buffer for small values to reduce memory
  allocator pressure)
- all others: “taint”: factor out checking a string for being
  a valid integer from getint() to getnum() and call getnum()
  when converting a variable that was imported from the environment
  to integer to check if it is purely numeric

 11 files changed, 200 insertions(+), 70 deletions(-)


[ Impact ]
- the evaluate-region editing command is still broken
  (I can’t think of any other obvious codepath that triggers
  the lexer issue)
- environment variables COLUMNS, LINES, SECONDS, etc. can be
  used for shell DoS, possibly command execution
- memory leaks (minor, most “lost” blocks are cleaned up when
  a scope is left… if it is even left)
- files with \x02 in them possibly can’t be tab-completed
- pathnames with tildes in them tabcomplete to user homedirs
  instead; same with pattern expansion: 'echo \~bar/*'
- variables with {} or ~ in their values are escaped without
  quoting these characters making them not safe for re-entry
  into the shell (possibly changing their value); this also
  affects internal pass-escaped-values-around like typeset -f
  or even $(…)

[ Tests ]
- the “taint” thing has an automated test
- I wrote excessive code to test the changes to the character
  classes, this is about 600 LoC, which I omitted here to keep
  the diff small, as it’d be called manually while developing
  anyway
- most nōn-interactive functionality is covered by the testsuite
  (which is also run via autopkgtests)
- I tested the escaping and tab-completion things as well as
  the lexer / evaluate-region thing manually

I also did a full rebuild of MirBSD itself with the changes
applied which exercises the shell quite a bit.

[ Risks ]
- the “taint” thing changes the way some scripts may operate,
  but the previous behaviour was unsafe and other shells are
  the same; mksh is de-facto leaf in Debian (I looked at shunit2
  for the previous unblock, and it’ll basically ignore mksh);
  I guess this is medium risk for mksh (which is why this should
  simmer in sid for a while first, though my users tend to find
  bugs rather quickly) but low risk for Debian or the release
  overall considering its virtually-leafness
- the escaping changes only add backslashes that are safe (extra
  backslashes in those cases can’t hurt anyway, eg. = is already
  almost needlessly escaped)
- the memory leaks are low-risk as “obvious”
- same for the lexer change

[ Checklist ]
  [x] all changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in testing

[ Other info ]
(let it simmer in sid for a few days first)

unblock mksh/59c-6
diff -Nru mksh-59c/debian/changelog mksh-59c/debian/changelog
--- mksh-59c/debian/changelog	2021-03-13 19:09:48.000000000 +0100
+++ mksh-59c/debian/changelog	2021-05-03 03:26:28.000000000 +0200
@@ -1,3 +1,28 @@
+mksh (59c-6) unstable; urgency=medium
+
+  * Clear “taint” on most actions mutating a variable
+
+ -- Thorsten Glaser <tg@mirbsd.de>  Mon, 03 May 2021 03:26:28 +0200
+
+mksh (59c-5) unstable; urgency=medium
+
+  * Apply targeted fixes, intended for bullseye:
+    - [tg] Plug some memory leaks
+    - [tg] Harden conversion of imported variables to integer, like
+      Perl “taint”: imported variables will now lose the value when
+      converting to integer but they are not purely numeric
+      (CVE-2019-14868 was a similar issue in AT&T ksh); honour
+      -o posix for leading-zero as octal, though (but continue not
+      when importing array indicēs)
+    - [tg] Fix lexer token state corruption when reading new input;
+      makes evaluate-region editing command actually useful
+    - [tg] Fix proper escaping/quoting and tab completion for tilde,
+      curly braces and \x02, either escaped or not
+    Note: patches are reduced to minimum change for bullseye
+  * Rebuild for outdated Built-Using
+
+ -- Thorsten Glaser <tg@mirbsd.de>  Sun, 02 May 2021 23:52:52 +0200
+
 mksh (59c-4) unstable; urgency=low
 
   * Update to upstream CVS HEAD
diff -pruN mksh_59c-4/check.t mksh_59c-6/check.t
--- mksh_59c-4/check.t	2021-05-03 00:29:50.000000000 +0200
+++ mksh_59c-6/check.t	2021-05-03 03:57:57.000000000 +0200
@@ -31,7 +31,7 @@
 # (2013/12/02 20:39:44) http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date
 
 expected-stdout:
-	KSH R59 2021/03/13
+	KSH R59 2021/05/03
 description:
 	Check base version of full shell
 stdin:
@@ -13230,6 +13230,41 @@ expected-stdout:
 	2=\x7Cfoo-e \x4B
 	3=\x7Cfoo-e \x4B
 ---
+name: env-intvars
+description:
+	Check that importing integers fails except for numbers
+stdin:
+	unset foo bar
+	print 1 $foo , $(typeset -p bar) .
+	print 2 $(foo=123 "$__progname" -c 'integer foo; print -- $foo' 2>&1) , \
+	    $(env 'bar[123]=baz' "$__progname" -c 'typeset -p bar') .
+	print 3 $(foo='abc[$(echo >&2 fowled)0]' "$__progname" -c 'integer foo; print -- $foo' 2>&1) , \
+	    $(env 'bar[$(echo >&2 fowled)0]=baz' "$__progname" -c 'typeset -p bar') .
+	print 4 $(foo=0123 "$__progname" +o posix -c 'integer foo; print -- $foo' 2>&1) , \
+	    $(env 'bar[0123]=baz' "$__progname" +o posix -c 'typeset -p bar') .
+	# ksh93 does not do this:
+	print 5 $(foo=0123 "$__progname" -o posix -c 'integer foo; print -- $foo' 2>&1) .
+	# at import time FPOSIX is not yet set
+	print 6 $(foo=0x123 "$__progname" -c 'integer foo; print -- $foo' 2>&1) , \
+	    $(env 'bar[0x123]=baz' "$__progname" -c 'typeset -p bar') .
+	print 7 $(foo=12#123 "$__progname" -c 'integer foo; print -- $foo' 2>&1) , \
+	    $(env 'bar[12#123]=baz' "$__progname" -c 'typeset -p bar') .
+	print 8 $(foo=1+1 "$__progname" -c 'integer foo; print -- $foo' 2>&1) , \
+	    $(env 'bar[1+1]=baz' "$__progname" -c 'typeset -p bar') .
+	print 9 $(a=1 b=2 c=a "$__progname" -c 'typeset -p c; c=b; typeset -p c; integer c; typeset -p c') .
+	print 0 $(a=1 b=2 c=a "$__progname" -c 'typeset -p c;      typeset -p c; integer c; typeset -p c') .
+expected-stdout:
+	1 , .
+	2 123 , set -A bar typeset -x bar[123]=baz .
+	3 0 , .
+	4 123 , set -A bar typeset -x bar[123]=baz .
+	5 8#123 .
+	6 16#123 , .
+	7 12#123 , .
+	8 0 , .
+	9 typeset -x c=a typeset -x c=b typeset -i -x c=2 .
+	0 typeset -x c=a typeset -x c=a typeset -i -x c=0 .
+---
 name: utilities-getopts-1
 description:
 	getopts sets OPTIND correctly for unparsed option
diff -pruN mksh_59c-4/edit.c mksh_59c-6/edit.c
--- mksh_59c-4/edit.c	2021-05-03 00:29:50.000000000 +0200
+++ mksh_59c-6/edit.c	2021-05-03 03:57:57.000000000 +0200
@@ -74,6 +74,7 @@ static struct {
 #define XCF_COMMAND_FILE (XCF_COMMAND | XCF_FILE)
 #define XCF_IS_COMMAND	BIT(3)	/* return flag: is command */
 #define XCF_IS_NOSPACE	BIT(4)	/* return flag: do not append a space */
+#define XCF_IS_HOMEDIR	BIT(5)	/* return flag: tilde needs slash */
 
 static char editmode;
 static int xx_cols;			/* for Emacs mode */
@@ -92,7 +93,7 @@ static int x_do_comment(char *, ssize_t,
 static void x_print_expansions(int, char * const *, bool);
 static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***);
 static size_t x_longest_prefix(int, char * const *);
-static void x_glob_hlp_add_qchar(char *);
+static char *x_glob_hlp_add_qchar(char *);
 static char *x_glob_hlp_tilde_and_rem_qchar(char *, bool);
 static size_t x_basename(const char *, const char *);
 static void x_free_words(int, char **);
@@ -297,21 +298,32 @@ x_print_expansions(int nwords, char * co
 
 /*
  * Convert backslash-escaped string to QCHAR-escaped
- * string useful for globbing; loses QCHAR unless it
- * can squeeze in, eg. by previous loss of backslash
+ * string useful for globbing
  */
-static void
+static char *
 x_glob_hlp_add_qchar(char *cp)
 {
-	char ch, *dp = cp;
+	char ch, *dp;
 	bool escaping = false;
+	XString xs;
+	size_t n;
+
+	if (memchr(cp, QCHAR, (n = strlen(cp)))) {
+		Xinit(xs, dp, n, ATEMP);
+	} else {
+		xs.len = n + 1;
+		xs.areap = NULL; /* won’t be used */
+		xs.beg = dp = cp;
+		xs.end = xs.beg + xs.len;
+	}
 
 	while ((ch = *cp++)) {
 		if (ch == '\\' && !escaping) {
 			escaping = true;
 			continue;
 		}
-		if (escaping || (ch == QCHAR && (cp - dp) > 1)) {
+		XcheckN(xs, dp, 2);
+		if (escaping || ch == QCHAR) {
 			/*
 			 * empirically made list of chars to escape
 			 * for globbing as well as QCHAR itself
@@ -324,6 +336,7 @@ x_glob_hlp_add_qchar(char *cp)
 			case ORD('['):
 			case ORD('\\'):
 			case ORD('`'):
+			case ORD('~'):
 				*dp++ = QCHAR;
 				break;
 			}
@@ -332,6 +345,7 @@ x_glob_hlp_add_qchar(char *cp)
 		*dp++ = ch;
 	}
 	*dp = '\0';
+	return (Xstring(xs, dp));
 }
 
 /*
@@ -367,8 +381,12 @@ x_glob_hlp_tilde_and_rem_qchar(char *s,
 	}
 
 	/* ... convert it from backslash-escaped via QCHAR-escaped... */
-	if (magic_flag)
-		x_glob_hlp_add_qchar(s);
+	if (magic_flag) {
+		cp = x_glob_hlp_add_qchar(s);
+		if (cp != s)
+			afree(s, ATEMP);
+		s = cp;
+	}
 	/* ... to unescaped, for comparison with the matches */
 	cp = dp = s;
 
@@ -391,25 +409,26 @@ x_glob_hlp_tilde_and_rem_qchar(char *s,
 static int
 x_file_glob(int *flagsp, char *toglob, char ***wordsp)
 {
-	char **words, *cp;
+	char **words, *cp, *qglob;
 	int nwords;
 	XPtrV w;
 	struct source *s, *sold;
 
 	/* remove all escaping backward slashes */
-	x_glob_hlp_add_qchar(toglob);
+	qglob = x_glob_hlp_add_qchar(toglob);
 
 	/*
 	 * Convert "foo*" (toglob) to an array of strings (words)
 	 */
 	sold = source;
 	s = pushs(SWSTR, ATEMP);
-	s->start = s->str = toglob;
+	s->start = s->str = qglob;
 	source = s;
 	if (yylex(ONEWORD | LQCHAR) != LWORD) {
 		source = sold;
 		internal_warningf(Tfg_badsubst);
-		return (0);
+		nwords = 0;
+		goto out;
 	}
 	source = sold;
 	afree(s, ATEMP);
@@ -434,7 +453,7 @@ x_file_glob(int *flagsp, char *toglob, c
 		struct stat statb;
 
 		/* Expand any tilde and drop all QCHAR for comparison */
-		toglob = x_glob_hlp_tilde_and_rem_qchar(toglob, false);
+		qglob = x_glob_hlp_tilde_and_rem_qchar(qglob, false);
 
 		/*
 		 * Check if globbing failed (returned glob pattern),
@@ -444,7 +463,7 @@ x_file_glob(int *flagsp, char *toglob, c
 		 * to glob something which evaluated to an empty
 		 * string (e.g., "$FOO" when there is no FOO, etc).
 		 */
-		if ((strcmp(words[0], toglob) == 0 &&
+		if ((strcmp(words[0], qglob) == 0 &&
 		    stat(words[0], &statb) < 0) ||
 		    words[0][0] == '\0') {
 			x_free_words(nwords, words);
@@ -456,6 +475,9 @@ x_file_glob(int *flagsp, char *toglob, c
 	if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
 		x_free_words(nwords, words);
 
+ out:
+	if (qglob != toglob)
+		afree(qglob, ATEMP);
 	return (nwords);
 }
 
@@ -671,7 +693,7 @@ x_cf_glob(int *flagsp, const char *buf,
 
 		if (*toglob == '~' && /* not vdirsep */ !vstrchr(toglob, '/')) {
 			/* neither for '~foo' (but '~foo/bar') */
-			*flagsp |= XCF_IS_NOSPACE;
+			*flagsp |= XCF_IS_HOMEDIR;
 			goto dont_add_glob;
 		}
 
@@ -2884,11 +2906,13 @@ do_complete(
 	x_adjust();
 	/*
 	 * append a space if this is a single non-directory match
-	 * and not a parameter or homedir substitution
+	 * and not a parameter substitution, slash for homedir
 	 */
-	if (nwords == 1 && !mksh_cdirsep(words[0][nlen - 1]) &&
-	    !(flags & XCF_IS_NOSPACE)) {
-		x_ins(T1space);
+	if (nwords == 1 && !mksh_cdirsep(words[0][nlen - 1])) {
+		if (flags & XCF_IS_HOMEDIR)
+			x_ins("/");
+		else if (!(flags & XCF_IS_NOSPACE))
+			x_ins(T1space);
 	}
 
 	x_free_words(nwords, words);
@@ -5536,11 +5560,14 @@ complete_word(int cmd, int count)
 
 		/*
 		 * append a space if this is a non-directory match
-		 * and not a parameter or homedir substitution
+		 * and not a parameter substitution, slash for homedir
 		 */
-		if (match_len > 0 && !mksh_cdirsep(match[match_len - 1]) &&
-		    !(flags & XCF_IS_NOSPACE))
-			rval = putbuf(T1space, 1, false);
+		if (match_len > 0 && !mksh_cdirsep(match[match_len - 1])) {
+			if (flags & XCF_IS_HOMEDIR)
+				rval = putbuf("/", 1, false);
+			else if (!(flags & XCF_IS_NOSPACE))
+				rval = putbuf(T1space, 1, false);
+		}
 	}
 	x_free_words(nwords, words);
 
diff -pruN mksh_59c-4/eval.c mksh_59c-6/eval.c
--- mksh_59c-4/eval.c	2020-05-05 23:34:54.000000000 +0200
+++ mksh_59c-6/eval.c	2021-05-03 03:57:57.000000000 +0200
@@ -1300,7 +1300,7 @@ varsub(Expand *xp, const char *sp, const
 			if (sc & 2) {
 				stype = 0;
 				XPinit(wv, 32);
-				vp = global(arrayname(sp));
+				vp = arraybase(sp);
 				do {
 					if (vp->flag & ISSET)
 						XPput(wv, shf_smprintf(Tf_lu,
@@ -1347,7 +1347,7 @@ varsub(Expand *xp, const char *sp, const
 		case ORD('#'):
 			  switch (sc & 3) {
 			case 3:
-				vp = global(arrayname(sp));
+				vp = arraybase(sp);
 				if (vp->flag & (ISSET|ARRAY))
 					zero_ok = true;
 				sc = 0;
@@ -1458,7 +1458,7 @@ varsub(Expand *xp, const char *sp, const
 		/* do what we can */
 		if (sc & 2) {
 			XPinit(wv, 32);
-			vp = global(arrayname(sp));
+			vp = arraybase(sp);
 			do {
 				if (vp->flag & ISSET)
 					XPput(wv, str_val(vp));
diff -pruN mksh_59c-4/misc.c mksh_59c-6/misc.c
--- mksh_59c-4/misc.c	2021-05-03 00:29:50.000000000 +0200
+++ mksh_59c-6/misc.c	2021-05-03 03:57:57.000000000 +0200
@@ -2398,6 +2398,7 @@ chvt(const Getopt *go)
 			errorf(Tf_sD_s_s, "chvt", Tcant_open, dv);
 		}
 	}
+	afree(cp, ATEMP);
 	if (go->optarg[0] != '!') {
 		switch (fork()) {
 		case -1:
diff -pruN mksh_59c-4/mksh.1 mksh_59c-6/mksh.1
--- mksh_59c-4/mksh.1	2021-05-03 00:29:50.000000000 +0200
+++ mksh_59c-6/mksh.1	2021-05-03 03:57:57.000000000 +0200
@@ -84,7 +84,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd March 13, 2021
+.Dd $Mdocdate: May 2 2021 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -6324,7 +6324,7 @@ replaces the inserted text string with t
 .Pp
 The tab completion escapes characters the same way as the following code:
 .Bd -literal
-print \-nr \-\- "${x@/[\e"\-\e$\e&\-*:\-?[\e\e\e\`\e{\-\e}${IFS\-$\*(aq \et\en\*(aq}]/\e\e$KSH_MATCH}"
+print \-nr \-\- "${x@/[\e"\-\e$\e&\-*:\-?[\e\e\e\`\e{\-\e\*(TI${IFS\-$\*(aq \et\en\*(aq}]/\e\e$KSH_MATCH}"
 .Ed
 .Ss Vi editing mode
 .Em Note:
diff -pruN mksh_59c-4/mksh.faq mksh_59c-6/mksh.faq
--- mksh_59c-4/mksh.faq	2021-05-03 00:29:50.000000000 +0200
+++ mksh_59c-6/mksh.faq	2021-05-03 03:57:57.000000000 +0200
@@ -1,4 +1,4 @@
-RCSID: $MirOS: src/bin/mksh/mksh.faq,v 1.17+locale-tracking 2021/03/11 14:16:08 tg Exp $
+RCSID: $MirOS: src/bin/mksh/mksh.faq,v 1.19+locale-tracking 2021/05/02 08:26:03 tg Exp $
 ToC: spelling
 Title: How do you spell <tt>mksh</tt>? How do you pronounce it?
 
@@ -412,7 +412,7 @@ Title: What about programmable tab compl
 The shell itself provides static deterministic tab completion.
 However, you can use hooks like reprogramming the Tab key to a
 command line editor macro, and using the <tt>evaluate-region</tt>
-editor command (modulo a bugfix) together with <tt>quote-region</tt> and shell functions to
+editor command together with <tt>quote-region</tt> and shell functions to
 implement a programmable completion engine. Multiple people have
 been considering doing so in our IRC channel; we’ll hyperlink to
 these engines when they are available.
@@ -612,6 +612,20 @@ Title: Didn’t there used to be a sleep
  In that case, GNU coreutils’ sleep, which is built on older syscalls,
  may work if the copyleft licence isn’t a showstopper for you.</p>
 ----
+ToC: arith-import
+Title: Some integer variables are 0?
+
+<p class="boxhead">To mitigate potential exploits, variables imported
+ from the environment are not trusted in arithmetic context; that is…</p>
+<div class="boxtext">
+ <pre>
+	foo=1+1 mksh -c 'integer foo; print $foo'
+	foo=1+1 mksh -c 'integer foo=$foo; print $foo'
+ </pre>
+</div><p class="boxfoot">… will lose the value in the first line,
+ while the second line explicitly “untaints”, to use a Perl term,
+ the content. Purely numeric values will pass, though.</p>
+----
 ToC: string-concat
 Title: “+=” behaves differently from other shells
 
diff -pruN mksh_59c-4/sh.h mksh_59c-6/sh.h
--- mksh_59c-4/sh.h	2021-05-03 00:29:50.000000000 +0200
+++ mksh_59c-6/sh.h	2021-05-03 03:57:57.000000000 +0200
@@ -195,7 +195,7 @@
 #ifdef EXTERN
 __RCSID("$MirOS: src/bin/mksh/sh.h,v 1.906 2021/01/24 19:37:31 tg Exp $");
 #endif
-#define MKSH_VERSION "R59 2021/03/13"
+#define MKSH_VERSION "R59 2021/05/03"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -1411,9 +1411,9 @@ EXTERN bool really_exit;
 #define CiOCTAL	BIT(5)	/* 0‥7				*/
 #define CiQCL	BIT(6)	/* &();|			*/
 #define CiALIAS	BIT(7)	/* !,.@				*/
-#define CiQCX	BIT(8)	/* *[\\				*/
+#define CiQCX	BIT(8)	/* *[\\~			*/
 #define CiVAR1	BIT(9)	/* !*@				*/
-#define CiQCM	BIT(10)	/* /^~				*/
+#define CiQCM	BIT(10)	/* /^				*/
 #define CiDIGIT	BIT(11)	/* 89				*/
 #define CiQC	BIT(12)	/* "'				*/
 #define CiSPX	BIT(13)	/* \x0B\x0C			*/
@@ -1470,7 +1470,7 @@ EXTERN char ifs0;
 #define C_EDCMD	(CiGRAVE | CiQCL)
 /* \x09\x0A\x20"&'():;<=>`|	editor non-word characters */
 #define C_EDNWC	(CiANGLE | CiCOLON | CiEQUAL | CiGRAVE | CiNL | CiQC | CiQCL | CiSP | CiTAB)
-/* "#$&'()*:;<=>?[\\`{|}	editor quotes for tab completion */
+/* "#$&'()*:;<=>?[\\`{|}~	editor quotes for tab completion */
 #define C_EDQ	(CiANGLE | CiCOLON | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiQC | CiQCL | CiQCX | CiQUEST | CiSS)
 /* !‥~			POSIX graphical (alphanumerical plus punctuation) */
 #define C_GRAPH	(C_PUNCT | CiDIGIT | CiLOWER | CiOCTAL | CiUPPER)
@@ -1494,8 +1494,8 @@ EXTERN char ifs0;
 #define C_PRINT	(C_GRAPH | CiSP)
 /* !"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~	POSIX punctuation */
 #define C_PUNCT	(CiALIAS | CiANGLE | CiBRACK | CiCOLON | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiMINUS | CiPERCT | CiPLUS | CiQC | CiQCL | CiQCM | CiQCX | CiQUEST | CiSS | CiUNDER)
-/* \x09\x0A"#$&'()*;<=>?[\\]`|	characters requiring quoting, minus space */
-#define C_QUOTE	(CiANGLE | CiBRACK | CiEQUAL | CiGRAVE | CiHASH | CiNL | CiQC | CiQCL | CiQCX | CiQUEST | CiSS | CiTAB)
+/* \x09\x0A"#$&'()*;<=>?[\\]`{|}~	characters requiring quoting, minus space */
+#define C_QUOTE	(CiANGLE | CiBRACK | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiNL | CiQC | CiQCL | CiQCX | CiQUEST | CiSS | CiTAB)
 /* 0‥9A‥Fa‥f		hexadecimal digit */
 #define C_SEDEC	(CiDIGIT | CiHEXLT | CiOCTAL)
 /* \x09‥\x0D\x20	POSIX space class */
@@ -2749,7 +2749,7 @@ struct tbl *arraysearch(struct tbl *, ui
 char **makenv(void);
 void change_winsz(void);
 size_t array_ref_len(const char *) MKSH_A_PURE;
-char *arrayname(const char *);
+struct tbl *arraybase(const char *);
 mksh_uari_t set_array(const char *, bool, const char **);
 uint32_t hash(const void *) MKSH_A_PURE;
 uint32_t chvt_rndsetup(const void *, size_t) MKSH_A_PURE;
diff -pruN mksh_59c-4/shf.c mksh_59c-6/shf.c
--- mksh_59c-4/shf.c	2020-06-22 19:11:30.000000000 +0200
+++ mksh_59c-6/shf.c	2021-05-03 03:57:57.000000000 +0200
@@ -1206,7 +1206,7 @@ const uint32_t tpl_ctypes[128] = {
 	CiLOWER,	CiLOWER,	CiLOWER,	CiLOWER,
 	CiLOWER,	CiLOWER,	CiLOWER,	CiLOWER,
 	CiLOWER,	CiLOWER,	CiLOWER,	CiCURLY,
-	CiQCL,		CiCURLY,	CiQCM,		CiCNTRL
+	CiQCL,		CiCURLY,	CiQCX,		CiCNTRL
 };
 
 void
diff -pruN mksh_59c-4/syn.c mksh_59c-6/syn.c
--- mksh_59c-4/syn.c	2020-10-31 02:22:25.000000000 +0100
+++ mksh_59c-6/syn.c	2021-05-03 03:57:57.000000000 +0200
@@ -74,8 +74,8 @@ static int symbol;			/* yylex value */
 
 #define REJECT		(reject = true)
 #define ACCEPT		(reject = false)
-#define token(cf)	((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf)))
-#define tpeek(cf)	((reject) ? (symbol) : (REJECT, symbol = yylex(cf)))
+#define token(cf)	((reject ? 0 : (symbol = yylex(cf))), ACCEPT, symbol)
+#define tpeek(cf)	((reject ? 0 : (symbol = yylex(cf))), REJECT, symbol)
 #define musthave(c,cf)	do { 					\
 	if ((unsigned int)token(cf) != (unsigned int)(c))	\
 		syntaxerr(NULL);				\
diff -pruN mksh_59c-4/var.c mksh_59c-6/var.c
--- mksh_59c-4/var.c	2020-06-22 19:11:30.000000000 +0200
+++ mksh_59c-6/var.c	2021-05-03 03:57:57.000000000 +0200
@@ -3,7 +3,7 @@
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
  *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
- *		 2019
+ *		 2019, 2021
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -29,7 +29,7 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.237 2020/06/22 17:11:03 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.237+deb 2020/06/22 17:11:03 tg Exp $");
 
 /*-
  * Variables
@@ -57,6 +57,7 @@ static void getspec(struct tbl *);
 static void setspec(struct tbl *);
 static void unsetspec(struct tbl *, bool);
 static int getint(struct tbl *, mksh_ari_u *, bool);
+static int getnum(const char *, mksh_ari_u *, bool, bool);
 static const char *array_index_calc(const char *, bool *, uint32_t *);
 static struct tbl *vtypeset(int *, const char *, uint32_t, uint32_t, int, int);
 
@@ -494,6 +495,7 @@ setstr(struct tbl *vq, const char *s, in
 			vq->flag |= ALLOC;
 			vq->type = 0;
 		}
+		vq->flag &= ~IMPORT;
 		afree(salloc, ATEMP);
 	} else {
 		/* integer dest */
@@ -527,10 +529,6 @@ setint(struct tbl *vq, mksh_ari_t n)
 static int
 getint(struct tbl *vp, mksh_ari_u *nump, bool arith)
 {
-	mksh_uari_t c, num = 0, base = 10;
-	const char *s;
-	bool have_base = false, neg = false;
-
 	if (vp->flag & SPECIAL)
 		getspec(vp);
 	/* XXX is it possible for ISSET to be set and val.s to be NULL? */
@@ -540,7 +538,15 @@ getint(struct tbl *vp, mksh_ari_u *nump,
 		nump->i = vp->val.i;
 		return (vp->type);
 	}
-	s = vp->val.s + vp->type;
+	return (getnum(vp->val.s + vp->type, nump, arith,
+	    Flag(FPOSIX) && !(vp->flag & ZEROFIL)));
+}
+
+static int
+getnum(const char *s, mksh_ari_u *nump, bool arith, bool psxoctal)
+{
+	mksh_uari_t c, num = 0, base = 10;
+	bool have_base = false, neg = false;
 
 	do {
 		c = (unsigned char)*s++;
@@ -561,8 +567,7 @@ getint(struct tbl *vp, mksh_ari_u *nump,
 			base = 16;
 			++s;
 			goto getint_c_style_base;
-		} else if (Flag(FPOSIX) && ctype(s[0], C_DIGIT) &&
-		    !(vp->flag & ZEROFIL)) {
+		} else if (psxoctal && ctype(s[0], C_DIGIT)) {
 			/* interpret as octal (deprecated) */
 			base = 8;
  getint_c_style_base:
@@ -641,10 +646,11 @@ setint_v(struct tbl *vq, struct tbl *vp,
 void
 setint_n(struct tbl *vq, mksh_ari_t num, int newbase)
 {
-	if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) {
-		vq->flag &= ~ALLOC;
+	if (!(vq->flag & INTEGER)) {
+		if (vq->flag & ALLOC)
+			afree(vq->val.s, vq->areap);
+		vq->flag &= ~(ALLOC | IMPORT);
 		vq->type = 0;
-		afree(vq->val.s, vq->areap);
 	}
 	vq->val.i = num;
 	if (newbase != 0)
@@ -938,7 +944,7 @@ vtypeset(int *ep, const char *var, uint3
 
 	set &= ~(LOCAL|LOCAL_COPY);
 
-	vpbase = (vp->flag & ARRAY) ? global(arrayname(tvar)) : vp;
+	vpbase = (vp->flag & ARRAY) ? arraybase(tvar) : vp;
 
 	/*
 	 * only allow export and readonly flag to be set; AT&T ksh
@@ -961,7 +967,7 @@ vtypeset(int *ep, const char *var, uint3
 		 */
 		for (t = vpbase; t; t = t->u.array) {
 			bool fake_assign;
-			char *s = NULL;
+			const char *s = NULL;
 			char *free_me = NULL;
 
 			fake_assign = (t->flag & ISSET) && (!val || t != vp) &&
@@ -983,13 +989,27 @@ vtypeset(int *ep, const char *var, uint3
 				t->type = 0;
 				t->flag &= ~ALLOC;
 			}
-			t->flag = (t->flag | set) & ~clr;
 			/*
 			 * Don't change base if assignment is to be
 			 * done, in case assignment fails.
 			 */
-			if ((set & INTEGER) && base > 0 && (!val || t != vp))
-				t->type = base;
+			if (set & INTEGER) {
+				if (base > 0 && (!val || t != vp))
+					t->type = base;
+				/*
+				 * Do not permit content from the
+				 * environment to e.g. execute commands.
+				 */
+				if ((t->flag & IMPORT) && fake_assign) {
+					mksh_ari_u num;
+
+					if (getnum(s, &num, true,
+					    tobool(Flag(FPOSIX))) == -1)
+						s = "0";
+					clr |= IMPORT;
+				}
+			}
+			t->flag = (t->flag | set) & ~clr;
 			if (set & (LJUST|RJUST|ZEROFIL))
 				t->u2.field = field;
 			if (fake_assign) {
@@ -1019,7 +1039,7 @@ vtypeset(int *ep, const char *var, uint3
 
 	if (vappend) {
 		size_t tlen;
-		if ((vp->flag & (ISSET|ALLOC|SPECIAL|INTEGER|UCASEV_AL|LCASEV|LJUST|RJUST)) != (ISSET|ALLOC)) {
+		if ((vp->flag & (ISSET|ALLOC|SPECIAL|INTEGER|UCASEV_AL|LCASEV|LJUST|RJUST|IMPORT)) != (ISSET|ALLOC)) {
 			/* cannot special-case this */
 			strdup2x(tvar, str_val(vp), val);
 			val = tvar;
@@ -1038,9 +1058,11 @@ vtypeset(int *ep, const char *var, uint3
 			/* done after assignment to override default */
 			if (base > 0)
 				vp->type = base;
-		} else
+		} else {
 			/* setstr can't fail (readonly check already done) */
 			setstr(vp, val, KSH_RETURN_ERROR | 0x4);
+			vp->flag |= (set & IMPORT);
+		}
 
 		/* came here from vappend? need to free temp val */
 		if (vappend)
@@ -1610,19 +1632,25 @@ array_ref_len(const char *cp)
 }
 
 /*
- * Make a copy of the base of an array name
+ * same effect as global(copy of the base of an array name)
  */
-char *
-arrayname(const char *str)
+struct tbl *
+arraybase(const char *str)
 {
 	const char *p;
-	char *rv;
-
-	if (!(p = cstrchr(str, '[')))
-		/* Shouldn't happen, but why worry? */
-		strdupx(rv, str, ATEMP);
-	else
-		strndupx(rv, str, p - str, ATEMP);
+	char *s, sbuf[32];
+	size_t n;
+	struct tbl *rv;
+
+	n = strlen(str);
+	if ((p = memchr(str, '[', n)))
+		n = p - str;
+	s = n < sizeof(sbuf) ? sbuf : alloc(n + 1, ATEMP);
+	memcpy(s, str, n);
+	s[n] = '\0';
+	rv = global(s);
+	if (s != sbuf)
+		afree(s, ATEMP);
 
 	return (rv);
 }

--- End Message ---
--- Begin Message ---
Unblocked.

--- End Message ---

Reply to: