summaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
authortb <tb@openbsd.org>2020-09-13 15:39:09 +0000
committertb <tb@openbsd.org>2020-09-13 15:39:09 +0000
commitfea13ced53c9f0c1aee26b71d8517fefa97e9d0e (patch)
treef5d3c97f872128df975a76ed3f8f21804240445d /bin
parentAdd new x509 certificate chain validator in x509_verify.c (diff)
downloadwireguard-openbsd-fea13ced53c9f0c1aee26b71d8517fefa97e9d0e.tar.xz
wireguard-openbsd-fea13ced53c9f0c1aee26b71d8517fefa97e9d0e.zip
Fix "$@" splitting with empty IFS
One uncommon but useful way of writing shell scripts is to start off by disabling field/word splitting (IFS='') and pathname expansion/globbing (set -f), re-enabling either or both only for the commands that need them, e.g. within a subshell. This helps avoid a lot of snags with field splitting and globbing if you forget to quote a variable somewhere, adding to the general robustness of a script. (In fact it eliminates much of the need to quote variable/parameter expansions, with empty removal remaining as the only issue.) Unfortunately OpenBSD ksh (like all pdksh variants except mksh) has a POSIX compliance bug that is a show stopper for this approach: "$@" does not generate words (arguments) if IFS is empty. As a result, the separate command arguments represented by "$@" become a single argument. So passing on an intact set of positional parameters to a command or function is impossible with field splitting disabled. Of course this is illogical: the quoted special parameter "$@" generates zero or more words, it doesn't split any words, so the contents of IFS (or lack thereof) should be neither here nor there. It's old ksh88 behaviour copied by the original pdksh, but it violates POSIX and it has been fixed many years ago in ksh93 and all other POSIX shells. From Martijn Dekker (who also wrote the above paragraphs) back in 2016. Thanks to Avi Halachmi for reminding us of the issue. ok czarkoff deraadt kn
Diffstat (limited to 'bin')
-rw-r--r--bin/ksh/eval.c65
1 files changed, 48 insertions, 17 deletions
diff --git a/bin/ksh/eval.c b/bin/ksh/eval.c
index ba4b73f404e..2f22c2ee2eb 100644
--- a/bin/ksh/eval.c
+++ b/bin/ksh/eval.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: eval.c,v 1.65 2019/06/28 13:34:59 deraadt Exp $ */
+/* $OpenBSD: eval.c,v 1.66 2020/09/13 15:39:09 tb Exp $ */
/*
* Expansion - quoting, separation, substitution, globbing
@@ -47,6 +47,8 @@ typedef struct Expand {
#define IFS_WORD 0 /* word has chars (or quotes) */
#define IFS_WS 1 /* have seen IFS white-space */
#define IFS_NWS 2 /* have seen IFS non-white-space */
+#define IFS_IWS 3 /* beginning of word, ignore IFS white-space */
+#define IFS_QUOTE 4 /* beg.w/quote, becomes IFS_WORD unless "$@" */
static int varsub(Expand *, char *, char *, int *, int *);
static int comsub(Expand *, char *);
@@ -217,7 +219,17 @@ expand(char *cp, /* input word */
c = *sp++;
break;
case OQUOTE:
- word = IFS_WORD;
+ switch (word) {
+ case IFS_QUOTE:
+ /* """something */
+ word = IFS_WORD;
+ break;
+ case IFS_WORD:
+ break;
+ default:
+ word = IFS_QUOTE;
+ break;
+ }
tilde_ok = 0;
quote = 1;
continue;
@@ -297,6 +309,8 @@ expand(char *cp, /* input word */
if (f&DOBLANK)
doblank++;
tilde_ok = 0;
+ if (word == IFS_QUOTE && type != XNULLSUB)
+ word = IFS_WORD;
if (type == XBASE) { /* expand? */
if (!st->next) {
SubType *newst;
@@ -358,6 +372,11 @@ expand(char *cp, /* input word */
f |= DOTEMP_;
/* FALLTHROUGH */
default:
+ /* '-' '+' '?' */
+ if (quote)
+ word = IFS_WORD;
+ else if (dp == Xstring(ds, dp))
+ word = IFS_IWS;
/* Enable tilde expansion */
tilde_ok = 1;
f |= DOTILDE;
@@ -387,10 +406,17 @@ expand(char *cp, /* input word */
*/
x.str = trimsub(str_val(st->var),
dp, st->stype);
- if (x.str[0] != '\0' || st->quote)
+ if (x.str[0] != '\0') {
+ word = IFS_IWS;
type = XSUB;
- else
+ } else if (quote) {
+ word = IFS_WORD;
+ type = XSUB;
+ } else {
+ if (dp == Xstring(ds, dp))
+ word = IFS_IWS;
type = XNULLSUB;
+ }
if (f&DOBLANK)
doblank++;
st = st->prev;
@@ -422,6 +448,10 @@ expand(char *cp, /* input word */
if (f&DOBLANK)
doblank++;
st = st->prev;
+ if (quote || !*x.str)
+ word = IFS_WORD;
+ else
+ word = IFS_IWS;
continue;
case '?':
{
@@ -463,12 +493,8 @@ expand(char *cp, /* input word */
type = XBASE;
if (f&DOBLANK) {
doblank--;
- /* not really correct: x=; "$x$@" should
- * generate a null argument and
- * set A; "${@:+}" shouldn't.
- */
- if (dp == Xstring(ds, dp))
- word = IFS_WS;
+ if (dp == Xstring(ds, dp) && word != IFS_WORD)
+ word = IFS_IWS;
}
continue;
@@ -503,7 +529,12 @@ expand(char *cp, /* input word */
if (c == 0) {
if (quote && !x.split)
continue;
+ if (!quote && word == IFS_WS)
+ continue;
+ /* this is so we don't terminate */
c = ' ';
+ /* now force-emit a word */
+ goto emit_word;
}
if (quote && x.split) {
/* terminate word for "$@" */
@@ -554,15 +585,15 @@ expand(char *cp, /* input word */
* -----------------------------------
* IFS_WORD w/WS w/NWS w
* IFS_WS -/WS w/NWS -
- * IFS_NWS -/NWS w/NWS w
+ * IFS_NWS -/NWS w/NWS -
+ * IFS_IWS -/WS w/NWS -
* (w means generate a word)
- * Note that IFS_NWS/0 generates a word (at&t ksh
- * doesn't do this, but POSIX does).
*/
- if (word == IFS_WORD ||
- (!ctype(c, C_IFSWS) && c && word == IFS_NWS)) {
- char *p;
-
+ if ((word == IFS_WORD) || (word == IFS_QUOTE) || (c &&
+ (word == IFS_IWS || word == IFS_NWS) &&
+ !ctype(c, C_IFSWS))) {
+ char *p;
+ emit_word:
*dp++ = '\0';
p = Xclose(ds, dp);
if (fdo & DOBRACE_)