CVE-2019-11487: Use After Free (CWE-416)
value: The Linux kernel before 5.1-rc5 allows page->_refcount reference count overflow, with resultant use-after-free issues, if about 140 GiB of RAM exists. This is related to fs/fuse/dev.c, fs/pipe.c, fs/splice.c, include/linux/mm.h, include/linux/pipe_fs_i.h, kernel/trace/trace.c, mm/gup.c, and mm/hugetlb.c. It can occur with FUSE requests.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 7.8
Published: 2019-04-23T22:29Z
Repository: https://github.com/torvalds/linux
Language: Unknown
Commit date: 2019-04-14 15:09:40 -0700
Commit message: Merge branch 'page-refs' (page ref overflow)

Merge page ref overflow branch.

Jann Horn reported that he can overflow the page ref count with
sufficient memory (and a filesystem that is intentionally extremely
slow).

Admittedly it's not exactly easy.  To have more than four billion
references to a page requires a minimum of 32GB of kernel memory just
for the pointers to the pages, much less any metadata to keep track of
those pointers.  Jann needed a total of 140GB of memory and a specially
crafted filesystem that leaves all reads pending (in order to not ever
free the page references and just keep adding more).

Still, we have a fairly straightforward way to limit the two obvious
user-controllable sources of page references: direct-IO like page
references gotten through get_user_pages(), and the splice pipe page
duplication.  So let's just do that.

* branch page-refs:
  fs: prevent page refcount overflow in pipe_buf_get
  mm: prevent get_user_pages() from overflowing page refcount
  mm: add 'try_get_page()' helper function
  mm: make page ref count overflow check tighter and more explicit

Diff: diff --cc include/linux/pipe_fs_i.h
index 787d224ff43e,3f2a42c11e20..abb2dac3da9b
--- a/include/linux/pipe_fs_i.h
+++ b/include/linux/pipe_fs_i.h
@@@ -169,15 -178,14 +171,15 @@@ void pipe_wait(struct pipe_inode_info *
  
  struct pipe_inode_info *alloc_pipe_info(void);
  void free_pipe_info(struct pipe_inode_info *);
  
  /* Generic pipe buffer ops functions */
- void generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *);
+ bool generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *);
  int generic_pipe_buf_confirm(struct pipe_inode_info *, struct pipe_buffer *);
  int generic_pipe_buf_steal(struct pipe_inode_info *, struct pipe_buffer *);
  void generic_pipe_buf_release(struct pipe_inode_info *, struct pipe_buffer *);
 +void pipe_buf_mark_unmergeable(struct pipe_buffer *buf);
  
  extern const struct pipe_buf_operations nosteal_pipe_buf_ops;
  
  /* for F_SETPIPE_SZ and F_GETPIPE_SZ */
  long pipe_fcntl(struct file *, unsigned int, unsigned long arg);
diff --cc kernel/trace/trace.c
index 21153e64bf1c,0f300d488c9f..6c24755655c7
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@@ -7039,20 -6833,25 +7039,24 @@@ static void buffer_pipe_buf_release(str
  	ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->page);
  	kfree(ref);
  	buf->private = 0;
  }
  
- static void buffer_pipe_buf_get(struct pipe_inode_info *pipe,
+ static bool buffer_pipe_buf_get(struct pipe_inode_info *pipe,
  				struct pipe_buffer *buf)
  {
  	struct buffer_ref *ref = (struct buffer_ref *)buf->private;
  
+ 	if (ref->ref > INT_MAX/2)
+ 		return false;
+ 
  	ref->ref++;
+ 	return true;
  }
  
  /* Pipe buffer operations for a buffer. */
  static const struct pipe_buf_operations buffer_pipe_buf_ops = {
 -	.can_merge		= 0,
  	.confirm		= generic_pipe_buf_confirm,
  	.release		= buffer_pipe_buf_release,
  	.steal			= generic_pipe_buf_steal,
  	.get			= buffer_pipe_buf_get,
  };