Add validation checking for outarg offset and outarg size values passed in by the server. MAX_LFS_FILESIZE is the maximum file size supported. The fuse_notify_store_out and fuse_notify_retrieve_out structs take in a uint64_t offset. Add logic to ensure: * outarg.offset is less than MAX_LFS_FILESIZE * outarg.offset + outarg.size cannot exceed MAX_LFS_FILESIZE * potential uint64_t overflow is fixed when adding outarg.offset and outarg.size. Signed-off-by: Joanne Koong --- fs/fuse/dev.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 6d59cbc877c6..7558ff337413 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1781,7 +1781,11 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, if (size - sizeof(outarg) != outarg.size) return -EINVAL; + if (outarg.offset >= MAX_LFS_FILESIZE) + return -EINVAL; + nodeid = outarg.nodeid; + num = min(outarg.size, MAX_LFS_FILESIZE - outarg.offset); down_read(&fc->killsb); @@ -1794,13 +1798,12 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, index = outarg.offset >> PAGE_SHIFT; offset = outarg.offset & ~PAGE_MASK; file_size = i_size_read(inode); - end = outarg.offset + outarg.size; + end = outarg.offset + num; if (end > file_size) { file_size = end; - fuse_write_update_attr(inode, file_size, outarg.size); + fuse_write_update_attr(inode, file_size, num); } - num = outarg.size; while (num) { struct folio *folio; unsigned int folio_offset; @@ -1880,7 +1883,7 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, num = min(outarg->size, fc->max_write); if (outarg->offset > file_size) num = 0; - else if (outarg->offset + num > file_size) + else if (num > file_size - outarg->offset) num = file_size - outarg->offset; num_pages = (num + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; @@ -1962,6 +1965,9 @@ static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size, fuse_copy_finish(cs); + if (outarg.offset >= MAX_LFS_FILESIZE) + return -EINVAL; + down_read(&fc->killsb); err = -ENOENT; nodeid = outarg.nodeid; -- 2.47.3 Simplify the folio parsing logic in fuse_notify_store() and fuse_retrieve(). In particular, calculate the index by tracking pos, which allows us to remove calculating nr_pages, and use "pos" in place of outarg's offset field. Suggested-by: Jingbo Xu Signed-off-by: Joanne Koong --- fs/fuse/dev.c | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 7558ff337413..9cbd5b64d9c9 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1765,10 +1765,9 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, struct address_space *mapping; u64 nodeid; int err; - pgoff_t index; - unsigned int offset; unsigned int num; loff_t file_size; + loff_t pos; loff_t end; if (size < sizeof(outarg)) @@ -1785,7 +1784,8 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, return -EINVAL; nodeid = outarg.nodeid; - num = min(outarg.size, MAX_LFS_FILESIZE - outarg.offset); + pos = outarg.offset; + num = min(outarg.size, MAX_LFS_FILESIZE - pos); down_read(&fc->killsb); @@ -1795,10 +1795,8 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, goto out_up_killsb; mapping = inode->i_mapping; - index = outarg.offset >> PAGE_SHIFT; - offset = outarg.offset & ~PAGE_MASK; file_size = i_size_read(inode); - end = outarg.offset + num; + end = pos + num; if (end > file_size) { file_size = end; fuse_write_update_attr(inode, file_size, num); @@ -1808,19 +1806,18 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, struct folio *folio; unsigned int folio_offset; unsigned int nr_bytes; - unsigned int nr_pages; + pgoff_t index = pos >> PAGE_SHIFT; folio = filemap_grab_folio(mapping, index); err = PTR_ERR(folio); if (IS_ERR(folio)) goto out_iput; - folio_offset = ((index - folio->index) << PAGE_SHIFT) + offset; - nr_bytes = min_t(unsigned, num, folio_size(folio) - folio_offset); - nr_pages = (offset + nr_bytes + PAGE_SIZE - 1) >> PAGE_SHIFT; + folio_offset = offset_in_folio(folio, pos); + nr_bytes = min(num, folio_size(folio) - folio_offset); err = fuse_copy_folio(cs, &folio, folio_offset, nr_bytes, 0); - if (!folio_test_uptodate(folio) && !err && offset == 0 && + if (!folio_test_uptodate(folio) && !err && folio_offset == 0 && (nr_bytes == folio_size(folio) || file_size == end)) { folio_zero_segment(folio, nr_bytes, folio_size(folio)); folio_mark_uptodate(folio); @@ -1831,9 +1828,8 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, if (err) goto out_iput; + pos += nr_bytes; num -= nr_bytes; - offset = 0; - index += nr_pages; } err = 0; @@ -1865,7 +1861,6 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, { int err; struct address_space *mapping = inode->i_mapping; - pgoff_t index; loff_t file_size; unsigned int num; unsigned int offset; @@ -1876,15 +1871,16 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, size_t args_size = sizeof(*ra); struct fuse_args_pages *ap; struct fuse_args *args; + loff_t pos = outarg->offset; - offset = outarg->offset & ~PAGE_MASK; + offset = offset_in_page(pos); file_size = i_size_read(inode); num = min(outarg->size, fc->max_write); - if (outarg->offset > file_size) + if (pos > file_size) num = 0; - else if (num > file_size - outarg->offset) - num = file_size - outarg->offset; + else if (num > file_size - pos) + num = file_size - pos; num_pages = (num + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; num_pages = min(num_pages, fc->max_pages); @@ -1907,31 +1903,27 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, args->in_pages = true; args->end = fuse_retrieve_end; - index = outarg->offset >> PAGE_SHIFT; - while (num && ap->num_folios < num_pages) { struct folio *folio; unsigned int folio_offset; unsigned int nr_bytes; - unsigned int nr_pages; + pgoff_t index = pos >> PAGE_SHIFT; folio = filemap_get_folio(mapping, index); if (IS_ERR(folio)) break; - folio_offset = ((index - folio->index) << PAGE_SHIFT) + offset; + folio_offset = offset_in_folio(folio, pos); nr_bytes = min(folio_size(folio) - folio_offset, num); - nr_pages = (offset + nr_bytes + PAGE_SIZE - 1) >> PAGE_SHIFT; ap->folios[ap->num_folios] = folio; ap->descs[ap->num_folios].offset = folio_offset; ap->descs[ap->num_folios].length = nr_bytes; ap->num_folios++; - offset = 0; + pos += nr_bytes; num -= nr_bytes; total_len += nr_bytes; - index += nr_pages; } ra->inarg.offset = outarg->offset; ra->inarg.size = total_len; -- 2.47.3 Use DIV_ROUND_UP() instead of manually computing round-up division calculations. Reviewed-by: "Darrick J. Wong" Reviewed-by: Horst Birthelmer Signed-off-by: Joanne Koong --- fs/fuse/dev.c | 2 +- fs/fuse/file.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 9cbd5b64d9c9..adfedf436b17 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1882,7 +1882,7 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, else if (num > file_size - pos) num = file_size - pos; - num_pages = (num + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; + num_pages = DIV_ROUND_UP(num + offset, PAGE_SIZE); num_pages = min(num_pages, fc->max_pages); num = min(num, num_pages << PAGE_SHIFT); diff --git a/fs/fuse/file.c b/fs/fuse/file.c index eba70ebf6e77..a4342b269cb9 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2170,7 +2170,7 @@ static bool fuse_folios_need_send(struct fuse_conn *fc, loff_t pos, WARN_ON(!ap->num_folios); /* Reached max pages */ - if ((bytes + PAGE_SIZE - 1) >> PAGE_SHIFT > fc->max_pages) + if (DIV_ROUND_UP(bytes, PAGE_SIZE) > fc->max_pages) return true; if (bytes > max_bytes) -- 2.47.3 Replace open-coded (x & ~PAGE_MASK) with offset_in_page(). Reviewed-by: "Darrick J. Wong" Reviewed-by: Horst Birthelmer Signed-off-by: Joanne Koong --- fs/fuse/readdir.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index c2aae2eef086..c88194e52d18 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -52,7 +52,7 @@ static void fuse_add_dirent_to_cache(struct file *file, } version = fi->rdc.version; size = fi->rdc.size; - offset = size & ~PAGE_MASK; + offset = offset_in_page(size); index = size >> PAGE_SHIFT; /* Dirent doesn't fit in current page? Jump to next page. */ if (offset + reclen > PAGE_SIZE) { @@ -392,7 +392,7 @@ static enum fuse_parse_result fuse_parse_cache(struct fuse_file *ff, void *addr, unsigned int size, struct dir_context *ctx) { - unsigned int offset = ff->readdir.cache_off & ~PAGE_MASK; + unsigned int offset = offset_in_page(ff->readdir.cache_off); enum fuse_parse_result res = FOUND_NONE; WARN_ON(offset >= size); @@ -518,13 +518,13 @@ static int fuse_readdir_cached(struct file *file, struct dir_context *ctx) index = ff->readdir.cache_off >> PAGE_SHIFT; if (index == (fi->rdc.size >> PAGE_SHIFT)) - size = fi->rdc.size & ~PAGE_MASK; + size = offset_in_page(fi->rdc.size); else size = PAGE_SIZE; spin_unlock(&fi->rdc.lock); /* EOF? */ - if ((ff->readdir.cache_off & ~PAGE_MASK) == size) + if (offset_in_page(ff->readdir.cache_off) == size) return 0; page = find_get_page_flags(file->f_mapping, index, -- 2.47.3