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/unsetenvno están obligados a ser seguros para hilos;getenvdevuelve un puntero que puede quedar invalidado por llamadas posteriores. - En programas multihilo, una carrera entre
setenvygetenv(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 deenviron, y modificar las propias cadenas. - Bloquear solo
setenv/getenves insuficiente si el código puede tocarenvirondirectamente. - La API de
getenvdevuelve 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, perogetenvsigue 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
forkyexec, 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/tgetenvreentrantes/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
setenven 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
envpaexecve/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.