Setenv Thread-Safe नहीं है और C इसे ठीक नहीं करना चाहता
Unix का `setenv`/`getenv` API environment variables के लिए मूल रूप से thread-safe नहीं है, और POSIX स्पष्ट रूप से इसकी अनुमति देता है, जिससे आधुनिक multi-threaded programs (या Go और Rust जैसी भाषाओं) में कभी-कभार लेकिन गंभीर crashes हो सकते हैं। Commenters इस पर बहस करते हैं कि असली bug क्या mutable, process-global environment itself है, और बहुत से लोग तर्क देते हैं कि env vars को startup के बाद immutable माना जाना चाहिए और libraries को explicit configuration APIs देने चाहिए। अन्य लोग मौजूदा thread-safe implementations (जैसे Solaris/Illumos और Windows-style copy-out calls) की ओर इशारा करते हैं और या तो नए, सुरक्षित C library functions या एक “post-C” standard library की मांग करते हैं जो legacy footguns को हटाए लेकिन compatibility बनाए रखे।
मुख्य समस्या: setenv/getenv और thread safety
- POSIX स्पष्ट रूप से कहता है कि
setenv/unsetenvका thread-safe होना आवश्यक नहीं है;getenvएक pointer लौटाता है जो बाद की calls से invalid हो सकता है। - Multithreaded programs में,
setenvकाgetenv(या किसी env-उपयोग करने वाली libc call) के साथ race होना crashes या bogus data का कारण बन सकता है। - कुछ लोग कहते हैं कि इससे API “broken and unfixable” हो जाती है, क्योंकि यह बिना सुरक्षित variant के global mutable state expose करती है।
इसे ठीक करना क्यों कठिन है
- Programs कई channels के through environment mutate कर सकते हैं:
setenv/putenv,environके through लिखना, और strings को स्वयं modify करना। - सिर्फ
setenv/getenvको lock करना पर्याप्त नहीं है यदि code कोenvironसीधे touch करने की अनुमति है। getenvका API internal pointers लौटाता है; आप existing code को potentially तोड़े बिना backing storage को safely rearrange या free नहीं कर सकते।- Backwards compatibility एक बड़ा objection है: बहुत सा legacy code current semantics पर निर्भर करता है।
Systems और languages में अंतर
- कुछ libc implementations (Solaris/Illumos, कुछ BSDs, Apple) locking जोड़ती हैं और अक्सर पुराने env storage को “leak” करती हैं ताकि use-after-free से बचा जा सके; अन्य (musl, कुछ BSDs) बिल्कुल lock नहीं करतीं।
- glibc में historically unsafe races थे; recent versions ने
setenvके around lock जोड़ा है, लेकिन env resize होने परgetenvअभी भी racy रहता है। - Windows APIs (
GetEnvironmentVariable,GetEnvironmentStrings) caller buffers में copy करती हैं और effectively thread-safe हैं। - Rust की stdlib env access को lock में wrap करती है और owned copies लौटाती है, लेकिन C में FFI invariants तोड़ सकता है; इससे time zone handling के around वास्तविक CVEs हुए।
- Go को भी ऐसी समस्याएँ मिलीं जब उसके DNS/time code ने libc को call किया, जो env vars पढ़ता है।
Use cases और क्या env mutate होना चाहिए
- कई लोग तर्क देते हैं कि process environment को immutable मानना चाहिए: startup पर या
forkऔरexecके बीच set किया जाए, runtime config bus के रूप में न इस्तेमाल हो। - अन्य लोग वास्तविक cases की ओर इशारा करते हैं: shells, test harnesses, debuggers, process launchers, और libraries जो config केवल env vars के through expose करती हैं।
Proposed fixes और workarounds
- New APIs: reentrant/thread-safe
getenv_r/getenv_s/tgetenvजो caller buffers में copy करें; संभवतः पुराने APIs को समय के साथ deprecate किया जाए। - Implementation tricks: पुराने env blocks को leak करना (Solaris/Illumos, Eyra) ताकि UAF से बचा जा सके; internal mutexes जोड़ना; या multithreaded code में
setenvपर crash/panic करना ताकि bugs सामने आएँ। - Higher-level advice: threads शुरू होने के बाद env modify न करें; ऐसी libraries से बचें जो ऐसा करती हैं; explicit configuration APIs का उपयोग करें या
execve/posix_spawnकोenvpपास करें।
Meta-discussion: जिम्मेदारी और standards
- एक पक्ष: C/POSIX documented रूप से “correct” हैं; programmers को manpages पढ़नी चाहिए और threaded code में non-reentrant APIs का उपयोग नहीं करना चाहिए।
- दूसरा पक्ष: sharp edge को document करना उसे बनाए रखने का justification नहीं है; standards को evolve होना चाहिए ताकि footguns कम हों, भले ही नए APIs और complexity की कीमत पर।