BLOG | OFICINA DEL CTO

Más allá de la C

Publicado el 7 de febrero de 2023

Durante décadas, C y sus descendientes inmediatos han sido los lenguajes de programación preferidos para la programación de sistemas. A principios de la década de 1970, cuando se desarrolló C, fue un importante paso adelante respecto del lenguaje ensamblador. Cincuenta años después, podemos hacerlo mejor.

La seguridad informática no estaba en la mente de la mayoría de las personas en 1970, ni durante muchos años después. La primera gran vulnerabilidad de seguridad en Internet ocurrió muchos años después, en 1988. Se le conocía como el gusano de Internet y aprovechaba un desbordamiento de búfer, donde una matriz en la memoria era indexada fuera de sus límites.  

En la familia del lenguaje C, que incluye C++, las matrices no tienen controles de límites. Depende del programador garantizar que se acceda correctamente a la matriz. Por lo tanto, los errores de desbordamiento de búfer son comunes. Peor aún, es fácil provocar deliberadamente un desbordamiento del búfer y, con ello, acceder a una memoria a la que no se debería acceder.

El desbordamiento del búfer es sólo un ejemplo de inseguridad de la memoria . Otros ejemplos relacionados incluyen la aritmética de punteros y los punteros colgantes (también conocidos como errores de uso después de la liberación). Hoy en día, tenemos muchos lenguajes de programación que emplean una variedad de técnicas para garantizar que cualquier programa escrito en esos lenguajes estará libre de problemas de seguridad de memoria. La familia de lenguajes C no ofrece tal garantía; la seguridad de la memoria nunca fue un objetivo de diseño de estos lenguajes.

Aproximadamente el 70% de las vulnerabilidades de seguridad se deben a violaciones de la seguridad de la memoria. Esta afirmación está respaldada por datos abrumadores. La seguridad de la memoria tiene en cuenta:

  • El 67% de las vulnerabilidades explotadas se revelaron y detectaron en la práctica (según la estimación de Google Project Zero 2021 ).
  • 90% de las vulnerabilidades de Android ( según Google ). Si bien esta cifra ha disminuido recientemente debido al uso de Rust, el 89 % de las vulnerabilidades explotables remotas siguen estando vinculadas a la seguridad de la memoria.
  • 70% de las vulnerabilidades de Microsoft (según Microsoft).
  • La mayoría de las correcciones recientes de vulnerabilidades de Apple (en Catalina , Big Sur , Monterey , Safari , iOS , tvOS , watchOS ).

En julio de 2022, 5/6 de las vulnerabilidades corregidas en Chrome 103.0.5060.134 eran problemas de seguridad de la memoria. Está claro que eliminar los errores de seguridad de la memoria sería de enorme ayuda. La industria sabe cómo hacerlo desde hace muchos años: utilizar lenguajes de programación seguros para la memoria. Históricamente, el problema siempre ha sido el coste asociado en términos de rendimiento. Debido a la importancia de la seguridad de la memoria, la gente ha estado trabajando en nuevas estrategias para lograrla sin los costos operativos tradicionales. Hoy sabemos cómo eliminar errores de seguridad de memoria con poco o ningún costo de tiempo de ejecución. Los lenguajes, como Rust, utilizan innovaciones en el diseño del sistema de tipos para garantizar la seguridad de la memoria sin un costoso soporte en tiempo de ejecución.

Esto nos lleva a una conclusión ineludible: dejar de escribir nuevo código de sistemas en C/C++ (o, más generalmente, en lenguajes no seguros para la memoria).

Esto no es un llamado a reescrituras masivas e indiscriminadas del código existente. Reemplazar el software existente es costoso y no está exento de riesgos. Sin embargo, la industria debe dejar de agravar el problema añadiendo más código inseguro para la memoria a las bases de código existentes. Para el código existente, priorice la reescritura de los componentes más sensibles: aquellos que son responsables de validar o consumir entradas de usuarios no confiables, aquellos que se ejecutan en un contexto privilegiado, aquellos que se ejecutan fuera de un entorno protegido, etc.

