The Information Systems and Computer Applications examination covers material that is usually taught in an introductory college-level business information systems course.

El grave fallo de seguridad en GnuTLS (y II)

En un artículo anterior ya hablamos del origen y de las consecuencias para la seguridad del fallo en la librería GnuTLS, ahora nos centraremos en la forma de solucionarlo centrándonos en las distintas estrategias en función de la situación en la que se encuentre nuestra distribución.

 

¿Qué es más seguro el software libre o el privativo?

Esta es una eterna pregunta eterna, que a lo largo de la historia, ha provocado encarnizadas discusiones en los foros más diversos, pero para no entrar en la polémica, no consideraremos el hecho de que en el caso del software libre el código fuente está disponible. Creo que es evidente, tras los sonados fallos de seguridad recientes, que la disponibilidad del código fuente no ha ayudado mucho a evitarlos, es más, considero que sin herramientas automatizadas como las que proporciona Coverity, es complicado auditar miles de líneas de código fuente. Por ello creo que hay que avanzar en este tipo de herramientas y al mismo tiempo, mejorar las pruebas que se realizan del código.

Si atendemos a las estadísticas de fallos detectados en el código fuente, como índice de la calidad y/o seguridad del software, creo que la mejor referencia es el Informe anual de Coverity 2013 [PDF]. Si tenemos en cuenta que el estándar de la industria del software para considerar que un programa tiene calidad suficiente, es de menos de 1 fallo por cada 1.000 líneas de código, hemos de decir, que tanto el software libre, como el software privativo, pasan ampliamente el corte, con una densidad media de 0,59 para el caso del software libre y de 0,72 para el software privativo, como vemos, unos datos relativamente similares y mejores que el estándar. Aunque hay proyectos de software libre, como Python, que destacan de forma notable sobre la media, con una densidad de fallos de solamente 0,005.

Sin embargo, creo que es justo decir que las empresas de software privativo han hecho un gran esfuerzo en los últimos años, al pasar de una densidad de 20 a 30 errores por cada 1.000 líneas de código que presentaban 2006, a los muy aceptables 0,72 de media de la actualidad. Hay que destacar, que esas mismas fechas el software libre presentaba una densidad de solamente 0.434 errores.

Pero ¿cuál es el motivo por el que ha aumentado el índice de fallos en el software libre y ha disminuido tan notablemente el del software privativo?. Creo que el problema está en el aumento de las líneas de código de los proyectos, algo que ocurre casi inevitablemente al madurar los mismos y aumentar las prestaciones de librerías y programas. El software libre, por su modelo de desarrollo distribuido y mayores dificultades para auditar el software, es muy propenso a que aumenten los fallos cuando aumentan las líneas de código si no se establecen estrategias adecuadas para evitarlo.

Lo que sí es cierto, es que normalmente, cuando son detectados, los fallos se reparan más rápidamente en el caso del software libre, algo que también se refleja en el informe. Este hecho fue evidente con los fallos de seguridad detectados durante el concurso Pwn2own, durante el que fueron hackeados los principales navegadores del mercado. En este caso, con un concurso que se llevó a cabo entre los días 12 y 13 de marzo de 2014, la versión de Mozilla Firefox que solucionaba los problemas detectados, la 28.0, ya se podía descargar de los servidores de Mozilla el día 18 de Marzo.

Pero este no es un caso aislado, esta rapidez en la resolución de fallos también fue detectada por Coverity en ediciones anteriores del informe, destacando el caso de Amanda (software libre para realizar copias de seguridad), que tras arrojar el mayor índice de fallos cuando se analizó el código, la comunidad de desarrolladores de dicha aplicación respondió rápidamente y en menos de una semana, se corrigieron todos los errores, pasando a ser el proyecto de software libre con menos errores de todos los revisados. Por lo tanto, si consideramos el tiempo de reacción dentro de la métrica, podemos decir que el software libre también es más seguro que el privativo.

Pero ¿qué ocurrió con GnuTLS?. Según los datos de Coverity, GnuTLS se comenzó a analizar con su herramienta Coverity Scan en abril de 2011, algo que se hizo regularmente hasta junio de 2011, pero pasó mucho tiempo sin auditorías, hasta la última, que se realizó el pasado día 17 de abril de 2014, posiblemente con un afán de mejorar el código tras el fallo “goto result”. Los datos de esta auditoría son estos:

