Since at least 2012, environment variables have been at least as secure as ordinary memory:
commit b409e578d9a4ec95913e06d8fea2a33f1754ea69
Author: Cong Wang <xiyou.wangcong@gmail.com>
Date: Thu May 31 16:26:17 2012 -0700
proc: clean up /proc/<pid>/environ handling
You can't read another process's environment unless you can ptrace-read the process, and if you can ptrace-read the process you know all its secrets anyway.
Just to clarify, that means that by default every process of the same user can access the variables. But it doesn't really matter because by default every process of the same user can read any secret directly from the target process anyway, right?
And that the right thing to do if you want to harden your system is to disallow ptrace-read, and not bother changing software that uses environment variables?
Because I think most people that just try will be able to read the variables of any process on their computer.
Yes, you can consider all processes running under the same user as able to peek at each other's data. This is the point of running under the same uid: sharing data.One uid should be considered one security domain, with any separators inside it being guardrails, not brick walls.
If you want to prevent other processes from peeking into your process, run it under a different uid. Again. that's the point. A bunch of good software does that, running only small privileged bits in separate processes, and running some / bulk of the processes under an uid with severely limited privileges. See e.g. Postfix MTA, or the typical API server + reverse proxy split.
I don't think that this part of Unix is somehow problematic, or needs changing.
I think this part of Unix is exceedingly problematic. I want to be able to run program that don't have the ability to do anything that I, personally, can do. Ideally this would be doable without nasty kludges or root's help.
Linux has some ways to accomplish this, for example:
- seccomp. It can be done quite securely, but running general purpose software in seccomp is not necessarily a good way to prevent it from acting like the running user.
- Namespaces. Unless user namespaces are in use, only root can set this up. With user namespaces, anyone can do it, but the attack surface against the kernel is huge, mount namespaces are somewhat porous (infamously, /proc/self/exe is a hole), and they can be a bit resource-intensive, especially if you want to use network namespaces for anything interesting. Also, user namespaces have kind of obnoxious interactions with ordinary filesystem uids (if I'm going to run some complex containerized stack with access to a specific directory within my home directory, who should own the files in there, and how do I achieve that without root's help?). And userns without subuid doesn't isolate very strongly, and subuid need's root's help.
- Landlock. Kind of cool, kind of limited.
- Tools like Yama. On Ubuntu, with Yama enabled (the default), programs can't generally ptrace (or read /proc/self/environ) from other programs running as the same user.
In any case, once you've done something to prevent a process from accessing /proc/fd/PID for other pids belonging to the same user (e.g. pidns) and/or restricted ptrace (e.g. PR_SET_DUMPABLE), then environ gets protected.
It's not your (unprivileged user account's) place to decide the security posture of the entire system, that's why you're running into issues with root. Even Yama, an LSM, requires root (or de facto equivalent) for initial setup (as it should).
Namespaces, if done incorrectly, can significantly increase the attack surface of the entire system (mount namespaces especially need to be treated with care) and same with regards to anything to do with user accounts.
The real security barrier on most operating systems to date is the user account and if you want full isolation (modulo kernel bugs), run a process as a separate user if you're that concerned about leakage (or ideally, don't run it at all).
> It's not your (unprivileged user account's) place to decide the security posture of the entire system, that's why you're running into issues with root.
Tell that to literally any unprivileged user who would like to run any sort of software without exposing their entire account to any possible code execution bug or malicious code in the software they're running.
> The real security barrier on most operating systems to date is the user account and if you want full isolation (modulo kernel bugs), run a process as a separate user if you're that concerned about leakage (or ideally, don't run it at all).
That a very 1980s or maybe 1990s perspective, I think. It's 2025. I have some computers. They are mine. I trust myself to administer them and do pretty much anything to them, but I would prefer not to trust all my software. Sure, I can, very awkwardly, try to isolate things by using multiple uids, but it's extremely awkward and I see no technical reason why uids are a good solution here.
And there are no shortage of technical reasons why uids are a horrible solution. I would like to run unprivileged software and delegate some degree of privilege to them -- imagine a web browser or an IDE or whatever. I, as the owner of the machine, can make a uid. But I would also like my IDE to isolate various things it runs from each other and from the IDE as a whole, and it cannot do that because it does not own the machine and should absolutely not be running as root.
I think the main point here is that... well if you can help it, don't run untrusted software, since it's by definition not trusted. There are some times where you can't really get around it (JavaScript is an increasingly big example of this and there are many ecosystems in which you are prevented from running trusted software without great difficulty) and there are many general protections that are in OSes that will help you there.
On Linux you have some combination of Landlock, AppArmor, SELinux, calling prctl(PR_SET_NO_NEW_PRIVS), and the kitchen sink. On FreeBSD you have capsicum. Windows has integrity labeling + a bunch of stuff related to Job objects + a few things to disable win32k.sys calls.
But these are helpful and shouldn't be considered a panacea. The expectation is that you're delegating authority to a computer program to perform a certain task. Do computer programs abuse that authority sometimes? Absolutely. But nonetheless that's the fundamental model of most computer security, thanks in part to its usefulness.
I skimmed the docs. It appears that this is a mechanism whereby a developer can restrict what their app can do via some configuration in Xcode. This seems almost unbelievably weak — developers want more privileges for their app, not fewer (especially developers of semi-malicious tracker-style SDKs), and the app sandbox seems entirely capable of, say, allowing a document editor app to open a document in a separate sandbox.
Yes, if we talk about interactive use, where the user may want to run a program with isolation on a whim, and can't be bothered to prepare a separate account for it.
Namespaces, to my mind, are a huge help, starting from trivial chroot and nsenter, all the way to bubblewrap [2] and containers (rootless, of course).
Also, with a properly prepared disk image, and the OS state frozen when everything heavyweight has started already, you can spawn a full-blown VM in well under a second, and run a questionable binary in it.
It would be fun to have "domains" within a user account, with their own views of the file system, network, process tree, etc, and an easy way to interactively run programs in different domains. Again, it's pretty doable; whoever creates an ergonomic product to do it, will win! (Well, not really, because most developers sadly run macOS, which lacks all these niceties, but has its own mechanisms, not very accessible to the user.)
Part of the problem with namespaces in general comes from setuid/setgid binaries. Now, nosuid helps, but the argument goes (and this is why dropping privileges on Linux sometimes requires root) that blocking setuid can actually increase attack surface (because many programs, as part of their initialization routines setuid to a specific service user). Blocking setuid blocks this which can lead to a program unintentionally running, paradoxically, with too many privileges (or the incorrect set of ones).
And in the case of allowing setuid and filesystem views, the issue here becomes that an unprivileged user could create a view of the filesystem that has an /etc/passwd and /etc/shadow file that the attacker knows in their home directory. Run some setuid program (like su or sudo) with this view and we've successfully became root, breaking out of the sandbox.
And you can't whitelist /etc/passwd or whatever either. This is why allowing anyone to play with mount points is fraught with danger.
Now is suid/sgid a fundamental part of a Unix-like system? No, but setuid was created in the world that was and even though these are arguably bugs, releasing a Linux kernel that creates a bunch of security holes in userspace is a very very bad breakage of userspace.
---
No New Privileges does make this a bit better (as you can never gain more privileges than you already have, and this is a one-way door) and the kernel docs even say that eventually unshare or chroot may be allowed when operating under no new privileges
But this is currently why you can't chroot as an unprivileged user as you can trivially blow through the security domain on most Linux distributions
I've never tried to use ptrace-read to read secrets from a running process's memory so I can't comment on that part.
I had always assumed getting secrets from a running process' memory was non-trivial and/or required root permissions but maybe that's a bad assumption.
However, reading a process' environment is as trivial as:
`cat /proc/123456/environ`
as long as it's ran by the same user. No ptrace-read required.
You do need PTRACE access to pid 123456 in order to access that file. It is transparent to you, but the kernel will use the current task's PTRACE_ATTACH access when attempting to get that information.
By default, on most distributions, a user has PTRACE_ATTACH to all processes owned by it. This can be configured with ptrace_scope:
cmdline is a different story.