mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] jffs2: prevent xattr_sem -> fs_reclaim deadlock
@ 2026-10-29  9:36 Shuai Liu
  0 siblings, 0 replies; only message in thread
From: Shuai Liu @ 2026-10-29  9:36 UTC (permalink / raw)
  To: linux-mtd
  Cc: dwmw2, richard, chengzhihao1, syzbot+fdfd8fdf716c90baef0f,
	linux-kernel, Shuai Liu

do_jffs2_setxattr() and several other xattr paths allocate memory
(e.g. jffs2_alloc_xattr_datum() via kmem_cache_alloc() inside
create_xattr_datum()) and write to flash while holding
c->xattr_sem, using GFP_KERNEL.  Such allocations may enter direct
reclaim and take fs_reclaim, establishing the
xattr_sem -> fs_reclaim dependency.

kswapd, on the other hand, holds fs_reclaim while shrinking
dcaches and inodes: shrink_slab() -> prune_dcache_sb() ->
dentry_kill() -> evict() -> jffs2_do_clear_inode() ->
jffs2_xattr_delete_inode() takes down_write(&c->xattr_sem),
giving the reverse fs_reclaim -> xattr_sem dependency.  The
resulting lock cycle can deadlock:

    CPU0                    CPU1
    ----                    ----
    lock(xattr_sem)         lock(fs_reclaim)
    lock(fs_reclaim)        lock(xattr_sem)

syzbot reports exactly this circular dependency with lockdep [1]:
kswapd0 acquiring xattr_sem in jffs2_xattr_delete_inode() while
holding fs_reclaim, with the recorded chain showing
do_jffs2_setxattr() allocating (jffs2_alloc_xattr_datum() ->
kmem_cache_alloc() -> fs_reclaim_acquire) while holding
xattr_sem.

Fix it by wrapping every critical section that holds c->xattr_sem
and can allocate memory or write flash with
memalloc_nofs_save()/memalloc_nofs_restore(), so allocations made
under xattr_sem never enter direct reclaim.  This cuts the
xattr_sem -> fs_reclaim edge; reclaim contexts taking xattr_sem
remain harmless.  The covered paths are:

  - jffs2_xattr_delete_inode(): delete_xattr_ref() writes flash
  - check_xattr_ref_inode(): load_xattr_datum() allocates,
    delete_xattr_ref() writes flash
  - the write-lock upgrade sections of jffs2_listxattr() and
    do_jffs2_getxattr(): load_xattr_datum() allocates
  - do_jffs2_setxattr(): create_xattr_datum() allocates,
    save_xattr_ref() writes flash (both lock sections)
  - jffs2_garbage_collect_xattr_datum() /
    jffs2_garbage_collect_xattr_ref(): jffs2_reserve_space_gc()
    may trigger GC, save_xattr_datum()/save_xattr_ref() write
    flash
  - jffs2_verify_xattr(): do_verify_xattr_datum() reads flash

jffs2_xattr_free_inode() is deliberately left alone: it only
frees memory and cannot allocate.

A related but distinct cycle between fs_reclaim and &f->sem
(jffs2_do_read_inode() allocates while holding f->sem during
iget) also exists; it is a separate issue not addressed here.

Verified with the syzbot reproducer under QEMU (mtdram + JFFS2 +
OverlayFS, CONFIG_PROVE_LOCKING=y): without this patch lockdep
reports the xattr_sem <-> fs_reclaim cycle reproduced above;
with this patch that warning no longer appears.

[1] https://syzkaller.appspot.com/bug?id=eefefd759ef3b48ddad4da086ff8f4b9194576f9

Reported-by: syzbot+fdfd8fdf716c90baef0f@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=fdfd8fdf716c90baef0f
Signed-off-by: Shuai Liu <847807867@qq.com>
---
 fs/jffs2/xattr.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/fs/jffs2/xattr.c b/fs/jffs2/xattr.c
index 6b4f68593c29..7b873fe2e08d 100644
--- a/fs/jffs2/xattr.c
+++ b/fs/jffs2/xattr.c
@@ -15,6 +15,7 @@
 
 #include <linux/kernel.h>
 #include <linux/slab.h>
