Relax the condition that input files must be from the same file systems. Add checks that input parameters adhere semantics. If no copy_file_range() support is found, then do generic checks for the unsupported page cache ranges, LFS, limits, and clear setuid/setgid if not running as root before calling do_splice_direct(). Update atime,ctime,mtime afterwards. Signed-off-by: Olga Kornievskaia <kolga@xxxxxxxxxx> --- fs/read_write.c | 66 ++++++++++++++++++++++++++++++++++++++++++------------ include/linux/fs.h | 7 ++++++ mm/filemap.c | 6 ++--- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/fs/read_write.c b/fs/read_write.c index 7b9e59d..2d309b0 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -1540,6 +1540,44 @@ static ssize_t do_sendfile(int out_fd, int in_fd, loff_t *ppos, } #endif +ssize_t generic_copy_file_range(struct file *file_in, loff_t pos_in, + struct file *file_out, loff_t pos_out, + loff_t len, unsigned int flags) +{ + ssize_t ret; + loff_t size_in = i_size_read(file_inode(file_in)), count; + + /* preform generic checks for unsupported page cache ranges, LFS + * limits. If pos exceeds the limit, returns EFBIG + */ + count = min(len, size_in - pos_in); + ret = generic_access_check_limits(file_in, pos_in, &count); + if (ret) + goto done; + ret = generic_write_check_limits(file_out, pos_out, &count); + if (ret) + goto done; + /* If not running as root, clear setuid/setgid bits. This keeps + * people from modifying setuid and setgid binaries. + */ + if (!IS_NOSEC(file_inode(file_out))) { + ret = file_remove_privs(file_out); + if (ret) + goto done; + } + + ret = do_splice_direct(file_in, &pos_in, file_out, &pos_out, + count > MAX_RW_COUNT ? MAX_RW_COUNT : count, 0); + + file_accessed(file_in); + if (!(file_out->f_mode & FMODE_NOCMTIME)) + file_update_time(file_out); + +done: + return ret; +} +EXPORT_SYMBOL(generic_copy_file_range); + /* * copy_file_range() differs from regular file read and write in that it * specifically allows return partial success. When it does so is up to @@ -1552,6 +1590,7 @@ ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in, struct inode *inode_in = file_inode(file_in); struct inode *inode_out = file_inode(file_out); ssize_t ret; + loff_t size_in; if (flags != 0) return -EINVAL; @@ -1577,6 +1616,15 @@ ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in, if (len == 0) return 0; + /* Ensure offsets don't wrap. */ + if (pos_in + len < pos_in || pos_out + len < pos_out) + return -EINVAL; + + size_in = i_size_read(inode_in); + /* Ensure that source range is within EOF. */ + if (pos_in >= size_in || pos_in + len > size_in) + return -EINVAL; + file_start_write(file_out); /* @@ -1597,22 +1645,12 @@ ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in, } } - if (file_out->f_op->copy_file_range) { + if (file_out->f_op->copy_file_range) ret = file_out->f_op->copy_file_range(file_in, pos_in, file_out, pos_out, len, flags); - if (ret != -EOPNOTSUPP) - goto done; - } - - /* this could be relaxed once generic cross fs support is added */ - if (inode_in->i_sb != inode_out->i_sb) { - ret = -EXDEV; - goto done; - } - - ret = do_splice_direct(file_in, &pos_in, file_out, &pos_out, - len > MAX_RW_COUNT ? MAX_RW_COUNT : len, 0); - + else + ret = generic_copy_file_range(file_in, pos_in, file_out, + pos_out, len, flags); done: if (ret > 0) { fsnotify_access(file_in); diff --git a/include/linux/fs.h b/include/linux/fs.h index c95c080..c88ad09 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1874,6 +1874,9 @@ extern ssize_t vfs_readv(struct file *, const struct iovec __user *, unsigned long, loff_t *, rwf_t); extern ssize_t vfs_copy_file_range(struct file *, loff_t , struct file *, loff_t, size_t, unsigned int); +extern ssize_t generic_copy_file_range(struct file *file_int, loff_t pos_in, + struct file *file_out, loff_t pos_out, + loff_t len, unsigned int flags); extern int generic_remap_file_range_prep(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t *count, @@ -3016,6 +3019,10 @@ static inline void remove_inode_hash(struct inode *inode) extern int generic_file_mmap(struct file *, struct vm_area_struct *); extern int generic_file_readonly_mmap(struct file *, struct vm_area_struct *); extern ssize_t generic_write_checks(struct kiocb *, struct iov_iter *); +extern int generic_access_check_limits(struct file *file, loff_t pos, + loff_t *count); +extern int generic_write_check_limits(struct file *file, loff_t pos, + loff_t *count); extern int generic_remap_checks(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t *count, unsigned int remap_flags); diff --git a/mm/filemap.c b/mm/filemap.c index 81adec8..894f3ae 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -2829,8 +2829,7 @@ struct page *read_cache_page_gfp(struct address_space *mapping, * LFS limits. If pos is under the limit it becomes a short access. If it * exceeds the limit we return -EFBIG. */ -static int generic_access_check_limits(struct file *file, loff_t pos, - loff_t *count) +int generic_access_check_limits(struct file *file, loff_t pos, loff_t *count) { struct inode *inode = file->f_mapping->host; loff_t max_size = inode->i_sb->s_maxbytes; @@ -2844,8 +2843,7 @@ static int generic_access_check_limits(struct file *file, loff_t pos, return 0; } -static int generic_write_check_limits(struct file *file, loff_t pos, - loff_t *count) +int generic_write_check_limits(struct file *file, loff_t pos, loff_t *count) { loff_t limit = rlimit(RLIMIT_FSIZE); -- 1.8.3.1