3 ms·
Interesting read. Another interesting fact is that `setenv()` is fundamentally broken on POSIX, and should essentially never be called in library code. In appli
by simonask 1y ago
Interesting read. Another interesting fact is that `setenv()` is fundamentally broken on POSIX, and should essentially never be called in library code. In application code, it should be called only in the absence of any alternative, and certainly before any threads have started.
The reason is that `getenv()` hands out raw pointers to the variables, so overwriting a variable using `setenv()` is impossible to guard against. Treat with extreme caution.
- wmf 1y agoAFAIK Solaris solved that problem but Linux refuses to copy their solution.
- foofoo12 1y agoLink?
- ori_b 1y agoTheir solution was that setenv leaks memory rather than overwriting in place. FreeBSD does the same. See here for discussion: https://freebsd-current.freebsd.narkive.com/NwqZQDWm/fix-for-memory-leak-in-setenv-unsetenv https://freebsd-current.freebsd.narkive.com/NwqZQDWm/fix-for...
- monocasa 1y agoSure. But practically the amount you leak is infinitesimal. For a lot of applications that's the right call given the rest of the posix semantics you're constrained to and the kinds and frequency of data you pass via env vars.
- cbsmith 1y agoAn infinitesimal leak becomes a problem if it is done an infinite number of times... execve seems like the preferable choice on a lot of grounds.
- dwattttt 1y agoIt would be a problem, except that the behaviour you're moving away from is a stale pointer. So surely any application that'd be leaking under that new behaviour would be crashing today.
- 1718627440 1y agoNo it always leaks, regardless if your program now correctly invalidates all derived pointers upon calling setenv.
- ori_b 1y agoThe same advice applies to a correct program regardless of the implementation: using setenv is a bug. Only use getenv. Getenv in a program without setenv is fine in both implementations. Setenv is unusable with all conforming implementations. To pass environments to children, use execve. The Linux behavior allows a careful single threaded program to use setenv correctly. The BSD/Solaris behavior makes all usage incorrect, but the incorrectness comes in the form of a memory leak, which is preferable to a security issue, usually. There's no correct, portable use of setenv. If you call it, it's a bug.
- monocasa 1y agoWhich is why I said > frequency of data Practically, if you're moving enough data through setenv that the memory leaked versus the steady state fluctuation of the program is at all visible, you've got much bigger problems.
- ori_b 1y agoYou don't need setenv. It's just a particularly broken way of assigning to a global char*.
- monocasa 1y agoAnd I personally don't use it or would be likely to approve a review that uses it. But it I were implementing POSIX and had to implement it, I would almost certainly make Sun's choice thinking of it as the least evil given the expected use cases.
- kokada 1y agoYes. Changing the implementation to the Solaris/FreeBSD solution now would probably break lots of applications that nowadays works without issue.
- Analemma_ 1y agoIIRC the Solaris implementation of getenv()/setenv() leaks memory, because they want it to still be POSIX-compatible and the POSIX API has a "obey the spec, don't leak memory, be thread-safe -- pick 2" thing going on and you can't actually have a bug-free solution that keeps compatibility with decades of Unix applications. Arguably that's less harmful than the thread-safety issues on Linux, but there's no perfect solution here as long as POSIX stays what it is.
- nrds 1y agoI think it's worth specifically calling out that the right way to set environment variables is in execve() only, as communication across an exec() is the precise niche for which they are the right tool.
- sureglymop 1y agoWhy are they the right tool for that instead of passing data with program args or another way of IPC?
- fullstop 1y agoProgram args can be seen with tools like 'ps', so passing credentials that way is a poor choice.
- sureglymop 1y agoWell yeah of course. What you could do though would be to have e.g. --secrets-file and at startup time the application reads that file to get the secret. Then you could use file permissions to make sure only the (application) user running the application can read that file (or even more extreme, the application destroys the file after reading it). I think that would still be better than env vars, which are more likely to leak somewhere you didn't intend them to.
- hinkley 1y agoThey can also be caught by bash and system audit logs.
- darrenf 1y agoFWIW, environment variables (edit: of your own processes) can also be seen with `ps`. ps wwwex | grep [w]wwex 31109 pts/0 R+ 0:00 ps wwwex GDM_LANG=en_GB.utf8 STARSHIP_SHELL=fish GDMSESSION=xfce STARSHIP_SESSION_KEY=2904922223926273 XDG_CURRENT_DESKTOP=XFCE LC_NUMERIC=en_GB.UTF-8 TERMINFO=/usr/share/terminfo LC_MONETARY=en_GB.UTF-8 SHELL=/bin/fish LC_ADDRESS=en_GB.UTF-8 ... many, many more ...
- skissane 1y agoNetBSD has had getenv_r() for ages, [0] and I believe FreeBSD has very recently copied it. [1] If FreeBSD has it, macOS might eventually adopt it too. People tried already to get it into both glibc and POSIX, and it was rejected, but if it becomes more common on other platforms, that increases the chance they’ll both eventually accept it. [0] https://man.netbsd.org/getenv_r.3 https://man.netbsd.org/getenv_r.3 [1] https://github.com/freebsd/freebsd-src/commit/873420ca1e6e8a2459684f5b5d3e557a8ef75928 https://github.com/freebsd/freebsd-src/commit/873420ca1e6e8a...
- layer8 1y agoYou still don’t know if some library you use calls getenv() and stores the returned pointer (or, potentially, runs in a different thread). Using getenv_r() protects your code against setenv(), but other code that may be using getenv() is still unprotected against your (or anyone else’s) use of setenv().
- skissane 1y ago> You still don’t know if some library you use calls getenv() Well, you can know, if you check their imported symbols. (I suppose they could be getting it via dlsym, but what are the odds of that...) So if getenv_r() is added to the C library, and over time third party libraries started adopting it, you could get to the point that you could know no code in your process is calling getenv(), because none of the libraries your process loads import that symbol. They could even add a glibc tunable to make getenv() call abort()... then you could be very sure nobody is calling getenv(), because if anyone ever did, boom
- Ferret7446 1y agoWhy would you ever call setenv in library code to begin with?
- simonask 1y agoI mean, it would be perfectly fine if it wasn’t for the fact that it’s not perfectly fine…