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.