+#include <linux/sched/mm.h>
 #include <linux/fs.h>
 #include <linux/time.h>
 #include <linux/pagemap.h>
@@ -598,10 +599,20 @@ void jffs2_xattr_delete_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache
 	/* It's called from jffs2_evict_inode() on inode removing.
 	   When an inode with XATTR is removed, those XATTRs must be removed. */
 	struct jffs2_xattr_ref *ref, *_ref;
+	unsigned int nofs;
 
 	if (!ic || ic->pino_nlink > 0)
 		return;
 
+	/*
+	 * delete_xattr_ref() below writes flash nodes.  This function runs
+	 * from inode eviction, i.e. memory reclaim may be waiting for this
+	 * lock.  Keep allocations under xattr_sem out of direct reclaim so
+	 * that holders of xattr_sem never wait on fs_reclaim -- otherwise
+	 * reclaim contexts taking xattr_sem while holding fs_reclaim can
+	 * deadlock with the setxattr() paths doing the reverse.
+	 */
+	nofs = memalloc_nofs_save();
 	down_write(&c->xattr_sem);
 	for (ref = ic->xref; ref; ref = _ref) {
 		_ref = ref->next;
@@ -609,6 +620,7 @@ void jffs2_xattr_delete_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache
 	}
 	ic->xref = NULL;
 	up_write(&c->xattr_sem);
+	memalloc_nofs_restore(nofs);
 }
 
 void jffs2_xattr_free_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic)
@@ -638,10 +650,12 @@ static int check_xattr_ref_inode(struct jffs2_sb_info *c, struct jffs2_inode_cac
 	 * one will be removed.
 	 */
 	struct jffs2_xattr_ref *ref, *cmp, **pref, **pcmp;
+	unsigned int nofs;
 	int rc = 0;
 
 	if (likely(ic->flags & INO_FLAGS_XATTR_CHECKED))
 		return 0;
+	nofs = memalloc_nofs_save();
 	down_write(&c->xattr_sem);
  retry:
 	rc = 0;
@@ -683,6 +697,7 @@ static int check_xattr_ref_inode(struct jffs2_sb_info *c, struct jffs2_inode_cac
 	ic->flags |= INO_FLAGS_XATTR_CHECKED;
  out:
 	up_write(&c->xattr_sem);
+	memalloc_nofs_restore(nofs);
 
 	return rc;
 }
@@ -973,6 +988,7 @@ ssize_t jffs2_listxattr(struct dentry *dentry, char *buffer, size_t size)
 	const char *prefix;
 	ssize_t prefix_len, len, rc;
 	int retry = 0;
+	unsigned int nofs = 0;
 
 	rc = check_xattr_ref_inode(c, ic);
 	if (unlikely(rc))
@@ -990,6 +1006,7 @@ ssize_t jffs2_listxattr(struct dentry *dentry, char *buffer, size_t size)
 				retry = 1;
 				up_read(&c->xattr_sem);
 				down_write(&c->xattr_sem);
+				nofs = memalloc_nofs_save();
 				goto retry;
 			} else {
 				rc = load_xattr_datum(c, xd);
@@ -1027,6 +1044,7 @@ ssize_t jffs2_listxattr(struct dentry *dentry, char *buffer, size_t size)
 		up_read(&c->xattr_sem);
 	} else {
 		up_write(&c->xattr_sem);
+		memalloc_nofs_restore(nofs);
 	}
 	return rc;
 }
