It's possible that calling sendfile() to copy the data from a memfd to
itself may result in doing a memcpy() with overlapping arguments.
To avoid undefined behavior here, replace memcpy() with memmove() and
rename memcpy_to_page()/memcpy_from_page() accordingly.

This problem has been detected with KMSAN and syzkaller.

Signed-off-by: Alexander Potapenko <gli...@google.com>
---
For the record, the following program:
===================================
  // autogenerated by syzkaller (http://github.com/google/syzkaller)

  #ifndef __NR_memfd_create
  #define __NR_memfd_create 319
  #endif

  #include <sys/mman.h>
  #include <sys/time.h>
  #include <sys/types.h>
  #include <sys/wait.h>

  #include <pthread.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>

  static uint64_t current_time_ms()
  {
    struct timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts)) _exit(1);
    return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
  }

  int fd;
  void *page;

  void* thr(void* arg)
  {
    sendfile(fd, fd, page, 0xfec);
    return 0;
  }

  void test()
  {
    int i;
    pthread_t th[2];

    page = mmap(0x20000000, 0x1000, 0x3, 0x32, -1, 0);
    char buf[1] = "\0";
    fd = syscall(__NR_memfd_create, buf, 0);
    write(fd, buf, 1);

    for (i = 0; i < 2; i++) {
      pthread_create(&th[i], 0, thr, NULL);
      usleep(10000);
    }
    usleep(100000);
  }

  int main()
  {
    int iter;
    for (iter = 0; iter < 10; iter++) {
      int pid = fork();
      if (pid < 0)
        _exit(1);
      if (pid == 0) {
        test();
        _exit(0);
      }
      int status = 0;
      uint64_t start = current_time_ms();
      for (;;) {
        int res;
        if (current_time_ms() - start > 5 * 1000) {
          kill(-pid, SIGKILL);
          kill(pid, SIGKILL);
          while (waitpid(-1, &status, __WALL) != pid) {
          }
          return 0;
        }
        usleep(1000);
      }
    }
    return 0;
  }
===================================

triggers calls to memcpy() with overlapping arguments:

==================================================================
BUG: memcpy-param-overlap in generic_perform_write+0x551/0xa20
__msan_memcpy(ffff88013c6e3001, ffff88013c6e3000, 105)
CPU: 0 PID: 1040 Comm: probe Not tainted 4.11.0-rc5+ #2562
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
Call Trace:
 __dump_stack lib/dump_stack.c:16
 dump_stack+0x143/0x1b0 lib/dump_stack.c:52
 __msan_memcpy+0x91/0x1e0 mm/kmsan/kmsan_instr.c:359
 memcpy_from_page lib/iov_iter.c:433
 iov_iter_copy_from_user_atomic+0x98e/0x1320 lib/iov_iter.c:721
 generic_perform_write+0x551/0xa20 mm/filemap.c:2843
 __generic_file_write_iter+0x3fa/0x8f0 mm/filemap.c:2960
 generic_file_write_iter+0x705/0xa80 mm/filemap.c:2988
 call_write_iter ./include/linux/fs.h:1733
 vfs_iter_write+0x481/0x580 fs/read_write.c:391
 iter_file_splice_write+0xa87/0x12f0 fs/splice.c:771
 do_splice_from fs/splice.c:873
 direct_splice_actor+0x1ae/0x210 fs/splice.c:1040
 splice_direct_to_actor+0x683/0xd40 fs/splice.c:995
 do_splice_direct+0x327/0x430 fs/splice.c:1083
 do_sendfile+0x8a3/0x12e0 fs/read_write.c:1379
 SYSC_sendfile64+0x1ab/0x2b0 fs/read_write.c:1434
 SyS_sendfile64+0x9a/0xc0 fs/read_write.c:1426
 do_syscall_64+0x72/0xa0 arch/x86/entry/common.c:285
 entry_SYSCALL64_slow_path+0x25/0x25 arch/x86/entry/entry_64.S:246
RIP: 0033:0x43f4da
RSP: 002b:00007f1f1bba4db8 EFLAGS: 00000206 ORIG_RAX: 0000000000000028
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 000000000043f4da
RDX: 0000000020000000 RSI: 0000000000000003 RDI: 0000000000000003
RBP: 00007f1f1bba4dd0 R08: 00007f1f1bba5700 R09: 00007f1f1bba5700
R10: 0000000000000fec R11: 0000000000000206 R12: 0000000000000000
R13: 0000000000000000 R14: 00007f1f1bba59c0 R15: 00007f1f1bba5700
==================================================================

---
 lib/iov_iter.c | 36 +++++++++++++++++++-----------------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/lib/iov_iter.c b/lib/iov_iter.c
