Source: zfs-linux Version: 2.4.4-1 Severity: important Tags: upstream fixed-upstream patch Forwarded: https://github.com/openzfs/zfs/pull/19253
Hello, Could you include the attached fix in the next unstable upload? Upstream PR #19253 fixes a deadlock in zfs_xattr_owner_unlinked() that can prevent a pool from syncing. It was reviewed by Brian Behlendorf and merged upstream on 8 October. The patch applies unchanged to 2.4.4-1, touches one file, and makes no ABI or on-disk format changes. I built the patched Debian packages in an unstable/amd64 Docker container and tested them in a sid VM on Linux 7.2.11+deb14-amd64. Both release and debug modules passed Debian's kernel smoke suite, all 24 test cases in the tmpfile/xattr/mmap groups, and mmap/xattr/unlink stress with xattr=sa and xattr=dir. Pool sync, export and reimport completed without errors. Both patched and unpatched builds report the same strlcat checkabi failures; direct ABI comparisons between their libraries pass. I can provide build/test logs or a Salsa merge request if helpful. Thanks, Jaromir Hamala
From e9e24ce2c8046c2573d49468e1133770b649018f Mon Sep 17 00:00:00 2001 From: Jaromir Hamala <[email protected]> Date: Thu, 8 Oct 2026 18:25:07 +0200 Subject: [PATCH] ZIL: avoid deadlock in xattr owner check zfs_xattr_owner_unlinked() runs with an assigned transaction. A final zrele() can run inode or vnode cleanup inline. During Linux writeback, cleanup may wait for the caller's own I_SYNC state. Cleanup may also open another transaction while the current transaction remains assigned. The caller cannot return to commit its transaction, so the txg remains open and cannot sync. Treat the input znode as borrowed on all platforms. Release only parents acquired by zfs_zget() with zfs_zrele_async(). This removes the Linux zhold()/zrele() pair and the platform split. Check the current walk node in the assertion. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Jaromir Hamala <[email protected]> Closes #19253 --- module/zfs/zfs_log.c | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/module/zfs/zfs_log.c b/module/zfs/zfs_log.c index 1bec2850b..11652a63c 100644 --- a/module/zfs/zfs_log.c +++ b/module/zfs/zfs_log.c @@ -37,6 +37,7 @@ #include <sys/spa.h> #include <sys/zfs_fuid.h> #include <sys/dsl_dataset.h> +#include <sys/zfs_vnops.h> /* * These zfs_log_* functions must be called within a dmu tx, in one @@ -219,50 +220,30 @@ zfs_xattr_owner_unlinked(znode_t *zp) { int unlinked = 0; znode_t *dzp; -#ifdef __FreeBSD__ znode_t *tzp = zp; /* - * zrele drops the vnode lock which violates the VOP locking contract - * on FreeBSD. See comment at the top of zfs_replay.c for more detail. - */ - /* - * if zp is XATTR node, keep walking up via z_xattr_parent until we - * get the owner + * zp is borrowed and must not be released here. Only release + * references acquired by zfs_zget(), and release them asynchronously + * because zfs_log_* functions are called with an assigned transaction. + * + * If zp is an XATTR node, keep walking up via z_xattr_parent until we + * get the owner. */ while (tzp->z_pflags & ZFS_XATTR) { - ASSERT3U(zp->z_xattr_parent, !=, 0); + ASSERT3U(tzp->z_xattr_parent, !=, 0); if (zfs_zget(ZTOZSB(tzp), tzp->z_xattr_parent, &dzp) != 0) { unlinked = 1; break; } if (tzp != zp) - zrele(tzp); + zfs_zrele_async(tzp); tzp = dzp; unlinked = tzp->z_unlinked; } if (tzp != zp) - zrele(tzp); -#else - zhold(zp); - /* - * if zp is XATTR node, keep walking up via z_xattr_parent until we - * get the owner - */ - while (zp->z_pflags & ZFS_XATTR) { - ASSERT3U(zp->z_xattr_parent, !=, 0); - if (zfs_zget(ZTOZSB(zp), zp->z_xattr_parent, &dzp) != 0) { - unlinked = 1; - break; - } - - zrele(zp); - zp = dzp; - unlinked = zp->z_unlinked; - } - zrele(zp); -#endif + zfs_zrele_async(tzp); return (unlinked); } -- 2.53.0

