Es conocido desde hace tiempo que hay una vulnerabilidad de DoS (Denial of Service, Denegación de Servicio) que afecta a muchos UNIX (¿cuál es el plural de UNIX? UNICES suena fatal :-D), así como en GNU/Linux. Con simplemente escribir este conjuro en una shell, terminamos por colgar el sistema:
:(){ :|:&};:
Menuda movida, ¿no? :-O
Lo primero de todo, ¿qué significa ese código? Bien, si sustituimos «:» por «función» e indentamos bien, se entiende mejor:
:(){ :|:&};:
funcion () { funcion | funcion & }; funcion
funcion () {
funcion | funcion &
};
funcion
Es decir, primero definimos una función y luego la llamamos. Dentro de esa función, se llama de nuevo a esa función (recursión) dos veces y se une esos dos procesos por una tubería (pipe). Además se pone un & al final para que todo eso se ejecute en segundo plano.
A fin de cuentas lo que tenemos es un proceso que se llama a sí mismo (lo que ocasiona un bucle), pero que no tiene condición de finalización (bucle infinito) y lanza dos procesos hijos cada vuelta del bucle y los une por una tubería (más del doble de recursos necesarios cada vez).
Lo dicho, esto es más viejo que el picor, aunque hay scripts realmente sencillos que hacen cosas parecidas con distinto código. En Journey’s End nos plantean uno:
#!/bin/sh
$0 &
exec $0
Así que el problema no está en el conjuro en sí (no tendría sentido poner en nuestro IDS esa cadena para detectarla como maliciosa, porque habría infinitas cadenas que harían lo mismo), sino en la teoría que subyace: un usuario con un límite demasiado amplio de procesos de usuario creados, puede acabar los recursos del sistema.
¿Cuál es mi límite de procesos? Lo podemos conocer fácilmente con el comando ulimit:
ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 12273
max locked memory (kbytes, -l) 32
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 12273
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
El número máximo en este sistema es 12273 para mi usuario, más que suficiente para dejarlo tuerto 🙁
Si quisiéramos evitar este problema, bastaría con cambiar ese límite, así:
ulimit -u 1000
De esta manera, evitamos el problema, aunque solamente para la shell en la que hemos ejecutado este comando. Podríamos lanzarlo en el /etc/profile o en algún script de inicio de sesión, pero es bastante más elegante modificarlo en el fichero limits.conf.
grep "nproc" /etc/security/limits.conf
# - nproc - max number of processes
#@student hard nproc 20
#@faculty soft nproc 20
#@faculty hard nproc 50
#ftp hard nproc 0
Como vemos, tenemos varios ejemplos que limitan los procesos a 20 o a 50 en función del grupo al que pertenezcan. También se pueden definir límites por usuario o se puede establecer un límite general:
% hard nproc 1000
Con esto limitamos el número máximo de procesos a 1000 para todos los usuarios.
Y entonces podemos preguntarnos ¿cómo la gente de GNU/Linux es tan torpe de poner un límite que nos puede colgar el sistema operativo? La respuesta tiene que ver no solo con la cantidad de procesos, sino con el peso de cada uno de ellos. Si fijamos a 1000 el número máximo de procesos por usuario, nos libramos de las dos bombas fork que hemos visto, pero modificando ligeramente la segunda, podríamos seguir tirando el sistema para muchos menos procesos:
#!/bin/sh
$0 &
find . &
exec $0
Quizá entonces no solamente haya que limitar el número de procesos, sino otros límites definidos en el fichero limits.conf.
Cuando leamos que tal malware permite ejecutar «código arbitrario» en nuestro sistema, nos podremos acordar de este torpe código y de cómo el solito es capaz de tumbar todo el servidor, desde una cuenta de usuario :-O