From 7e7f1372452bcae7451f9a6e68d4b691b068bb82 Mon Sep 17 00:00:00 2001 From: Johnothan King Date: Fri, 18 Sep 2020 03:17:20 -0700 Subject: [PATCH] Fix a crash on unsetting preset alias (re: ddaa145b) (#133) The following set of commands caused ksh to crash: $ unalias history; unalias r Memory fault When ksh is compiled with -D_std_malloc, the crash always occurs when the 'r' alias is removed with 'unalias r', although with vmalloc 'unalias history' must be run first for the crash to occur. With the native malloc, the crash message is also different: $ unalias history; unalias r free(): invalid pointer Abort This crash happens because when an alias is unset, _nv_unset removes the NV_NOFREE flag which results in an invalid use of free(3) as nv_isattr no longer detects NV_NOFREE afterward. The history and r aliases shouldn't be freed from memory by nv_delete because those aliases are given the NV_NOFREE attribute. src/cmd/ksh93/bltins/typeset.c: - Save the state of NV_NOFREE for aliases to fix the crash caused by 'unalias r'. src/cmd/ksh93/tests/alias.sh: - Use unalias on both history and r to check for the crash. 'unalias -a' can't be used to replicate the crash. Co-authored-by: Martijn Dekker --- src/cmd/ksh93/bltins/typeset.c | 12 ++++++++++-- src/cmd/ksh93/tests/alias.sh | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 8b91cc20bbc2..4e7e8ea2b4fa 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -1162,7 +1162,7 @@ static int unall(int argc, char **argv, register Dt_t *troot, Shell_t* shp) register const char *name; volatile int r; Dt_t *dp; - int nflag=0,all=0,isfun,jmpval; + int nflag=0,all=0,isfun,jmpval,nofree_attr; struct checkpt buff; NOT_USED(argc); if(troot==shp->alias_tree) @@ -1292,6 +1292,14 @@ static int unall(int argc, char **argv, register Dt_t *troot, Shell_t* shp) } } + /* + * When aliases are removed from the tree, the NV_NOFREE attribute must be used for + * preset aliases since those are given the NV_NOFREE attribute. _nv_unset discards + * NV_NOFREE so the status of NV_NOFREE is obtained now to prevent an invalid free crash. + */ + if(troot==shp->alias_tree) + nofree_attr = nv_isattr(np,NV_NOFREE); /* note: returns bitmask, not boolean */ + if(!nv_isnull(np) || nv_size(np) || nv_isattr(np,~(NV_MINIMAL|NV_NOFREE))) _nv_unset(np,0); if(troot==shp->var_tree && shp->st.real_fun && (dp=shp->var_tree->walk) && dp==shp->st.real_fun->sdict) @@ -1311,7 +1319,7 @@ static int unall(int argc, char **argv, register Dt_t *troot, Shell_t* shp) } /* The alias has been unset by call to _nv_unset, remove it from the tree */ else if(troot==shp->alias_tree) - nv_delete(np,troot,nv_isattr(np,NV_NOFREE)); + nv_delete(np,troot,nofree_attr); #if 0 /* causes unsetting local variable to expose global */ else if(shp->var_tree==troot && shp->var_tree!=shp->var_base && nv_search((char*)np,shp->var_tree,HASH_BUCKET|HASH_NOSCOPE)) diff --git a/src/cmd/ksh93/tests/alias.sh b/src/cmd/ksh93/tests/alias.sh index be0099f38b9f..e6a23e56c763 100755 --- a/src/cmd/ksh93/tests/alias.sh +++ b/src/cmd/ksh93/tests/alias.sh @@ -109,9 +109,9 @@ alias foo=bar unalias foo unalias foo && err_exit 'unalias should return non-zero when a previously set alias is unaliased twice' -# Removing a preset alias should work without an error from free(3) -err=$(set +x; { "$SHELL" -i -c 'unalias history'; } 2>&1) && [[ -z $err ]] \ -|| err_exit "removing a preset alias does not work (got $(printf %q "$err"))" +# Removing the history and r aliases should work without an error from free(3) +err=$(set +x; { "$SHELL" -i -c 'unalias history; unalias r'; } 2>&1) && [[ -z $err ]] \ +|| err_exit "the 'history' and 'r' aliases can't be removed (got $(printf %q "$err"))" # ====== exit $((Errors<125?Errors:125))