Re: [PATCH V12 07/12] famfs: MAP_CREATE ioctl and fmap ingest (ABI 44)

[email protected] Mon, 03 Aug 2026 02:42:26 +0000
Newsgroups dev.linux.lists.nvdimm,org.kernel.vger.linux-cxl
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 7 potential issue(=
s) to consider:

New issues:
- [Critical] The FAMFSIOC_MAP_CREATE ioctl lacks a privilege check, allowin=
g unprivileged users to map arbitrary physical DAX memory.
- [High] 64-bit file size and extent lengths are silently truncated to size=
_t on 32-bit architectures.
- [High] Missing overflow checks on extent lengths and physical offsets.
- [High] Race condition in state publication leads to permanent file corrup=
tion (Denial of Service).
- [High] Persistent memory allocations for file metadata lack __GFP_ACCOUNT=
, bypassing memcg limits.
- [Low] Format string type mismatch for size_t arguments causes compiler wa=
rnings.

Pre-existing issues:
- [High] NULL pointer dereference in famfs_meta_to_dax_offset_interleaved.
--

--- Patch [7]: [PATCH V12 07/12] famfs: MAP_CREATE ioctl and fmap ingest (A=
BI 44) ---
commit 7c28fdf850baa1b17b165378da6b0515248fad98
Author: John Groves <[email protected]>

famfs: MAP_CREATE ioctl and fmap ingest (ABI 44)

This commit adds the famfs file ioctl handler, including the
FAMFSIOC_MAP_CREATE ioctl to ingest user-provided mapping extent messages.
It parses both simple and interleaved wire formats into a famfs_file_meta
structure that is published on the inode.

> diff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal.h
> --- a/fs/famfs/famfs_internal.h
> +++ b/fs/famfs/famfs_internal.h
> @@ -15,8 +15,52 @@
>  #include <linux/bits.h>
>  #include <linux/build_bug.h>
> =20
> +#include <linux/famfs_ioctl.h>
> +
>  extern const struct file_operations famfs_file_operations;
[ ... ]
> +struct famfs_file_meta {
> +	bool                   error;
> +	enum famfs_file_type   file_type;
> +	size_t                 file_size;

[Severity: High]
Will this type choice truncate the 64-bit file size to 32 bits on 32-bit
architectures? If fmh.file_size exceeds 4GB, the upper 32 bits will be
lost, which could silently corrupt the file size and allow validation
checks to be bypassed.

> +	enum famfs_ioc_ext_type fm_extent_type;
> +	u64                    dev_bitmap; /* referenced daxdev indices */
> +	union { /* This will make code a bit more readable */
> +		struct {
> +			size_t         fm_nextents;
> +			struct famfs_meta_simple_ext  *se;
> +		};
> +		struct {
> +			size_t         fm_niext;
> +			struct famfs_meta_interleaved_ext *ie;
> +		};
> +	};
> +};

