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.