[Slackdocs] Security & bug alert: /bin/ash 'getopts'
Martijn Dekker
martijn at inlv.org
Thu Jan 10 21:20:24 CET 2013
I found a bug with the standard built-in option parsing command
"getopts" in Slackware's version of /bin/ash. The use of the "shift"
command in any shell function called from a 'while getopts ...' loop
wrongly resets getopts's internal counter variable OPTIND to 1 in the
caller environment, causing the loop to continue infinitely.
This is not normal or POSIX-compliant behaviour. POSIX documentation
confirms this:
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_05
It may not explicitly mention getopts or $OPTIND, but it is clearly in
the spirit of the standard that shell functions should be able to
manipulate their private positional parameters without affecting the
parsing of the main positional parameters.
I wrote a small test script (see below) with which I confirmed that only
/bin/ash, and in fact only Slackware's version of /bin/ash, exhibits
this behaviour. Zsh, ksh, bash, and even Heirloom 'sh', a pre-POSIX
historical shell implementation, behave correctly. I also confirmed that
'ash' on OpenSUSE 11.0 (quite old) behaves correctly, as well as the ash
derivative 'dash' in Debian and others, and every Bourne/POSIX-ish shell
I could find in Mac OS X and NetBSD.
This is not just a bug but also a security issue:
- Compliant code should not cause infinite loops: denial of service problem.
- While I was trying to Ctrl-C out of the infinite loop (before I
programmed a loop counter), ash segfaulted on at least one occasion. Any
segfault represents a potentially exploitable security hole.
My test script is provided below for reference. Please use it to
reproduce my tests and confirm the problem.
A simple and effective solution may be to replace the old version of ash
that ships with Slackware with a more recent ash from Red
Hat/CentOS/SUSE or with dash from Debian.
Thanks,
- Martijn Dekker, Groningen, Netherlands
#! /bin/ash
ash_bug_trigger() {
shift # this use of 'shift' triggers the bug
printf '%s = %s\n' "$1" "$2"
}
# if script was invoked without dummy options for getopts, then set some
if test $# = 0; then
set -- -ajunk -b it -cx -dBlah -e morejunk -f"It's looking good!"
fi
loopcount=0
while getopts ':a:b:c:d:e:f:' myOption; do
printf '(OPTIND = %d) ' $OPTIND
ash_bug_trigger junkargument "$myOption" "$OPTARG"
loopcount=`expr $loopcount + 1`
if test $loopcount -ge 25 ; then
echo "Infinite loop detected; getopts/\$OPTIND bug found!" 1>&2
exit 1
fi
done
echo "No getopts/\$OPTIND infinite loop bug found."
exit 0
############## Expected output: ###############
# (OPTIND = 2) a = junk
# (OPTIND = 4) b = it
# (OPTIND = 5) c = x
# (OPTIND = 6) d = Blah
# (OPTIND = 8) e = morejunk
# (OPTIND = 9) f = It's looking good!
# No getopts/$OPTIND infinite loop bug found.
################# Bug output: #################
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# (OPTIND = 2) a = junk
# Infinite loop detected; getopts/$OPTIND bug found!
More information about the Slackdocs
mailing list