Right now dump_emit() is the only coredump helper that writes buffers
larger than a page in one call. For elf notes that can easily blow past
PAGE_SIZE. That's annoying because neither pipes nor af_unix sockets
take such writes in one piece.

If a signal arrives while the writer is waiting they drop a short write.
With the coredump records work coming up that means header and its data
are desynchronized. A write that fits in one pipe buffer or one skb
doesn't suffer from this.

So split all writes up, including elf notes, and cap every write at a
page. The coredump socket already raises sk_sndbuf far enough for a page
to fit a single skb and pipes always work that way.

That means dump_interrupted() is now checked once per page. So a large
coredump stops earlier (good). An empty write no longer issues a
zero-length write. The rlimit core check stays where it was. It
continues refusing whole writes.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 fs/coredump.c | 37 ++++++++++++++++++++++++++++++-------
 1 file changed, 30 insertions(+), 7 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index d837819031ff..d61f36239f91 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1215,19 +1215,21 @@ void vfs_coredump(const kernel_siginfo_t *siginfo)
  * do on a core-file: use only these functions to write out all the
  * necessary info.
  */
-static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr)
+/* One write, never more than a page. See __dump_emit(). */
+static bool dump_emit_chunk(struct coredump_params *cprm, const void *addr,
+                           int nr)
 {
        struct file *file = cprm->file;
        loff_t pos = file->f_pos;
        ssize_t n;
 
-       if (cprm->written + nr > cprm->limit)
-               return false;
        if (dump_interrupted())
                return false;
+
        n = __kernel_write(file, addr, nr, &pos);
        if (n != nr)
                return false;
+
        file->f_pos = pos;
        cprm->written += n;
        cprm->pos += n;
@@ -1235,6 +1237,24 @@ static bool __dump_emit(struct coredump_params *cprm, 
const void *addr, int nr)
        return true;
 }
 
+static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr)
+{
+       if (cprm->written + nr > cprm->limit)
+               return false;
+
+       while (nr) {
+               int chunk = min_t(int, nr, PAGE_SIZE);
+
+               if (!dump_emit_chunk(cprm, addr, chunk))
+                       return false;
+
+               addr += chunk;
+               nr -= chunk;
+       }
+
+       return true;
+}
+
 static bool __dump_skip(struct coredump_params *cprm, size_t nr)
 {
        static char zeroes[PAGE_SIZE];
@@ -1247,13 +1267,16 @@ static bool __dump_skip(struct coredump_params *cprm, 
size_t nr)
                return true;
        }
 
-       while (nr > PAGE_SIZE) {
-               if (!__dump_emit(cprm, zeroes, PAGE_SIZE))
+       while (nr) {
+               size_t chunk = min_t(size_t, nr, PAGE_SIZE);
+
+               if (!__dump_emit(cprm, zeroes, chunk))
                        return false;
-               nr -= PAGE_SIZE;
+
+               nr -= chunk;
        }
 
-       return __dump_emit(cprm, zeroes, nr);
+       return true;
 }
 
 /* Flush the accumulated hole before writing data. */

-- 
2.53.0


Reply via email to