NVDIMM Device and Persistent Memory development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "John Groves" <john@jagalactic.com>
Cc: nvdimm@lists.linux.dev, linux-cxl@vger.kernel.org
Subject: Re: [PATCH V8 04/10] famfs_fuse: Create files with famfs fmaps
Date: Thu, 13 Aug 2026 22:08:33 +0000	[thread overview]
Message-ID: <20260813220837.3F58E1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <0100019d063ff336-a7f1bca1-63b7-497c-93dc-69155efd8a68-000000@email.amazonses.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] Integer overflow and undefined behavior when calculating `dev_bitmap` with a 32-bit signed shift.
- [High] Recursive `inode_lock()` deadlock on `O_TRUNC` file open.
- [Medium] Data truncation of file size and mapping size on 32-bit architectures.
- [Medium] Dynamic toggling of `S_DAX` flag introduces a data race and violates VFS caching invariants.
- [Low] Dangling pointer window in `famfs_meta_free` due to incorrect cleanup ordering.
- [Low] Meaningless local `cmpxchg` race check acts as dead code.
- [Low] Uninitialized variable accessed and printed in error message.
- [Low] Implicit cast of untrusted UAPI enum to internal enum.
--

--- Patch [4]: [PATCH V8 04/10] famfs_fuse: Create files with famfs fmaps ---
commit 588d2cb1a397d88983186c80e9fec874ef42a12e
Author: John Groves <john@groves.net>

famfs_fuse: Create files with famfs fmaps

This patch introduces the ability to allocate and initialize in-memory famfs
file metadata mapped to DAX memory. It adds structures for resolving file
maps and sets up the initialization path based on FUSE GET_FMAP responses.

> --- a/fs/fuse/famfs.c
> +++ b/fs/fuse/famfs.c

[ ... ]