Último análisis: 17 de abril de 2014
Líneas de código analizadas: 150.350
Densidad cada 1.000 líneas: 0,55

Cambios desde la comprobación del 10 de junio de 2011
Nuevos fallos detectados 73
Eliminados 366

Fallos en la versión actual:
Totales: 487
Pendientes: 82
Solucionados: 403

Como se puede ver, no son unos datos malos, de hecho, son mejores que la media del privativo del informe de Coverity, que para los proyectos en c y c++. de entre 100.000 y 500.000 líneas de código muestran una densidad de errores 0,81, pero peores para el software libre, que para ese mismo número de líneas, muestra una densidad de 0,50.

Aunque es complicado ver si los fallos remanentes de GnuTLS en este momento, como ocurrió con el “goto result”, son críticos o no para la funcionalidad y/o seguridad de la librería. De todos modos, creo que es sano realizar este tipo de auditorías del código de forma sistemática y evitar, como ocurrió con GnuTLS, el pasar varios años sin auditarlo de ninguna forma, puesto que como he indicado anteriormente, es complicado detectar fallos cuando crecen el número de líneas, o hay muchos desarrolladores en el proyecto.

 

Estrategia de actualización

Una vez que hemos tomado conciencia de que nuestro software es vulnerable, las dos vías son actualizar o parchear. Si nuestra distribución continua manteniendo soporte, la solución es sencilla, esperar que aparezca el paquete .dbm o .rpm correspondiente en los repositorios y actualizarlo lo antes posible. En este sentido, es muy recomendable considerar a la hora de elegir una distribución, el hacerlo sobre una LTS (Long Time Support), que como Ubuntu 14.04 LTS “Trusty Tahr”, que está previsto que tenga 5 años de soporte.

Si nuestra distribución ya no tiene soporte, lo más recomendable es actualizarse a una que sí lo tenga. Si hemos tenido la precaución de tener una partición /home, dicho proceso no debe ser demasiado traumático ya que nuestros datos no se deberían ver afectados si tenemos cuidado durante la instalación y elegimos las opciones correctas.

Sin embargo, puede que no nos interese o no podamos, por los motivos que sea, el proceder a la actualización de toda la distribución, en ese caso la única opción pasa por actualizar el paquete o modificarlo adecuadamente, opción que no tienen los usuarios de software privativo cuando pierden el soporte, ya que para actualizarlo/compilarlo o para parcherarlo en estas circunstancias, es imprescindible contar con el código fuente del mismo.

Podemos pensar que para actualizarlo/compilarlo, nos bastará con bajarnos la última versión de la librería del repositorio correspondiente y usar la secuencia de mandatos:

./configure
make
make test
make install

Dicha secuencia es posible que nos sirva en muchos casos, puede que en la gran mayoría, pero tiene sus riesgos que debemos evaluar. Por ejemplo, si cogemos la última versión de OpenSSL, es decir, la 1.0.1g y usamos los mandatos anteriores para instalarla en una Mandriva 2010 (Mandriva usa la OpenSSL 0.9.8 de 25 Mar 2009 y no está afectada por el fallo “Heartbleed”, pero la usaremos a modo de ejemplo de lo que estamos explicando), lo que lograremos realmente, es romper el sistema de paquetes que dependen de esta librería y con ello, no podremos instalar o desinstalar ningún paquete .rpm, lo que podemos considerar un desastre que nos puede llevar a tener que instalar todo el software, con todo lo que implica si nuestra distribución está muy “tocada” y no se han usado paquetes .rpm o .deb, para instalar todo lo que necesitamos o hemos modificado en ella y mantenemos copia de los mismos.

Hay que tener en cuenta, que cuantas más dependencias, más riesgos se corren y hay dos tipos de dependencias, las que tiene el programa o librerías para poder ser complilada y las de los otros paquetes que dependen de ella.

