[coredump] don't use __kernel_write() on kmap_local_page()
authorAl Viro <viro@zeniv.linux.org.uk>
Mon, 26 Sep 2022 15:59:14 +0000 (11:59 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Wed, 28 Sep 2022 18:28:40 +0000 (14:28 -0400)
passing kmap_local_page() result to __kernel_write() is unsafe -
random ->write_iter() might (and 9p one does) get unhappy when
passed ITER_KVEC with pointer that came from kmap_local_page().

Fix by providing a variant of __kernel_write() that takes an iov_iter
from caller (__kernel_write() becomes a trivial wrapper) and adding
dump_emit_page() that parallels dump_emit(), except that instead of
__kernel_write() it uses __kernel_write_iter() with ITER_BVEC source.

Fixes: 3159ed57792b "fs/coredump: use kmap_local_page()"
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/coredump.c
fs/internal.h
fs/read_write.c

index 9f4aae2021093f9de95208973b4856d4947adc2d..1ab4f5b76a1e74065f6acd272ef1e75020d4d1f2 100644 (file)
@@ -832,6 +832,38 @@ static int __dump_skip(struct coredump_params *cprm, size_t nr)
        }
 }
 
+static int dump_emit_page(struct coredump_params *cprm, struct page *page)
+{
+       struct bio_vec bvec = {
+               .bv_page        = page,
+               .bv_offset      = 0,
+               .bv_len         = PAGE_SIZE,
+       };
+       struct iov_iter iter;
+       struct file *file = cprm->file;
+       loff_t pos = file->f_pos;
+       ssize_t n;
+
+       if (cprm->to_skip) {
+               if (!__dump_skip(cprm, cprm->to_skip))
+                       return 0;
+               cprm->to_skip = 0;
+       }
+       if (cprm->written + PAGE_SIZE > cprm->limit)
+               return 0;
+       if (dump_interrupted())
+               return 0;
+       iov_iter_bvec(&iter, WRITE, &bvec, 1, PAGE_SIZE);
+       n = __kernel_write_iter(cprm->file, &iter, &pos);
+       if (n != PAGE_SIZE)
+               return 0;
+       file->f_pos = pos;
+       cprm->written += PAGE_SIZE;
+       cprm->pos += PAGE_SIZE;
+
+       return 1;
+}
+
 int dump_emit(struct coredump_params *cprm, const void *addr, int nr)
 {
        if (cprm->to_skip) {
@@ -863,7 +895,6 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start,
 
        for (addr = start; addr < start + len; addr += PAGE_SIZE) {
                struct page *page;
-               int stop;
 
                /*
                 * To avoid having to allocate page tables for virtual address
@@ -874,10 +905,7 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start,
                 */
                page = get_dump_page(addr);
                if (page) {
-                       void *kaddr = kmap_local_page(page);
-
-                       stop = !dump_emit(cprm, kaddr, PAGE_SIZE);
-                       kunmap_local(kaddr);
+                       int stop = !dump_emit_page(cprm, page);
                        put_page(page);
                        if (stop)
                                return 0;
index 87e96b9024ce1db0ee8b6b53a45e0122d7a5eb71..3e206d3e317c404bb8f014844b8d78be0dedc846 100644 (file)
@@ -16,6 +16,7 @@ struct shrink_control;
 struct fs_context;
 struct user_namespace;
 struct pipe_inode_info;
+struct iov_iter;
 
 /*
  * block/bdev.c
@@ -221,3 +222,5 @@ ssize_t do_getxattr(struct user_namespace *mnt_userns,
 int setxattr_copy(const char __user *name, struct xattr_ctx *ctx);
 int do_setxattr(struct user_namespace *mnt_userns, struct dentry *dentry,
                struct xattr_ctx *ctx);
+
+ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *pos);
index 1a261dcf1778a0862a34600ec3ad3d83c69cfcbc..328ce8cf9a85eef7057ffe0a35902a15c8af8486 100644 (file)
@@ -496,14 +496,9 @@ static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t
 }
 
 /* caller is responsible for file_start_write/file_end_write */
-ssize_t __kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos)
+ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *pos)
 {
-       struct kvec iov = {
-               .iov_base       = (void *)buf,
-               .iov_len        = min_t(size_t, count, MAX_RW_COUNT),
-       };
        struct kiocb kiocb;
-       struct iov_iter iter;
        ssize_t ret;
 
        if (WARN_ON_ONCE(!(file->f_mode & FMODE_WRITE)))
@@ -519,8 +514,7 @@ ssize_t __kernel_write(struct file *file, const void *buf, size_t count, loff_t
 
        init_sync_kiocb(&kiocb, file);
        kiocb.ki_pos = pos ? *pos : 0;
-       iov_iter_kvec(&iter, WRITE, &iov, 1, iov.iov_len);
-       ret = file->f_op->write_iter(&kiocb, &iter);
+       ret = file->f_op->write_iter(&kiocb, from);
        if (ret > 0) {
                if (pos)
                        *pos = kiocb.ki_pos;
@@ -530,6 +524,18 @@ ssize_t __kernel_write(struct file *file, const void *buf, size_t count, loff_t
        inc_syscw(current);
        return ret;
 }
+
+/* caller is responsible for file_start_write/file_end_write */
+ssize_t __kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos)
+{
+       struct kvec iov = {
+               .iov_base       = (void *)buf,
+               .iov_len        = min_t(size_t, count, MAX_RW_COUNT),
+       };
+       struct iov_iter iter;
+       iov_iter_kvec(&iter, WRITE, &iov, 1, iov.iov_len);
+       return __kernel_write_iter(file, &iter, pos);
+}
 /*
  * This "EXPORT_SYMBOL_GPL()" is more of a "EXPORT_SYMBOL_DONTUSE()",
  * but autofs is one of the few internal kernel users that actually