Hmm, as always, standards are a great source of interesting discussions...
In POSIX, the environment is defined as extern char **environ;. However, there's no specification on whether the content of this environment is writable.
That said, the standard includes this statement:
If the application modifies the pointers to which environ points, the behavior of all interfaces described in the System Interfaces volume of POSIX.1-2017 is undefined.
So that part is clear. My assumption is that this is because POSIX environment-modifying functions use malloc()/free() (or something similar) for the strings.
But the standard doesn’t explicitly forbid or allow overwriting the contents of those strings.
Isn’t this more of a “live view”? So yes, there is a brief time window where the secret is visible through the /proc interface. But any time the attack surface is reduced, there’s a benefit.
The best and most reliable way I know to change or clear the environment, specifically to ensure that sensitive values are removed, is to re-exec, which you can do with std::process::Command and its unix extensions.
This would need to be done before starting any child processes which may inherit the sensitive values. I doubt that there's a reasonable way to limit the scope of this kind of program design to "call this crate method," just due to the realities of the underlying OS interfaces.
Linux /proc/*/environ is[1] your actual starting environment.[2] It seems like a snapshot because modifying the environment via the standard APIs generally involves allocating some new environment area for your libc to manage.
Testing in a shell won't actually reflect this though; those keep their own store of environment (and non-environment) variables, as opposed to modifying their own environment (environ).[3] Here's a C program that demonstrates it instead.
Agreed and understood. When I've done this, I've generally arranged for the re-exec step to pass forward any non-confidential information I need to preserve (eg. via new env vars, the via the filesystem, via IPC channels, or via command line flags). The program then does two funamentally different things - one "privileged" part that provides information and runs execvp, and one "unprivileged" part that receives information and runs after execvp.
I'm speaking from experience on this. In a past life, I worked on a PaaS vendor's build system, which ingests customer code (arbitrary) and runs a build process (customer-defined) on it. That build system ran inside LXC containers, but needed to report information and metrics out of the container, and used a privileged token to set those reporting channels up.
The belt-and-suspenders approach we used was for the build supervisor to pass a single-use reporting URL forward, re-execing the supervisor to remove all memory of the token used to create it. We also invalidated the token as soon as the reporting URL was set up, in case the token leaked. To the best of my knowledge, in the decade-plus that system has been running, the build tokens have never been part of a compromise, and we had no shortage of abuse of the build process from which those compromises could have arisen.
Be careful with that. Some less knowledgeable system administrator might not know about this and check whether secrets get leaked through the environment some other way, where they are no longer visible, and conclude the system to be secure.
If something can't be made safe, make it obviously not safe, so that users won't be misled.