Skip to content

Commit

Permalink
Fix $_ corruption in subshell disc functs (re: 88a1f3d, 430e478)
Browse files Browse the repository at this point in the history
The _ variable is used for various context-dependent things and is
sometimes a name reference (see man page under Variables). The $_
state changes are handled by set_instance() and unset_instance() in
xec.c. In the following reproducer, $_ became corrupted, crashing
the shell:

        foo.get() { .sh.value=foo; }
        bar.get() { .sh.value=${ echo foo; }; }
        (: $bar; : $foo)
        echo "$_"

So this involves two .get discipline functions, a virtual subshell,
and a shared-state command substitution in the second function.

The $_ corruption in the reproducer was happening as of 88a1f3d,
which began to robustify discipline functions by introducing a
context push and sigsetjmp/siglongjmp to ensure they can restore
state if an error occurs. As of that commit, the reproducer prints
the empty string, and no value can be assigned to _ afterwards.
While the state is restored correctly, it fails to actually execute
a siglongjmp when necessary. For example, if sh_subfork() is called
to fork a virtual subshell (causing jmpval == SH_JMPSUB), we need
to siglongjmp in the parent process, or entirely incorrect things
are going to happen, such as unset_instance() not being called when
it should be. Commit 430e478 introduced just such a sh_subfork()
call, exposing this problem.

The bug was further exacerbated by 430e478. As of that commit, the
reproducer crashes the shell. The real problem here is not in that
commit, though, but in faulty sigsetjmp/siglongjmp logic in
sh_fun(), the main interface to call a function, which is used by
the discipline function routines. If a shell command run via the
sh_funct() call executed a siglongjmp, it failed (among a few other
things) to call unset_instance() to restore the state of $_.

Moral of the story:
1. After sigsetjmp and sh_pushcontext, never fail to siglongjmp for
   a sufficiently high jmpval (see SH_JMP* in fault.h, and all the
   uses of sh_pushcontext()).
2. Always make sure the state is fully restored before siglongjmp.

Thanks to @phidebian for all the help analysing the root cause of
this problem.

src/cmd/ksh93/sh/xec.c: sh_fun():
- Fix sigsetjmp logic by (a) making it cover the sh_funct() call,
  so that a siglongjmp from elsewhere will return here, and (b)
  delaying our own siglongjmp call until state is fully restored.
- Push context with the jmpval SH_JMPCMD for a built-in command
  (which can also be run via this function) and SH_JMPFUN for a
  function; this could be needed for correct siglongjmp behaviour
  if an error occurs elsewhere, as various code paths will be able
  to detect whether it was a command or a function that failed.
- While we're here, switch AST stack use from old stak(3) to new
  stk(3) API. This was already done in many places and will
  eventually be done in the entire codebase for consistency.

src/cmd/ksh93/sh/nvdisc.c: assign(), lookup():
- Push context with SH_JMPFUN as we're going to run a function.
- Add the missing siglongjmp if jmpval >= SH_JMPFUN. Do not
  siglongjmp until the state is fully restored.

Resolves: #616
  • Loading branch information
McDutchie committed Apr 3, 2023
1 parent 486f8dc commit d446015
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 28 deletions.
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ This documents significant changes in the dev branch of ksh 93u+m.
For full details, see the git log at: https://github.com/ksh93/ksh
Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library.

2023-04-03:

- Fixed multiple crashing bugs in discipline functions invoked from non-forked
subshells, including corruption of the $_ variable.

2023-04-02:

- Corrected the 2022-07-05 fix for the detection of a syntax error in
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/ksh93/include/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

#define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */
#define SH_RELEASE_SVER "1.1.0-alpha" /* semantic version number: https://semver.org */
#define SH_RELEASE_DATE "2023-04-02" /* must be in this format for $((.sh.version)) */
#define SH_RELEASE_DATE "2023-04-03" /* must be in this format for $((.sh.version)) */
#define SH_RELEASE_CPYR "(c) 2020-2023 Contributors to ksh " SH_RELEASE_FORK

/* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */
Expand Down
12 changes: 8 additions & 4 deletions src/cmd/ksh93/sh/nvdisc.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ static void assign(Namval_t *np,const char* val,int flags,Namfun_t *handle)
Namval_t node;
union Value *up = np->nvalue.up;
Namval_t *tp, *nr; /* for 'typeset -T' types */
int jmpval = 0;
if(val && (tp=nv_type(np)) && (nr=nv_open(val,sh.var_tree,NV_VARNAME|NV_ARRAY|NV_NOADD|NV_NOFAIL)) && tp==nv_type(nr))
{
char *sub = nv_getsub(np);
Expand Down Expand Up @@ -280,7 +281,6 @@ static void assign(Namval_t *np,const char* val,int flags,Namfun_t *handle)
if(nq && !isblocked(bp,type))
{
struct checkpt checkpoint;
int jmpval;
int savexit = sh.savexit;
Lex_t *lexp = (Lex_t*)sh.lex_context, savelex;
int bflag;
Expand All @@ -290,7 +290,7 @@ static void assign(Namval_t *np,const char* val,int flags,Namfun_t *handle)
block(bp,type);
if(bflag = (type==APPEND && !isblocked(bp,LOOKUPS)))
block(bp,LOOKUPS);
sh_pushcontext(&checkpoint, 1);
sh_pushcontext(&checkpoint, SH_JMPFUN);
jmpval = sigsetjmp(checkpoint.buff, 0);
if(!jmpval)
sh_fun(nq,np,NULL);
Expand Down Expand Up @@ -370,6 +370,8 @@ static void assign(Namval_t *np,const char* val,int flags,Namfun_t *handle)
nq->nvalue.rp->running=0;
_nv_unset(nq,0);
}
if(jmpval >= SH_JMPFUN)
siglongjmp(*sh.jmplist,jmpval);
}

/*
Expand All @@ -384,10 +386,10 @@ static char* lookup(Namval_t *np, int type, Sfdouble_t *dp,Namfun_t *handle)
char *cp=0;
Namval_t node;
union Value *up = np->nvalue.up;
int jmpval = 0;
if(nq && !isblocked(bp,type))
{
struct checkpt checkpoint;
int jmpval;
int savexit = sh.savexit;
Lex_t *lexp = (Lex_t*)sh.lex_context, savelex;
/* disciplines like PS2 may run at parse time; save, reinit and restore the lexer state */
Expand All @@ -406,7 +408,7 @@ static char* lookup(Namval_t *np, int type, Sfdouble_t *dp,Namfun_t *handle)
}
block(bp,type);
block(bp, UNASSIGN); /* make sure nv_setdisc doesn't invalidate 'vp' by freeing it */
sh_pushcontext(&checkpoint, 1);
sh_pushcontext(&checkpoint, SH_JMPFUN);
jmpval = sigsetjmp(checkpoint.buff, 0);
if(!jmpval)
sh_fun(nq,np,NULL);
Expand Down Expand Up @@ -449,6 +451,8 @@ static char* lookup(Namval_t *np, int type, Sfdouble_t *dp,Namfun_t *handle)
nq->nvalue.rp->running=0;
_nv_unset(nq,0);
}
if(jmpval >= SH_JMPFUN)
siglongjmp(*sh.jmplist,jmpval);
return cp;
}

Expand Down
49 changes: 26 additions & 23 deletions src/cmd/ksh93/sh/xec.c
Original file line number Diff line number Diff line change
Expand Up @@ -3210,24 +3210,27 @@ static void sh_funct(Namval_t *np,int argn, char *argv[],struct argnod *envlist,
}

