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