Hiding arguments from ps

There are many articles on the Interwobble telling you how to set the process title on Linux; they all concentrate on the problem of placing an arbitrarily long string in argv[0] to report status information in the process list.

But what if argv[0] is fine, and what you want to do is remove the following arguments from the process list? Perhaps they contain sensitive information, or perhaps they’re just likely to be surprising.

A fictional example may look like this (I’ve added the $ to demonstrate a later problem):

11234 ?        S      0:00 some-program --username=foo --passphrase=bar$

 How not to do it

The first thing you’ll probably try is just setting those arguments in the argv list to NULL. For example, with code like this:

for (i = 1; i < argc; i++)
    argv[i] = NULL;

That won’t work because that array is just a list of pointers to the real area in which the arguments are stored, and is local to your main function. Modifying it has no effect what ps and similar see.

The next thing you might try, as a C programmer, is setting the first character of each argument to a NULL byte. It’s not unreasonable to assume that these strings are likely to be NULL-terminated. You might use code like:

for (i = 1; i < argc; i++)
    strcpy (argv[i], "");

But that doesn’t seem to work either, in fact, what you get in the process list is something rather odd and unexpected!

11234 ?        S      0:00 some-program  -username=foo  -passphrase=bar$

All that’s done is lost the first character of each argument.

In frustration, you might try overwriting the entire argument with NULL bytes, e.g.

for (i = 1; i < argc; i++)
    memset (argv[i], '\0', strlen (argv[i]));

Checking the process list, that will have seemed to have worked…

11234 ?        S      0:00 some-program                                $

On closer inspection though, it’s not perfect. You’ll notice that the line ends with spaces in place of where the arguments used to be. If the number or length of arguments you’re trying to hide is particularly long, you can end up with strange blank lines in ps output.

Not ideal.

 Why this happens

To understand why this happens, we need to understand how the kernel reports the command line to ps. ps reads the /proc/PID/cmdline file, with contains the entire command line as a single character array, with each argument terminated by a NULL byte.

Looking at it with od -t c, we see something like:

0000000   s   o   m   e   -   p   r   o   g   r   a   m  \0   -   -   u
0000020   s   e   r   n   a   m   e   =   f   o   o  \0   -   -   p   a
0000040   s   s   p   h   r   a   s   e   =   b   a   r  \0

When we changed just the first character to a NULL byte, we ended up with:

0000000   s   o   m   e   -   p   r   o   g   r   a   m  \0  \0   -   u
0000020   s   e   r   n   a   m   e   =   f   o   o  \0  \0   -   p   a
0000040   s   s   p   h   r   a   s   e   =   b   a   r  \0

ps is just assuming that \0 represents any argument gap, so outputs an ordinary space character every time it encounters one. This is why the first character of each argument appeared to be replaced by a space, and the rest were still printed.

When we overwrite the arguments, we ended up with:

0000000   s   o   m   e   -   p   r   o   g   r   a   m  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000040  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0

Writing all those zeros didn’t change the length of the command line character array, so ps got all those NULLs and output a space character for each one, assuming it was an argument break.

What we need to is decrease the length of this character array, so ps only sees the first argument.

 How to do it

The kernel maintains internal pointers to the start and end of the arguments array in the process address space. When you ask for the cmdline file in /proc, it replies with all of the characters between these two positions and a length of the difference.

The kernel doesn’t provide us with any method of moving this pointer, so we’d initially appear to be stuck.

However there turns out to be a very nasty solution.

In order to support processes changing their title, the kernel has to support the arguments possibly overrunning into the environment space. If it thinks this has happened, it concatenates the environment space onto the end of the buffer and returns the entire area with the length set to the length up to the first NULL byte.

All we need to do is fool the kernel into thinking we’ve overrun into the environment space, it’ll then just call strlen on the arguments list and return only the first argument; exactly what we want!

What technique does the kernel use for deciding whether or not the arguments have overrun into the environment space? It checks whether the final character in the argument space is NULL or not.

Thus paradoxically, to hide the argument list from ps, we remove the NULL

if (argc > 1) {
    char *arg_end;

    arg_end = argv[argc-1] + strlen (argv[argc-1]);
    *arg_end = ' ';

The check against argc is necessary, as we don’t want to remove the NULL character on the end of argv[0] as that would cause the first environment variable to be inadvertently concatenated.

Now when we look at ps, we see just the program name:

11234 ?        S      0:00 some-program$

And even when we look in /proc/PID/cmdline, we only see the program name and no additional arguments.

0000000   s   o   m   e   -   p   r   o   g   r   a   m  \0

Now read this

Songs of Innocence

The most surprising thing about Apple’s move to preload the new U2 album onto iPhones isn’t that they did it, but that people are surprised that users are angry about it. For example John Gruber declared “Nailed It” in response to... Continue →