> +static int
> +famfs_fuse_meta_alloc(
> +	void *fmap_buf,
> +	size_t fmap_buf_size,
> +	struct famfs_file_meta **metap)
> +{
> +	struct fuse_famfs_fmap_header *fmh;

[ ... ]

> +	meta->error = false;
> +	meta->file_type = fmh->file_type;
> +	meta->file_size = fmh->file_size;

[Severity: Medium]
Does this truncate the file size on 32-bit architectures?

The FUSE protocol correctly provides this as a 64-bit value, but the
famfs_file_meta structure declares file_size as a size_t. On 32-bit systems,
size_t is a 32-bit unsigned integer.

Any file size larger than 4GB will be silently truncated when assigned here,
which will later result in incorrect sizes passed to i_size_write().

> +	meta->fm_extent_type = fmh->ext_type;

[Severity: Low]
Is it safe to directly assign this FUSE UAPI enum to the internal kernel
enum?

The code assigns a UAPI uint32_t (enum famfs_ext_type) directly to a
kernel internal enum famfs_extent_type. While the integer values currently
align identically, this bypasses type safety and will fail silently if the
enumerated types ever diverge.

> +
> +	switch (fmh->ext_type) {
> +	case FUSE_FAMFS_EXT_SIMPLE: {
> +		struct fuse_famfs_simple_ext *se_in;

[ ... ]

> +		for (i = 0; i < fmh->nextents; i++) {
> +			meta->se[i].dev_index  = se_in[i].se_devindex;
> +			meta->se[i].ext_offset = se_in[i].se_offset;
> +			meta->se[i].ext_len    = se_in[i].se_len;
> +
> +			/* Record bitmap of referenced daxdev indices */
> +			meta->dev_bitmap |= (1 << meta->se[i].dev_index);

[Severity: High]
Can this shift operation invoke undefined behavior or corrupt the bitmap?

The FUSE server provides a dev_index which is used to set a bit in a 64-bit
bitmap. Because 1 is a signed 32-bit integer literal, a dev_index of 31
results in a negative value that sign-extends to 0xFFFFFFFF80000000 when
promoted to u64, incorrectly setting bits 32-63.

If dev_index is 32 or greater, the shift invokes undefined behavior. Using
1ULL would prevent this.

> +
> +			errs += famfs_check_ext_alignment(&meta->se[i]);
> +
> +			extent_total += meta->se[i].ext_len;
> +		}
> +		break;
> +	}
> +
> +	case FUSE_FAMFS_EXT_INTERLEAVE: {

[ ... ]

> +		for (i = 0; i < niext; i++) {
> +			u64 nstrips;
> +			struct fuse_famfs_simple_ext *sie_in;
> +
> +			/* ie_in = one interleaved extent in fmap_buf */
> +			ie_in = fmap_buf + next_offset;
> +
> +			/* Move past one interleaved extent header in fmap_buf */
> +			next_offset += sizeof(*ie_in);
> +			if (next_offset > fmap_buf_size) {
> +				pr_err("%s:%d: fmap_buf underflow offset/size %ld/%ld\n",
> +				       __func__, __LINE__, next_offset,
> +				       fmap_buf_size);
> +				return -EINVAL;
> +			}
> +
> +			if (!IS_ALIGNED(ie_in->ie_chunk_size, PMD_SIZE)) {
> +				pr_err("%s: chunk_size %lld not PMD-aligned\n",
> +				       __func__, meta->ie[i].fie_chunk_size);

[Severity: Low]
Does this error message print an uninitialized value?

During the interleaved extent alignment checks, if ie_chunk_size is not
PMD-aligned, the error message attempts to log meta->ie[i].fie_chunk_size.

However, this field has not yet been assigned from the FUSE payload. The
assignment happens several lines below this check, so this will print 0
instead of the actual invalid chunk size.

> +				return -EINVAL;
> +			}

[ ... ]

> +			/* Inner loop is over strips */
> +			for (j = 0; j < nstrips; j++) {
> +				struct famfs_meta_simple_ext *strips_out;
> +				u64 devindex = sie_in[j].se_devindex;
> +				u64 offset   = sie_in[j].se_offset;
> +				u64 len      = sie_in[j].se_len;
> +
> +				strips_out = meta->ie[i].ie_strips;
> +				strips_out[j].dev_index  = devindex;
> +				strips_out[j].ext_offset = offset;
> +				strips_out[j].ext_len    = len;
> +
> +				/* Record bitmap of referenced daxdev indices */
> +				meta->dev_bitmap |= (1 << devindex);

[Severity: High]
As above, can this shift operation invoke undefined behavior or corrupt the
bitmap if devindex is 32 or greater?

> +
> +				extent_total += len;
> +				errs += famfs_check_ext_alignment(&strips_out[j]);
> +				size_remainder -= len;
> +			}
> +		}

[ ... ]

> +	if (cmpxchg(metap, NULL, meta) != NULL) {
> +		pr_debug("%s: fmap race detected\n", __func__);
> +		return 0; /* fmap already installed */
> +	}

[Severity: Low]
Does this cmpxchg check accomplish its intended race detection?

The metap argument points to a purely local stack variable inside the caller
famfs_file_init_dax(). Since this variable is completely local to the
thread, the cmpxchg will always succeed.

This makes the race detection dead code. The actual functional race check
correctly occurs later via famfs_meta_set().

> +	retain_and_null_ptr(meta);
> +
> +	return 0;
> +}
> +
> +/**
> + * famfs_file_init_dax() - init famfs dax file metadata

[ ... ]

> +int
> +famfs_file_init_dax(
> +	struct fuse_mount *fm,
> +	struct inode *inode,
> +	void *fmap_buf,
> +	size_t fmap_size)
> +{
> +	struct fuse_inode *fi = get_fuse_inode(inode);
> +	struct famfs_file_meta *meta = NULL;
> +	int rc;

[ ... ]

> +	rc = famfs_fuse_meta_alloc(fmap_buf, fmap_size, &meta);
> +	if (rc)
> +		goto errout;
> +
> +	/* Publish the famfs metadata on fi->famfs_meta */
> +	inode_lock(inode);

[Severity: High]
Could this unconditional lock acquisition cause an A-A self-deadlock?

When a file is opened with O_TRUNC and writeback caching is enabled, the
fuse_open() path acquires the inode_lock to perform truncation.

If the filesystem is mounted with famfs iomap support, the open path calls
fuse_get_fmap(), which in turn calls famfs_file_init_dax().

This function unconditionally calls inode_lock() again to install the
metadata. Since the same thread already holds the lock, it will deadlock.

> +
> +	if (famfs_meta_set(fi, meta) == NULL) {
> +		i_size_write(inode, meta->file_size);
> +		inode->i_flags |= S_DAX;

[Severity: Medium]
Is it safe to dynamically toggle the DAX flag on an active inode?

This modifies the flags using a read-modify-write without the standard
inode_set_flags() helper.

More importantly, changing S_DAX after an inode has been instantiated
breaks VFS invariants. If page cache pages were populated prior to this
flag change, subsequent I/O will use DAX paths while stale page cache pages
remain, potentially triggering VM_BUG_ON or silent data corruption.

> +	} else {
> +		pr_debug("%s: file already had metadata\n", __func__);
> +		__famfs_meta_free(meta);
> +		/* rc is 0 - the file is valid */
> +	}
> +
> +	inode_unlock(inode);
> +	return 0;

[ ... ]

> --- /dev/null
> +++ b/fs/fuse/famfs_kfmap.h
> @@ -0,0 +1,67 @@

[ ... ]

> +/*
> + * Each famfs dax file has this hanging from its fuse_inode->famfs_meta
> + */
> +struct famfs_file_meta {
> +	bool                   error;
> +	enum famfs_file_type   file_type;
> +	size_t                 file_size;

[Severity: Medium]
Should this be a u64 or loff_t?

As mentioned earlier, declaring file_size as a size_t on 32-bit systems
limits it to a 32-bit unsigned integer, causing truncation for files
larger than 4GB.

> +	enum famfs_extent_type fm_extent_type;
> +	u64 dev_bitmap; /* bitmap of referenced daxdevs by index */

[ ... ]

> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1642,6 +1642,9 @@ extern void fuse_sysctl_unregister(void);
>  /* famfs.c */
>  
>  #if IS_ENABLED(CONFIG_FUSE_FAMFS_DAX)

[ ... ]

>  static inline void famfs_meta_free(struct fuse_inode *fi)
>  {
> -	famfs_meta_set(fi, NULL);
> +	if (fi->famfs_meta != NULL) {
> +		__famfs_meta_free(fi->famfs_meta);
> +		famfs_meta_set(fi, NULL);
> +	}
>  }

[Severity: Low]
Does this create a dangling pointer window?

The metadata memory is freed via __famfs_meta_free() before the pointer
is nullified by famfs_meta_set().

While this occurs during inode eviction where concurrency is limited, it
creates a brief use-after-free window and breaks the expected safety
guarantees of a cleanup helper. Reversing the order would be safer.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260318203054.4344.fuse@groves.net?part=4

  reply	other threads:[~2026-08-13 22:08 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-19  1:27 [PATCH BUNDLE v8] famfs: Fabric-Attached Memory File System John Groves
2026-03-19  1:27 ` [PATCH V8 0/8] dax: prepare for famfs John Groves
2026-03-19  1:28   ` [PATCH V8 1/8] dax: move dax_pgoff_to_phys from [drivers/dax/] device.c to bus.c John Groves
2026-03-19  1:28   ` [PATCH V8 2/8] dax: Factor out dax_folio_reset_order() helper John Groves
2026-03-19 11:30     ` Jonathan Cameron
2026-03-21  0:27       ` John Groves
2026-03-19  1:28   ` [PATCH V8 3/8] dax: add fsdev.c driver for fs-dax on character dax John Groves
2026-03-19 12:20     ` Jonathan Cameron
2026-03-21  0:44       ` John Groves
2026-03-23 12:12         ` Jonathan Cameron
2026-03-23 17:21           ` John Groves
2026-03-19  1:29   ` [PATCH V8 4/8] dax: Save the kva from memremap John Groves
2026-03-19  1:29   ` [PATCH V8 5/8] dax: Add dax_operations for use by fs-dax on fsdev dax John Groves
2026-03-19  1:30   ` [PATCH V8 6/8] dax: Add dax_set_ops() for setting dax_operations at bind time John Groves
2026-03-19  1:30   ` [PATCH V8 7/8] dax: Add fs_dax_get() func to prepare dax for fs-dax usage John Groves
2026-03-19  1:30   ` [PATCH V8 8/8] dax: export dax_dev_get() John Groves
2026-03-19  1:30 ` [PATCH V8 00/10] famfs: port into fuse John Groves
2026-03-19 13:17   ` [PATCH V8 01/10] famfs_fuse: Update macro s/FUSE_IS_DAX/FUSE_IS_VIRTIO_DAX/ John Groves
2026-03-19 13:18   ` [PATCH V8 02/10] famfs_fuse: Basic fuse kernel ABI enablement for famfs John Groves
2026-03-19 13:18   ` [PATCH V8 03/10] famfs_fuse: Plumb the GET_FMAP message/response John Groves
2026-03-19 13:19   ` [PATCH V8 04/10] famfs_fuse: Create files with famfs fmaps John Groves
2026-08-13 22:08     ` sashiko-bot [this message]
2026-03-19 13:19   ` [PATCH V8 05/10] famfs_fuse: GET_DAXDEV message and daxdev_table John Groves
2026-08-13 22:21     ` sashiko-bot
2026-03-19 13:19   ` [PATCH V8 06/10] famfs_fuse: Plumb dax iomap and fuse read/write/mmap John Groves
2026-03-19 13:19   ` [PATCH V8 07/10] famfs_fuse: Add holder_operations for dax notify_failure() John Groves
2026-03-19 13:20   ` [PATCH V8 08/10] famfs_fuse: Add DAX address_space_operations with noop_dirty_folio John Groves
2026-03-19 13:20   ` [PATCH V8 09/10] famfs_fuse: Add famfs fmap metadata documentation John Groves
2026-03-19 13:20   ` [PATCH V8 10/10] famfs_fuse: Add documentation John Groves

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260813220837.3F58E1F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=john@jagalactic.com \
    --cc=linux-cxl@vger.kernel.org \
    --cc=nvdimm@lists.linux.dev \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox