[PATCH RFC] ocfs2: fix circular locking dependency in ocfs2_mknod()

"syzbot" <[email protected]>
Newsgroups dev.linux.lists.syzbot
Message-ID <[email protected]>
A circular locking dependency was detected involving three locks:
ip_alloc_sem, j_trans_barrier (the journal transaction barrier), and
ip_xattr_sem.

======================================================
WARNING: possible circular locking dependency detected

Chain exists of:
  &oi->ip_alloc_sem --> &journal->j_trans_barrier --> &oi->ip_xattr_sem

 Possible unsafe locking scenario:

       CPU0                    CPU1
  lock(&oi->ip_xattr_sem);
                               lock(&journal->j_trans_barrier);
                               lock(&oi->ip_xattr_sem);
  lock(&oi->ip_alloc_sem);

 *** DEADLOCK ***
======================================================

The lock inversion occurs in ocfs2_mknod() -> ocfs2_init_acl().
ocfs2_mknod() starts a transaction via ocfs2_start_trans(), acquiring
j_trans_barrier. While holding this transaction handle, it calls
ocfs2_init_acl(), which acquires ip_xattr_sem to read the default ACL of
the parent directory. This violates the strict OCFS2 lock ordering rule
that inode semaphores (ip_alloc_sem, ip_xattr_sem, etc.) must be acquired
before starting a transaction.

To resolve this, fetch the default ACL before starting the transaction in
ocfs2_mknod() and pass it down to ocfs2_init_acl(). We use get_inode_acl()
to retrieve the ACL, which safely handles recursive cluster locking and
correctly takes ip_xattr_sem before any transaction is started. The same
logic is applied to ocfs2_init_security_and_acl(), the only other caller of
ocfs2_init_acl().

Fixes: 16c8d569f570 ("ocfs2/acl: use 'ip_xattr_sem' to protect getting extended attribute")
Assisted-by: Gemini:gemini-3.6-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=1fed2de07d8e11a3ec1b
Link: https://syzkaller.appspot.com/ai_job?id=22bc5699-2231-4026-98db-8f9510b94925
To: "Joel Becker" <[email protected]>
To: "Joseph Qi" <[email protected]>
To: "Mark Fasheh" <[email protected]>
To: <[email protected]>
To: "piaojun" <[email protected]>
Cc: <[email protected]>

