Re: [PATCH V11 6/9] famfs_fuse: Plumb dax iomap and fuse read/write/mmap

[email protected] Mon, 20 Jul 2026 03:57:03 +0000
Newsgroups dev.linux.lists.nvdimm,org.kernel.vger.linux-cxl
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] famfs_fuse_write_iter and famfs_fuse_read_iter completely bypass mandatory VFS locking, security privilege stripping, and POSIX semantics.
- [High] famfs_fuse_mmap fails to set the VM_MIXEDMAP flag, guaranteeing a BUG_ON() crash during DAX page faults.
- [High] Explicitly returning -EIO for splice operations on famfs files breaks standard system calls like sendfile() and splice().
- [High] 32-bit architectures suffer from silent data truncation due to the use of size_t and off_t for 64-bit file offsets and sizes.
- [Medium] Concurrent, lockless modification of `meta->error` in `famfs_file_bad()` creates a data race.
--

--- Patch [6]: [PATCH V11 6/9] famfs_fuse: Plumb dax iomap and fuse read/write/mmap ---
commit 26226f89d9106affa50e5f7fc0a4be4656c4531c
Author: John Groves <[email protected]>

famfs_fuse: Plumb dax iomap and fuse read/write/mmap

This commit adds read, write, and mmap support for famfs files using the
dev_dax_iomap interface. It handles file offset resolution and page faults
via DAX upcalls.

> diff --git a/fs/fuse/famfs.c b/fs/fuse/famfs.c
> index a2a7dd631dc0f..ac56317944d93 100644
> --- a/fs/fuse/famfs.c
> +++ b/fs/fuse/famfs.c

[ ... ]

