5fc298
From 35a233228ff0105892b3edc86a0cdda06282a9ac Mon Sep 17 00:00:00 2001
5fc298
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
5fc298
Date: Tue, 18 Oct 2022 18:23:53 +0200
5fc298
Subject: [PATCH] coredump: avoid deadlock when passing processed backtrace
5fc298
 data
5fc298
5fc298
We would deadlock when passing the data back from the forked-off process that
5fc298
was doing backtrace generation back to the coredump parent. This is because we
5fc298
fork the child and wait for it to exit. The child tries to write too much data
5fc298
to the output pipe, and and after the first 64k blocks on the parent because
5fc298
the pipe is full. The bug surfaced in Fedora because of a combination of four
5fc298
factors:
5fc298
- 87707784c70dc9894ec613df0a6e75e732a362a3 was backported to v251.5, which
5fc298
  allowed coredump processing to be successful.
5fc298
- 1a0281a3ebf4f8c16d40aa9e63103f16cd23bb2a was NOT backported, so the output
5fc298
  was very verbose.
5fc298
- Fedora has the ELF package metadata available, so a lot of output can be
5fc298
  generated. Most other distros just don't have the information.
5fc298
- gnome-calendar crashes and has a bazillion modules and 69596 bytes of output
5fc298
  are generated for it.
5fc298
5fc298
Fixes https://bugzilla.redhat.com/show_bug.cgi?id=2135778.
5fc298
5fc298
The code is changed to try to write data opportunistically. If we get partial
5fc298
information, that is still logged. In is generally better to log partial
5fc298
backtrace information than nothing at all.
5fc298
5fc298
(cherry picked from commit 076b807be472630692c5348c60d0c2b7b28ad437)
5fc298
5fc298
Resolves: #2149074
5fc298
---
5fc298
 src/shared/elf-util.c | 37 +++++++++++++++++++++++++++++++------
5fc298
 1 file changed, 31 insertions(+), 6 deletions(-)
5fc298
5fc298
diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c
5fc298
index 392ed9f31b..644fbae9ce 100644
5fc298
--- a/src/shared/elf-util.c
5fc298
+++ b/src/shared/elf-util.c
5fc298
@@ -30,6 +30,9 @@
5fc298
 #define THREADS_MAX 64
5fc298
 #define ELF_PACKAGE_METADATA_ID 0xcafe1a7e
5fc298
 
5fc298
+/* The amount of data we're willing to write to each of the output pipes. */
5fc298
+#define COREDUMP_PIPE_MAX (1024*1024U)
5fc298
+
5fc298
 static void *dw_dl = NULL;
5fc298
 static void *elf_dl = NULL;
5fc298
 
5fc298
@@ -700,13 +703,13 @@ int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, cha
5fc298
                 return r;
5fc298
 
5fc298
         if (ret) {
5fc298
-                r = RET_NERRNO(pipe2(return_pipe, O_CLOEXEC));
5fc298
+                r = RET_NERRNO(pipe2(return_pipe, O_CLOEXEC|O_NONBLOCK));
5fc298
                 if (r < 0)
5fc298
                         return r;
5fc298
         }
5fc298
 
5fc298
         if (ret_package_metadata) {
5fc298
-                r = RET_NERRNO(pipe2(json_pipe, O_CLOEXEC));
5fc298
+                r = RET_NERRNO(pipe2(json_pipe, O_CLOEXEC|O_NONBLOCK));
5fc298
                 if (r < 0)
5fc298
                         return r;
5fc298
         }
5fc298
@@ -750,8 +753,24 @@ int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, cha
5fc298
                         goto child_fail;
5fc298
 
5fc298
                 if (buf) {
5fc298
-                        r = loop_write(return_pipe[1], buf, strlen(buf), false);
5fc298
-                        if (r < 0)
5fc298
+                        size_t len = strlen(buf);
5fc298
+
5fc298
+                        if (len > COREDUMP_PIPE_MAX) {
5fc298
+                                /* This is iffy. A backtrace can be a few hundred kilobytes, but too much is
5fc298
+                                 * too much. Let's log a warning and ignore the rest. */
5fc298
+                                log_warning("Generated backtrace is %zu bytes (more than the limit of %u bytes), backtrace will be truncated.",
5fc298
+                                            len, COREDUMP_PIPE_MAX);
5fc298
+                                len = COREDUMP_PIPE_MAX;
5fc298
+                        }
5fc298
+
5fc298
+                        /* Bump the space for the returned string.
5fc298
+                         * Failure is ignored, because partial output is still useful. */
5fc298
+                        (void) fcntl(return_pipe[1], F_SETPIPE_SZ, len);
5fc298
+
5fc298
+                        r = loop_write(return_pipe[1], buf, len, false);
5fc298
+                        if (r == -EAGAIN)
5fc298
+                                log_warning("Write failed, backtrace will be truncated.");
5fc298
+                        else if (r < 0)
5fc298
                                 goto child_fail;
5fc298
 
5fc298
                         return_pipe[1] = safe_close(return_pipe[1]);
5fc298
@@ -760,13 +779,19 @@ int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, cha
5fc298
                 if (package_metadata) {
5fc298
                         _cleanup_fclose_ FILE *json_out = NULL;
5fc298
 
5fc298
+                        /* Bump the space for the returned string. We don't know how much space we'll need in
5fc298
+                         * advance, so we'll just try to write as much as possible and maybe fail later. */
5fc298
+                        (void) fcntl(json_pipe[1], F_SETPIPE_SZ, COREDUMP_PIPE_MAX);
5fc298
+
5fc298
                         json_out = take_fdopen(&json_pipe[1], "w");
5fc298
                         if (!json_out) {
5fc298
                                 r = -errno;
5fc298
                                 goto child_fail;
5fc298
                         }
5fc298
 
5fc298
-                        json_variant_dump(package_metadata, JSON_FORMAT_FLUSH, json_out, NULL);
5fc298
+                        r = json_variant_dump(package_metadata, JSON_FORMAT_FLUSH, json_out, NULL);
5fc298
+                        if (r < 0)
5fc298
+                                log_warning_errno(r, "Failed to write JSON package metadata, ignoring: %m");
5fc298
                 }
5fc298
 
5fc298
                 _exit(EXIT_SUCCESS);
5fc298
@@ -801,7 +826,7 @@ int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, cha
5fc298
 
5fc298
                 r = json_parse_file(json_in, NULL, 0, &package_metadata, NULL, NULL);
5fc298
                 if (r < 0 && r != -ENODATA) /* ENODATA: json was empty, so we got nothing, but that's ok */
5fc298
-                        return r;
5fc298
+                        log_warning_errno(r, "Failed to read or parse json metadata, ignoring: %m");
5fc298
         }
5fc298
 
5fc298
         if (ret)