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: f734301d22
("linux: add basic fallocate(mode=0/2) compatibility")
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
Co-authored-by: gality369 <gality369@example.com>
Closes #18458
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user