Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock X-Debbugs-Cc: t...@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 <t...@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 <t...@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); }