1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00

autoload: Add loop detection

It was trivial to crash ksh by making an autoloaded function
definition file autoload itself, causing a stack overflow due to
infinite recursion. This commit adds loop detection that stops a
function that is being autoloaded from autoloading itself either
directly or indirectly, without removing the ability of autoloaded
function definition files to autoload other functions.

src/cmd/ksh93/sh/path.c: funload():
- Detect loops by checking if the path of a function to be
  autoloaded was already added to a new internal static tree,
  and if not, adding it while the function is being loaded.

src/cmd/ksh93/tests/path.sh:
- Add regression test.
- Tweak a couple of others to be freeze- and crash-proof.

NEWS:
- Add this fix + a forgotten entry for the previous fix (6f3b23e6).

Fixes: https://github.com/ksh93/ksh/issues/136
This commit is contained in:
Martijn Dekker 2021-02-04 16:20:27 +00:00
parent 6f3b23e6f4
commit a410bc480f
2 changed files with 55 additions and 5 deletions

View file

@ -577,7 +577,8 @@ char *path_fullname(Shell_t *shp,const char *name)
static void funload(Shell_t *shp,int fno, const char *name) static void funload(Shell_t *shp,int fno, const char *name)
{ {
char *pname,*oldname=shp->st.filename, buff[IOBSIZE+1]; char *pname,*oldname=shp->st.filename, buff[IOBSIZE+1];
Namval_t *np; Namval_t *np, *np_loopdetect;
static Dt_t *loopdetect_tree;
struct Ufunction *rp,*rpfirst; struct Ufunction *rp,*rpfirst;
int savestates = sh_getstate(), oldload=shp->funload, savelineno = shp->inlineno; int savestates = sh_getstate(), oldload=shp->funload, savelineno = shp->inlineno;
pname = path_fullname(shp,stakptr(PATH_OFFSET)); pname = path_fullname(shp,stakptr(PATH_OFFSET));
@ -607,6 +608,11 @@ static void funload(Shell_t *shp,int fno, const char *name)
free((void*)pname); free((void*)pname);
return; return;
} }
if(!loopdetect_tree)
loopdetect_tree = dtopen(&_Nvdisc,Dtoset);
else if(nv_search(pname,loopdetect_tree,0))
errormsg(SH_DICT,ERROR_exit(ERROR_NOEXEC),"autoload loop: %s in %s",name,pname);
np_loopdetect = nv_search(pname,loopdetect_tree,NV_ADD);
sh_onstate(SH_NOALIAS); sh_onstate(SH_NOALIAS);
shp->readscript = (char*)name; shp->readscript = (char*)name;
shp->st.filename = pname; shp->st.filename = pname;
@ -631,6 +637,7 @@ static void funload(Shell_t *shp,int fno, const char *name)
shp->inlineno = savelineno; shp->inlineno = savelineno;
shp->st.filename = oldname; shp->st.filename = oldname;
sh_setstate(savestates); sh_setstate(savestates);
nv_delete(np_loopdetect,loopdetect_tree,0);
if(pname) if(pname)
errormsg(SH_DICT,ERROR_exit(ERROR_NOEXEC),e_funload,name,pname); errormsg(SH_DICT,ERROR_exit(ERROR_NOEXEC),e_funload,name,pname);
} }

View file