/*
* external interface to execute a function without arguments
* external interface to execute a function
* <np> is the function node
* If <nq> is not-null, then sh.name and sh.subscript will be set
*/
int sh_fun(Namval_t *np, Namval_t *nq, char *argv[])
{
int offset = 0;
char *base;
Namval_t node;
struct checkpt *checkpoint;
int jmpval = 0;
int jmpthresh;
int offset = 0;
char *base;
Namval_t node;
struct Namref nr;
long mode = 0;
char *prefix = sh.prefix;
int n=0;
char *av[3];
Fcin_t save;
int n=0;
char *av[3];
Fcin_t save;
fcsave(&save);
if((offset=staktell())>0)
base=stakfreeze(0);
if((offset=stktell(sh.stk))>0)
base=stkfreeze(sh.stk,0);
sh.prefix = 0;
if(!argv)
{
Expand All @@ -3239,36 +3242,36 @@ int sh_fun(Namval_t *np, Namval_t *nq, char *argv[])
n++;
if(nq)
mode = set_instance(nq,&node, &nr);
if(is_abuiltin(np))
jmpthresh = is_abuiltin(np) ? SH_JMPCMD : SH_JMPFUN;
checkpoint = (struct checkpt*)stkalloc(sh.stk,sizeof(struct checkpt));
sh_pushcontext(checkpoint, jmpthresh);
jmpval = sigsetjmp(checkpoint->buff,1);
if(jmpval == 0)
{
int jmpval;
struct checkpt *buffp = (struct checkpt*)stkalloc(sh.stk,sizeof(struct checkpt));
Shbltin_t *bp = &sh.bltindata;
sh_pushcontext(buffp,SH_JMPCMD);
jmpval = sigsetjmp(buffp->buff,1);
if(jmpval == 0)
if(is_abuiltin(np))
{
Shbltin_t *bp = &sh.bltindata;
bp->bnode = np;
bp->ptr = nv_context(np);
errorpush(&buffp->err,0);
errorpush(&checkpoint->err,0);
error_info.id = argv[0];
opt_info.index = opt_info.offset = 0;
opt_info.disc = 0;
sh.exitval = 0;
sh.exitval = (funptr(np))(n,argv,bp);
}
sh_popcontext(buffp);
if(jmpval>SH_JMPCMD)
siglongjmp(*sh.jmplist,jmpval);
else
sh_funct(np,n,argv,NULL,sh_isstate(SH_ERREXIT));
}
else
sh_funct(np,n,argv,NULL,sh_isstate(SH_ERREXIT));
sh_popcontext(checkpoint);
if(nq)
unset_instance(nq, &node, &nr, mode);
fcrestore(&save);
if(offset>0)
stakset(base,offset);
stkset(sh.stk,base,offset);
sh.prefix = prefix;
if(jmpval >= jmpthresh)
siglongjmp(*sh.jmplist,jmpval);
return sh.exitval;
}

Expand Down
26 changes: 26 additions & 0 deletions src/cmd/ksh93/tests/variables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1571,5 +1571,31 @@ got=$(set +x; { read foo=bar; } <<<baz 2>&1)
[[ e=$? -eq 1 && $got == *"$exp" ]] || err_exit 'read foo=bar' \
"(expected status 1, *$(printf %q "$exp"); got status $e, $(printf %q "$got"))"
# ======
# Corruption of $_ causing crash when running subshell + discipline functions + shared-state comsub
# https://github.com/ksh93/ksh/issues/616
exp=$'OK1\nOK2'
got=$(set +x; { "$SHELL" -c '
foo.get()
{
.sh.value=foo;
}
bar.get()
{
.sh.value=${ echo foo; }
}
: OK1
(
: $bar
: $foo
)
echo "$_"
: OK2
echo "$_"
';} 2>&1)
[[ e=$? -eq 0 && $got == "$exp" ]] || err_exit '$_ corruption' \
"(expected status 0, $(printf %q "$exp");" \
"got status $e$( ((e>128)) && print -n /SIG && kill -l "$e"), $(printf %q "$got"))"
# ======
exit $((Errors<125?Errors:125))

0 comments on commit d446015

Please sign in to comment.