Esta postura, aunque ampliamente sostenida, todavía resulta controvertida para algunos. A continuación se presentan algunos de los argumentos más comunes a favor del statu quo y nuestras respuestas a ellos.

  • Los errores no son específicos de los lenguajes de programación.  
    • La mayoría de las vulnerabilidades resultan de errores de seguridad de la memoria. No es posible tener errores de seguridad de memoria en lenguajes seguros para la memoria como Rust. Por lo tanto, utilizar un lenguaje seguro para la memoria evitará que se creen la mayoría de las vulnerabilidades.
  • Las revisiones de código detectarán errores.
    • Los estudios muestran que, si bien las revisiones de código pueden mejorar las cosas, a menudo pasan por alto muchos problemas; ciertamente no encuentran todo.
  • Los módulos inseguros hacen que todo el argumento sea discutible.
    • El código inseguro se delinea explícitamente en secciones muy pequeñas de código, de modo que los recursos se puedan concentrar en validarlo.
  • Una implementación de lenguaje seguro para la memoria podría tener errores.
    • Efectivamente, pero las probabilidades son mucho menores. Los compiladores son relativamente pequeños y están rigurosamente probados. Además, arreglar un compilador arreglará automáticamente todos los programas compilados con él, a diferencia de arreglar un error en un solo programa.
  • Sólo los programadores novatos o incompetentes cometen estos errores.
    • Por el contrario, los datos anteriores proceden de proyectos de sistemas importantes, como el navegador Chrome y los sistemas operativos Windows y MacOS. Estos proyectos cuentan con algunos de los desarrolladores más competentes y experimentados de la industria y, aun así, todavía presentan problemas con la seguridad de la memoria.
  • Estos problemas se pueden evitar siguiendo las mejores prácticas.
    • Ver arriba. Los equipos mencionados siguen prácticas muy rigurosas y utilizan las mejores herramientas disponibles.
  • No es práctico reescribir todo el código.
    • Nadie aboga por una reescritura completa de todo el código. En lugar de ello, se recomienda un enfoque que se centre en elementos clave (altos privilegios, gran superficie de ataque, central para las garantías de seguridad) y escribir código nuevo en un lenguaje seguro para la memoria.
  • El rendimiento es inadecuado.
    • El rendimiento de Rust está aproximadamente a la par con C/C++. Las pequeñas diferencias no justifican riesgos de seguridad. También hay situaciones en las que los programas Rust pueden ser más rápidos.
  • Los lenguajes de programación de sistemas seguros de memoria como Rust son demasiado nuevos.
  • Existen otras soluciones como el análisis estático, el fuzzing y el sandbox.

Ver Cuantificación de la inseguridad de la memoria y las reacciones a ella para una discusión detallada de los puntos anteriores.

A pesar de las objeciones, el impulso para los cambios necesarios está creciendo. Por ejemplo, se están realizando grandes inversiones en la Open Source Software Security Foundation (respaldada por la Fundación Linux). La seguridad de la memoria se ha debatido en EE.UU. Informes del Senado y de la NSAConsumer Reports también trabaja para identificar los diversos incentivos que podrían acelerar este movimiento ofreciendo una serie de recomendaciones para empresas y agencias estatales.

Para resumir:

La importancia de la informática para la sociedad ha crecido enormemente en los últimos cincuenta años. El panorama de amenazas a nuestra infraestructura informática también ha cambiado radicalmente en las últimas décadas. Sin embargo, los lenguajes de programación que utilizamos para construir nuestros sistemas informáticos no han cambiado en consecuencia. La falta de seguridad de la memoria es la mayor fuente de vulnerabilidades de seguridad en el software. Esto no es exclusivo de ningún tipo específico de software, ya que en todos lados donde se usan lenguajes que no son seguros para la memoria abundan los problemas de seguridad de la memoria. Y en cifras, ninguna otra clase de vulnerabilidad se acerca.

Ahora tenemos los medios para abordar este problema creciente y es crucial que, como industria, lo hagamos. Afortunadamente, cada vez hay más conciencia de la situación, pero el problema es urgente y no hay tiempo que perder.