From 8eec2c793cf782b0b067a94cf3442d6e118e8d77 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 10 Nov 2025 15:13:36 -0800 Subject: [PATCH] 54064: avoid crash on named references to argv/ARGC, improve valid_nameref() Leaves some edgecase issues unresolved (see tests). --- ChangeLog | 6 +++ Src/params.c | 67 +++++++++++++++++++++------------- Test/K01nameref.ztst | 87 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 26 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3f1503dbe..2f2c682b6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2025-11-10 Bart Schaefer + + * 54064: Src/params.c, Test/K01nameref.ztst: avoid crash on named + references to argv/ARGC, improve syntax checks in valid_nameref(). + Leaves some edgecase issues unresolved (see tests). + 2025-11-10 Oliver Kiddle * 53402, 54042: Src/Zle/termquery.c, Src/Zle/zle_main.c, diff --git a/Src/params.c b/Src/params.c index b76fb8a6b..ccb73c9b6 100644 --- a/Src/params.c +++ b/Src/params.c @@ -1309,7 +1309,6 @@ isident(char *s) /* Require balanced [ ] pairs with something between */ if (!(ss = parse_subscript(++ss, 1, ']'))) return 0; - untokenize(s); return !ss[1]; } @@ -3247,8 +3246,8 @@ assignsparam(char *s, char *val, int flags) return NULL; } if (*val && (v->pm->node.flags & PM_NAMEREF)) { - if (!valid_refname(val)) { - zerr("invalid variable name: %s", val); + if (!valid_refname(val, v->pm->node.flags)) { + zerr("invalid name reference: %s", val); zsfree(val); unqueue_signals(); errflag |= ERRFLAG_ERROR; @@ -6505,30 +6504,48 @@ upscope_upper(Param pm, int reflevel) /**/ static int -valid_refname(char *val) +valid_refname(char *val, int flags) { - char *t = itype_end(val, INAMESPC, 0); + char *t; - if (idigit(*val)) - return 0; - if (*t != 0) { - if (*t == '[') { - tokenize(t = dupstring(t+1)); - while ((t = parse_subscript(t, 0, ']')) && *t++ == Outbrack) { - if (*t == Inbrack) - ++t; - else - break; - } - if (t && *t) { - /* zwarn("%s: stuff after subscript: %s", val, t); */ - t = NULL; - } - } else if (t[1] || !(*t == '!' || *t == '?' || - *t == '$' || *t == '-' || - *t == '0' || *t == '_')) { - /* Skipping * @ # because of doshfunc() implementation */ - t = NULL; + if (flags & PM_UPPER) { + /* Upward reference to positionals is doomed to fail */ + if (idigit(*val)) + return 0; + t = itype_end(val, INAMESPC, 0); + if ((t - val == 4) && + (!strncmp(val, "argv", 4) || + !strncmp(val, "ARGC", 4))) + return 0; + } else if (idigit(*val)) { + t = val; + while (*++t) + if (!idigit(*t)) + break; + if (*t && *t != '[') /* Need to test Inbrack here too? */ + return 0; + } else + t = itype_end(val, INAMESPC, 0); + + if (t == val) { + if (!(*t == '!' || *t == '?' || + *t == '$' || *t == '-' || + *t == '_')) + return 0; + ++t; + } + if (*t == '[') { + /* Another bit of isident() to emulate */ + tokenize(t = dupstring(t+1)); + while ((t = parse_subscript(t, 0, ']')) && *t++ == Outbrack) { + if (*t == Inbrack) + ++t; + else + break; + } + if (t && *t) { + /* zwarn("%s: stuff after subscript: %s", val, t); */ + return 0; } } return !!t; diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index 5d229a94e..29a7af0de 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -531,7 +531,7 @@ F:ksh93 does not implement this either unset -n ptr1 typeset -n ptr1='not[2]good' 1:invalid nameref -*?*invalid variable name: not\[2\]good +*?*invalid name reference: not\[2\]good unset -n ptr1 unset hash @@ -1181,6 +1181,91 @@ F:previously this could create an infinite recursion and crash >h2: ref1=f1 ref2=f1 ref3=f1 >f1: ref1=f1 ref2=f1 ref3=f1 +# +# The following two tests are linked, do not separate +# + + edgelocal() ( local -n x=$1; typeset -p x; print -r $x ) + edgeupper() ( local -nu x=$1; typeset -p x; print -r $x ) + for edge in argv ARGC \@ \* \# 0 1 01 \! \? - _ + do + edgelocal $edge + edgelocal "$edge""[1]" + edgeupper $edge + done +0:references to builtin specials +F:Subscripting on 1 01 ! ? - should print first character but do not +>typeset -n x=argv +>argv +>typeset -n x='argv[1]' +>argv[1] +>typeset -n x=ARGC +>1 +>typeset -n x='ARGC[1]' +>1 +>typeset -n x=0 +>edgelocal +>typeset -n x='0[1]' +>e +>typeset -n x=1 +> +>typeset -n x='1[1]' +> +>typeset -n x=01 +> +>typeset -n x='01[1]' +> +>typeset -n x=! +>0 +>typeset -n x='![1]' +> +>typeset -un x=! +>0 +>typeset -n x='?' +>0 +>typeset -n x='?[1]' +> +>typeset -un x='?' +>0 +>typeset -n x=- +>569X +>typeset -n x='-[1]' +> +>typeset -un x=- +>569X +>typeset -n x=_ +>x +>typeset -n x='_[1]' +>x +>typeset -un x=_ +>x +?edgeupper: invalid name reference: argv +?edgeupper: invalid name reference: ARGC +?edgelocal: invalid name reference: @ +?edgelocal: invalid name reference: @[1] +?edgeupper: invalid name reference: @ +?edgelocal: invalid name reference: * +?edgelocal: invalid name reference: *[1] +?edgeupper: invalid name reference: * +?edgelocal: invalid name reference: # +?edgelocal: invalid name reference: #[1] +?edgeupper: invalid name reference: # +?edgeupper: invalid name reference: 0 +?edgeupper: invalid name reference: 1 +?edgeupper: invalid name reference: 01 + + edgelocal \$ + edgelocal '$[1]' + edgeupper \$ + unfunction edgelocal edgeupper +0qf:references to $$ +F:$$[1] reference should print the first digit of $$ but prints nothing +>typeset -n x='$' +>$$ +>typeset -n x='\$[1]' +>$$[1] +>$$ + # # The following tests are run in interactive mode, using PS1 as an # assignable special with side-effects. This crashed at one time.