COMMAND

    ld-linux.so

SYSTEMS AFFECTED

    Linux

PROBLEM

    Joost Witteveen posted about another problem with linker.  Here it
    goes:

        $ LD_PRELOAD=libdoesntexist /bin/su
        /bin/su: error in loading shared libraries
        libdoesntexist: cannot open shared object file: No such file or directory

    Linux dynamic linkers don't ignore LD_PRELOAD for setuid binaries.
    The documentation  of the  old (libc5,  aka ld.so  1.8.1, 1.9 etc)
    said  (incorrectly)  that  LD_PRELOAD  was  ignored when executing
    setuid binaries.  But apparently the wisdom is that libraries that
    are installed on the system should be well written, and it  should
    be safe for them to be specified in LD_PRELOAD.

    Suppose you've got a 10000+ line C++ library that does  linguistic
    analysis of  various languages.  Now _somewhere_  in that library,
    someone on the project added the following code:

        class foo{
        public:
          foo(){
            char buf[10], *s=getenv("FOO");
            if(s)
              strcpy(buf,s); // Buffer overrun, etc.
            // [more code here]
          }
        };
        foo bar; // initialise this variable. (before main() is called)

    Having this library installed in any location in /usr/lib, or /lib
    (more general: any directory listed in /etc/ld.so.conf) will  make
    your whole system vurnerable for attack.  Again one may argue that
    libraries like this don't belong in /usr/lib.  But you argue  that
    way, then basically  no big library  belongs in /usr/lib.   Again,
    not current practice.

    Because many of these wrapper  libraries will be written based  on
    the assumption  that their  code will  not be  part of  any setuid
    binary, the wrapping  functions are not  "defensively programmed",
    and as such may well be vurnerable to buffer overruns etc.  It may
    be argued that these libraries  don't belong in /usr/lib etc,  but
    the fact is that (maybe due to the misleading documentation)  they
    currently often are.

    As an example, take another artificial-intelligence library that
    makes attempts at answering simple english questions. One friday
    afternoon the linguists added the function "crypt(const *char q)"
    that attempts to not give a straight answer to the question, but
    a cryptic one.  Naturally, this question q is copied to a 80
    byte-long array on the stack.  Well, you get the idea: type

        LD_PRELOAD=libAI.so.2.3 /bin/su

    and su will  eventually call crypt(key,salt),  with key being  the
    user-typed (max 128  chars, see getpass(3))  password.  Should  be
    root in there somewhere.

SOLUTION

    If this happens  to you, either  you are using  an old ld-linux.so
    or your /bin/su is not suid root.  To test your system, try  this:
    Compile both  "hack" and  "libhack" (by  Roman Drahtmueller),  set
    LD_PRELOAD (and do 'export LD_PRELOAD'!) to the shared library and
    test "hack" set and unset  suid-root. Its error output looks  like
    this:

        rzlin1:/tmp/roman $ export LD_PRELOAD=/tmp/roman/foo
        rzlin1:/tmp/roman $ ls
        ls: can't load library '/tmp/roman/foo'
        rzlin1:/tmp/roman $

    Scripts follow:

    /*- hack.c -- gcc -o hack hack.c -*/
    #include <stdio.h>

    int main()
    {
    FILE *fd;
    setuid(geteuid());
    fd=fopen("/dev/null","r");
    if(fd)
        printf("File could be opened. Shared lib not loaded.\n");
    exit(0);
    }
    /*-- end hack.c --*/

    /*- hacklib.c (the shared lib) -- gcc -shared -o libhack.so hacklib.c -*/
    /*                     For Solaris: cc -G -o libhack.so hacklib.c      */
    #include <stdio.h>
    #include <errno.h>

    FILE *fopen(const char *filename, const char *mode)
    {
    printf("shared library loaded. uid: %i euid: %i \n",getuid(),geteuid());
    /* Lots of evil stuff in here. Use your imagination. */
    errno=-EINVAL;
    return NULL;
    }
    /*-- end hacklib.c --*/

    So, there are two dynamic linkers used by the Linux community, the
    old ld (ld-linux.so.1) maintained by David Engle and the newer  ld
    part of the GNU libc (aka glibc aka libc6).  ld-linux used to  not
    ignore  LD_PRELOAD  and  LD_LIBRARY_PATH  for setuid/gid programs.
    This changed in version 1.6.7 and was further refined in 1.7.6 and
    1.7.11.  That version changed ld-linux.so to delete all variations
    of LD_PRELOAD  and LD_LIBRARY_PATH  for set[ug]id  programs.  This
    changed in version 1.9.0. That version changed ld-linux.so to load
    the libraries listed in LD_PRELOAD for setuid/gid programs as long
    as  they  could  be  loaded  securely.  "Securely"  means that the
    libraries  in  LD_PRELOAD  must  not  contain   '/'  in  them  and
    therefore will be loaded  from the configured library  directories
    (/lib, /usr/lib, etc) and not from  a user supplied one.  The  GNU
    dynamic  linker  in   a  similar  move   ignored  LD_PRELOAD   for
    setuid/guid binaries. Ulrich Drepper  changed it to allow  loading
    "securely" libraries  from LD_PRELOAD  for setuid/gid  programs on
    Jan 20, 1997.  This system is vulnerable to an attacker preloading
    an  old  library  with  known  vulnerabilities  that  has not been
    deleted  from  the  library  directory  while running a setuid/gid
    program.   The  correct  solution  is  to  ignore  LD_PRELOAD  for
    setuid/gid program and  use /etc/ld.so.preload for  global preload
    libraries.   ld.so.preload  was  introduced  in  version  1.8.0 of
    ld-linux and is part of almost every other ld.