Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Manipulating process name and arguments by way of argv

Tags:

c

linux

argv

procfs

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.

like image 913
Zrcadlo Avatar asked Jan 28 '26 19:01

Zrcadlo


1 Answers

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");
            }
like image 58
S.S. Anne Avatar answered Jan 31 '26 07:01

S.S. Anne