--- Begin Message ---
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);
}
--- End Message ---