lunes, 12 de septiembre de 2011

FreeBSD IPF: NAT duplicate entry causes kernel crash



Hace algún tiempo, fui a ver un servidor, que bajo FreeBSD 7.2 actuaba como firewall utilizando IPFilter y hacia NAT entre otros equipos de la LAN interna. El mismo había tenido unos reinicios inesperados, sin saber el verdadero origen de los mismos. Esto de por si representaba un problema, ya que incrementaba el downtime del mismo, y debiendo a que el las particiones del OS se encontraban bajo UFS, podía corromper los filesystems y forzar a la ejecución de fsck entre reboot y reboot.

Al acceder al equipo, encuentro en /var/log/messages.1 que el equipo, había booteado a las 9:42 AM del día 4 de Diciembre, por lo cual fijandome en logs anteriores encontré que el mismo había entrado en panic, y que había generado el coredump: vmcore.8.

En el directorio /var/crash encuentro varios coredumps de periodos de tiempos comprendidos entre el año 2008 y 2011.

Por lo cual comienzo a debuggear el coredump generado (vmcore.8), con kgdb. Al ejecutar esto, me muestra la siguiente información:
Fatal trap 12: page fault while in kernel mode
cpuid = 0; apic id = 00
fault virtual address = 0x4
fault code = supervisor read, page not present
instruction pointer = 0x20:0xc33ac8ab
stack pointer = 0x28:0xc2f909bc
frame pointer = 0x28:0xc2f90a38
code segment = base 0x0, limit 0xfffff, type 0x1b
= DPL 0, pres 1, def32 1, gran 1
processor eflags = interrupt enabled, resume, IOPL = 0
current process = 21 (irq17: xl1)
trap number = 12
panic: page fault
cpuid = 0
Uptime: 11h41m30s
Physical memory: 499 MB
Dumping 83 MB: 68 52 36 20 4

#0 doadump () at pcpu.h:196
196 __asm __volatile("movl %%fs:0,%0" : "=r" (td));
Se puede apreciar que se intenta cargar una página de memoria, que parece no estar presente, o incorrectamente mapeada, por lo cual, el kernel crashea con page fault, ejecutando el trap 12.
En la línea current process, se encuentra que es en el IRQ 17, en lo que es la interfaz xl1 conectada al equipo. Al hacer ifconfig xl1, encontramos lo siguiente:
xl1: flags=8843 metric 0 mtu 1500
options=9
ether 00:10:4b:c6:65:3b
inet 190.122.08.91 netmask 0xfffffff8 broadcast 190.122.08.124
media: Ethernet autoselect (100baseTX )
status: active
Dicha interfaz se encuentra activa, y con la dirección de IP Pública 190.122.08.91, en la cual se produce el NAT.

Dentro de kgdb, al hacer un backtrace encuentro lo siguiente:

(kgdb) bt
#0 doadump () at pcpu.h:196
#1 0xc07ec1f7 in boot (howto=260) at /usr/src/sys/kern/kern_shutdown.c:418
#2 0xc07ec4c9 in panic (fmt=Variable "fmt" is not available.
) at /usr/src/sys/kern/kern_shutdown.c:574
#3 0xc0b18f2c in trap_fatal (frame=0xc2f9097c, eva=4) at /usr/src/sys/i386/i386/trap.c:939
#4 0xc0b191b0 in trap_pfault (frame=0xc2f9097c, usermode=0, eva=4) at /usr/src/sys/i386/i386/trap.c:852
#5 0xc0b19c2c in trap (frame=0xc2f9097c) at /usr/src/sys/i386/i386/trap.c:530
#6 0xc0afe20b in calltrap () at /usr/src/sys/i386/i386/exception.s:159
#7 0xc33ac8ab in nat_new () from /boot/kernel/ipl.ko
#8 0xc33b0574 in fr_checknatin () from /boot/kernel/ipl.ko
#9 0xc33c9723 in fr_check () from /boot/kernel/ipl.ko
#10 0xc33c170e in fr_check_wrapper () from /boot/kernel/ipl.ko
#11 0xc0897418 in pfil_run_hooks (ph=0xc0d03100, mp=0xc2f90be8, ifp=0xc3191400, dir=1, inp=0x0) at /usr/src/sys/net/pfil.c:78
#12 0xc08d76e2 in ip_input (m=0xc32eaa00) at /usr/src/sys/netinet/ip_input.c:416
#13 0xc0895bb5 in netisr_dispatch (num=2, m=0xc32eaa00) at /usr/src/sys/net/netisr.c:185
#14 0xc088bb51 in ether_demux (ifp=0xc3191400, m=0xc32eaa00) at /usr/src/sys/net/if_ethersubr.c:834
#15 0xc088bf43 in ether_input (ifp=0xc3191400, m=0xc32eaa00) at /usr/src/sys/net/if_ethersubr.c:692
#16 0xc09ec818 in xl_rxeof (sc=0xc3199000) at /usr/src/sys/pci/if_xl.c:2022
#17 0xc09eed24 in xl_intr (arg=0xc3199000) at /usr/src/sys/pci/if_xl.c:2257
#18 0xc07c9f5b in ithread_loop (arg=0xc3190300) at /usr/src/sys/kern/kern_intr.c:1088
#19 0xc07c6a59 in fork_exit (callout=0xc07c9da0 , arg=0xc3190300, frame=0xc2f90d38) at /usr/src/sys/kern/kern_fork.c:810
#20 0xc0afe2b0 in fork_trampoline () at /usr/src/sys/i386/i386/exception.s:264

(kgdb) p *0xc33ac8ab
$1 = 251937163
Si se realiza el seguimiento del backtrace, se encuentra, con lo que posiblemente sea un paquete intentando atravesar el modelo OSI/ISO (Físico, Enlace, Red, Transporte..) haciendo llamadas como por ejeplo a xl_intr, xl_rxeof, ether_input, ether_demux, netisr_dispatch, ip_input, hasta llegar a nat_new donde se produce la falla, y salta a la función calltrap(), la cual hace la llamada para producir el trap (trap y trap_pfault), el cual se convierte en trap_fatal (debido a que es un pagefault) y hace saltar a la funcion panic(), que finaliza escribiendo el coredump (volcado de memoria), y haciendo un reboot del equipo.

Lo que indica que el crash se produce al acceder a la función nat_new(), mapeado su segmento .text en la dirección 0xc33ac8ab, cuyo código de fuente se encuentra en /usr/src/sys/contrib/ipfilter/netinet/ip_nat.c, en el cual se realiza el encapsulado, fragmentación y traslado de direcciones, para aplicar NAT.

Dicha función se compone de tres secciones:

  • Crear una nueva estructura NAT para la regla MAP para las conexiones entrantes.
  • Crear una nueva estructura NAT para la regla RDR para las conexiones salientes.
  • Construir dicha estructuras y ponerla dentro de la tabla de NAT.
También función new_nat() recibe como parámetros:
  • np(I) -> Puntero a la regla de NAT
  • fin(I) -> Puntero a la información paquete, obtenido en las funciones anteriores.
  • flags(i) -> Que se encuentran en el último paquete.
  • direction(i) -> Dirección del paquete (IN/OUT) para luego la función new_nat() pueda aplicar MAP o RDR.
Por lo cual el pagefault lo puede estar dando al pasarle a la función, el argumento np(I). El cual apunte a una dirección de memoria inexistente o invalida, y al intentar leer de ella crashea.

Para reproducir el problema, se puede hacer lo siguiente:

