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