Por ejemplo, si intento compilar la última versión estable de la librería GnuTLS, la 3.2.9 en una Mandriva 2010, con mi configuración actual, compruebo que la única dependencia que tengo es una librería criptográfica denominada Nettle, en su versión 2.7.1. Nettle se añadió como dependencia a partir de la versión 2.10.5 de GnuTLS. Sin embargo, si yo intento desinstalar GnuTLS en Mandriva 2010 mediante su gestor de paquetes, el sistema me avisa de que hay 650 paquetes que dependen de GnuTLS, por lo que un poco de cautela no nos vendrá mal antes de decidirnos por instalar directamente la última versión de GnuTLS.

Podemos pensar que es tan sencillo como compilar e instalar Nettle y/o Gmplib, que también necesitaremos para compilar las últimas versiones de GnuTLS, si no la tenemos instalada. Pero también hay que tener en cuenta, que todo lo que instalemos puede tener otras dependencias, que pueden ser versiones más recientes de librerías existentes o de herramientas de compilación o nuevas versiones, por lo que cuanto menos dependencias generemos en nuestra actualización mejor que mejor.

En el caso de Nettle, cuando lo intento compilar solamente me lanza una dependencia, y aunque solamente se usa para las pruebas del código, es la OpenSSL 1.0.1g, que como hemos dicho, es incompatible con el sistema de paquetes .rpm de la Mandriva 2010, aunque también es cierto, que no es frecuente que una actualización a una versión más moderna de una librería genere este tipo de incompatibilidades en Linux.

En este punto hay dos opciones:

a) Tomar el código fuente de la misma versión de la librería GnuTLS que tenemos instalada en nuestro sistema, que en el caso de Mandriva 2010, es la 2.8.4, e intentar parchearla. Para ello, la tendremos que comparar con el código final del archivo /lib/x509/verify.c y con que es el parche que han generado los desarrolladores para resolver el problema, para ver si lo podemos hacer sin grandes problemas.

Para ello accedemos al ftp del proyecto y nos bajamos dicha versión, junto con suarchivo de firma. Alternativamente nos podemos bajar el .rpm con el código fuente de Mandriva.

Ahora, lo primero que tenemos que hacer, es comprobar la firma. Para ello, con gpg (GnuPG) instalado en nuestro sistema, importaremos al anillo de confianza la clave de Simón, que es uno de los desarrolladores de GnuTLS y seguidamente usaremos el siguiente mandato desde el directorio en el que se encuentran los dos archivos anteriores:

gpg --verify gnutls-2.8.4.tar.bz2.sig
gpg: Firmado el vie 18 sep 2009 10:51:45 CEST usando clave RSA ID B565716F
gpg: Firma correcta de "Simon Josefsson <<a href="mailto:simon@josefsson.org">simon@josefsson.org</a>>"
gpg:                 alias "Simon Josefsson <<a href="mailto:simon@yubico.com">simon@yubico.com</a>>"

Una vez comprobada la firma mediante el mensaje “Firma correcta de “Simon Josefsson

tar -xjvf gnutls-2.8.4.tar.bz2

Seguidamente analizaremos si es posible parchear el archivo sin problemas. Como premisa general consideraremos que el archivo no se podrá parchear directamente mediante el mandato patch de Linux, por lo que deberemos hacerlo a mano. Como sabemos, el archivo fuente del problema es “/lib/x509/verify.c”, así que nos toca compararlo con el parche y el código fuente de la última versión disponible, en lo que al problema se refiere. Como veremos, la función “check_if_ca”, se puede parchear sin problemas, ya que el código es casi idéntico a la de la versión anterior a la 3.2.9 de GnuTLS. En el caso de la función “_gnutls_verify_certificate2”, veremos que aunque se puede parchear, las cosas se complican algo más, sobre todo, si no se tiene mucha experiencia de programación en C.

b) Por lo tanto, tendremos que recurrir a la segunda opción, es decir, instalar una versión de la librería más moderna que la que estamos usando en ese momento, lo que puede tener sus riesgos.

El proceso de elección se basa en ir buscando en las distintas versiones disponibles de GnuTLS, hasta encontrar una que se parezca más a lo que tenemos que modificar, pero que no suponga un problema de dependencias. Por ejemplo, podemos probar con la ultima versión de GnuTLS que no necesitaba Nettle, es decir la 2.10.5. Si queremos conocer las diferencias entre las distintas versiones de GnuTLS, podemos recurrir a la Wikipedia.