---
diff --git a/fs/ocfs2/acl.c b/fs/ocfs2/acl.c
index af1e2cedb..a16386c23 100644
--- a/fs/ocfs2/acl.c
+++ b/fs/ocfs2/acl.c
@@ -353,7 +353,7 @@ int ocfs2_init_acl(handle_t *handle,
 		   struct inode *inode,
 		   struct inode *dir,
 		   struct buffer_head *di_bh,
-		   struct buffer_head *dir_bh,
+		   struct posix_acl *default_acl,
 		   struct ocfs2_alloc_context *meta_ac,
 		   struct ocfs2_alloc_context *data_ac)
 {
@@ -364,12 +364,8 @@ int ocfs2_init_acl(handle_t *handle,
 
 	if (!S_ISLNK(inode->i_mode)) {
 		if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
-			down_read(&OCFS2_I(dir)->ip_xattr_sem);
-			acl = ocfs2_get_acl_nolock(dir, ACL_TYPE_DEFAULT,
-						   dir_bh);
-			up_read(&OCFS2_I(dir)->ip_xattr_sem);
-			if (IS_ERR(acl))
-				return PTR_ERR(acl);
+			if (default_acl)
+				acl = posix_acl_dup(default_acl);
 		}
 		if (!acl) {
 			mode = inode->i_mode & ~current_umask();
diff --git a/fs/ocfs2/acl.h b/fs/ocfs2/acl.h
index 667c6f03f..c1ba2eb27 100644
--- a/fs/ocfs2/acl.h
+++ b/fs/ocfs2/acl.h
@@ -21,7 +21,7 @@ int ocfs2_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
 		      struct posix_acl *acl, int type);
 extern int ocfs2_acl_chmod(struct inode *, struct buffer_head *);
 extern int ocfs2_init_acl(handle_t *, struct inode *, struct inode *,
-			  struct buffer_head *, struct buffer_head *,
+			  struct buffer_head *, struct posix_acl *,
 			  struct ocfs2_alloc_context *,
 			  struct ocfs2_alloc_context *);
 
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 1277666c7..6bfc2b9ab 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -27,6 +27,7 @@
 #include <linux/highmem.h>
 #include <linux/quotaops.h>
 #include <linux/iversion.h>
+#include <linux/posix_acl.h>
 
 #include <cluster/masklog.h>
 
@@ -256,6 +257,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
 	sigset_t oldset;
 	int did_block_signals = 0;
 	struct ocfs2_dentry_lock *dl = NULL;
+	struct posix_acl *default_acl = NULL;
 
 	trace_ocfs2_mknod(dir, dentry, dentry->d_name.len, dentry->d_name.name,
 			  (unsigned long long)OCFS2_I(dir)->ip_blkno,
@@ -362,6 +364,15 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
 		goto leave;
 	}
 
+	if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
+		default_acl = get_inode_acl(dir, ACL_TYPE_DEFAULT);
+		if (IS_ERR(default_acl)) {
+			status = PTR_ERR(default_acl);
+			default_acl = NULL;
+			goto leave;
+		}
+	}
+
 	handle = ocfs2_start_trans(osb, ocfs2_mknod_credits(osb->sb,
 							    S_ISDIR(mode),
 							    xattr_credits));
@@ -411,7 +422,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
 		inc_nlink(dir);
 	}
 
-	status = ocfs2_init_acl(handle, inode, dir, new_fe_bh, parent_fe_bh,
+	status = ocfs2_init_acl(handle, inode, dir, new_fe_bh, default_acl,
 			 meta_ac, data_ac);
 
 	if (status < 0) {
@@ -488,6 +499,9 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
 	if (meta_ac)
 		ocfs2_free_alloc_context(meta_ac);
 
+	if (default_acl)
+		posix_acl_release(default_acl);
+
 	/*
 	 * We should call iput after the i_rwsem of the bitmap been
 	 * unlocked in ocfs2_free_alloc_context, or the
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index fcddd3c13..1b020f33f 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -26,6 +26,7 @@
 #include <linux/module.h>
 #include <linux/string.h>
 #include <linux/security.h>
+#include <linux/posix_acl.h>
 
 #include <cluster/masklog.h>
 
@@ -7257,6 +7258,7 @@ int ocfs2_init_security_and_acl(struct inode *dir,
 {
 	int ret = 0;
 	struct buffer_head *dir_bh = NULL;
+	struct posix_acl *default_acl = NULL;
 
 	ret = ocfs2_init_security_get(inode, dir, qstr, NULL);
 	if (ret) {
@@ -7269,10 +7271,23 @@ int ocfs2_init_security_and_acl(struct inode *dir,
 		mlog_errno(ret);
 		goto leave;
 	}
-	ret = ocfs2_init_acl(NULL, inode, dir, NULL, dir_bh, NULL, NULL);
+
+	if (OCFS2_SB(dir->i_sb)->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
+		default_acl = get_inode_acl(dir, ACL_TYPE_DEFAULT);
+		if (IS_ERR(default_acl)) {
+			ret = PTR_ERR(default_acl);
+			default_acl = NULL;
+			goto unlock;
+		}
+	}
+
+	ret = ocfs2_init_acl(NULL, inode, dir, NULL, default_acl, NULL, NULL);
 	if (ret)
 		mlog_errno(ret);
 
+	if (default_acl)
+		posix_acl_release(default_acl);
+unlock:
 	ocfs2_inode_unlock(dir, 0);
 	brelse(dir_bh);
 leave:


base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f
-- 
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).

See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.