                       L0pht Security Advisory
                    Advisory released Jan 27 1997

                  Application: Solaris libc getopt(3)

             Vulnerability Scope: Solaris 2.5 distributions

          Severity: Non-priveledged users can exploit a vulnerability
             in the getopt(3) routine inside libc. As most SUID programs
             in Solaris are dynamically linked, users can gain root
             priveledges.

                      Author: mudge@l0pht.com

Overview:

A buffer overflow condition exists in the getopt(3) routine. By supplying
an invalid option and replacing argv[0] of a SUID program that uses the
getopt(3) function with the appropriate address and machine code instructions,
it is possible to overwrite the saved stack frame and upon return(s) force
the processor to execute user supplied instructions with elevated permissions.


Description:

While evaluating programs in the Solaris Operating System environment
it became apparent that changing many programs trust argv[0] to never
exceed a certain length. In addition it seemed as though getopt was
simply copying argv[0] into a fixed size character array.

  ./test >>& ccc
  Illegal instruction (core dumped)

Knowing that the code in ./test was overflow free it seemed that the problem
must exist in one of the functions dynamically linked in at runtime through
ld.so. A quick gander through the namelist showed a very limited range of
choices for the problem to exist in.

  00020890 B _end
  0002088c B _environ
  00010782 R _etext
           U _exit
  00010760 ? _fini
  0001074c ? _init
  00010778 R _lib_version
  000105ac T _start
           U atexit
  0002088c W environ
           U exit
  0001067c t fini_dummy
  0002087c d force_to_data
  0002087c d force_to_data
  000106e4 t gcc2_compiled.
  00010620 t gcc2_compiled.
           U getopt
  00010740 t init_dummy
  00010688 T main

Next we checked out getopt() - as it looked like the most likely
suspect.

  #include <stdio.h>

  main(int argc, char **argv)
  {
    int opt;

    while ((opt = getopt(argc, argv, "a")) != EOF) {
      switch (opt) {
      }
    }
  }

  >gcc -o test test.c
  >./test -z
  ./test: illegal option -- z

Note the name it threw back at the beggining of the error message. It was
quite obvious that they are just yanking argv[0]. Changing argv[0] in
the test program confirms this.

  for (i=0; i< 4096; i++)
    buffer[i] = 0x41;

   argv[0] = buffer;

With the above in place we see the following result:
  >./test -z
  [lot's of A's removed]AAAAAAAAA: illegal option -- z
  Bus error (core dumped)

By yanking out the object file from the static archive libc that is supplied
with Solaris our culprit was spotted [note - we assumed that libc.a was
built from the same code base that libc.so was].

  > nm getopt.o
           U _dgettext
  00000000 T _getopt
  00000000 D _sp
           U _write
  00000000 W getopt
           U optarg
           U opterr
           U optind
           U optopt
           U sprintf
           U strchr
           U strcmp
           U strlen

Here we see one of the infamous non-bounds-checking routines: sprintf();
More than likely the code inside getopt.c looks something like the following:

  getopt.c:
    char opterr[SOMESIZE];
    ...
    sprintf(opterr, argv[0]...);

Thus, whenever you pass in a non-existant option to a program that uses getopt
you run into the potential problem with trusting that argv[0] is smaller
than the space that has been allocated for opterr[].

This is interesting on the Sparc architecture as getopt() is usually called
out of main() and you need two returns [note - there are certain situations
in code on Sparc architectures that allow you to switch execution to your
own code without needing two returns. Take a look at the TBR for some
enjoyable hacking] due to the sliding register windows. Some quick analysis
of SUID programs on a standard Solaris 2.5 box show that most of these
programs exit() or more likely call some form of usage()-exit() in the
default case for getopt and thus are not exploitable. However, at least
two of these programs provide the necessary returns to throw your
address into the PC :
     passwd(1)
     login(1)

On Solaris X86 you do not need these double returns and thus a whole world of
SUID programs allow unpriveledged users to gain root access:
    (list of programs vulnerable too big to put here. sigh.)

Exploit:


  $./exploit "/bin/passwd" 4375 2> foo
  # id
  uid=0(root) gid=1(other)

  [ note: the source code for the exploit will be made available on the
    www.l0pht.com/advisories.html page in a couple of days. Hey, we have
    day jobs and sometimes spare time is impossible to come by. ]

Fixes:
  For those with source:
  If you are one of the few people who have a source code license the fix
  should be fairly simple. Replace the sprintf() routine in getopt.c with
  snprintf() and rebuld libc.

  Super Ugly kludge fix:
  If you don't have the source code available (like most of us), one solution
  is to use adb to change the name for getopt with something like getopz,
  yank a publicly available getopt.c, and put it in place of getopt.
  If anyone can tell me how to yank the object files out of dynamically
  linked libraries it would be appreciated as you suffer performance
  hits among larger problems by doing this from the static library Sun
  provides as, of course, it is not PIC code.

Thanks:
  Special thanks go out to ][ceman for his co-work on this project.

mudge@l0pht.com

---
Check out http://www.l0pht.com/advisories.html for other l0pht advisories
---

