7

I have a process whose environment is the following:

root@a-vm:/proc/1363# hexdump -C environ
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
0000016c

I've never seen anything like this; I expect environ to contain nul terminated key=value pairs, so this output violates all sorts of assertions. Am I looking at a known kernel bug, or is there some legitimate way in Unix/Linux to accomplish this? (…and if so, why? why does the kernel even allow this nonsense?)

(on Linux, 3.13.0/Ubuntu Trusty)

(I ran into this while trying to figure out why this process is not writing some temporary output to the correct location; it's suppose to use a certain directory for temp storage, and it's informed of that directory via setting the env variable TMP; but I'm setting TMP to something that looks like a very normal path, not a bunch of nuls, and I've never seen a completely empty env anyways.)

  • /proc/<pid>/environ is the initial environment for the process. From the output, it looks like the process with pid 1363 was started with an empty initial environment. – Andy Dalton Dec 13 '16 at 19:18
  • 2
    If that were true, wouldn't the file be either empty, or a single ␀, not hundreds of them? – Thanatos Dec 13 '16 at 20:50
  • Depends on if you pass a NULL pointer or if you pass a buffer will null characters. If you pass NULL, then yes, you'll see an empty file. – Andy Dalton Dec 14 '16 at 21:42
16

This is not nonsense, there is a legitimate way in Linux to accomplish this, and your expectations are erroneous.

The argument and environment strings passed to a program's startup code by the kernel are stored in ordinary application-space virtual memory, just like any other program data; and, just like any other program data variables, they are modifiable. It is quite legitimate for programs to modify them.

(Note that this is from the point of view of what the kernel supplies and enforces. What the standards for particular programming languages may say is not necessarily the same. But as far as the kernel is concerned, it's just an area of application-space virtual memory for program data that is readable and writable. The kernel doesn't care what programming language you compiled your machine code from.)

The /proc/${PID}/environ file is just a window onto this application-space virtual memory. Rather than remember the actual environment data of the process, Linux just remembers the start and end addresses of the environment area that it started the process with, and the /proc/${PID}/environ file just reads out whatever is in that memory right now. You should not expect that this file contains a list of ␀-terminated strings. That is an erroneous expectation.

There's no GNU C library function for modifying the memory that contains these strings. But various programs have their own functions to do so.

For one example, consider OpenSSH. The OpenSSH server modifies what ps shows for its argument vector, to read things like sshd: JdeBP [priv].

The OpenSSH server contains code that tries to imitate on Linux what it can do with the BSD C library on OpenBSD. On OpenBSD there's a BSD C library function named setproctitle() that re-writes the process argument vector as reported by the ps command. It calls sysctl() to pass a new argument vector to the kernel, which ps can read out with sysctl(). FreeBSD has a similar function.

On Linux, as explained, the kernel doesn't remember actual arguments and environment, merely the start and end addresses of the memory areas where it initially placed them when starting up the process. So the Linux port of OpenSSH has a compatibility setproctitle() function that overwrites the aforesaid memory area, instead.

This compatibility function calculates the total size of the environment area and the argument area, and overwrites all of it with the new argument string. It does this because in the usual case programs that call setproctitle() want to write in a longer set of argument data than what the process originally had. sshd often does. So it allows the new arguments to overwrite the environment area that follows the argument area, giving programs more room for longer sets of argument strings.

Importantly, it also pads the unused part of the area that it has not needed to overwrite, out to the original length of the total argument and environment data, with ␀s.

And what you are seeing is the exact result of this. If you find an OpenSSH server process on your system, you'll find that it, too, has lots of ␀s in its /proc/${PID}/environ.

Further reading

  • 1
    It should be noted that the setproctitle implementations some projects pull in on Linux (e.g. via libbsd) are all full of serious undefined behavior and should not be used. Clobbering memory that doesn't belong to you (e.g. to which the standard library, dynamic linker, etc. might have references) for the sake of displaying pretty messages in ps/top output is utterly unjustifiable. – R.. Dec 13 '16 at 23:04
  • 2
    @R.. I don't see how overwriting that memory would be C undefined behaviour, even if other parts of the process have references to it. Could you elaborate? – marcelm Dec 14 '16 at 8:11
  • 1
    @marcelm: OK. To start out we have to assume POSIX, since otherwise there's no environ pointer to even get to the memory by. On environ, POSIX says: "Any application that directly modifies the pointers to which the environ variable points has undefined behavior." It does seem to be permitted to modify the pointed-to strings (subject to memory synchronization constraints), but it's not clear whether it's defined to leave them in a form where they're not valid environment entries ("X=Y" form). – R.. Dec 14 '16 at 16:42
  • 1
    One thing that's most certainly not valid (well-defined) is doing pointer arithmetic past the end of the string environ[0] and/or relying on it butting up to (and being able to be considered a combined object with) environ[1]. In particular, if environ[0] is not null-terminated after a change to it (as setproctitle can cause it to be after putting message text over top of it) then out-of-bounds pointer arithmetic will happen later when the string is accessed. – R.. Dec 14 '16 at 16:45
2

This is totally doable by writing NULs to the memory location the environment variables reside in:

#include <stdio.h>
#include <unistd.h>

extern char **environ;

int main(void)
{
    int i;
    char *p = *environ;
    /* hopefully your ENV is longer than this */
    for (i = 0; i < 10; i++) *(p + i) = 0;
    printf("hexdump -C /proc/%d/environ\n", getpid());
    sleep(99999);
}

If you instead start a program with an empty environment, then the environ file will be totally empty:

execle("/bin/sleep", "sleep", "999", (char *)NULL, (char *const) NULL)

So this case is something done by or to the process once it is running (and there's little to prevent this from happening unless you somehow locked that memory and then setenv(3) calls might be problematical...).

  • I think you'll find this is wrong. /proc/$pid/environ does not (and has no way to, since the executable might not even have symbols) track down the environ object and follow pointers. It simply dumps the contents of the memory range the kernel used for passing in the initial environment values. – R.. Dec 13 '16 at 23:07
  • @R.. if it's wrong, then why did hexdump on Centos 6 show exactly 10 NUL written across the start of the environment for the process? – thrig Dec 13 '16 at 23:21
  • Oh, sorry, I misread it. It's actually overwriting the strings pointed to by environ[0], not the pointers in environ[] itself. – R.. Dec 13 '16 at 23:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.