]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
xfs: avoid dereferencing log items after push callbacks
authorYuto Ohnuki <ytohnuki@amazon.com>
Tue, 10 Mar 2026 18:38:38 +0000 (18:38 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 11 Apr 2026 12:16:37 +0000 (14:16 +0200)
commit 79ef34ec0554ec04bdbafafbc9836423734e1bd6 upstream.

After xfsaild_push_item() calls iop_push(), the log item may have been
freed if the AIL lock was dropped during the push. Background inode
reclaim or the dquot shrinker can free the log item while the AIL lock
is not held, and the tracepoints in the switch statement dereference
the log item after iop_push() returns.

Fix this by capturing the log item type, flags, and LSN before calling
xfsaild_push_item(), and introducing a new xfs_ail_push_class trace
event class that takes these pre-captured values and the ailp pointer
instead of the log item pointer.

Reported-by: syzbot+652af2b3c5569c4ab63c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=652af2b3c5569c4ab63c
Fixes: 90c60e164012 ("xfs: xfs_iflush() is no longer necessary")
Cc: stable@vger.kernel.org # v5.9
Signed-off-by: Yuto Ohnuki <ytohnuki@amazon.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/xfs/xfs_trace.c
fs/xfs/xfs_trace.h
fs/xfs/xfs_trans_ail.c

index d269ef57ff0166c0ef17d0c9b58b06e59932c673..dcf9af0108c1d56e98684206fafe7722b06fb505 100644 (file)
@@ -22,6 +22,7 @@
 #include "xfs_trans.h"
 #include "xfs_log.h"
 #include "xfs_log_priv.h"
+#include "xfs_trans_priv.h"
 #include "xfs_buf_item.h"
 #include "xfs_quota.h"
 #include "xfs_dquot_item.h"
index a9e3081b6625d039bf9d04c8f34d5504b84b7d45..a7245de495a5ceb89d94f3c8c6aa7ec669370273 100644 (file)
@@ -47,6 +47,7 @@
 #include <linux/tracepoint.h>
 
 struct xfs_agf;
+struct xfs_ail;
 struct xfs_alloc_arg;
 struct xfs_attr_list_context;
 struct xfs_buf_log_item;
@@ -1335,14 +1336,41 @@ TRACE_EVENT(xfs_log_force,
 DEFINE_EVENT(xfs_log_item_class, name, \
        TP_PROTO(struct xfs_log_item *lip), \
        TP_ARGS(lip))
-DEFINE_LOG_ITEM_EVENT(xfs_ail_push);
-DEFINE_LOG_ITEM_EVENT(xfs_ail_pinned);
-DEFINE_LOG_ITEM_EVENT(xfs_ail_locked);
-DEFINE_LOG_ITEM_EVENT(xfs_ail_flushing);
 DEFINE_LOG_ITEM_EVENT(xfs_cil_whiteout_mark);
 DEFINE_LOG_ITEM_EVENT(xfs_cil_whiteout_skip);
 DEFINE_LOG_ITEM_EVENT(xfs_cil_whiteout_unpin);
 
+DECLARE_EVENT_CLASS(xfs_ail_push_class,
+       TP_PROTO(struct xfs_ail *ailp, uint type, unsigned long flags, xfs_lsn_t lsn),
+       TP_ARGS(ailp, type, flags, lsn),
+       TP_STRUCT__entry(
+               __field(dev_t, dev)
+               __field(uint, type)
+               __field(unsigned long, flags)
+               __field(xfs_lsn_t, lsn)
+       ),
+       TP_fast_assign(
+               __entry->dev = ailp->ail_log->l_mp->m_super->s_dev;
+               __entry->type = type;
+               __entry->flags = flags;
+               __entry->lsn = lsn;
+       ),
+       TP_printk("dev %d:%d lsn %d/%d type %s flags %s",
+                 MAJOR(__entry->dev), MINOR(__entry->dev),
+                 CYCLE_LSN(__entry->lsn), BLOCK_LSN(__entry->lsn),
+                 __print_symbolic(__entry->type, XFS_LI_TYPE_DESC),
+                 __print_flags(__entry->flags, "|", XFS_LI_FLAGS))
+)
+
+#define DEFINE_AIL_PUSH_EVENT(name) \
+DEFINE_EVENT(xfs_ail_push_class, name, \
+       TP_PROTO(struct xfs_ail *ailp, uint type, unsigned long flags, xfs_lsn_t lsn), \
+       TP_ARGS(ailp, type, flags, lsn))
+DEFINE_AIL_PUSH_EVENT(xfs_ail_push);
+DEFINE_AIL_PUSH_EVENT(xfs_ail_pinned);
+DEFINE_AIL_PUSH_EVENT(xfs_ail_locked);
+DEFINE_AIL_PUSH_EVENT(xfs_ail_flushing);
+
 DECLARE_EVENT_CLASS(xfs_ail_class,
        TP_PROTO(struct xfs_log_item *lip, xfs_lsn_t old_lsn, xfs_lsn_t new_lsn),
        TP_ARGS(lip, old_lsn, new_lsn),
index f51df7d94ef74ca8e069f1ceec5eedacde3191df..fc2cbac4a8b70d72a6194dc9a10d49c2d0781ad5 100644 (file)
@@ -389,6 +389,12 @@ xfsaild_resubmit_item(
        return XFS_ITEM_SUCCESS;
 }
 
+/*
+ * Push a single log item from the AIL.
+ *
+ * @lip may have been released and freed by the time this function returns,
+ * so callers must not dereference the log item afterwards.
+ */
 static inline uint
 xfsaild_push_item(
        struct xfs_ail          *ailp,
@@ -474,20 +480,26 @@ xfsaild_push(
 
        lsn = lip->li_lsn;
        while ((XFS_LSN_CMP(lip->li_lsn, target) <= 0)) {
-               int     lock_result;
+               int             lock_result;
+               uint            type = lip->li_type;
+               unsigned long   flags = lip->li_flags;
+               xfs_lsn_t       item_lsn = lip->li_lsn;
 
                /*
                 * Note that iop_push may unlock and reacquire the AIL lock.  We
                 * rely on the AIL cursor implementation to be able to deal with
                 * the dropped lock.
+                *
+                * The log item may have been freed by the push, so it must not
+                * be accessed or dereferenced below this line.
                 */
                lock_result = xfsaild_push_item(ailp, lip);
                switch (lock_result) {
                case XFS_ITEM_SUCCESS:
                        XFS_STATS_INC(mp, xs_push_ail_success);
-                       trace_xfs_ail_push(lip);
+                       trace_xfs_ail_push(ailp, type, flags, item_lsn);
 
-                       ailp->ail_last_pushed_lsn = lsn;
+                       ailp->ail_last_pushed_lsn = item_lsn;
                        break;
 
                case XFS_ITEM_FLUSHING:
@@ -503,22 +515,22 @@ xfsaild_push(
                         * AIL is being flushed.
                         */
                        XFS_STATS_INC(mp, xs_push_ail_flushing);
-                       trace_xfs_ail_flushing(lip);
+                       trace_xfs_ail_flushing(ailp, type, flags, item_lsn);
 
                        flushing++;
-                       ailp->ail_last_pushed_lsn = lsn;
+                       ailp->ail_last_pushed_lsn = item_lsn;
                        break;
 
                case XFS_ITEM_PINNED:
                        XFS_STATS_INC(mp, xs_push_ail_pinned);
-                       trace_xfs_ail_pinned(lip);
+                       trace_xfs_ail_pinned(ailp, type, flags, item_lsn);
 
                        stuck++;
                        ailp->ail_log_flush++;
                        break;
                case XFS_ITEM_LOCKED:
                        XFS_STATS_INC(mp, xs_push_ail_locked);
-                       trace_xfs_ail_locked(lip);
+                       trace_xfs_ail_locked(ailp, type, flags, item_lsn);
 
                        stuck++;
                        break;