3

I have a program written in C which runs on Linux only. I want to be able to change the process name as shown in the ps command. For this I am directly changing the string in argv[0] and also using prctl(PR_SET_NAME, argv[0]) call from the main thread. I also want to access the /proc/self/cmdline from dynamically loaded shared libraries and in future maybe even from other programs.

I read that for this to work, I have to use the original memory space starting at argv[0]. The ELF standard specifies that this space is \0 separated from environ space. Looking into ps_status.c from Postgres code, one can see that they are using all this space for argv strings. And really, when I memset this space to 'a', I can see over 3000 chars in ps and read it from /proc filesystem both. Problem starts when I try to use this space to dynamically (at runtime) create new arguments in this space. (I have read and from basic tests know that Chrome/Chromium does something similar - export status of it's forked processes in ps by command line arguments.) Anything which contains the NULL delimiter in space reaching into originally environment is treated as an end. (I originally had 105 chars in cmdline arguments, I am able to get 130 chars but others arguments up to this 3000 char mark are not read.) From this I gather that the system remembers the original size and is only letting me "read over" until the end of string. (Changing the char** argv pointer is no help.)

But the Chrome is somehow doing this. Looking into command_line.cc source I see no immediate way how.

Is it even possible to do this this way? And if so, how? To tell the Linux kernel that the size of argv memory and argc changed?

Thank you.

Zrcadlo
  • 33
  • 4
  • Change them+one of the `execxxx()` functions. – wildplasser Sep 01 '19 at 22:00
  • Voting to reopen. This is not a duplicate of the linked question, as it's about changing the value of `argv` from the process's perspective, but this one is about changing the arguments from the kernel's perspective as seen by `ps`, etc. – Joseph Sible-Reinstate Monica Sep 01 '19 at 22:13
  • 1
    The question was previously closed as a duplicate of [Is it possible to change argv or do I need create an adjusted copy of it?](https://stackoverflow.com/questions/963493/) That isn't a direct duplicate of this question because, as @JosephSible noted, the other question is about changing the `argv` array within the program without caring about what the kernel or `ps` program sees — whereas what the kernel and `ps` sees is the crux of this question. – Jonathan Leffler Sep 01 '19 at 22:40

2 Answers2

4

PR_SET_MM_ARG_START and PR_SET_MM_ARG_END let you do this, if you're root (more specifically, if the process has the CAP_SYS_RESOURCE capability).

Usage:

prctl(PR_SET_NAME, constructed_argv[0]);
prctl(PR_SET_MM, PR_SET_MM_ARG_START, constructed_argv, 0, 0);
prctl(PR_SET_MM, PR_SET_MM_ARG_END, end_of_constructed_argv, 0, 0);

Here's an example of well-documented usage of them from systemd:

            /* Now, let's tell the kernel about this new memory */
            if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
                    /* HACK: prctl() API is kind of dumb on this point.  The existing end address may already be
                     * below the desired start address, in which case the kernel may have kicked this back due
                     * to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
                     * action).  The proper solution would be to have a prctl() API that could set both start+end
                     * simultaneously, or at least let us query the existing address to anticipate this condition
                     * and respond accordingly.  For now, we can only guess at the cause of this failure and try
                     * a workaround--which will briefly expand the arg space to something potentially huge before
                     * resizing it to what we want. */
                    log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");

                    if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
                            log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
                            (void) munmap(nn, nn_size);
                            goto use_saved_argv;
                    }

                    if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
                            log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
                            goto use_saved_argv;
                    }
            } else {
                    /* And update the end pointer to the new end, too. If this fails, we don't really know what
                     * to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
                     * and continue. */
                    if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
                            log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
            }
S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
2

Chrome doesn't have a special trick; rather, it's cheating. It is overwriting into the environ area, but it uses spaces instead of null bytes to separate arguments. This looks exactly the same in ps, but if you examine the /proc/PID/environ file with xxd or similar, you'll see what it's doing. This lets it basically ignore the constraint you found of "Anything which contains the NULL delimiter in space reaching into originally environment is treated as an end."

  • Interesting. But I cannot use this as I plan to build the `int argc, char** argv` programmatically from read of `/proc/$/cmdline` and this would cause misfires. But that you for your `prctl` pointer. – Zrcadlo Sep 02 '19 at 01:39