be0c12
From 9b30c003c8f80bf44f18168d07ea11c48e6d8864 Mon Sep 17 00:00:00 2001
be0c12
From: Lennart Poettering <lennart@poettering.net>
be0c12
Date: Wed, 7 Jul 2021 15:57:51 +0200
be0c12
Subject: [PATCH] process-util: explicitly handle processes lacking parents in
be0c12
 get_process_ppid()
be0c12
be0c12
Let's make sure we signal out-of-band via an error message if a process
be0c12
doesn't have a parent process whose PID we could return. Otherwise we'll
be0c12
too likely hide errors, as we return an invalid PID 0, which in other
be0c12
contexts has special meaning (i.e. usually "myself").
be0c12
be0c12
Replaces: #20153
be0c12
be0c12
This is based on work by @dtardon, but goes a different route, by
be0c12
ensuring we propagate a proper error in this case.
be0c12
be0c12
This modernizes the function in question a bit in other ways, i.e.
be0c12
renames stuff and makes the return parameter optional.
be0c12
be0c12
(cherry picked from commit 0c4d1e6d96a549054bfe0597d197f829838917f1)
be0c12
be0c12
Resolves: #1977569
be0c12
---
be0c12
 src/basic/process-util.c     | 27 +++++++++++++-------
be0c12
 src/coredump/coredump.c      | 23 +++++++++--------
be0c12
 src/test/test-process-util.c | 48 +++++++++++++++++++++++++++++++++---
be0c12
 3 files changed, 74 insertions(+), 24 deletions(-)
be0c12
be0c12
diff --git a/src/basic/process-util.c b/src/basic/process-util.c
be0c12
index 0a4a747ba4..6016d83d41 100644
be0c12
--- a/src/basic/process-util.c
be0c12
+++ b/src/basic/process-util.c
be0c12
@@ -603,20 +603,23 @@ int get_process_environ(pid_t pid, char **env) {
be0c12
         return 0;
be0c12
 }
be0c12
 
be0c12
-int get_process_ppid(pid_t pid, pid_t *_ppid) {
be0c12
-        int r;
be0c12
+int get_process_ppid(pid_t pid, pid_t *ret) {
be0c12
         _cleanup_free_ char *line = NULL;
be0c12
         long unsigned ppid;
be0c12
         const char *p;
be0c12
+        int r;
be0c12
 
be0c12
         assert(pid >= 0);
be0c12
-        assert(_ppid);
be0c12
 
be0c12
         if (pid == 0 || pid == getpid_cached()) {
be0c12
-                *_ppid = getppid();
be0c12
+                if (ret)
be0c12
+                        *ret = getppid();
be0c12
                 return 0;
be0c12
         }
be0c12
 
be0c12
+        if (pid == 1) /* PID 1 has no parent, shortcut this case */
be0c12
+                return -EADDRNOTAVAIL;
be0c12
+
be0c12
         p = procfs_file_alloca(pid, "stat");
be0c12
         r = read_one_line_file(p, &line);
be0c12
         if (r == -ENOENT)
be0c12
@@ -624,9 +627,8 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) {
be0c12
         if (r < 0)
be0c12
                 return r;
be0c12
 
be0c12
-        /* Let's skip the pid and comm fields. The latter is enclosed
be0c12
-         * in () but does not escape any () in its value, so let's
be0c12
-         * skip over it manually */
be0c12
+        /* Let's skip the pid and comm fields. The latter is enclosed in () but does not escape any () in its
be0c12
+         * value, so let's skip over it manually */
be0c12
 
be0c12
         p = strrchr(line, ')');
be0c12
         if (!p)
be0c12
@@ -640,10 +642,17 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) {
be0c12
                    &ppid) != 1)
be0c12
                 return -EIO;
be0c12
 
be0c12
-        if ((long unsigned) (pid_t) ppid != ppid)
be0c12
+        /* If ppid is zero the process has no parent. Which might be the case for PID 1 but also for
be0c12
+         * processes originating in other namespaces that are inserted into a pidns. Return a recognizable
be0c12
+         * error in this case. */
be0c12
+        if (ppid == 0)
be0c12
+                return -EADDRNOTAVAIL;
be0c12
+
be0c12
+        if ((pid_t) ppid < 0 || (long unsigned) (pid_t) ppid != ppid)
be0c12
                 return -ERANGE;
be0c12
 
be0c12
-        *_ppid = (pid_t) ppid;
be0c12
+        if (ret)
be0c12
+                *ret = (pid_t) ppid;
be0c12
 
be0c12
         return 0;
be0c12
 }
be0c12
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
be0c12
index 2a130e8838..fb3a6ecfe9 100644
be0c12
--- a/src/coredump/coredump.c
be0c12
+++ b/src/coredump/coredump.c
be0c12
@@ -591,8 +591,7 @@ static int get_process_ns(pid_t pid, const char *namespace, ino_t *ns) {
be0c12
         return 0;
be0c12
 }
be0c12
 
