Merge branch 'uaccess-work.iov_iter' of git://git.kernel.org/pub/scm/linux/kernel...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 8 Jul 2017 03:39:20 +0000 (20:39 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 8 Jul 2017 03:39:20 +0000 (20:39 -0700)
Pull iov_iter hardening from Al Viro:
 "This is the iov_iter/uaccess/hardening pile.

  For one thing, it trims the inline part of copy_to_user/copy_from_user
  to the minimum that *does* need to be inlined - object size checks,
  basically. For another, it sanitizes the checks for iov_iter
  primitives. There are 4 groups of checks: access_ok(), might_fault(),
  object size and KASAN.

   - access_ok() had been verified by whoever had set the iov_iter up.
     However, that has happened in a function far away, so proving that
     there's no path to actual copying bypassing those checks is hard
     and proving that iov_iter has not been buggered in the meanwhile is
     also not pleasant. So we want those redone in actual
     copyin/copyout.

   - might_fault() is better off consolidated - we know whether it needs
     to be checked as soon as we enter iov_iter primitive and observe
     the iov_iter flavour. No need to wait until the copyin/copyout. The
     call chains are short enough to make sure we won't miss anything -
     in fact, it's more robust that way, since there are cases where we
     do e.g. forced fault-in before getting to copyin/copyout. It's not
     quite what we need to check (in particular, combination of
     iovec-backed and set_fs(KERNEL_DS) is almost certainly a bug, not a
     cause to skip checks), but that's for later series. For now let's
     keep might_fault().

   - KASAN checks belong in copyin/copyout - at the same level where
     other iov_iter flavours would've hit them in memcpy().

   - object size checks should apply to *all* iov_iter flavours, not
     just iovec-backed ones.

  There are two groups of primitives - one gets the kernel object
  described as pointer + size (copy_to_iter(), etc.) while another gets
  it as page + offset + size (copy_page_to_iter(), etc.)

  For the first group the checks are best done where we actually have a
  chance to find the object size. In other words, those belong in inline
  wrappers in uio.h, before calling into iov_iter.c. Same kind as we
  have for inlined part of copy_to_user().

  For the second group there is no object to look at - offset in page is
  just a number, it bears no type information. So we do them in the
  common helper called by iov_iter.c primitives of that kind. All it
  currently does is checking that we are not trying to access outside of
  the compound page; eventually we might want to add some sanity checks
  on the page involved.

  So the things we need in copyin/copyout part of iov_iter.c do not
  quite match anything in uaccess.h (we want no zeroing, we *do* want
  access_ok() and KASAN and we want no might_fault() or object size
  checks done on that level). OTOH, these needs are simple enough to
  provide a couple of helpers (static in iov_iter.c) doing just what we
  need..."

* 'uaccess-work.iov_iter' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  iov_iter: saner checks on copyin/copyout
  iov_iter: sanity checks for copy to/from page primitives
  iov_iter/hardening: move object size checks to inlined part
  copy_{to,from}_user(): consolidate object size checks
  copy_{from,to}_user(): move kasan checks and might_fault() out-of-line

1  2 
include/linux/uio.h
lib/iov_iter.c

diff --combined include/linux/uio.h
index 55cd54a0e94100cc71b4ddeb03d8b35392a32b7a,243e2362fe1a8a1ea8b0480c362d10c679d4ea95..342d2dc225b95b51e36d0f9a8a37c990e69f4d74
@@@ -10,6 -10,7 +10,7 @@@
  #define __LINUX_UIO_H
  
  #include <linux/kernel.h>
+ #include <linux/thread_info.h>
  #include <uapi/linux/uio.h>
  
  struct page;
@@@ -91,26 -92,58 +92,79 @@@ size_t copy_page_to_iter(struct page *p
                         struct iov_iter *i);
  size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
                         struct iov_iter *i);