@@ -1040,6 +1058,7 @@ int do_jffs2_getxattr(struct inode *inode, int xprefix, const char *xname,
 	struct jffs2_xattr_datum *xd;
 	struct jffs2_xattr_ref *ref, **pref;
 	int rc, retry = 0;
+	unsigned int nofs = 0;
 
 	rc = check_xattr_ref_inode(c, ic);
 	if (unlikely(rc))
@@ -1059,6 +1078,7 @@ int do_jffs2_getxattr(struct inode *inode, int xprefix, const char *xname,
 				retry = 1;
 				up_read(&c->xattr_sem);
 				down_write(&c->xattr_sem);
+				nofs = memalloc_nofs_save();
 				goto retry;
 			} else {
 				rc = load_xattr_datum(c, xd);
@@ -1089,6 +1109,7 @@ int do_jffs2_getxattr(struct inode *inode, int xprefix, const char *xname,
 		up_read(&c->xattr_sem);
 	} else {
 		up_write(&c->xattr_sem);
+		memalloc_nofs_restore(nofs);
 	}
 	return rc;
 }
@@ -1102,6 +1123,7 @@ int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname,
 	struct jffs2_xattr_datum *xd;
 	struct jffs2_xattr_ref *ref, *newref, **pref;
 	uint32_t length, request;
+	unsigned int nofs;
 	int rc;
 
 	rc = check_xattr_ref_inode(c, ic);
@@ -1120,6 +1142,7 @@ int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname,
 	}
 
 	/* Find existing xattr */
+	nofs = memalloc_nofs_save();
 	down_write(&c->xattr_sem);
  retry:
 	for (ref=ic->xref, pref=&ic->xref; ref; pref=&ref->next, ref=ref->next) {
@@ -1189,6 +1212,7 @@ int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname,
 		JFFS2_WARNING("jffs2_reserve_space()=%d, request=%u\n", rc, request);
 		unrefer_xattr_datum(c, xd);
 		up_write(&c->xattr_sem);
+		memalloc_nofs_restore(nofs);
 		return rc;
 	}
 	if (ref)
@@ -1206,6 +1230,7 @@ int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname,
 	}
  out:
 	up_write(&c->xattr_sem);
+	memalloc_nofs_restore(nofs);
 	jffs2_complete_reservation(c);
 	return rc;
 }
@@ -1226,8 +1251,10 @@ int jffs2_garbage_collect_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xatt
 				      struct jffs2_raw_node_ref *raw)
 {
 	uint32_t totlen, length, old_ofs;
+	unsigned int nofs;
 	int rc = 0;
 
+	nofs = memalloc_nofs_save();
 	down_write(&c->xattr_sem);
 	if (xd->node != raw)
 		goto out;
@@ -1255,6 +1282,7 @@ int jffs2_garbage_collect_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xatt
 	if (!rc)
 		jffs2_mark_node_obsolete(c, raw);
 	up_write(&c->xattr_sem);
+	memalloc_nofs_restore(nofs);
 	return rc;
 }
 
@@ -1262,8 +1290,10 @@ int jffs2_garbage_collect_xattr_ref(struct jffs2_sb_info *c, struct jffs2_xattr_
 				    struct jffs2_raw_node_ref *raw)
 {
 	uint32_t totlen, length, old_ofs;
+	unsigned int nofs;
 	int rc = 0;
 
+	nofs = memalloc_nofs_save();
 	down_write(&c->xattr_sem);
 	BUG_ON(!ref->node);
 
@@ -1289,6 +1319,7 @@ int jffs2_garbage_collect_xattr_ref(struct jffs2_sb_info *c, struct jffs2_xattr_
 	if (!rc)
 		jffs2_mark_node_obsolete(c, raw);
 	up_write(&c->xattr_sem);
+	memalloc_nofs_restore(nofs);
 	return rc;
 }
 
@@ -1298,8 +1329,10 @@ int jffs2_verify_xattr(struct jffs2_sb_info *c)
 	struct jffs2_eraseblock *jeb;
 	struct jffs2_raw_node_ref *raw;
 	uint32_t totlen;
+	unsigned int nofs;
 	int rc;
 
+	nofs = memalloc_nofs_save();
 	down_write(&c->xattr_sem);
 	list_for_each_entry_safe(xd, _xd, &c->xattr_unchecked, xindex) {
 		rc = do_verify_xattr_datum(c, xd);
@@ -1322,6 +1355,7 @@ int jffs2_verify_xattr(struct jffs2_sb_info *c)
 		spin_unlock(&c->erase_completion_lock);
 	}
 	up_write(&c->xattr_sem);
+	memalloc_nofs_restore(nofs);
 	return list_empty(&c->xattr_unchecked) ? 1 : 0;
 }
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-29  9:36 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-29  9:36 [PATCH] jffs2: prevent xattr_sem -> fs_reclaim deadlock Shuai Liu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®