En este caso, y para ahorrar trabajo a los lectores, una vez bajado el código fuente de esta versión y comprobada la firma, el parche que propongo aplicar para solucionar el problema de GnuTLS es este que muestro seguidamente. A pesar de esto, recomiendo echar un vistazo al código del archivo “verify.c” resultante tras aplicar el parche y compararlo con el de la versión 3.2.9 GnuTLS. Como se podrá comprobar, mi parche se parece mucho al creado para la versión 3.2.9 por el desarrollador Nikos Mavrogiannopoulos, que por otra parte, es el código en el que se basa el mismo, a excepción de la inicialización a cero de la variable “ret”, que no aparece en el código de la versión 3.2.9:

--- verify.c	2010-12-06 14:04:44.000000000 +0100
+++ verify.nuevo	2014-03-09 18:24:37.489058578 +0100
@@ -116,7 +116,7 @@
   if (result < 0)
     {
       gnutls_assert ();
-      goto cleanup;
+      goto fail;
     }
 
   result =
@@ -125,7 +125,7 @@
   if (result < 0)
     {
       gnutls_assert ();
-      goto cleanup;
+      goto fail;
     }
 
   result =
@@ -133,7 +133,7 @@
   if (result < 0)
     {
       gnutls_assert ();
-      goto cleanup;
+      goto fail;
     }
 
   result =
