Imagine you get a signal during getenv itself with the mutex held. Then your signal handler calls getenv. (On the other hand -- getenv is not marked async-signal-safe, so this use is already illegal.)
If it's not a "recursive mutex" (where you can call lock within the same thread on the same mutex more than once consecutively and it handled that), it's possible to lock on itself again (say in code which is recursive)...
The problem (with get/set/putenv as they are) was isn't the non-use of a mutex. It's the "meaning" of the pointer returned to by getenv(). It returns a char*. Nevermind the persistance of that value - you can work around that by deliberately leaking memory - but it's writeable. Whether it's a good idea to do so ... well. But simply locking "inside" these funcs doesn't solve all the / your issues.