Overwrite environment variable in-place

is there an existing crate to overwrite the environ variable in-place?

i want to clear a secret from an environment variable by overwriting it with an equal length string.

is there an existing crate for this? it only needs to work on linux.

if not, i can implement it fairly easily.

(I don't have an answer to your actual question but note that) there's no guarantee the memory is writable.

It may not worth it. You can always read /proc/self/environ for original envs anyway.

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.

No. It's a snapshot of env at the process creation time. You can test it yourself.

2 Likes

Ah, I see! I fell into an interesting trap here. I ran a quick check:

$ grep SOME_TEST /proc/self/environ
$ export SOME_TEST=hello
$ grep SOME_TEST /proc/self/environ

But of course, this won’t work because grep is a newly started process… :joy:

The trick is to use the $$ special variable for the shell's PID:

$ grep USER /proc/$$/environ
grep: /proc/140092/environ: binary file matches
$ unset USER
$ grep USER /proc/$$/environ
grep: /proc/140092/environ: binary file matches
$ export URLO_TEST="Hello Rustaceans"
$ grep URLO /proc/$$/environ
1 Like

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.

4 Likes

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.

#include <stdlib.h>
#include <stdio.h>

extern char **environ;

int main(int argc, char* argv[]) {
    printf("Before: %p\n", environ);
    setenv("A", "B", 1);
    printf("After:  %p\n", environ);
    return 0;
}

  1. the memory chunk of ↩︎

  2. unless modified via prctl ↩︎

  3. Though I'm sure there's some shell that's an exception... ↩︎

4 Likes

Sure, if you re-exec you can remove those variables from the environment, but you also have to lose your current memory state.

If your goal is to get the value into private memory, you're back where you started.

You can send the secret over IPC with a suitable choice of protocol. Then once it is received, close the channel/socket/etc.

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.

1 Like

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.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.