Lo hice y lo entendí

El blog de Vicente Navarro
04 may

Fork Bombs, ulimit y limits.conf

En los últimos días en la blogosfera se han puesto de moda las Fork Bombs o Bombas Fork a raíz de un post en Kriptópolis seguido por una explicación más seria posterior y hasta un chiste friki de los buenos (ya me preguntaba yo que qué sistema operativo usarían los marcianos de marras, y resulta que es un Linux con bash).

Yo diría que había leído anteriormente sobre el tema, pero no consigo acordarme ni dónde ni cuándo, así que me he preguntado si estaría equivocado y esto llevaría poco tiempo circulando, pero en los históricos del artículo de la Wikipedia en Inglés se puede ver que alguien añadió el ejemplo en el 2004.

Hay Fork Bombs para todas las plataformas, pero la más bonita que he visto es esta para bash:

:(){ :|:& };:

Teniendo en cuenta que : es sólo el nombre de la función, no es ni más ni menos que una forma ofuscada de escribir esto:

forkbomb () {
forkbomb | forkbomb &
}
forkbomb

Es decir, definimos una función recursiva forkbomb que lo que hace es llamarse a sí misma otras dos (podrían ser más) veces (forkbomb | forkbomb &) hasta que el sistema se colapsa por no tener final el proceso.

Para evitar que esto dañe seriamente al sistema y lo deje inusable, es necesario limitar el número máximo de procesos que un usuario puede ejecutar. Todos los UNIX tienen una forma u otra de especificar esto, y en Linux, desde dentro de la shell podemos especificarlo con ulimit -u maxprocs, tal y como nos explica la página de manual de bash (ulimit es un comando interno de bash):

ulimit [-SHacdefilmnpqrstuvx [limit]]

   Provides control over the resources available to the shell and to processes started by it, on systems that allow such control. The -H and -S options specify that the hard or soft limit is set for the given resource. A hard limit cannot be increased once it is set; a soft limit may be increased up to the value of the hard limit. If neither -H nor -S is specified, both the soft and hard limits are set. The value of limit can be a number in the unit specified for the resource or one of the special values hard, soft, or unlimited, which stand for the current hard limit, the current soft limit, and no limit, respectively. If limit is omitted, the current value of the soft limit of the resource is printed, unless the -H option is given. When more than one resource is specified, the limit name and unit are printed before the value. Other options are interpreted as follows:
[...]
   -u The maximum number of processes available to a single user
[...]

Por tanto, si tenemos un límite hard ya no se puede poner uno superior:

$ ulimit -u 1000
$ ulimit -u
1000
$ ulimit -u 2000
-su: ulimit: max user processes: cannot modify limit: Operation not permitted

lo cual podría venir perfecto para especificar en el /etc/profile y en el /etc/bash.bashrc (para abarcar sesiones de login y no login) un ulimit que ningún usuario (excepto root) pueda superar en esa shell (afecta a la shell en ejecución y a todos sus hijos) .

Pero en realidad, la forma estándar de configurarlo es en el /etc/security/limits.conf, fichero de configuración perteneciente al Linux-PAM. Poniendo en él una línea como ésta:

*      hard   nproc      4096

todos los procesos (y sobre todo, sus hijos) que hagan uso de los módulos del PAM, respetarán los límites que se hayan establecido.

En Debian podemos ver fácilmente qué ficheros de configuración hacen uso del módulo pam_limits.so:

# pwd
/etc/pam.d
# grep pam_limits *
atd:session    required   pam_limits.so
cron:session    required   pam_limits.so
kdm:session    required     pam_limits.so
kdm-np:session    required     pam_limits.so
login:session    required   pam_limits.so
ssh:session    required     pam_limits.so
su:# session    required   pam_limits.so
xdm:session             required        pam_limits.so

Asimismo, podemos ver que los procesos más sensibles también hacen uso del pam:

# ldd /usr/sbin/sshd | grep pam
      libpam.so.0 => /lib/libpam.so.0 (0x00002abea92be000)
 
# ldd /bin/login | grep pam
      libpam.so.0 => /lib/libpam.so.0 (0x00002b33d5d9e000)
      libpam_misc.so.0 => /lib/libpam_misc.so.0 (0x00002b33d5ea6000)

Si seguimos centrándonos en Debian, encontramos el manual Securing Debian que en su capítulo 4, trata precisamente este tema:

4.11.1 User authentication: PAM
4.11.2 Limiting resource usage: the limits.conf file

El que los límites especificados por ulimit para el número de procesos no afecte a root es algo que Linus aceptó en el 2001 tras la llegada de un parche a la LKML necesario para:

When a user has a low RLIMIT_NPROC set in limits.conf, the user fails
to log in. This is because the programs using pam basically do the
following:

1) apply rlimits, setting RLIMIT_NPROC to eg. 10
2) fork() to spawn the shell, which fails if root has more processes running than the per-user limit
3) change to the user’s UID
4) exec() the shell

