COMMAND
locate (and others)
SYSTEMS AFFECTED
Linux, FreeBSD
PROBLEM
Sergey V. Kolychev reported following. He had had problem with
locate from findutils-4.1.24.rpm from Redhat-5.1. It segfaults if
we have huge directory at incoming ftp which created by exploits
for ftpd realpath hole. If updatedb puts to locate database that
directory then locate segfaults (getline.c 104 row by gdb) guess
is it can be used for running arbitrary commands if root runs
locate. Latest Redhat-6.0 findutils-4.1.31.rpm is still based on
findutils-4.1 as well as findutils-4.1.24.
Nearz factory99 found same thing earlier. There are others
programs vulnerable with long file/directories names. They found
in `locate' another buffer overflow with pathnames. Creating a
very long directory and subdirectorys (about 9k of total lenght)
and running `updatedb' script the long directory will be in
database of locate then just typing: "locate AAAAAA" you will
get a Segmentation fault. And debugging:
$ gdb locate
. . .
(gdb) r AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAA...etc...
Program received signal SIGSEGV, Segmentation fault.
0x80497b9 in getstr (lineptr=0xbffffac8, n=0xbffffac4, stream=0x804ba08,
terminator=0 '\000', offset=65551) at getline.c:104
104 *read_pos++ = c;
(gdb)
So the wrong thing is the `offset' var, the function `getstr'
read s from a stream until a `terminator' was not found. This
functions write the data readed in buffer lineptr+offset. The
initial size of buffer is 1026, buf getstr realloc if necessary,
but it doesn't check if buffer+offset is out of bound. So
offset+65551 will point to outside of buffer. We don't known if
is possible to overflow the stack but if yes a regular can create
a directory with a shellcode that copies a shell setuid to her
home directory inside the name and wait until `updatedb' isn't run
(normaly it's called by crontab periodically). Then if root run
locate ... the shellcode you have the uid 0.
...it's not the end, NF99 found another problem in `rm -r' when
they tried to delete the big directory (created in test of wuftp
and locate). The "rm" looks like to recursively enter in
subdirectories to start `rmdir'ing from the end of path (rmdir
doesn't remove non-empty directories), which leads one to think
that 'rm' have a predefined buffer size of current directory, when
it's full it stop the recursive function and try to delete the
next directory and fails with: "Directory not empty". The `mc'
(midinight commander) have the same problem but it doesn't show
anything, simplely don't remove the directory. You can only
remove the long pathname if you change it to mmmmmmmm to the
midle of the path and 'rm -r' from there, and back to original
path and remove the rest. Others tested and problematic programs:
mc, mcedit, joe, vche, pico, pine -F, pine -f, vi, which, ed
This was tested on: Linux 2.2.1-i486 / slackware 3.6 / find_utils
4.1 .
Przemyslaw Frasunek noticed a similar problem with /usr/bin/find
on FreeBSD. By creating _very_ long and deep directory structure
it's possible to segfault /usr/bin/find (it's also used in
/etc/periodic scripts, which runs on root). Example follows.
Create a directory structure with 300 subdirectories, each 255
chars length (source is below, also it's possible to do it via
ftpd, because it calls mkdir() and chdir()).
lagoon:venglin:/tmp/jc> find example > /dev/null
Segmentation fault (core dumped)
Gdb shows, that functions puts() was overflowed, when it tried to
print a very long path. Also other system tools (rm, ls) has big
problems with such directory structures.
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <strings.h>
#define DUMP 0x41
main(int argc, char *argv[]) {
char buf[255];
int i = 0;
if (argc < 3) { fprintf(stderr, "usage: %s <dir> <depth>\n", argv[0]); exit(1); }
if(chdir(argv[1])) { fprintf(stderr, "error in chdir(): %s\n", strerror(errno)); exit(1); }
memset(buf, DUMP, 255);
for(i=0;i<(atoi(argv[2]))-1;i++) {
if(mkdir(buf, (S_IRWXU | S_IRWXG | S_IRWXO))) { fprintf(stderr, "error in mkdir() after %d iterations: %s\n", i, strerror(errno)); exit(1); }
if(chdir(buf)) { fprintf(stderr, "error in chdir() after %d iterations: %s\n", i, strerror(errno)); exit(1); }
}
exit(0);
}
SOLUTION
Normally updatedb is runned by nobody nogroup (by crontab) and
the permissons of `locatedb' is 644 (rw-r--r--) you can change
owner and permissions of `locate' to this user.
chown nobody.nogroup /usr/bin/locate
chown 4711 /usr/bin/locate
So if the user try to exploit he will get a shell setuid nobody.