1. 33
  1.  

  2. 11

    More generically covered by the rule “don’t do anything to change global state in multi-threaded code.

    From the end of the post:

    None of this is new, but we do re-discover it roughly every five years.

    Accurate.

    1. 1

      I imagine this is a good example where pledge would be useful? Fork threads and then pledge not to call getenv again?

      1. 6

        Unlikely, since getenv()/setenv() aren’t system calls (so the kernel would have no real way of enforcing such a promise).

      2. 1

        What does Erlang do about this internally? It must surely use getenv/setenv somewhere in library code.

        1. 2

          I could very easily see Erlang simply having one process who’s job it is to manage access to getenv and setenv. I suspect they actually use some kind of locking scheme, but I don’t know enough BEAM to be certain.

          1. 1

            Does anyone know a good way to navigate the Erlang source code? I’m a little spoiled by all the code navigation support that golang.org has, and wonder if Erlang has anything similar

            1. 1

              The same question for Go. I use setenv() a lot in test code to do various things and have never encountered this behavior. I’m not interacting, though, with a library that uses libc when doing so, at least not in cases I can think of. Curious enough to look into this later.

              1. 1

                Also interesting is that environment variables are passed directly to calls to Exec. Does Exec in libc also take an env parameter?

                1. 2

                  man 2 execve

                2. 1

                  So, at least in Go, the first call to os.Getenv, or os.Setenv copies the environment into a map. For os.Setenv, there’s an additional call to setenv_c, which I didn’t successfully fund the definition of (just on my phone). Also, it appears that a setenv in another (external) library won’t actually show up in a subsequent os.Getenv call, if the environment was already copied.

                  1. 1

                    Setenv in golang uses a Read-Write mutex, which is how a lot of other racy go constructs (like their regular expressions) add thread safety. (Which is why I think Erlang does something similar). Interestingly enough, golang has its wrapper around setenv/getenv in it’s syscall package.

                    https://golang.org/src/syscall/env_unix.go?s=1927:1963#L83

                    1. 2

                      I noticed that before, but missed this:

                      // setenv_c and unsetenv_c are provided by the runtime but are no-ops
                      // if cgo isn't loaded.
                      func setenv_c(k, v string)
                      func unsetenv_c(k string)
                      

                      So, they basically copy the environment on first interaction with it, use a Lock, and don’t try to do anything fancy unless you’re using cgo. With cgo, I bet you end up with the same problems as described in the blog post… something to test out in my “copious” amounts of free time.