[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