- size_t copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i);
- size_t copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);
- bool copy_from_iter_full(void *addr, size_t bytes, struct iov_iter *i);
- size_t copy_from_iter_nocache(void *addr, size_t bytes, struct iov_iter *i);
+ size_t _copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i);
+ size_t _copy_from_iter(void *addr, size_t bytes, struct iov_iter *i);
+ bool _copy_from_iter_full(void *addr, size_t bytes, struct iov_iter *i);
+ size_t _copy_from_iter_nocache(void *addr, size_t bytes, struct iov_iter *i);
+ bool _copy_from_iter_full_nocache(void *addr, size_t bytes, struct iov_iter *i);
+ static __always_inline __must_check
+ size_t copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i)
+ {
+       if (unlikely(!check_copy_size(addr, bytes, true)))
+               return bytes;
+       else
+               return _copy_to_iter(addr, bytes, i);
+ }
+ static __always_inline __must_check
+ size_t copy_from_iter(void *addr, size_t bytes, struct iov_iter *i)
+ {
+       if (unlikely(!check_copy_size(addr, bytes, false)))
+               return bytes;
+       else
+               return _copy_from_iter(addr, bytes, i);
+ }
+ static __always_inline __must_check
+ bool copy_from_iter_full(void *addr, size_t bytes, struct iov_iter *i)
+ {
+       if (unlikely(!check_copy_size(addr, bytes, false)))
+               return false;
+       else
+               return _copy_from_iter_full(addr, bytes, i);
+ }
+ static __always_inline __must_check
+ size_t copy_from_iter_nocache(void *addr, size_t bytes, struct iov_iter *i)
+ {
+       if (unlikely(!check_copy_size(addr, bytes, false)))
+               return bytes;
+       else
+               return _copy_from_iter_nocache(addr, bytes, i);
+ }
+ static __always_inline __must_check
+ bool copy_from_iter_full_nocache(void *addr, size_t bytes, struct iov_iter *i)
+ {
+       if (unlikely(!check_copy_size(addr, bytes, false)))
+               return false;
+       else
+               return _copy_from_iter_full_nocache(addr, bytes, i);
+ }
 +#ifdef CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE
 +/*
 + * Note, users like pmem that depend on the stricter semantics of
 + * copy_from_iter_flushcache() than copy_from_iter_nocache() must check for
 + * IS_ENABLED(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) before assuming that the
 + * destination is flushed from the cache on return.
 + */
- size_t copy_from_iter_flushcache(void *addr, size_t bytes, struct iov_iter *i);
++size_t _copy_from_iter_flushcache(void *addr, size_t bytes, struct iov_iter *i);
 +#else
- static inline size_t copy_from_iter_flushcache(void *addr, size_t bytes,
-                                      struct iov_iter *i)
++#define _copy_from_iter_flushcache _copy_from_iter_nocache
++#endif
++
++static __always_inline __must_check
++size_t copy_from_iter_flushcache(void *addr, size_t bytes, struct iov_iter *i)
 +{
-       return copy_from_iter_nocache(addr, bytes, i);
++      if (unlikely(!check_copy_size(addr, bytes, false)))
++              return bytes;
++      else
++              return _copy_from_iter_flushcache(addr, bytes, i);
 +}
- #endif
- bool copy_from_iter_full_nocache(void *addr, size_t bytes, struct iov_iter *i);
++
  size_t iov_iter_zero(size_t bytes, struct iov_iter *);
  unsigned long iov_iter_alignment(const struct iov_iter *i);
  unsigned long iov_iter_gap_alignment(const struct iov_iter *i);
diff --combined lib/iov_iter.c
index c9a69064462f8c0bf3b1a7b58623c14e7b0d371f,b3b2ee8a20b54ac597046bd1dfd5c3a6d9627c46..52c8dd6d8e8290fa8df614e0017542a600b9c536
        }                                                       \
  }
  