be0c12
-static int get_mount_namespace_leader(pid_t pid, pid_t *container_pid) {
be0c12
-        pid_t cpid = pid, ppid = 0;
be0c12
+static int get_mount_namespace_leader(pid_t pid, pid_t *ret) {
be0c12
         ino_t proc_mntns;
be0c12
         int r = 0;
be0c12
 
be0c12
@@ -602,8 +601,12 @@ static int get_mount_namespace_leader(pid_t pid, pid_t *container_pid) {
be0c12
 
be0c12
         for (;;) {
be0c12
                 ino_t parent_mntns;
be0c12
+                pid_t ppid;
be0c12
 
be0c12
-                r = get_process_ppid(cpid, &ppid);
be0c12
+                r = get_process_ppid(pid, &ppid);
be0c12
+                if (r == -EADDRNOTAVAIL) /* Reached the top (i.e. typically PID 1, but could also be a process
be0c12
+                                          * whose parent is not in our pidns) */
be0c12
+                        return -ENOENT;
be0c12
                 if (r < 0)
be0c12
                         return r;
be0c12
 
be0c12
@@ -611,17 +614,13 @@ static int get_mount_namespace_leader(pid_t pid, pid_t *container_pid) {
be0c12
                 if (r < 0)
be0c12
                         return r;
be0c12
 
be0c12
-                if (proc_mntns != parent_mntns)
be0c12
-                        break;
be0c12
-
be0c12
-                if (ppid == 1)
be0c12
-                        return -ENOENT;
be0c12
+                if (proc_mntns != parent_mntns) {
be0c12
+                        *ret = ppid;
be0c12
+                        return 0;
be0c12
+                }
be0c12
 
be0c12
-                cpid = ppid;
be0c12
+                pid = ppid;
be0c12
         }
be0c12
-
be0c12
-        *container_pid = ppid;
be0c12
-        return 0;
be0c12
 }
be0c12
 
be0c12
 /* Returns 1 if the parent was found.
be0c12
diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c
be0c12
index 26e3247993..6b14ff592b 100644
be0c12
--- a/src/test/test-process-util.c
be0c12
+++ b/src/test/test-process-util.c
be0c12
@@ -19,6 +19,7 @@
be0c12
 #include "macro.h"
be0c12
 #include "parse-util.h"
be0c12
 #include "process-util.h"
be0c12
+#include "procfs-util.h"
be0c12
 #include "signal-util.h"
be0c12
 #include "stdio-util.h"
be0c12
 #include "string-util.h"
be0c12
@@ -56,9 +57,12 @@ static void test_get_process_comm(pid_t pid) {
be0c12
         assert_se(get_process_cmdline(pid, 1, false, &d) >= 0);
be0c12
         log_info("PID"PID_FMT" cmdline truncated to 1: '%s'", pid, d);
be0c12
 
be0c12
-        assert_se(get_process_ppid(pid, &e) >= 0);
be0c12
-        log_info("PID"PID_FMT" PPID: "PID_FMT, pid, e);
be0c12
-        assert_se(pid == 1 ? e == 0 : e > 0);
be0c12
+        r = get_process_ppid(pid, &e);
be0c12
+        assert_se(pid == 1 ? r == -EADDRNOTAVAIL : r >= 0);
be0c12
+        if (r >= 0) {
be0c12
+                log_info("PID"PID_FMT" PPID: "PID_FMT, pid, e);
be0c12
+                assert_se(e > 0);
be0c12
+        }
be0c12
 
be0c12
         assert_se(is_kernel_thread(pid) == 0 || pid != 1);
be0c12
 
be0c12
@@ -585,6 +589,43 @@ static void test_ioprio_class_from_to_string(void) {
be0c12
         test_ioprio_class_from_to_string_one("-1", -1);
be0c12
 }
be0c12
 
be0c12
+static void test_get_process_ppid(void) {
be0c12
+        uint64_t limit;
be0c12
+        int r;
be0c12
+
be0c12
+        log_info("/* %s */", __func__);
be0c12
+
be0c12
+        assert_se(get_process_ppid(1, NULL) == -EADDRNOTAVAIL);
be0c12
+
be0c12
+        /* the process with the PID above the global limit definitely doesn't exist. Verify that */
be0c12
+        assert_se(procfs_tasks_get_limit(&limit) >= 0);
be0c12
+        assert_se(limit >= INT_MAX || get_process_ppid(limit+1, NULL) == -ESRCH);
be0c12
+
be0c12
+        for (pid_t pid = 0;;) {
be0c12
+                _cleanup_free_ char *c1 = NULL, *c2 = NULL;
be0c12
+                pid_t ppid;
be0c12
+
be0c12
+                r = get_process_ppid(pid, &ppid);
be0c12
+                if (r == -EADDRNOTAVAIL) {
be0c12
+                        log_info("No further parent PID");
be0c12
+                        break;
be0c12
+                }
be0c12
+
be0c12
+                assert_se(r >= 0);
be0c12
+
be0c12
+                /* NOTE: The size is SIZE_MAX in the original commit, but it would require backporting a
be0c12
+                 * lot more stuff to support that (the current version of get_process_cmdline() just fails with
be0c12
+                 * ENOMEM). UINT16_MAX should be enough for practical purposes.
be0c12
+                 */
be0c12
+                assert_se(get_process_cmdline(pid, UINT16_MAX, true, &c1) >= 0);
be0c12
+                assert_se(get_process_cmdline(ppid, UINT16_MAX, true, &c2) >= 0);
be0c12
+
be0c12
+                log_info("Parent of " PID_FMT " (%s) is " PID_FMT " (%s).", pid, c1, ppid, c2);
be0c12
+
be0c12
+                pid = ppid;
be0c12
+        }
be0c12
+}
be0c12
+
be0c12
 int main(int argc, char *argv[]) {
be0c12
         log_set_max_level(LOG_DEBUG);
be0c12
         log_parse_environment();
be0c12
@@ -614,6 +655,7 @@ int main(int argc, char *argv[]) {
be0c12
         test_safe_fork();
be0c12
         test_pid_to_ptr();
be0c12
         test_ioprio_class_from_to_string();
be0c12
+        test_get_process_ppid();
be0c12
 
be0c12
         return 0;
be0c12
 }