chiark / gitweb /
util-lib: rework rename_process() to be able to make use of PR_SET_MM_ARG_START
[elogind.git] / src / basic / process-util.c
index 09bc12a80afb75346fd8f10df6cb831aa95e437c..74fac0835710e45465cd4e66065708af1a74d130 100644 (file)
@@ -274,27 +274,100 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
 }
 
 #if 0 /// UNNEEDED by elogind
-void rename_process(const char name[8]) {
-        assert(name);
+int rename_process(const char name[]) {
+        static size_t mm_size = 0;
+        static char *mm = NULL;
+        bool truncated = false;
+        size_t l;
+
+        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
+         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
+         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
+         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
+         * truncated.
+         *
+         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
+
+        if (isempty(name))
+                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
 
-        /* This is a like a poor man's setproctitle(). It changes the
-         * comm field, argv[0], and also the glibc's internally used
-         * name of the process. For the first one a limit of 16 chars
-         * applies, to the second one usually one of 10 (i.e. length
-         * of "/sbin/init"), to the third one one of 7 (i.e. length of
-         * "systemd"). If you pass a longer string it will be
-         * truncated */
+        l = strlen(name);
 
+        /* First step, change the comm field. */
         (void) prctl(PR_SET_NAME, name);
+        if (l > 15) /* Linux process names can be 15 chars at max */
+                truncated = true;
+
+        /* Second step, change glibc's ID of the process name. */
+        if (program_invocation_name) {
+                size_t k;
+
+                k = strlen(program_invocation_name);
+                strncpy(program_invocation_name, name, k);
+                if (l > k)
+                        truncated = true;
+        }
+
+        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
+         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
+         * the end. This is the best option for changing /proc/self/cmdline.*/
+        if (mm_size < l+1) {
+                size_t nn_size;
+                char *nn;
+
+                /* Let's not bother with this if we don't have euid == 0. Strictly speaking if people do weird stuff
+                 * with capabilities this could work even for euid != 0, but our own code generally doesn't do that,
+                 * hence let's use this as quick bypass check, to avoid calling mmap() if PR_SET_MM_ARG_START fails
+                 * with EPERM later on anyway. After all geteuid() is dead cheap to call, but mmap() is not. */
+                if (geteuid() != 0) {
+                        log_debug("Skipping PR_SET_MM_ARG_START, as we don't have privileges.");
+                        goto use_saved_argv;
+                }
+
+                nn_size = PAGE_ALIGN(l+1);
+                nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+                if (nn == MAP_FAILED) {
+                        log_debug_errno(errno, "mmap() failed: %m");
+                        goto use_saved_argv;
+                }
+
+                strncpy(nn, name, nn_size);
+
+                /* 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) {
+                        log_debug_errno(errno, "PR_SET_MM_ARG_START failed, proceeding without: %m");
+                        (void) munmap(nn, nn_size);
+                        goto use_saved_argv;
+                }
+
+                /* 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");
 
-        if (program_invocation_name)
-                strncpy(program_invocation_name, name, strlen(program_invocation_name));
+                if (mm)
+                        (void) munmap(mm, mm_size);
+
+                mm = nn;
+                mm_size = nn_size;
+        } else
+                strncpy(mm, name, mm_size);
+
+use_saved_argv:
+        /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
+         * it still looks here */
 
         if (saved_argc > 0) {
                 int i;
 
-                if (saved_argv[0])
-                        strncpy(saved_argv[0], name, strlen(saved_argv[0]));
+                if (saved_argv[0]) {
+                        size_t k;
+
+                        k = strlen(saved_argv[0]);
+                        strncpy(saved_argv[0], name, k);
+                        if (l > k)
+                                truncated = true;
+                }
 
                 for (i = 1; i < saved_argc; i++) {
                         if (!saved_argv[i])
@@ -303,6 +376,8 @@ void rename_process(const char name[8]) {
                         memzero(saved_argv[i], strlen(saved_argv[i]));
                 }
         }
+
+        return !truncated;
 }
 #endif // 0