Se debe crear un archivo llamado tcpfrag.nat, con el siguiente contenido:
[out,xl1]
4500 00a0 0000 0100 3f06 7555 0101 0101 0201 0101
0401 0019 0000 0000 0000 0000 5010 2000 86b7 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Luego ejecutando:
#: ipftest -F hex -N tcpfrag.nat -i tcpfrag.pkt
Esto es debido a que en el archivo /etc/ipnat.rules, se encuentran cargadas las siguientes reglas sobre la interfaz de red xl1.
bimap xl1 172.18.2.12/32 -> 190.122.08.91/32
bimap xl1 172.18.2.13/32 -> 190.122.08.91/32
bimap xl1 172.18.2.14/32 -> 190.122.08.91/32
bimap xl1 172.18.2.15/32 -> 190.122.08.91/32
Según busque entre los reportes de bugs de FreeBSD, el problema existe y el motivo del problema puede ser debido a que un fragmento TCP que matchee con una regla que es para otro cualquier protocolo, debido a que se duplica su entrada en la cache de NAT.

Dicha referencia puede ser encontrada en el siguiente link:
http://www.freebsd.org/cgi/query-pr.cgi?pr=137392

Dicho bug, se encuentra abierto al momento, y lleva reportado ya 2 años (Agosto del 2009), fue categorizado como "Medio". Debido a que es posible generar en un host remoto, paquetes con una payload ligeramente modificada y producir una denegación de servicio en el servidor, haciendo entrar en panic el kernel, y produciendo por lo tanto un reboot forzado del mismo.

Según encontré, aún no hay ningún parche oficial para solucionar dicho problema, y en esta empresa se necesita la utilización de las reglas bimap, por lo cual, la única solución accesible y razonable que encontré es migrar dichas reglas a Packet Filter. En el caso de Solaris, no tuve la oportunidad de testearlo, si alguién lo hace, por favor me avisa!. 

4 comentarios:

  1. Hola, perdoname que te corrija.
    El bug donde lo viste, esta marcado como ducplicado, y si vas al link del duplicado, dice cerrado, si fue arreglado.
    http://www.freebsd.org/cgi/query-pr.cgi?pr=3D1316=

    Saludos.

    ResponderEliminar
  2. Gracias! Arme este post basándome en un informé que realice para octubre del año pasado, hasta entonces se encontraba abierto. Gracias por la info!.

    ResponderEliminar
  3. De nada, aunque, me corrijo!!, mira, si bien el bug fue cerrado por duplicado, fue "mal cerrado".
    Y por lo que veo, existe aun incluso en freebsd 8.2 stable:

    http://lists.freebsd.org/pipermail/freebsd-net/2011-February/028064.html

    o kern/138177 net [ipfilter] FreeBSD crashing repeatedly in ip_nat.c:257

    Es muy comico, los bsdderos dicen que *BSD es mas seguro que GNU/Linux, y sin fallos, y esto?, te crashean el server de forma remota :P.
    Esta activo, pregunte en su canal (#freebsd freenode):

    hi folks, this bug is fixed in 8.2 stable?.
    http://lists.freebsd.org/pipermail/freebsd-net/2011-February/028064.html
    o kern/138177 net [ipfilter] FreeBSD crashing repeatedly in ip_nat.c:257
    http://www.freebsd.org/cgi/query-pr.cgi?pr=138177
    wow
    critical and open
    from 2009
    anon: hard to say, there's not enough feedback from the submitter to determine if it's fixed or not.

    En vista de eso, y la lista que podrás ver de fallos en network que aún posee FreeBSD, se podría decir que la seguridad que ostentan los de FreeBSD es una falsa seguridad, solo basada en la fama de OpenBSD?.
    Tenes idea si OpenBSD posee el mismo fallo?.

    Saludos y gracias!.

    ResponderEliminar
  4. Excelente info :-) En OpenBSD no lo experimenté. De todas formas IPF es algo que ya esta deprecado, ya que esto se soluciona migrando a PF que ofrece ventajas prácticas aún mayores que IPF. De todas formas, no quita que FreeBSD sea un excelente OS.

    ResponderEliminar