Setenv No es Seguro para Hilos y C No Quiere Arreglarlo

La API de Unix `setenv`/`getenv` para variables de entorno no es fundamentalmente segura para hilos, y POSIX lo permite explícitamente, lo que lleva a fallos raros pero desagradables cuando programas modernos multihilo (o lenguajes como Go y Rust) dependen de ella. Los comentaristas debaten si el verdadero error es el propio entorno mutable y global del proceso, y muchos sostienen que las variables de entorno deberían tratarse como inmutables después del arranque y que las bibliotecas deberían ofrecer APIs explícitas de configuración. Otros señalan implementaciones ya seguras para hilos (como Solaris/Illumos y las llamadas estilo Windows que copian al salir) y piden nuevas funciones más seguras en la biblioteca C, o incluso una biblioteca estándar “post-C” que elimine trampas heredadas sin romper la compatibilidad.

Problema central: setenv/getenv y la seguridad en hilos

  • POSIX dice explícitamente que setenv/unsetenv no están obligados a ser seguros para hilos; getenv devuelve un puntero que puede quedar invalidado por llamadas posteriores.
  • En programas multihilo, una carrera entre setenv y getenv (o cualquier llamada de libc que use el entorno) puede causar fallos o datos incorrectos.
  • Algunos sostienen que esto hace que la API esté “rota e irremediable” porque expone estado global mutable sin una variante segura.

Por qué es difícil de arreglar

  • Los programas pueden modificar el entorno por varios canales: setenv/putenv, escribir a través de environ, y modificar las propias cadenas.
  • Bloquear solo setenv/getenv es insuficiente si el código puede tocar environ directamente.
  • La API de getenv devuelve punteros internos; no puedes reorganizar ni liberar de forma segura el almacenamiento subyacente sin romper potencialmente código existente.
  • La compatibilidad hacia atrás es una objeción importante: muchísimo código heredado depende de la semántica actual.

Cómo difieren los sistemas y los lenguajes

  • Algunas libcs (Solaris/Illumos, algunas BSD, Apple) añaden bloqueo y a menudo “filtran” el almacenamiento viejo del entorno para evitar use-after-free; otras (musl, algunas BSD) no bloquean en absoluto.
  • glibc históricamente tenía carreras inseguras; versiones recientes añadieron un lock alrededor de setenv, pero getenv sigue siendo propenso a carreras cuando el entorno se redimensiona.
  • Las APIs de Windows (GetEnvironmentVariable, GetEnvironmentStrings) copian a buffers del llamador y son, en la práctica, seguras para hilos.
  • La stdlib de Rust encapsula el acceso al entorno con un lock y devuelve copias propiedad del llamador, pero FFI hacia C aún puede romper invariantes; esto causó CVEs reales alrededor del manejo de zonas horarias.
  • Go también se topó con problemas cuando su código de DNS/tiempo llamaba a libc, que lee variables de entorno.

Casos de uso y si el entorno debería mutar

  • Muchos sostienen que el entorno del proceso debería tratarse como inmutable: fijarse al inicio o entre fork y exec, no usarse como bus de configuración en tiempo de ejecución.
  • Otros señalan casos reales: shells, harnesses de pruebas, depuradores, lanzadores de procesos y bibliotecas que solo exponen configuración mediante variables de entorno.

Soluciones propuestas y alternativas

  • Nuevas APIs: getenv_r/getenv_s/tgetenv reentrantes/seguras para hilos que copien a buffers del llamador; quizá deprecar las APIs antiguas con el tiempo.
  • Trucos de implementación: filtrar bloques viejos del entorno (Solaris/Illumos, Eyra) para evitar UAF; añadir mutexes internos; o hacer crash/panic en setenv en código multihilo para sacar a la luz los errores.
  • Consejo de nivel superior: no modificar el entorno después de que arranquen los hilos; evitar bibliotecas que lo hagan; usar APIs explícitas de configuración o pasar envp a execve/posix_spawn.

Meta-discusión: responsabilidad y estándares

  • Un lado: C/POSIX son “correctos” tal como están documentados; los programadores deben leer las páginas de manual y evitar APIs no reentrantes en código con hilos.
  • El otro lado: documentar una arista peligrosa no justifica conservarla; los estándares deberían evolucionar para reducir trampas, incluso a costa de nuevas APIs y complejidad.