> diff --git a/fs/famfs/famfs_file.c b/fs/famfs/famfs_file.c
> --- a/fs/famfs/famfs_file.c
> +++ b/fs/famfs/famfs_file.c
[ ... ]
> @@ -60,6 +81,250 @@
> +static int
> +famfs_file_init_dax(struct file *file, void __user *arg)
> +{
[ ... ]
> +	fmap_buf =3D kvmalloc(fmh.fmap_size, GFP_KERNEL);

[Severity: High]
Should these metadata allocations use the __GFP_ACCOUNT flag?
This allocation scales with user input. Without __GFP_ACCOUNT, an
unprivileged user mapping up to 4MB of metadata per file could bypass
memory cgroup limits and potentially trigger a host-wide out-of-memory
condition.

> +	if (!fmap_buf)
> +		return -ENOMEM;
> +
> +	if (copy_from_user(fmap_buf, arg, fmh.fmap_size)) {
> +		rc =3D -EFAULT;
> +		goto out;
> +	}
> +	next_offset =3D sizeof(fmh);	/* start of the extent list */
> +
> +	meta =3D kzalloc_obj(*meta, GFP_KERNEL);
> +	if (!meta) {
> +		rc =3D -ENOMEM;
> +		goto out;
> +	}
> +
> +	meta->error =3D false;
> +	meta->file_type =3D fmh.file_type;
> +	meta->file_size =3D fmh.file_size;
> +	meta->fm_extent_type =3D fmh.ext_type;
> +
> +	switch (fmh.ext_type) {
> +	case FAMFS_IOC_EXT_SIMPLE: {
> +		struct famfs_ioc_simple_ext *se_in =3D fmap_buf + next_offset;
[ ... ]
> +		for (i =3D 0; i < fmh.nextents; i++) {
> +			meta->se[i].dev_index  =3D se_in[i].se_devindex;
> +			meta->se[i].ext_offset =3D se_in[i].se_offset;
> +			meta->se[i].ext_len    =3D se_in[i].se_len;
> +
> +			if (meta->se[i].dev_index >=3D FAMFS_MAX_DAXDEVS) {
> +				rc =3D -EINVAL;
> +				goto out;
> +			}
> +			meta->dev_bitmap |=3D BIT_ULL(meta->se[i].dev_index);
> +			errs +=3D famfs_check_ext_alignment(&meta->se[i]);
> +			extent_total +=3D meta->se[i].ext_len;

[Severity: High]
Is there a potential integer overflow when accumulating extent_total here?
If the sum of ext_len wraps around, it might pass the extent_total check be=
low
even if the extents do not actually cover the file size. Could ext_offset +
ext_len also overflow U64_MAX and cause out-of-bounds physical memory
accesses when constructing the iomap?

> +		}
> +		break;
> +	}
[ ... ]
> +	/* Publish the famfs metadata on inode->i_private */
> +	inode_lock(inode);
> +	if (inode->i_private) {
> +		rc =3D -EEXIST; /* file already has famfs metadata */
> +	} else {
> +		inode->i_private =3D meta;
> +		i_size_write(inode, meta->file_size);

[Severity: High]
Could a concurrent reader observe the published metadata but a stale i_size?
Because inode->i_private is assigned before i_size_write(), a lockless
reader could call famfs_file_invalid() in the middle of this window. It
would see a size mismatch and incorrectly set meta->error =3D true, which
permanently marks the file as corrupted.

> +		inode->i_flags |=3D S_DAX;
> +		meta =3D NULL; /* owned by the inode now */
> +		rc =3D 0;
> +	}
> +	inode_unlock(inode);
> +
> +out:
> +	kvfree(fmap_buf);
> +	if (meta)
> +		famfs_meta_free(meta);
> +	return rc;
> +}
> +
> +/**
> + * famfs_file_ioctl() - Top-level famfs file ioctl handler
> + * @file: the file
> + * @cmd:  ioctl opcode
> + * @arg:  ioctl opcode argument (if any)
> + */
> +static long
> +famfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +	struct inode *inode =3D file_inode(file);
> +	struct famfs_fs_info *fsi =3D inode->i_sb->s_fs_info;
> +	long rc;
> +
> +	if (fsi->deverror && (cmd !=3D FAMFSIOC_NOP))
> +		return -ENODEV;
> +
> +	switch (cmd) {
> +	case FAMFSIOC_NOP:
> +		rc =3D 0;
> +		break;
> +
> +	case FAMFSIOC_MAP_CREATE:
> +		rc =3D famfs_file_init_dax(file, (void __user *)arg);

[Severity: Critical]
Does this ioctl command require a privilege check like capable(CAP_SYS_ADMI=
N)?
Without it, it appears any unprivileged user who can open the file could map
arbitrary physical DAX memory directly into their processes.

> +		break;
> +
> +	default:
> +		rc =3D -ENOTTY;
> +		break;
> +	}
> +
> +	return rc;
> +}
[ ... ]
> @@ -93,9 +397,25 @@ const struct vm_operations_struct famfs_file_vm_ops =
=3D {
>  static ssize_t
>  famfs_file_invalid(struct inode *inode)
>  {
> +	struct famfs_file_meta *meta =3D inode->i_private;
> +	size_t i_size =3D i_size_read(inode);
> +
> +	if (!meta) {
> +		pr_debug("%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 !=3D meta->file_size) {
> +		pr_warn("%s: i_size overwritten from %ld to %ld\n",
> +		       __func__, meta->file_size, i_size);

[Severity: Low]
Should this format string use %zu instead of %ld for size_t variables?
This can cause compiler warnings and misformatted logs on 32-bit platforms.

> +		meta->error =3D true;
> +		return -ENXIO;
> +	}

[ ... ]

[Severity: High]
This is a pre-existing issue, but can famfs_meta_to_dax_offset_interleaved()
dereference a NULL pointer if called on an uninitialized file?

fs/famfs/famfs_file.c:famfs_meta_to_dax_offset_interleaved() {
    ...
    struct famfs_file_meta *meta =3D inode->i_private;
    ...
    if (meta->fm_extent_type !=3D FAMFS_IOC_EXT_INTERLEAVE) {
        pr_debug("%s: bad extent type\n", __func__);
        goto err_out;
    }

    if (fsi->deverror || famfs_file_invalid(inode))
        goto err_out;
    ...
}

The meta pointer is dereferenced to check fm_extent_type before the call to
famfs_file_invalid(), which is where the check for a NULL meta pointer
actually happens. If the file is uninitialized (inode->i_private is NULL),
this will cause a panic.

--=20
Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/0100019fc572ca94-ec=
[email protected]?part=3D7