> +static int
> +famfs_fileofs_to_daxofs(struct inode *inode, struct iomap *iomap,
> +			loff_t file_offset, off_t len, unsigned int flags)
> +{

[Severity: High]
Does defining the len parameter as off_t truncate the mapping length on 32-bit
systems?

[ ... ]

> +const struct vm_operations_struct famfs_file_vm_ops = {
> +	.fault		= famfs_filemap_fault,
> +	.huge_fault	= famfs_filemap_huge_fault,
> +	.map_pages	= filemap_map_pages,

[Severity: High]
Is it correct to assign a page-cache-specific mapping function here? Since
DAX mappings bypass the page cache, using filemap_map_pages might lead to
unexpected behavior.

> +	.page_mkwrite	= famfs_filemap_mkwrite,
> +	.pfn_mkwrite	= famfs_filemap_mkwrite,
> +};

[ ... ]

> +static int
> +famfs_file_bad(struct inode *inode)
> +{
> +	struct fuse_inode *fi = get_fuse_inode(inode);
> +	struct famfs_file_meta *meta = fi->famfs_meta;
> +	size_t i_size = i_size_read(inode);
> +
> +	if (!meta) {
> +		pr_err("%s: un-initialized famfs file\n", __func__);
> +		return -EIO;
> +	}
> +	if (meta->error) {
> +		pr_debug("%s: previously detected metadata errors\n", __func__);
> +		return -EIO;
> +	}
> +	if (i_size != meta->file_size) {
> +		pr_warn("%s: i_size overwritten from %ld to %ld\n",
> +		       __func__, meta->file_size, i_size);
> +		meta->error = true;

[Severity: Medium]
Can concurrent I/O operations cause a data race on meta->error? Since read
and write paths can execute without the inode lock, multiple threads might
modify this flag simultaneously.

> +		return -ENXIO;
> +	}

[ ... ]

> +static ssize_t
> +famfs_fuse_rw_prep(struct kiocb *iocb, struct iov_iter *ubuf)
> +{
> +	struct inode *inode = iocb->ki_filp->f_mapping->host;
> +	size_t i_size = i_size_read(inode);

[Severity: High]
Might reading i_size_read() into a size_t truncate the 64-bit value on 32-bit
platforms? This would break I/O bounds checking for files larger than 4GB.
This also applies to the file_size field in the famfs_file_meta structure if
it is similarly defined as size_t.

> +	size_t count = iov_iter_count(ubuf);
> +	size_t max_count;
> +	ssize_t rc;

[ ... ]

> +ssize_t
> +famfs_fuse_read_iter(struct kiocb *iocb, struct iov_iter	*to)
> +{
> +	ssize_t rc;
> +
> +	rc = famfs_fuse_rw_prep(iocb, to);
> +	if (rc)
> +		return rc;
> +
> +	if (!iov_iter_count(to))
> +		return 0;
> +
> +	rc = dax_iomap_rw(iocb, to, &famfs_iomap_ops);

[Severity: Critical]
Does this read path need to acquire inode_lock_shared()? The standard VFS
read paths typically acquire it before calling dax_iomap_rw().

> +
> +	file_accessed(iocb->ki_filp);
> +	return rc;
> +}
> +
> +ssize_t
> +famfs_fuse_write_iter(struct kiocb *iocb, struct iov_iter *from)
> +{
> +	ssize_t rc;
> +
> +	rc = famfs_fuse_rw_prep(iocb, from);
> +	if (rc)
> +		return rc;
> +
> +	if (!iov_iter_count(from))
> +		return 0;
> +
> +	return dax_iomap_rw(iocb, from, &famfs_iomap_ops);
> +}

[Severity: Critical]
Does this write path omit mandatory VFS security and integrity checks?
Calling dax_iomap_rw() directly without acquiring inode_lock() bypasses
generic_write_checks() and file_remove_privs(), which could allow
unprivileged modification of setuid/setgid files and bypass O_APPEND
restrictions.

> +int
> +famfs_fuse_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> +	struct inode *inode = file_inode(file);
> +	ssize_t rc;
> +
> +	rc = famfs_file_bad(inode);
> +	if (rc)
> +		return rc;
> +
> +	file_accessed(file);
> +	vma->vm_ops = &famfs_file_vm_ops;
> +	vm_flags_set(vma, VM_HUGEPAGE);

[Severity: High]
Could this trigger a kernel panic during DAX page faults? The
vmf_insert_mixed() path expects VM_MIXEDMAP or VM_PFNMAP to be set on the
vma, but VM_MIXEDMAP appears to be omitted here.

> +	return 0;
> +}

[ ... ]

> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index b4e7b6a645870..2435a79cbb4a3 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c

[ ... ]

> @@ -1896,9 +1900,13 @@ static ssize_t fuse_splice_read(struct file *in, loff_t *ppos,
>  				unsigned int flags)
>  {
>  	struct fuse_file *ff = in->private_data;
> +	struct inode *inode = file_inode(in);
> +	struct fuse_inode *fi = get_fuse_inode(inode);
>  
>  	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
> -	if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
> +	if (fuse_file_famfs(fi))
> +		return -EIO; /* famfs does not use the page cache... */

[Severity: High]
Will returning -EIO break standard system calls like splice() and sendfile()
for famfs files? Should this fall back to iter_file_splice_read() or
copy_splice_read() instead?

> +	else if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
>  		return fuse_passthrough_splice_read(in, ppos, pipe, len, flags);
>  	else
>  		return filemap_splice_read(in, ppos, pipe, len, flags);
> @@ -1908,9 +1916,13 @@ static ssize_t fuse_splice_write(struct pipe_inode_info *pipe, struct file *out,
>  				 loff_t *ppos, size_t len, unsigned int flags)
>  {
>  	struct fuse_file *ff = out->private_data;
> +	struct inode *inode = file_inode(out);
> +	struct fuse_inode *fi = get_fuse_inode(inode);
>  
>  	/* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */
> -	if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
> +	if (fuse_file_famfs(fi))
> +		return -EIO; /* famfs does not use the page cache... */

[Severity: High]
Does hardcoding -EIO here also break splice writes? Falling back to
iter_file_splice_write() might be required to keep standard system calls
functioning.

> +	else if (fuse_file_passthrough(ff) && !(ff->open_flags & FOPEN_DIRECT_IO))
>  		return fuse_passthrough_splice_write(pipe, out, ppos, len, flags);
>  	else
>  		return iter_file_splice_write(pipe, out, ppos, len, flags);

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/0100019f7d9fbe81-6cb16662-2522-47ea-a152-fab0ee3d9b35-000000@email.amazonses.com?part=6