Skip to content

Commit

Permalink
Fix typeset attributes -a, -A, -l, -u leaking out of subshells
Browse files Browse the repository at this point in the history
If an array or upper/lowercase variable was declared with a null
initial value within a virtual/non-forked subshell, like:
	( typeset -a foo; ... )
	( typeset -A foo; ... )
	( typeset -l foo; ... )
	( typeset -u foo; ... )
then the type declaration leaked out of the subshell into the
parent shell environment, though without any values that may
subsequently have been assigned.

src/cmd/ksh93/bltins/typeset.c: setall():
- When deciding whether to create a virtual subshell scope for a
  variable, use sh_assignok(), which was actually designed for the
  purpose, instead of _nv_unset(). This allows getting rid of a
  tangled mess of special-casing that never worked quite right.

src/cmd/ksh93/tests/arrays.sh:
- Add regression tests checking that array declarations don't leak
  out of virtual subshells.

src/cmd/ksh93/tests/attributes.sh:
- Add regression tests for combining the 'export' and 'readonly'
  attributes with every other possible typeset attribute on unset
  variables. This also includes a subshell leak test for each one.

Fixes: #88
  • Loading branch information
McDutchie committed Jul 26, 2020
1 parent 1bc2c74 commit a2f13c1
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 5 deletions.
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Any uppercase BUG_* names are modernish shell bug IDs.
an unrecognized option. 'sleep -: 1' will now show a usage message and
exit instead of sleep for one second.

- Fixed a bug that caused the 'typeset' variable attributes -a, -A, -l, and
-u to leak out of a subshell if they were set without assigning a value.

2020-07-23:

- Fixed an infinite loop that could occur when ksh is the system's /bin/sh.
Expand Down
19 changes: 14 additions & 5 deletions src/cmd/ksh93/bltins/typeset.c
Original file line number Diff line number Diff line change
Expand Up @@ -691,13 +691,22 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp
if(!comvar && !iarray)
continue;
}
if(!nv_isarray(np) && !strchr(name,'=') && !(shp->envlist && nv_onlist(shp->envlist,name)))

/* Create local scope for virtual subshell */
if(shp->subshell)
{
if(comvar || (shp->last_root==shp->var_tree && (tp->tp || (!shp->st.real_fun && (nvflags&NV_STATIC)) || (!(flag&(NV_EXPORT|NV_RDONLY)) && nv_isattr(np,(NV_EXPORT|NV_IMPORT))==(NV_EXPORT|NV_IMPORT)))))
{
_nv_unset(np,0);
}
if(!nv_isattr(np,NV_NODISC|NV_ARRAY) && !nv_isvtree(np))
{
/*
* Variables with internal trap/discipline functions (LC_*, LINENO, etc.) need to be
* cloned, as moving them will remove the discipline function.
*/
np=sh_assignok(np,2);
}
else
np=sh_assignok(np,0);
}

if(troot==shp->var_tree)
{
if(iarray)
Expand Down
33 changes: 33 additions & 0 deletions src/cmd/ksh93/tests/arrays.sh
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ then err_exit 'arithmetic expressions in typeset not working'
fi
unset foo
typeset foo=bar
typeset -a foo
if [[ ${foo[0]} != bar ]]
then err_exit 'initial value not preserved when typecast to indexed array'
fi
unset foo
typeset foo=bar
typeset -A foo
if [[ ${foo[0]} != bar ]]
then err_exit 'initial value not preserved when typecast to associative'
Expand Down Expand Up @@ -674,5 +680,32 @@ actual=$("$SHELL" -c 'A[0]="'\''" B[0]=aa C[0]=aa; typeset -a') \
[[ $actual == "$expect" ]] || err_exit 'Incorrect output when listing indexed array' \
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
# ======
# Arrays in virtual/non-forked subshells
unset foo
(typeset -a foo)
[[ -n ${ typeset -p foo; } ]] && err_exit 'Null indexed array leaks out of subshell'
unset foo
(typeset -a foo=(1 2 3))
[[ -n ${ typeset -p foo; } ]] && err_exit 'Indexed array with init value leaks out of subshell'
unset foo
(typeset -a foo; foo=(1 2 3))
[[ -n ${ typeset -p foo; } ]] && err_exit 'Indexed array leaks out of subshell'
unset foo
(typeset -A foo)
[[ -n ${ typeset -p foo; } ]] && err_exit 'Null associative array leaks out of subshell'
unset foo
(typeset -A foo=([bar]=baz [lorem]=ipsum))
[[ -n ${ typeset -p foo; } ]] && err_exit 'Associative array with init value leaks out of subshell'
unset foo
(typeset -A foo; foo=([bar]=baz [lorem]=ipsum))
[[ -n ${ typeset -p foo; } ]] && err_exit 'Associative array leaks out of subshell'
# ======
exit $((Errors<125?Errors:125))
49 changes: 49 additions & 0 deletions src/cmd/ksh93/tests/attributes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -468,4 +468,53 @@ typeset -Z2 foo=3
export foo
[[ $(typeset -p foo) == 'typeset -x -Z 2 -R 2 foo=03' ]] || err_exit '-Z2 not working after export'

# ======
# unset exported readonly variables, combined with all other possible attributes
typeset -A expect=(
[a]='typeset -x -r -a foo'
[b]='typeset -x -r -b foo'
[i]='typeset -x -r -i foo'
[i37]='typeset -x -r -i 37 foo'
[l]='typeset -x -r -l foo'
[n]='typeset -n -r foo'
[s]='typeset -x -r -s -i 0 foo=0'
[u]='typeset -x -r -u foo'
[A]='typeset -x -r -A foo=()'
[C]='typeset -x -r foo=()'
[E]='typeset -x -r -E foo'
[E12]='typeset -x -r -E 12 foo'
[F]='typeset -x -r -F foo'
[F12]='typeset -x -r -F 12 foo'
[H]='typeset -x -r -H foo'
[L]='typeset -x -r -L 0 foo'
# [L17]='typeset -x -r -L 17 foo' # TODO: outputs '-L 0'
[Mtolower]='typeset -x -r -l foo'
[Mtoupper]='typeset -x -r -u foo'
[R]='typeset -x -r -R 0 foo'
# [R17]='typeset -x -r -R 17 foo' # TODO: outputs '-L 0'
[X]='typeset -x -r -X 32 foo'
[X17]='typeset -x -r -X 17 foo'
[S]='typeset -x -r foo'
[T]='typeset -x -r foo'
[Z]='typeset -x -r -Z 0 -R 0 foo'
# [Z13]='typeset -x -r -Z 13 -R 13 foo' # TODO: outputs 'typeset -x -r -Z 0 -R 0 foo'
)
for flag in a b i i37 l n s u A C E E12 F F12 H L Mtolower Mtoupper R X X17 S T Z
do unset foo
actual=$(
redirect 2>&1
export foo
(typeset "-$flag" foo; readonly foo; typeset -p foo)
typeset +x foo # unexport
leak=${ typeset -p foo; }
[[ -n $leak ]] && print "SUBSHELL LEAK: $leak"
)
if [[ $actual != "${expect[$flag]}" ]]
then err_exit "unset exported readonly with -$flag:" \
"expected $(printf %q "${expect[$flag]}"), got $(printf %q "$actual")"
fi
done
unset expect

# ======
exit $((Errors<125?Errors:125))

0 comments on commit a2f13c1

Please sign in to comment.