Es decir, para evitar que en un momento dado, root no pueda crear un nuevo proceso para un usuario debido a que root ya haya llegado a su límite de procesos. También es útil para no quedarnos nunca sin poder entrar al sistema por haber llegado ya al máximo de procesos.

El código del parche sigue estando tal cual en el kernel/fork.c de mi 2.6.20.6:

 996   if (atomic_read(&p->user->processes) >=
 997         p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
 998      if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) & &
 999            p->user != &root_user)
1000         goto bad_fork_free;
1001   }

Para muchos, esto es un grave problema de seguridad, pero en mi opinión, es tan grave como que el usuario root pueda borrar todas las particiones del disco, borrar todos los ficheros un filesystem o matar ese proceso tan crítico que no debe morir bajo ninguna circunstancia. Aunque haya quien piense lo contrario, yo creo que entra dentro de lo que se le puede permitir a root. Si un extraño entra a nuestro sistema con acceso de root, lo que menos nos tiene que preocupar son las Fork Bombs.

Otra cosa es que para usuarios distintos a root deba venir por defecto el limits.conf configurado con unos valores razonables, pero eso posiblemente ya entra dentro del compromiso que se decida alcanzar entre seguridad y usabilidad.

Para acabar, comentar una variante del Fork Bomb anterior:

echo "\$0&\$0">_;chmod +x _;./_

Como $0 almacena el valor del proceso actual, el script ejecutará:

./_ &
./_

contínuamente, con el agravante de que en este caso, lo que se intenta ejecutar no es una simple función de la memoria del bash, sino un nuevo proceso del disco que requiere cargar un nuevo bash en memoria.

La versión Windows de ese mismo Fork Bomb es un .bat con este contenido:

:s
start %0
goto s

Entradas relacionadas

18 Comentarios a “Fork Bombs, ulimit y limits.conf”

  • pasaba por aqui dice:

    en linux, una vez lanzada… como la paro sin reiniciar?

    por cierto, en windows, si entro por telnet y lanzo la fork bomb, en el administrador de tareas tengo un número variable de cmds, entre 30 y 50, pero el sistema siempre mantiene la respuesta. eso sí, no sé como pararlo tampoco.

    puedes indicar cómo parar sin reiniciar? gracias.

  • pasaba por aqui Si el usuario no tiene limitado el número de procesos, es tan rápido el crecimiento que no hay forma de pararlo antes de que colapse el sistema. Si lo haces antes de que ese colapso llegue (que tendría que ser muy deprisa, tanto que me parece casi imposible) o si quieres eliminar los procesos de un usuario que tenía límites de nproc y ha ejecutado una Fork Bomb, puedes ejecutar killall -9 -u usuario bash pero como los procesos se están reproduciendo constantemente, antes de que los mate a todos ya hay más, así que tendrías que hacer algo como esto desde root:

    while true
    do
       killall -9 -u usuario bash
    done

    hasta que ya no haya más bash pertenecientes al usuario y ya puedas cortar la ejecución del comando con el típico Control+C.

    En Windows, efectivamente, se llega a un número de procesos y ya no sube más, además con apariencia de que aún se puede interactuar con él, pero el Windows queda totalmente inusable porque no se puede ejecutar nada nuevo y por cada cmd que matamos en el administrador de tareas aparece uno nuevo.

  • Iván dice:

    Muy buen artículo, aunque no veo yo a mi mujer lánzando un ForkBomb en mi máquina… ;-) .

    Saludos, Iván.

  • Iván Gracias :-)

    Sí, sí, tú fíate de ellas :D

  • Rickardo dice:

    entonces en windows por ejemplo, con reiniciar el problema se acaba, no?

  • Rickardo Sí, al reiniciar el problema desaparecería.

  • Excepto por el pequeño detalle que significa reiniciar… perderian toda la información que hubiera en la memoria

  • Menda dice:

    Rickardo: como mola ver que los usuarios de Windows consideran el reinicio como un mal menor. Ya incluso se esperan que el problema les quede metido a perpetuidad.

  • Chus dice:

    uuuuuummmmmm y yo m pregunto y si ese .bat se iniciara con los programas que ejecuta windows de manera automatica al iniciar el sistema, habría alguna forma de solucionarlo?
    Bueno supongo q desde el modo aprueba de fallos no?

  • Chus Efectivamente, en el “Modo a Prueba de Fallos” o “Safe Mode”, todos los startup programs (especificados p.e. en la carpeta Inicio/Startup del menú de Programas o p.e. en HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion
    \Run) que normalmente se ejecutarían durante el arranque, no se ejecutan.

    Los siguientes documentos de Microsoft dan más detalles al respecto del tema:

  • abaca dice:

    No tiene nada que ver con el post, pero acabo de ver tu ‘número de 128 bits favorito’: ¿qué tiene de especial?¿por qué te gusta?

  • abaca Búscalo en Google ;)

  • Alguien dice:

    a mi no me funciono S=(

Trackbacks y pingbacks:

Tema LHYLE09, creado por Vicente Navarro