15 puntos por xguru 2023-11-23 | 3 comentarios | Compartir por WhatsApp
  • Las funciones setenv() y unsetenv() de C no se pueden usar de forma segura en programas que usan hilos
  • Estas funciones modifican estado global y pueden provocar conflictos cuando otro hilo llama a getenv()
  • También ocurren fallos en otros lenguajes que usan funciones de la biblioteca estándar de C, como os.Setenv de Go y std::env::set_var() de Rust
  • Se necesitaron 2 días para rastrear el problema relacionado y reportar el bug en un programa de Go
    • Esto se debe a que el resolvedor DNS interno de Go usa getaddrinfo(), que a su vez llama a getenv()
  • Pero este problema es muy antiguo. Ya había un artículo relacionado en 2017, y al final decía “¡nos vemos en 5 años, en 2022!”, pero volvieron a encontrarse con él en 2023
  • Esto es un defecto del estándar POSIX, que extendió el estándar de C para permitir modificar variables de entorno
    • La parte más frustrante es que muchas personas con capacidad de influir en el estándar o mantener bibliotecas de C no consideran que esto sea un problema
    • La razón es que la especificación deja claro que setenv() no puede usarse junto con hilos
    • Por lo tanto, si alguien hace eso, el fallo es culpa suya
  • Así que supuestamente “debemos leer con cuidado la especificación de todas las funciones, no usar software escrito por otros y no usar hilos”
    • Pero en el software moderno, esa es una suposición poco realista
    • En lugar de eso, habría que esforzarse por crear APIs que sean difíciles de romper y evolucionen junto con los cambios del ecosistema
  • Como C y su biblioteca estándar siguen desempeñando un papel importante en la base de la mayoría del software, hay que encontrar una forma de mejorarlos o una forma de dejarlos atrás

Por qué setenv() no es thread-safe

  • getenv() devuelve un char*, y la aplicación no necesita liberar ese valor más tarde
  • Mientras un hilo usa ese puntero, otro hilo puede cambiar la misma variable de entorno con setenv() o unsetenv()
  • El estándar de C solo incluye getenv(), pero la mayoría de las implementaciones siguen POSIX e incluyen funciones para modificar el entorno
  • putenv() agrega un char* al conjunto de variables de entorno, y si la aplicación modifica esa memoria después de que putenv() retorna, la variable de entorno también cambia
  • environ es un arreglo de punteros terminado en NULL (char**) que la aplicación puede leer y asignar, y el acceso a ese arreglo no es thread-safe

Cómo se implementan las variables de entorno

  • Cuando una aplicación sobrescribe una variable existente, la implementación debe decidir cómo manejarlo
  • glibc y Solaris/Illumos nunca liberan las variables de entorno, por lo que el valor devuelto por getenv() es inmutable y puede usarse de forma segura entre hilos
  • musl y FreeBSD/Apple sí liberan variables de entorno, por lo que puede haber fallos si otro hilo llama a setenv() y luego se usa el puntero devuelto por getenv()
  • Garantizar que el conjunto de variables de entorno se actualice de forma thread-safe es un segundo problema, y eso es lo que provoca fallos en glibc

Por qué los programas usan variables de entorno

  • Las variables de entorno son útiles para configurar bibliotecas compartidas o runtimes de lenguaje incluidos dentro de otros programas
  • Permiten que los usuarios cambien la configuración sin que el autor del programa tenga que pasarla explícitamente
  • Muchas bibliotecas llaman a getenv(), y los programas necesitan cambiar estas variables para configurar las bibliotecas que usan

Este problema debe resolverse, y se puede hacer así

  • Me parece absurdo que esto haya sido un problema conocido durante tanto tiempo
  • Se han desperdiciado miles de horas depurando el problema o discutiendo soluciones alternativas
  • Formas de resolverlo
    • Crear una implementación thread-safe como en Illumos/Solaris
      • Esto tiene algunas limitaciones. setenv() filtra memoria y sigue sin ser seguro si el programa usa putenv() o manipula directamente las variables de entorno
      • Aun así, es una mejora frente a las implementaciones actuales de Linux y Apple
    • Una segunda opción es agregar una API nueva que, por diseño, obtenga variables de entorno de forma thread-safe, como getenv_s() de Microsoft
  • La solución que prefiero es usar ambas
    • Reduce la probabilidad de problemas en programas y bibliotecas existentes, y ofrece un camino para evitar por completo el problema en código o lenguajes nuevos como Go y Rust
    • Agregar una función similar a getenv_s() que copie una variable de entorno a un búfer proporcionado por el usuario
    • Agregar una API thread-safe para iterar sobre todas las variables de entorno o copiar todas las variables
    • Marcar getenv() como obsoleta y recomendar en su lugar una nueva versión thread-safe de getenv()
    • Marcar putenv() como obsoleta y recomendar setenv() en su lugar
    • Marcar environ como obsoleto y recomendar funciones de variables de entorno en su lugar
    • Actualizar la implementación de las variables de entorno para que sea thread-safe

3 comentarios

 
ahwjdekf 2023-11-24

"Porque la especificación deja claramente establecido que no se puede usar setenv() junto con hilos" ==> al usar una API o un SDK, lo más básico entre lo básico es revisar con mucho cuidado la specification. Solo parece un uso forzado a la mala.

 
carnoxen 2025-01-24

El problema es usar una función que estuvo mal diseñada desde el principio

 
cosine20 2023-11-27

....