¿Por qué creamos CoyIM? - Lenguaje de Implementación

Feb 9, 2022

¡La versión 0.4 de CoyIM llegará muy pronto! Estamos muy orgullosos de ella y esperamos que todos ustedes la disfruten. Y aunque estamos hablando mucho sobre las numerosas mejoras en esta versión, también es importante dar un paso atrás y mirar las principales razones por las que decidimos crear CoyIM en primer lugar. Estas razones siguen siendo válidas hasta el día de hoy, y en algunos aspectos son aún más importantes. En un post anterior hicimos un repaso a la historia que condujo hasta la creación de CoyIM. En esta publicación queremos ahondar en una razón específica y explicar un poco más porqué esta es importante. Esta razón es la elección del lenguaje de implementación.

Existen muchos lenguajes de programación diferentes en el mundo. La mayoría se utilizan para situaciones especiales y no tienen tantos usuarios. Tan sólo existen unos diez o quince bastante grandes. Cuando se habla de lenguajes de programación, es común discutir si un lenguaje es de alto o bajo nivel. Lo que eso significa exactamente puede diferir según la discusión. Pero a menudo, un lenguaje de bajo nivel tendrá un mejor rendimiento, pero también requerirá que hagas más trabajo. Básicamente, se necesita más código para lograr una operación equivalente en un lenguaje de bajo nivel en comparación con un lenguaje de alto nivel. Por otro lado, un lenguaje de alto nivel ocultará o abstraerá ciertas cosas para hacer más fáciles la mayoría de tareas de programación. Esta abstracción también significa que será más fácil ejecutar un programa en diferentes tipos de máquinas. Entonces, si escribes un programa en assembler, este podría ser muy rápido, pero tendrías que escribir mucho código hasta para hacer lo más básico. Por otro lado, si escribes un programa equivalente en Ruby, éste podría ser diez o veinte veces más lento, pero podrías tomar el programa y ejecutarlo en diferentes tipos de computadoras sin tener que modificar nada. En general, tres diferencias son las más importantes cuando se habla de lenguajes de bajo nivel frente a los de alto nivel. En primer lugar, tenemos el rendimiento. En segundo lugar, el acceso a la funcionalidad de bajo nivel de la computadora y finalmente, la cantidad de trabajo de gestión que tendrás que hacer tú mismo.

Cuando hablamos de seguridad, la cuestión de la gestión es importante. Por ejemplo, en el lenguaje de programación C (que se considera de nivel bastante bajo), se debe realizar un seguimiento manual de la longitud de las cadenas (strings). Las cadenas se utilizan para almacenar texto que el programa quiere usar de varias maneras. Y, en general, hay dos formas de realizar un seguimiento de esto: ya sea utilizando un valor especial que no sea común en las cadenas, o almacenando manualmente la longitud de la cadena. Para muchos tipos de datos, se debe tener soporte para ese centinela y, en esos casos, la única forma de manejarlo es realizar un seguimiento de la longitud. Pero el truco es que debes recordar hacer esto en todas partes de tu código. Además, para conseguir buen rendimiento, es muy común limitar las cadenas a una longitud específica, pero si olvidas hacer esto en algún lugar, puedes terminar en una situación en la que un atacante podría inyectar más datos que los que caben en el espacio existente. Si esto sucede, los datos continuarán siendo escritos en otras partes de la memoria interna del programa. Esto puede corromper cualquier tipo de datos y la aplicación podría no saberlo. Si esto se hace maliciosamente, un atacante podría sobrescribir la memoria de una manera cuidadosamente planificada, de tal modo que el programa haga algo no deseado, dejándolo bajo el control del atacante. Este tipo de ataque se denomina ataque de desbordamiento de búfer, y sigue siendo una de las formas más comunes de atacar programas de bajo nivel. En un programa de alto nivel, este tipo de situación simplemente no puede ocurrir. Esto se debe a que el propio lenguaje se encarga de comprobar los límites de varios tipos de datos. Esta función se denomina seguridad de memoria. Y aunque esto hace que el programa sea más seguro, toda esta comprobación también tendrá un impacto en el rendimiento. Y a veces, sólo a veces, es posible que incluso necesites de esa inseguridad de la memoria para implementar características avanzadas.

Hay muchos otros tipos de funciones de gestión que los lenguajes de alto nivel pueden admitir, lo que los hará más seguros. El ejemplo anterior es sólo eso, un ejemplo. Pero en la mayoría de los casos, quitarle la responsabilidad al desarrollador significa que muchas clases de problemas de seguridad simplemente no podrán ocurrir.

Cuando se creó CoyIM, una de las principales motivaciones era brindar una alternativa a los clientes de chat desarrollados en lenguaje no seguro. Estos programas no sólo estaban escritos en C o C++, sino que eran programas bastante grandes que admitían muchas diferentes características y estaban implementados en muchos miles de líneas de código de bajo nivel. Al mismo tiempo, la falta de protección de memoria había sido explotada en estos programas de varias maneras, lo que demuestra que este riesgo no era solo teórico, sino que en realidad era algo que podría poner en peligro a los usuarios reales.

Escribimos CoyIM usando un lenguaje de programación llamado Go (o Golang). En ese momento, todavía era un idioma bastante nuevo, pero a lo largo de los años se ha convertido en uno de los principales lenguajes utilizados para el desarrollo de sistemas de diversos tipos. Golang es interesante porque en realidad no es un lenguaje de alto nivel. Pero tampoco es de un nivel extremadamente bajo. Para nosotros, la elección se hizo porque es un lenguaje seguro para la memoria, lo que significa que muchos tipos de ataques simplemente no son posibles. Al mismo tiempo, es portable de una manera poco común entre los lenguajes de nivel inferior. Esta portabilidad significa que podemos escribir código que se ejecutará en todas las plataformas principales sin ningún cambio. El nivel del lenguaje sigue siendo lo suficientemente bajo como para que sea posible implementar algoritmos que se ejecutan cercanos al hardware, sin sacrificar la seguridad. Además de eso, Golang tiene una enorme cantidad de librerías muy buenas disponibles para cualquier tipo de propósito. Esto, combinado con un sistema que funciona rápido y no utiliza mucha memoria, lo convirtió en un buen equilibrio de diferentes ventajas y desventajas. Obtuvimos seguridad de la gestión de memoria, sin sacrificar la velocidad o la simplicidad, ni en el desarrollo ni en el tiempo de ejecución.

En resumen, al usar Golang en lugar de otros posibles lenguajes de programación, encontramos un buen conjunto de características que redujeron significativamente el riesgo de muchos tipos de ataques, al mismo tiempo que conserva muchas cosas que hacen que el desarrollo sea fácil y eficaz. Ahora, siete años después de que se iniciara el proyecto, seguimos creyendo que Golang es la elección correcta para este tipo de herramienta, y la hemos usado para otros proyectos también. Y como pueden ver en la versión 0.4, nos permite ofrecer nuevas y potentes funciones mientras controla el riesgo de ataques.