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

6 thoughts on “Hiding arguments from ps

  1. Storyteller

    A long time ago there was a famous lib which got a hacked api. So a lot of people tried to get a workaround. They used the problems for their work.

    The author of the famous lib decided to correct the problematic api and all programms using it got problems.

    oh yes. I can remeber the author telling the other what they should have done instead of programming…

  2. i'm not even colourblind!

    Is your code in Super Light Blue supposed to be readable?

    Your site’s webdesign is otherwise good.

  3. Federico Lucifredi

    Cool stuff. I was thinking about this a few weeks ago, and it is nice to see it all summed up neatly!

  4. Reiner Herrmann

    I also was wondering sometimes how other programs were hiding their arguments and couldn’t find a good answer. Now I know it! :D

    Thank you for this very interesting article.

  5. Tinus

    Note that this is not useful as a security feature, as there will always be a period where the arguments are visible.

    An attacker can extend this period at will, by keeping the system busy in the right way.

    The purpose of the feature is to differentiate between processes, like the sendmail processes. You can see which one is the queuerunner, and which one is responsible for answering to the network.

    If your arguments contain secrets, you’re doing something wrong. Just about any program that accepts passwords on the command line also accepts passwords from a source that cannot be exposed, that is from a file (with appropriate permissions set) or from standard input.

    In case you’re wondering, you should also not use environment variables for this, they are not secure in all Unix variants. Use streams or files.

Comments are closed.