xfs: refuse a pNFS LAYOUTCOMMIT of a range without blocks

A client can commit a hole in a LAYOUTCOMMIT, because nfsd does not
check the extents against its layouts. xfs_iomap_write_unwritten() then
allocates written blocks there. A large hole overruns the block
reservation and shuts XFS down. A small one exposes stale data.

Refuse such a range with -EIO, using XFS_BMAPI_CONVERT_ONLY.

Link: https://lore.kernel.org/r/20261014130834.GB15773@lst.de
Fixes: 527851124d ("xfs: implement pNFS export operations")
Cc: stable@vger.kernel.org
Signed-off-by: Daejun Park <daejun7.park@samsung.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
This commit is contained in:
Daejun Park 2026-10-29 11:23:00 +09:00 committed by Carlos Maiolino
parent f3ca3498df
commit bcf0b7f18a
5 changed files with 29 additions and 7 deletions

View File

@ -666,7 +666,7 @@ xfs_dio_write_end_io(
* they are converted.
*/
if (flags & IOMAP_DIO_UNWRITTEN) {
error = xfs_iomap_write_unwritten(ip, offset, size, true);
error = xfs_iomap_write_unwritten(ip, offset, size, true, 0);
goto out;
}

View File

@ -237,7 +237,7 @@ xfs_end_ioend_write(
if (ioend->io_flags & IOMAP_IOEND_SHARED)
error = xfs_reflink_end_cow(ip, offset, size);
else if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN)
error = xfs_iomap_write_unwritten(ip, offset, size, false);
error = xfs_iomap_write_unwritten(ip, offset, size, false, 0);
if (!error &&
!(ioend->io_flags & IOMAP_IOEND_DIRECT) &&

View File

@ -614,7 +614,8 @@ xfs_iomap_write_unwritten(
xfs_inode_t *ip,
xfs_off_t offset,
xfs_off_t count,
bool update_isize)
bool update_isize,
uint32_t bmapi_flags)
{
xfs_mount_t *mp = ip->i_mount;
xfs_fileoff_t offset_fsb;
@ -676,8 +677,21 @@ xfs_iomap_write_unwritten(
*/
nimaps = 1;
error = xfs_bmapi_write(tp, ip, offset_fsb, count_fsb,
XFS_BMAPI_CONVERT, resblks, &imap,
&nimaps);
XFS_BMAPI_CONVERT | bmapi_flags,
resblks, &imap, &nimaps);
if (error == -ENOSR && (bmapi_flags & XFS_BMAPI_CONVERT_ONLY)) {
/*
* Nothing was mapped: the rest of the range starts
* with a hole or a delalloc extent. Nothing was
* changed either, but xfs_iext_count_extend() may
* have logged the inode, and cancelling a dirty
* transaction shuts the filesystem down. Commit it,
* as xfs_alloc_file_space() does on -ENOSR.
*/
error = xfs_trans_commit(tp);
xfs_iunlock(ip, XFS_ILOCK_EXCL);
return error ? error : -EIO;
}
if (error)
goto error_on_bmapi_transaction;

View File

@ -15,7 +15,8 @@ struct xfs_zone_alloc_ctx;
int xfs_iomap_write_direct(struct xfs_inode *ip, xfs_fileoff_t offset_fsb,
xfs_fileoff_t count_fsb, unsigned int flags,
struct xfs_bmbt_irec *imap, u64 *sequence);
int xfs_iomap_write_unwritten(struct xfs_inode *, xfs_off_t, xfs_off_t, bool);
int xfs_iomap_write_unwritten(struct xfs_inode *ip, xfs_off_t offset,
xfs_off_t count, bool update_isize, uint32_t bmapi_flags);
xfs_fileoff_t xfs_iomap_eof_align_last_fsb(struct xfs_inode *ip,
xfs_fileoff_t end_fsb);

View File

@ -344,7 +344,14 @@ xfs_fs_commit_blocks(
(end - 1) >> PAGE_SHIFT);
WARN_ON_ONCE(error);
error = xfs_iomap_write_unwritten(ip, start, length, false);
/*
* A client may only write to blocks that a layout handed out,
* and xfs_fs_map_blocks() allocates blocks for any hole in a
* write layout, so refuse to allocate blocks for a hole or a
* delalloc extent in the committed range.
*/
error = xfs_iomap_write_unwritten(ip, start, length, false,
XFS_BMAPI_CONVERT_ONLY);
if (error)
goto out_drop_iolock;
}