+ static int copyout(void __user *to, const void *from, size_t n)
+ {
+       if (access_ok(VERIFY_WRITE, to, n)) {
+               kasan_check_read(from, n);
+               n = raw_copy_to_user(to, from, n);
+       }
+       return n;
+ }
+ static int copyin(void *to, const void __user *from, size_t n)
+ {
+       if (access_ok(VERIFY_READ, from, n)) {
+               kasan_check_write(to, n);
+               n = raw_copy_from_user(to, from, n);
+       }
+       return n;
+ }
  static size_t copy_page_to_iter_iovec(struct page *page, size_t offset, size_t bytes,
                         struct iov_iter *i)
  {
        if (unlikely(!bytes))
                return 0;
  
+       might_fault();
        wanted = bytes;
        iov = i->iov;
        skip = i->iov_offset;
                from = kaddr + offset;
  
                /* first chunk, usually the only one */
-               left = __copy_to_user_inatomic(buf, from, copy);
+               left = copyout(buf, from, copy);
                copy -= left;
                skip += copy;
                from += copy;
                        iov++;
                        buf = iov->iov_base;
                        copy = min(bytes, iov->iov_len);
-                       left = __copy_to_user_inatomic(buf, from, copy);
+                       left = copyout(buf, from, copy);
                        copy -= left;
                        skip = copy;
                        from += copy;
  
        kaddr = kmap(page);
        from = kaddr + offset;
-       left = __copy_to_user(buf, from, copy);
+       left = copyout(buf, from, copy);
        copy -= left;
        skip += copy;
        from += copy;
                iov++;
                buf = iov->iov_base;
                copy = min(bytes, iov->iov_len);
-               left = __copy_to_user(buf, from, copy);
+               left = copyout(buf, from, copy);
                copy -= left;
                skip = copy;
                from += copy;
@@@ -227,6 -246,7 +246,7 @@@ static size_t copy_page_from_iter_iovec
        if (unlikely(!bytes))
                return 0;
  
+       might_fault();
        wanted = bytes;
        iov = i->iov;
        skip = i->iov_offset;
                to = kaddr + offset;
  
                /* first chunk, usually the only one */
-               left = __copy_from_user_inatomic(to, buf, copy);
+               left = copyin(to, buf, copy);
                copy -= left;
                skip += copy;
                to += copy;
                        iov++;
                        buf = iov->iov_base;
                        copy = min(bytes, iov->iov_len);
-                       left = __copy_from_user_inatomic(to, buf, copy);
+                       left = copyin(to, buf, copy);
                        copy -= left;
                        skip = copy;
                        to += copy;
  
        kaddr = kmap(page);
        to = kaddr + offset;
-       left = __copy_from_user(to, buf, copy);
+       left = copyin(to, buf, copy);
        copy -= left;
        skip += copy;
        to += copy;
                iov++;
                buf = iov->iov_base;
                copy = min(bytes, iov->iov_len);
-               left = __copy_from_user(to, buf, copy);
+               left = copyin(to, buf, copy);
                copy -= left;
                skip = copy;
                to += copy;
@@@ -535,14 -555,15 +555,15 @@@ static size_t copy_pipe_to_iter(const v
        return bytes;
  }
  
- size_t copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i)
+ size_t _copy_to_iter(const void *addr, size_t bytes, struct iov_iter *i)
  {
        const char *from = addr;
        if (unlikely(i->type & ITER_PIPE))
                return copy_pipe_to_iter(addr, bytes, i);
+       if (iter_is_iovec(i))
+               might_fault();
        iterate_and_advance(i, bytes, v,
-               __copy_to_user(v.iov_base, (from += v.iov_len) - v.iov_len,
-                              v.iov_len),
+               copyout(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),
                memcpy(v.iov_base, (from += v.iov_len) - v.iov_len, v.iov_len)
  
        return bytes;
  }
- EXPORT_SYMBOL(copy_to_iter);
+ EXPORT_SYMBOL(_copy_to_iter);
  
- size_t copy_from_iter(void *addr, size_t bytes, struct iov_iter *i)
+ size_t _copy_from_iter(void *addr, size_t bytes, struct iov_iter *i)
  {
        char *to = addr;
        if (unlikely(i->type & ITER_PIPE)) {
                WARN_ON(1);
                return 0;
        }
+       if (iter_is_iovec(i))
+               might_fault();
        iterate_and_advance(i, bytes, v,
-               __copy_from_user((to += v.iov_len) - v.iov_len, v.iov_base,
-                                v.iov_len),
+               copyin((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),
                memcpy((to += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
  
        return bytes;
  }
- EXPORT_SYMBOL(copy_from_iter);
+ EXPORT_SYMBOL(_copy_from_iter);
  
- bool copy_from_iter_full(void *addr, size_t bytes, struct iov_iter *i)
+ bool _copy_from_iter_full(void *addr, size_t bytes, struct iov_iter *i)
  {
        char *to = addr;
        if (unlikely(i->type & ITER_PIPE)) {
        if (unlikely(i->count < bytes))
                return false;
  
+       if (iter_is_iovec(i))
+               might_fault();
        iterate_all_kinds(i, bytes, v, ({
-               if (__copy_from_user((to += v.iov_len) - v.iov_len,
+               if (copyin((to += v.iov_len) - v.iov_len,
                                      v.iov_base, v.iov_len))
                        return false;
                0;}),
        iov_iter_advance(i, bytes);
        return true;
  }
- EXPORT_SYMBOL(copy_from_iter_full);
+ EXPORT_SYMBOL(_copy_from_iter_full);
  
- size_t copy_from_iter_nocache(void *addr, size_t bytes, struct iov_iter *i)
+ size_t _copy_from_iter_nocache(void *addr, size_t bytes, struct iov_iter *i)
  {
        char *to = addr;
        if (unlikely(i->type & ITER_PIPE)) {
  
        return bytes;
  }
- EXPORT_SYMBOL(copy_from_iter_nocache);
+ EXPORT_SYMBOL(_copy_from_iter_nocache);
  
- size_t copy_from_iter_flushcache(void *addr, size_t bytes, struct iov_iter *i)
 +#ifdef CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE
- EXPORT_SYMBOL_GPL(copy_from_iter_flushcache);
++size_t _copy_from_iter_flushcache(void *addr, size_t bytes, struct iov_iter *i)
 +{
 +      char *to = addr;
 +      if (unlikely(i->type & ITER_PIPE)) {
 +              WARN_ON(1);
 +              return 0;
 +      }
 +      iterate_and_advance(i, bytes, v,
 +              __copy_from_user_flushcache((to += v.iov_len) - v.iov_len,
 +                                       v.iov_base, v.iov_len),
 +              memcpy_page_flushcache((to += v.bv_len) - v.bv_len, v.bv_page,
 +                               v.bv_offset, v.bv_len),
 +              memcpy_flushcache((to += v.iov_len) - v.iov_len, v.iov_base,
 +                      v.iov_len)
 +      )
 +
 +      return bytes;
 +}
- bool copy_from_iter_full_nocache(void *addr, size_t bytes, struct iov_iter *i)
++EXPORT_SYMBOL_GPL(_copy_from_iter_flushcache);
 +#endif
 +
+ bool _copy_from_iter_full_nocache(void *addr, size_t bytes, struct iov_iter *i)
  {
        char *to = addr;
        if (unlikely(i->type & ITER_PIPE)) {
        iov_iter_advance(i, bytes);
        return true;
  }
- EXPORT_SYMBOL(copy_from_iter_full_nocache);
+ EXPORT_SYMBOL(_copy_from_iter_full_nocache);
+ static inline bool page_copy_sane(struct page *page, size_t offset, size_t n)
+ {
+       size_t v = n + offset;
+       if (likely(n <= v && v <= (PAGE_SIZE << compound_order(page))))
+               return true;
+       WARN_ON(1);
+       return false;
+ }
  
  size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
                         struct iov_iter *i)
  {
+       if (unlikely(!page_copy_sane(page, offset, bytes)))
+               return 0;
        if (i->type & (ITER_BVEC|ITER_KVEC)) {
                void *kaddr = kmap_atomic(page);
                size_t wanted = copy_to_iter(kaddr + offset, bytes, i);
@@@ -679,13 -692,15 +714,15 @@@ EXPORT_SYMBOL(copy_page_to_iter)
  size_t copy_page_from_iter(struct page *page, size_t offset, size_t bytes,
                         struct iov_iter *i)
  {
+       if (unlikely(!page_copy_sane(page, offset, bytes)))
+               return 0;
        if (unlikely(i->type & ITER_PIPE)) {
                WARN_ON(1);
                return 0;
        }
        if (i->type & (ITER_BVEC|ITER_KVEC)) {
                void *kaddr = kmap_atomic(page);
-               size_t wanted = copy_from_iter(kaddr + offset, bytes, i);
+               size_t wanted = _copy_from_iter(kaddr + offset, bytes, i);
                kunmap_atomic(kaddr);
                return wanted;
        } else
@@@ -722,7 -737,7 +759,7 @@@ size_t iov_iter_zero(size_t bytes, stru
        if (unlikely(i->type & ITER_PIPE))
                return pipe_zero(bytes, i);
        iterate_and_advance(i, bytes, v,
-               __clear_user(v.iov_base, v.iov_len),
+               clear_user(v.iov_base, v.iov_len),
                memzero_page(v.bv_page, v.bv_offset, v.bv_len),
                memset(v.iov_base, 0, v.iov_len)
        )
@@@ -735,14 -750,17 +772,17 @@@ size_t iov_iter_copy_from_user_atomic(s
                struct iov_iter *i, unsigned long offset, size_t bytes)
  {
        char *kaddr = kmap_atomic(page), *p = kaddr + offset;
+       if (unlikely(!page_copy_sane(page, offset, bytes))) {
+               kunmap_atomic(kaddr);
+               return 0;
+       }
        if (unlikely(i->type & ITER_PIPE)) {
                kunmap_atomic(kaddr);
                WARN_ON(1);
                return 0;
        }
        iterate_all_kinds(i, bytes, v,
-               __copy_from_user_inatomic((p += v.iov_len) - v.iov_len,
-                                         v.iov_base, v.iov_len),
+               copyin((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),
                memcpy((p += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)