@@ -141,7 +141,7 @@
   if (result < 0)
     {
       gnutls_assert ();
-      goto cleanup;
+      goto fail;
     }
 
   /* If the subject certificate is the same as the issuer
@@ -181,6 +181,7 @@
   else
     gnutls_assert ();
 
+fail:
   result = 0;
 
 cleanup:
@@ -274,7 +275,7 @@
   gnutls_datum_t cert_signed_data = { NULL, 0 };
   gnutls_datum_t cert_signature = { NULL, 0 };
   gnutls_x509_crt_t issuer = NULL;
-  int ret, issuer_version, result;
+  int ret = 0, issuer_version, result=0;
 
   if (output)
     *output = 0;
@@ -307,7 +308,7 @@
   if (issuer_version < 0)
     {
       gnutls_assert ();
-      return issuer_version;
+      return 0;
     }
 
   if (!(flags & GNUTLS_VERIFY_DISABLE_CA_SIGN) &&
@@ -328,6 +329,7 @@
   if (result < 0)
     {
       gnutls_assert ();
+      result = 0;
       goto cleanup;
     }
 
@@ -336,6 +338,7 @@
   if (result < 0)
     {
       gnutls_assert ();
+      result = 0;
       goto cleanup;
     }
 

Para aplicarlo, nos bastará con crear un archivo denominado “verify.patch”, con el contenido anterior y tras copiarlo al directorio en el que se encuentra “/lib/x509/verify.c”, ejecutaremos el mandato:

patch < verify.patch

Una vez aplicado el parche, podremos proceder a compilar la librería, para ello, usaremos la siguiente secuencia de mandatos:

./configure --prefix=/usr --with-default-trust-store-file=/etc/ssl/ca-bundle.crt --enable-gtk-doc
make
make check

Estos mandatos y en especial “configure”, nos informarán si todo está bien para compilar y si la compilación tiene éxito. Si nos aparece algún error en este proceso, lo más probable es que sea un problema de dependencias que deberemos resolver adecuadamente. He de decir, que mi distribución está configurada como estación de trabajo de desarrollo, por lo que la mayoría de los paquetes necesarios para poder configurar y compilar un programa ya estaban instalados y también es cierto que estos paquetes están bastante actualizados, por lo que estos tres mandatos se ejecutaron sin problemas.

Ahora, si todo está bien, estamos en disposición para instalar nuestra librería y la documentación asociada, lo que podemos hacer con los mandatos:

make install
make -C doc/reference install-data-local
ldconfig

Sin embargo, el mandato “make install”, aunque parece que es la opción más usada, tiene sus riesgos, sobre todo, si luego no está disponible la operación o regla “unistall” para “make”, que nos permitiría eliminar el código que se acaba de instalar en caso de problemas.

Para solucionarlo de forma elegante yo suelo usar el mandato checkinstall. Que tras la configuración, compilación del programa, comprobación del programa e introducción de unos datos que nos pide, nos permite generar un paquete .rpm o .deb, según la distribución que usemos, que será más sencillo de instalar y desinstalar en caso necesario. En este caso, dadas las enormes dependencias de GnuTLS en Mandriva 2010, no se recomienda desinstalar directamente el .rpm de la versión anterior antes de usar “make install”, lo que hace mucho más recomendable el hacerlo mediante un paquete .rpm, como si fuera una actualización más del sistema.

NOTA: Si hacemos varias pruebas de compilación antes de instalar, hay que seguir el procedimiento anterior en todas ellas y entre prueba y prueba, es necesario eliminar los archivos de la compilación anterior mediante el mandato “make clean”.

Para los más osados, les diré que tras instalar Nettle 2.7.1 y obviando instalar la última versión de SSL, pude compilar e instalar sin problemas la versión 3.2.9 de GnuTLS, que por otra parte, funciona también sin problemas y con ello, de paso, tengo Nettle, que aunque no era necesaria antes de la instalación de GnuTLS, es una librería, que junto congmplib, que es una librería de aritmética de precisión variable y que ya estaba instalada en mi sistema, son más que interesante para trastear cosas relacionadas con la criptografía.

Finalmente, ya que estamos, podemos actualizar el archivo /etc/ssl/ca-bundle.crt, que contiene los certificados de las CA en el formato adecuado, para actualizarlo, podemos seguir este procedimiento, que nos garantizará que tenemos los certificados adecuados y que se han eliminado los que ya no son necesarios o los que son inseguros, por lo que recomiendo usarlo de vez en cuando.

Con este artículo he intentado explicar algo de lo que posiblemente se habla en muchos sitios y que es inherente al software libre, la posibilidad de adaptarlo a nuestras necesidades, pero también es cierto, es que no se hace en muchas ocasiones, principalmente por ser una opción de último recurso. Pero como hemos podido ver, es posible y relativamente accesible, incluso para personas que no tienen sólidos conocimientos de programación, si tienen acceso al código que se ha modificado para solucionar el problema.

Evidentemente este procedimiento será más complicado o incluso, imposible, cuanto más se alargue la línea temporal, básicamente por el problema las dependencias directas e inversas del paquete que tengamos que actualizar. Evidentemente, cuanto más cerca se encuentre la versión del paquete a actualizar a la versión que usamos en nuestro sistema, menos problemas tendremos. Por ello, he considerado interesante hablar del procedimiento a seguir para parchear un fuente e instalarlo en nuestro sistema sin demasiados riesgos y hablando de los posibles problemas que se nos pueden presentar en el proceso.

Del mismo modo, cuanto más sepamos de programación, más sencillo se nos hará modificar los fuentes y en caso necesario, incluso podremos hacer un parche a nuestra medida ,si el que hay disponible no se adapta demasiado a nuestra versión del programa, lo que puede ser un buen paso previo para participar activamente en un futuro en proyectos de programación.

Evidentemente, complicado o no, hemos demostrado que es posible hacerlo y todo hay que decirlo, es una opción que no está disponible, ni siguiera como un último recurso, a los usuarios de software privativo, que tendrán que optar por pagar una nueva licencia y actualizarse a una nueva versión, aunque ello les suponga incluso la necesidad de actualizar todo o parte de su hardware o perder la posibilidad de usar determinado software que era de su interés. Evidentemente, si somos una empresa tentemos un serio problema de este tipo y no sabemos como solucionarlo, otra opción sería contratar los servicios de alguien que sepa hacerlo.

Desde mi punto de vista, el software libre, además de más seguro en este momento, nos proporciona opciones y oportunidades que deberíamos valorar seriamente desde el principio.

“Copyleft Fernando Acero Martín. Se permite la copia textual, la traducción y la distribución de este artículo entero en cualquier medio, a condición de que este aviso sea conservado. Se permite la cita. El autor no reclamará ninguna cantidad por el ejercicio de las dos autorizaciones anteriores. No autorizo a ninguna Entidad de Derechos de Autor a reclamar cantidad alguna en mi nombre.”

Comments are closed.