From ae37f05d87adba58e68e793fcddb47c356acec68 Mon Sep 17 00:00:00 2001 From: Gality <68463495+Gality369@users.noreply.github.com> Date: Thu, 7 May 2026 00:40:14 +0800 Subject: [PATCH] linux: verify stale znodes in legacy fallocate The mode=0 and FALLOC_FL_KEEP_SIZE preallocation path can reach zfs_freesp() directly and call zfs_statvfs() before going through the normal zpl_enter_verify_zp() boundary. When zfs_rezget() tears down a failed SA reload, a stale inode may remain alive in the VFS with z_sa_hdl cleared. The unchecked fallocate path can then reach sa_lookup(zp->z_sa_hdl, ...) through zfs_statvfs() or zfs_freesp() and crash on a NULL SA handle. Use zfs_enter_verify_zp() in zfs_statvfs() so stale znodes are rejected under the teardown lock for both fallocate and statfs. Also wrap the direct zfs_freesp() call in zpl_enter_verify_zp()/zfs_exit() so this path follows the same validation rules as the other Linux ZPL file operations. Fixes: f734301d2267 ("linux: add basic fallocate(mode=0/2) compatibility") Reviewed-by: Brian Behlendorf Signed-off-by: ZhengYuan Huang Co-authored-by: gality369 Closes #18458 --- module/os/linux/zfs/zfs_vfsops.c | 5 ++--- module/os/linux/zfs/zpl_file.c | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 9c0d9255184..d7b50242992 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -953,11 +953,12 @@ zfs_statfs_project(zfsvfs_t *zfsvfs, znode_t *zp, struct kstatfs *statp, int zfs_statvfs(struct inode *ip, struct kstatfs *statp) { + znode_t *zp = ITOZ(ip); zfsvfs_t *zfsvfs = ITOZSB(ip); uint64_t refdbytes, availbytes, usedobjs, availobjs; int err = 0; - if ((err = zfs_enter(zfsvfs, FTAG)) != 0) + if ((err = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) return (err); dmu_objset_space(zfsvfs->z_os, @@ -1013,8 +1014,6 @@ zfs_statvfs(struct inode *ip, struct kstatfs *statp) if (dmu_objset_projectquota_enabled(zfsvfs->z_os) && dmu_objset_projectquota_present(zfsvfs->z_os)) { - znode_t *zp = ITOZ(ip); - if (zp->z_pflags & ZFS_PROJINHERIT && zp->z_projid && zpl_is_valid_projid(zp->z_projid)) err = zfs_statfs_project(zfsvfs, zp, statp, bshift); diff --git a/module/os/linux/zfs/zpl_file.c b/module/os/linux/zfs/zpl_file.c index ffe227796f0..6d57bff5654 100644 --- a/module/os/linux/zfs/zpl_file.c +++ b/module/os/linux/zfs/zpl_file.c @@ -673,6 +673,8 @@ static long zpl_fallocate_common(struct inode *ip, int mode, loff_t offset, loff_t len) { cred_t *cr = CRED(); + znode_t *zp = ITOZ(ip); + zfsvfs_t *zfsvfs = ITOZSB(ip); loff_t olen; fstrans_cookie_t cookie; int error = 0; @@ -706,7 +708,7 @@ zpl_fallocate_common(struct inode *ip, int mode, loff_t offset, loff_t len) bf.l_len = len; bf.l_pid = 0; - error = -zfs_space(ITOZ(ip), F_FREESP, &bf, O_RDWR, offset, cr); + error = -zfs_space(zp, F_FREESP, &bf, O_RDWR, offset, cr); } else if ((mode & ~FALLOC_FL_KEEP_SIZE) == 0) { unsigned int percent = zfs_fallocate_reserve_percent; struct kstatfs statfs; @@ -721,7 +723,7 @@ zpl_fallocate_common(struct inode *ip, int mode, loff_t offset, loff_t len) * Use zfs_statvfs() instead of dmu_objset_space() since it * also checks project quota limits, which are relevant here. */ - error = zfs_statvfs(ip, &statfs); + error = -zfs_statvfs(ip, &statfs); if (error) goto out_unmark; @@ -734,8 +736,14 @@ zpl_fallocate_common(struct inode *ip, int mode, loff_t offset, loff_t len) error = -ENOSPC; goto out_unmark; } - if (!(mode & FALLOC_FL_KEEP_SIZE) && offset + len > olen) - error = zfs_freesp(ITOZ(ip), offset + len, 0, 0, FALSE); + if (!(mode & FALLOC_FL_KEEP_SIZE) && offset + len > olen) { + error = zpl_enter_verify_zp(zfsvfs, zp, FTAG); + if (error) + goto out_unmark; + + error = -zfs_freesp(zp, offset + len, 0, 0, FALSE); + zfs_exit(zfsvfs, FTAG); + } } out_unmark: spl_fstrans_unmark(cookie);