@ -476,6 +476,7 @@ actual=$(PATH=/dev/null "$SHELL" -c 'command -p ls /dev/null' 2>&1)
[[ $actual == "$expect" ]] || err_exit 'command -p fails to find standard utility' \ [[ $actual == "$expect" ]] || err_exit 'command -p fails to find standard utility' \
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))" "(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
# ======
# ksh segfaults if $PATH contains a .paths directory # ksh segfaults if $PATH contains a .paths directory
mkdir -p $tmp/paths-dir-crash/ mkdir -p $tmp/paths-dir-crash/
cat > $tmp/paths-dir-crash/run.sh <<- EOF cat > $tmp/paths-dir-crash/run.sh <<- EOF
@ -483,8 +484,21 @@ mkdir -p $tmp/paths-dir-crash/.paths
export PATH=$tmp/paths-dir-crash:$PATH export PATH=$tmp/paths-dir-crash:$PATH
print ok print ok
EOF EOF
[[ $($SHELL $tmp/paths-dir-crash/run.sh 2>/dev/null) == ok ]] || err_exit "ksh crashes if PATH contains a .paths directory" ofile=$tmp/dotpaths.out
trap 'sleep_pid=; while kill -9 $pid; do :; done 2>/dev/null; err_exit "PATH containing .paths directory: shell hung"' TERM
trap 'kill $sleep_pid; while kill -9 $pid; do :; done 2>/dev/null; trap - INT; kill -s INT $$"' INT
{ sleep 5; kill $$; } &
sleep_pid=$!
"$SHELL" "$tmp/paths-dir-crash/run.sh" >$ofile 2>&1 &
pid=$!
{ wait $pid; } 2>>$ofile
e=$?
trap - TERM INT
[[ $sleep_pid ]] && kill $sleep_pid
((!e)) && [[ $(<$ofile) == ok ]] || err_exit "PATH containing .paths directory:" \
"got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$(<$ofile)")"
# ======
# Check that 'command -p' and 'command -p -v' do not use the hash table (a.k.a. tracked aliases). # Check that 'command -p' and 'command -p -v' do not use the hash table (a.k.a. tracked aliases).
print 'echo "wrong path used"' > $tmp/ls print 'echo "wrong path used"' > $tmp/ls
chmod +x $tmp/ls chmod +x $tmp/ls
@ -523,6 +537,7 @@ then
"(expected $(printf %q "$exp"), got $(printf %q "$got"))" "(expected $(printf %q "$exp"), got $(printf %q "$got"))"
fi fi
# ======
# 'command -x' used to hang in an endless E2BIG loop on Linux and macOS # 'command -x' used to hang in an endless E2BIG loop on Linux and macOS
ofile=$tmp/command_x_chunks.sh ofile=$tmp/command_x_chunks.sh
trap 'sleep_pid=; while kill -9 $pid; do :; done 2>/dev/null; err_exit "'\''command -x'\'' hung"' TERM trap 'sleep_pid=; while kill -9 $pid; do :; done 2>/dev/null; err_exit "'\''command -x'\'' hung"' TERM
@ -681,9 +696,37 @@ fi
# ====== # ======
# Very long nonexistent command names used to crash # Very long nonexistent command names used to crash
# https://github.com/ksh93/ksh/issues/144 # https://github.com/ksh93/ksh/issues/144
{ PATH=/dev/null FPATH=/dev/null "$SHELL" -c "$(awk -v ORS= 'BEGIN { for(i=0;i<10000;i++) print "xxxxxxxxxx"; }')"; } 2>/dev/null ofile=$tmp/longname.err
(((e = $?) == 127)) || err_exit "Long nonexistent command name crashes shell" \ trap 'sleep_pid=; while kill -9 $pid; do :; done 2>/dev/null; err_exit "Long nonexistent command name: shell hung"' TERM
"(exit status $e$( ((e>128)) && print -n / && kill -l "$e"))" trap 'kill $sleep_pid; while kill -9 $pid; do :; done 2>/dev/null; trap - INT; kill -s INT $$"' INT
{ sleep 5; kill $$; } &
sleep_pid=$!
PATH=/dev/null FPATH=/dev/null "$SHELL" -c "$(awk -v ORS= 'BEGIN { for(i=0;i<10000;i++) print "xxxxxxxxxx"; }')" 2>/dev/null &
pid=$!
{ wait $pid; } 2>$ofile
e=$?
trap - TERM INT
[[ $sleep_pid ]] && kill $sleep_pid
((e == 127)) || err_exit "Long nonexistent command name:" \
"got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$(<$ofile)")"
# ======
# A function autoload recursion loop used to crash
# https://github.com/ksh93/ksh/issues/136
mkdir "$tmp/fun.$$" && cd "$tmp/fun.$$" || exit
echo 'self2' >self
echo 'self3' >self2
echo 'self4' >self3
echo 'self' >self4
cd ~- || exit
exp="$SHELL: line 2: autoload loop: self in $tmp/fun.$$/self
$SHELL: function, built-in or type definition for self4 not found in $tmp/fun.$$/self4
$SHELL: function, built-in or type definition for self3 not found in $tmp/fun.$$/self3
$SHELL: function, built-in or type definition for self2 not found in $tmp/fun.$$/self2
$SHELL: function, built-in or type definition for self not found in $tmp/fun.$$/self"
got=$({ FPATH=$tmp/fun.$$ "$SHELL" -c self; } 2>&1)
(((e = $?) == 126)) || err_exit 'Function autoload recursion loop:' \
"got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$got")"
# ====== # ======
exit $((Errors<125?Errors:125)) exit $((Errors<125?Errors:125))