index f835964c9485..097a8820e930 100644
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -427,17 +427,19 @@ void iov_iter_init(struct iov_iter *i, int direction,
 }
 EXPORT_SYMBOL(iov_iter_init);
 
-static void memcpy_from_page(char *to, struct page *page, size_t offset, 
size_t len)
+static void memmove_from_page(char *to, struct page *page, size_t offset,
+                             size_t len)
 {
        char *from = kmap_atomic(page);
-       memcpy(to, from + offset, len);
+       memmove(to, from + offset, len);
        kunmap_atomic(from);
 }
 
-static void memcpy_to_page(struct page *page, size_t offset, const char *from, 
size_t len)
+static void memmove_to_page(struct page *page, size_t offset, const char *from,
+                           size_t len)
 {
        char *to = kmap_atomic(page);
-       memcpy(to + offset, from, len);
+       memmove(to + offset, from, len);
        kunmap_atomic(to);
 }
 
@@ -525,7 +527,7 @@ static size_t copy_pipe_to_iter(const void *addr, size_t 
bytes,
                return 0;
        for ( ; n; idx = next_idx(idx, pipe), off = 0) {
                size_t chunk = min_t(size_t, n, PAGE_SIZE - off);
-               memcpy_to_page(pipe->bufs[idx].page, off, addr, chunk);
+               memmove_to_page(pipe->bufs[idx].page, off, addr, chunk);
                i->idx = idx;
                i->iov_offset = off + chunk;
                n -= chunk;
@@ -543,8 +545,8 @@ size_t copy_to_iter(const void *addr, size_t bytes, struct 
iov_iter *i)
        iterate_and_advance(i, bytes, v,
                __copy_to_user(v.iov_base, (from += v.iov_len) - v.iov_len,
                               v.iov_len),
-               memcpy_to_page(v.bv_page, v.bv_offset,
-                              (from += v.bv_len) - v.bv_len, v.bv_len),
+               memmove_to_page(v.bv_page, v.bv_offset,
+                               (from += v.bv_len) - v.bv_len, v.bv_len),
                memcpy(v.iov_base, (from += v.iov_len) - v.iov_len, v.iov_len)
        )
 
@@ -562,8 +564,8 @@ size_t copy_from_iter(void *addr, size_t bytes, struct 
iov_iter *i)
        iterate_and_advance(i, bytes, v,
                __copy_from_user((to += v.iov_len) - v.iov_len, v.iov_base,
                                 v.iov_len),
-               memcpy_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
-                                v.bv_offset, v.bv_len),
+               memmove_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
+                                 v.bv_offset, v.bv_len),
                memcpy((to += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
        )
 
@@ -586,8 +588,8 @@ bool copy_from_iter_full(void *addr, size_t bytes, struct 
iov_iter *i)
                                      v.iov_base, v.iov_len))
                        return false;
                0;}),
-               memcpy_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
-                                v.bv_offset, v.bv_len),
+               memmove_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
+                                 v.bv_offset, v.bv_len),
                memcpy((to += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
        )
 
@@ -606,8 +608,8 @@ size_t copy_from_iter_nocache(void *addr, size_t bytes, 
struct iov_iter *i)
        iterate_and_advance(i, bytes, v,
                __copy_from_user_inatomic_nocache((to += v.iov_len) - v.iov_len,
                                         v.iov_base, v.iov_len),
-               memcpy_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
-                                v.bv_offset, v.bv_len),
+               memmove_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
+                                 v.bv_offset, v.bv_len),
                memcpy((to += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
        )
 
@@ -629,8 +631,8 @@ bool copy_from_iter_full_nocache(void *addr, size_t bytes, 
struct iov_iter *i)
                                             v.iov_base, v.iov_len))
                        return false;
                0;}),
-               memcpy_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
-                                v.bv_offset, v.bv_len),
+               memmove_from_page((to += v.bv_len) - v.bv_len, v.bv_page,
+                                 v.bv_offset, v.bv_len),
                memcpy((to += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
        )
 
@@ -721,8 +723,8 @@ size_t iov_iter_copy_from_user_atomic(struct page *page,
        iterate_all_kinds(i, bytes, v,
                __copy_from_user_inatomic((p += v.iov_len) - v.iov_len,
                                          v.iov_base, v.iov_len),
-               memcpy_from_page((p += v.bv_len) - v.bv_len, v.bv_page,
-                                v.bv_offset, v.bv_len),
+               memmove_from_page((p += v.bv_len) - v.bv_len, v.bv_page,
+                                 v.bv_offset, v.bv_len),
                memcpy((p += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
        )
        kunmap_atomic(kaddr);
-- 
2.13.0.303.g4ebf302169-goog

Reply via email to