Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH v6 03/15] VFS: tidy up do_unlinkat()
From: NeilBrown @ 2025-11-13  0:18 UTC (permalink / raw)
  To: Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Jeff Layton, Chris Mason, David Sterba,
	David Howells, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Tyler Hicks, Miklos Szeredi, Chuck Lever,
	Olga Kornievskaia, Dai Ngo, Namjae Jeon, Steve French,
	Sergey Senozhatsky, Carlos Maiolino, John Johansen, Paul Moore,
	James Morris, Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek,
	Mateusz Guzik, Lorenzo Stoakes, Stefan Berger, Darrick J. Wong,
	linux-kernel, netfs, ecryptfs, linux-nfs, linux-unionfs,
	linux-cifs, linux-xfs, linux-security-module, selinux
In-Reply-To: <20251113002050.676694-1-neilb@ownmail.net>

From: NeilBrown <neil@brown.name>

The simplification of locking in the previous patch opens up some room
for tidying up do_unlinkat()

- change all "exit" labels to describe what will happen at the label.
- always goto an exit label on an error - unwrap the "if (!IS_ERR())" branch.
- Move the "slashes" handing inline, but mark it as unlikely()
- simplify use of the "inode" variable - we no longer need to test for NULL.

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: NeilBrown <neil@brown.name>
---
 fs/namei.c | 55 ++++++++++++++++++++++++++----------------------------
 1 file changed, 26 insertions(+), 29 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 231e1ffd4b8d..93c5fce2d814 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4755,65 +4755,62 @@ int do_unlinkat(int dfd, struct filename *name)
 	struct path path;
 	struct qstr last;
 	int type;
-	struct inode *inode = NULL;
+	struct inode *inode;
 	struct inode *delegated_inode = NULL;
 	unsigned int lookup_flags = 0;
 retry:
 	error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
 	if (error)
-		goto exit1;
+		goto exit_putname;
 
 	error = -EISDIR;
 	if (type != LAST_NORM)
-		goto exit2;
+		goto exit_path_put;
 
 	error = mnt_want_write(path.mnt);
 	if (error)
-		goto exit2;
+		goto exit_path_put;
 retry_deleg:
 	dentry = start_dirop(path.dentry, &last, lookup_flags);
 	error = PTR_ERR(dentry);
-	if (!IS_ERR(dentry)) {
+	if (IS_ERR(dentry))
+		goto exit_drop_write;
 
-		/* Why not before? Because we want correct error value */
-		if (last.name[last.len])
-			goto slashes;
-		inode = dentry->d_inode;
-		ihold(inode);
-		error = security_path_unlink(&path, dentry);
-		if (error)
-			goto exit3;
-		error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
-				   dentry, &delegated_inode);
-exit3:
+	/* Why not before? Because we want correct error value */
+	if (unlikely(last.name[last.len])) {
+		if (d_is_dir(dentry))
+			error = -EISDIR;
+		else
+			error = -ENOTDIR;
 		end_dirop(dentry);
+		goto exit_drop_write;
 	}
-	if (inode)
-		iput(inode);	/* truncate the inode here */
-	inode = NULL;
+	inode = dentry->d_inode;
+	ihold(inode);
+	error = security_path_unlink(&path, dentry);
+	if (error)
+		goto exit_end_dirop;
+	error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
+			   dentry, &delegated_inode);
+exit_end_dirop:
+	end_dirop(dentry);
+	iput(inode);	/* truncate the inode here */
 	if (delegated_inode) {
 		error = break_deleg_wait(&delegated_inode);
 		if (!error)
 			goto retry_deleg;
 	}
+exit_drop_write:
 	mnt_drop_write(path.mnt);
-exit2:
+exit_path_put:
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
-		inode = NULL;
 		goto retry;
 	}
-exit1:
+exit_putname:
 	putname(name);
 	return error;
-
-slashes:
-	if (d_is_dir(dentry))
-		error = -EISDIR;
-	else
-		error = -ENOTDIR;
-	goto exit3;
 }
 
 SYSCALL_DEFINE3(unlinkat, int, dfd, const char __user *, pathname, int, flag)
-- 
2.50.0.107.gf914562f5916.dirty


^ permalink raw reply related

* [PATCH v6 02/15] VFS: introduce start_dirop() and end_dirop()
From: NeilBrown @ 2025-11-13  0:18 UTC (permalink / raw)
  To: Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Jeff Layton, Chris Mason, David Sterba,
	David Howells, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Tyler Hicks, Miklos Szeredi, Chuck Lever,
	Olga Kornievskaia, Dai Ngo, Namjae Jeon, Steve French,
	Sergey Senozhatsky, Carlos Maiolino, John Johansen, Paul Moore,
	James Morris, Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek,
	Mateusz Guzik, Lorenzo Stoakes, Stefan Berger, Darrick J. Wong,
	linux-kernel, netfs, ecryptfs, linux-nfs, linux-unionfs,
	linux-cifs, linux-xfs, linux-security-module, selinux
In-Reply-To: <20251113002050.676694-1-neilb@ownmail.net>

From: NeilBrown <neil@brown.name>

The fact that directory operations (create,remove,rename) are protected
by a lock on the parent is known widely throughout the kernel.
In order to change this - to instead lock the target dentry  - it is
best to centralise this knowledge so it can be changed in one place.

This patch introduces start_dirop() which is local to VFS code.
It performs the required locking for create and remove.  Rename
will be handled separately.

Various functions with names like start_creating() or start_removing_path(),
some of which already exist, will export this functionality beyond the VFS.

end_dirop() is the partner of start_dirop().  It drops the lock and
releases the reference on the dentry.
It *is* exported so that various end_creating etc functions can be inline.

As vfs_mkdir() drops the dentry on error we cannot use end_dirop() as
that won't unlock when the dentry IS_ERR().  For now we need an explicit
unlock when dentry IS_ERR().  I hope to change vfs_mkdir() to unlock
when it drops a dentry so that explicit unlock can go away.

end_dirop() can always be called on the result of start_dirop(), but not
after vfs_mkdir().  After a vfs_mkdir() we still may need the explicit
unlock as seen in end_creating_path().

As well as adding start_dirop() and end_dirop()
this patch uses them in:
 - simple_start_creating (which requires sharing lookup_noperm_common()
        with libfs.c)
 - start_removing_path / start_removing_user_path_at
 - filename_create / end_creating_path()
 - do_rmdir(), do_unlinkat()

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: NeilBrown <neil@brown.name>
---
 fs/internal.h      |  3 ++
 fs/libfs.c         | 36 ++++++++---------
 fs/namei.c         | 98 ++++++++++++++++++++++++++++++++++------------
 include/linux/fs.h |  2 +
 4 files changed, 95 insertions(+), 44 deletions(-)

diff --git a/fs/internal.h b/fs/internal.h
index 9b2b4d116880..d08d5e2235e9 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -67,6 +67,9 @@ int vfs_tmpfile(struct mnt_idmap *idmap,
 		const struct path *parentpath,
 		struct file *file, umode_t mode);
 struct dentry *d_hash_and_lookup(struct dentry *, struct qstr *);
+struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
+			   unsigned int lookup_flags);
+int lookup_noperm_common(struct qstr *qname, struct dentry *base);
 
 /*
  * namespace.c
diff --git a/fs/libfs.c b/fs/libfs.c
index 1661dcb7d983..2d6657947abd 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -2290,27 +2290,25 @@ void stashed_dentry_prune(struct dentry *dentry)
 	cmpxchg(stashed, dentry, NULL);
 }
 
-/* parent must be held exclusive */
+/**
+ * simple_start_creating - prepare to create a given name
+ * @parent: directory in which to prepare to create the name
+ * @name:   the name to be created
+ *
+ * Required lock is taken and a lookup in performed prior to creating an
+ * object in a directory.  No permission checking is performed.
+ *
+ * Returns: a negative dentry on which vfs_create() or similar may
+ *  be attempted, or an error.
+ */
 struct dentry *simple_start_creating(struct dentry *parent, const char *name)
 {
-	struct dentry *dentry;
-	struct inode *dir = d_inode(parent);
+	struct qstr qname = QSTR(name);
+	int err;
 
-	inode_lock(dir);
-	if (unlikely(IS_DEADDIR(dir))) {
-		inode_unlock(dir);
-		return ERR_PTR(-ENOENT);
-	}
-	dentry = lookup_noperm(&QSTR(name), parent);
-	if (IS_ERR(dentry)) {
-		inode_unlock(dir);
-		return dentry;
-	}
-	if (dentry->d_inode) {
-		dput(dentry);
-		inode_unlock(dir);
-		return ERR_PTR(-EEXIST);
-	}
-	return dentry;
+	err = lookup_noperm_common(&qname, parent);
+	if (err)
+		return ERR_PTR(err);
+	return start_dirop(parent, &qname, LOOKUP_CREATE | LOOKUP_EXCL);
 }
 EXPORT_SYMBOL(simple_start_creating);
diff --git a/fs/namei.c b/fs/namei.c
index 39c4d52f5b54..231e1ffd4b8d 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2765,6 +2765,48 @@ static int filename_parentat(int dfd, struct filename *name,
 	return __filename_parentat(dfd, name, flags, parent, last, type, NULL);
 }
 
+/**
+ * start_dirop - begin a create or remove dirop, performing locking and lookup
+ * @parent:       the dentry of the parent in which the operation will occur
+ * @name:         a qstr holding the name within that parent
+ * @lookup_flags: intent and other lookup flags.
+ *
+ * The lookup is performed and necessary locks are taken so that, on success,
+ * the returned dentry can be operated on safely.
+ * The qstr must already have the hash value calculated.
+ *
+ * Returns: a locked dentry, or an error.
+ *
+ */
+struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
+			   unsigned int lookup_flags)
+{
+	struct dentry *dentry;
+	struct inode *dir = d_inode(parent);
+
+	inode_lock_nested(dir, I_MUTEX_PARENT);
+	dentry = lookup_one_qstr_excl(name, parent, lookup_flags);
+	if (IS_ERR(dentry))
+		inode_unlock(dir);
+	return dentry;
+}
+
+/**
+ * end_dirop - signal completion of a dirop
+ * @de: the dentry which was returned by start_dirop or similar.
+ *
+ * If the de is an error, nothing happens. Otherwise any lock taken to
+ * protect the dentry is dropped and the dentry itself is release (dput()).
+ */
+void end_dirop(struct dentry *de)
+{
+	if (!IS_ERR(de)) {
+		inode_unlock(de->d_parent->d_inode);
+		dput(de);
+	}
+}
+EXPORT_SYMBOL(end_dirop);
+
 /* does lookup, returns the object with parent locked */
 static struct dentry *__start_removing_path(int dfd, struct filename *name,
 					   struct path *path)
@@ -2781,10 +2823,9 @@ static struct dentry *__start_removing_path(int dfd, struct filename *name,
 		return ERR_PTR(-EINVAL);
 	/* don't fail immediately if it's r/o, at least try to report other errors */
 	error = mnt_want_write(parent_path.mnt);
-	inode_lock_nested(parent_path.dentry->d_inode, I_MUTEX_PARENT);
-	d = lookup_one_qstr_excl(&last, parent_path.dentry, 0);
+	d = start_dirop(parent_path.dentry, &last, 0);
 	if (IS_ERR(d))
-		goto unlock;
+		goto drop;
 	if (error)
 		goto fail;
 	path->dentry = no_free_ptr(parent_path.dentry);
@@ -2792,10 +2833,9 @@ static struct dentry *__start_removing_path(int dfd, struct filename *name,
 	return d;
 
 fail:
-	dput(d);
+	end_dirop(d);
 	d = ERR_PTR(error);
-unlock:
-	inode_unlock(parent_path.dentry->d_inode);
+drop:
 	if (!error)
 		mnt_drop_write(parent_path.mnt);
 	return d;
@@ -2910,7 +2950,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 }
 EXPORT_SYMBOL(vfs_path_lookup);
 
-static int lookup_noperm_common(struct qstr *qname, struct dentry *base)
+int lookup_noperm_common(struct qstr *qname, struct dentry *base)
 {
 	const char *name = qname->name;
 	u32 len = qname->len;
@@ -4223,21 +4263,18 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 */
 	if (last.name[last.len] && !want_dir)
 		create_flags &= ~LOOKUP_CREATE;
-	inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
-	dentry = lookup_one_qstr_excl(&last, path->dentry,
-				      reval_flag | create_flags);
+	dentry = start_dirop(path->dentry, &last, reval_flag | create_flags);
 	if (IS_ERR(dentry))
-		goto unlock;
+		goto out_drop_write;
 
 	if (unlikely(error))
 		goto fail;
 
 	return dentry;
 fail:
-	dput(dentry);
+	end_dirop(dentry);
 	dentry = ERR_PTR(error);
-unlock:
-	inode_unlock(path->dentry->d_inode);
+out_drop_write:
 	if (!error)
 		mnt_drop_write(path->mnt);
 out:
@@ -4256,11 +4293,26 @@ struct dentry *start_creating_path(int dfd, const char *pathname,
 }
 EXPORT_SYMBOL(start_creating_path);
 
+/**
+ * end_creating_path - finish a code section started by start_creating_path()
+ * @path: the path instantiated by start_creating_path()
+ * @dentry: the dentry returned by start_creating_path()
+ *
+ * end_creating_path() will unlock and locks taken by start_creating_path()
+ * and drop an references that were taken.  It should only be called
+ * if start_creating_path() returned a non-error.
+ * If vfs_mkdir() was called and it returned an error, that error *should*
+ * be passed to end_creating_path() together with the path.
+ */
 void end_creating_path(const struct path *path, struct dentry *dentry)
 {
-	if (!IS_ERR(dentry))
-		dput(dentry);
-	inode_unlock(path->dentry->d_inode);
+	if (IS_ERR(dentry))
+		/* The parent is still locked despite the error from
+		 * vfs_mkdir() - must unlock it.
+		 */
+		inode_unlock(path->dentry->d_inode);
+	else
+		end_dirop(dentry);
 	mnt_drop_write(path->mnt);
 	path_put(path);
 }
@@ -4592,8 +4644,7 @@ int do_rmdir(int dfd, struct filename *name)
 	if (error)
 		goto exit2;
 
-	inode_lock_nested(path.dentry->d_inode, I_MUTEX_PARENT);
-	dentry = lookup_one_qstr_excl(&last, path.dentry, lookup_flags);
+	dentry = start_dirop(path.dentry, &last, lookup_flags);
 	error = PTR_ERR(dentry);
 	if (IS_ERR(dentry))
 		goto exit3;
@@ -4602,9 +4653,8 @@ int do_rmdir(int dfd, struct filename *name)
 		goto exit4;
 	error = vfs_rmdir(mnt_idmap(path.mnt), path.dentry->d_inode, dentry);
 exit4:
-	dput(dentry);
+	end_dirop(dentry);
 exit3:
-	inode_unlock(path.dentry->d_inode);
 	mnt_drop_write(path.mnt);
 exit2:
 	path_put(&path);
@@ -4721,8 +4771,7 @@ int do_unlinkat(int dfd, struct filename *name)
 	if (error)
 		goto exit2;
 retry_deleg:
-	inode_lock_nested(path.dentry->d_inode, I_MUTEX_PARENT);
-	dentry = lookup_one_qstr_excl(&last, path.dentry, lookup_flags);
+	dentry = start_dirop(path.dentry, &last, lookup_flags);
 	error = PTR_ERR(dentry);
 	if (!IS_ERR(dentry)) {
 
@@ -4737,9 +4786,8 @@ int do_unlinkat(int dfd, struct filename *name)
 		error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
 				   dentry, &delegated_inode);
 exit3:
-		dput(dentry);
+		end_dirop(dentry);
 	}
-	inode_unlock(path.dentry->d_inode);
 	if (inode)
 		iput(inode);	/* truncate the inode here */
 	inode = NULL;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 03e450dd5211..9e7556e79d19 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3196,6 +3196,8 @@ extern void iterate_supers_type(struct file_system_type *,
 void filesystems_freeze(void);
 void filesystems_thaw(void);
 
+void end_dirop(struct dentry *de);
+
 extern int dcache_dir_open(struct inode *, struct file *);
 extern int dcache_dir_close(struct inode *, struct file *);
 extern loff_t dcache_dir_lseek(struct file *, loff_t, int);
-- 
2.50.0.107.gf914562f5916.dirty


^ permalink raw reply related

* [PATCH v6 01/15] debugfs: rename end_creating() to debugfs_end_creating()
From: NeilBrown @ 2025-11-13  0:18 UTC (permalink / raw)
  To: Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Jeff Layton, Chris Mason, David Sterba,
	David Howells, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Tyler Hicks, Miklos Szeredi, Chuck Lever,
	Olga Kornievskaia, Dai Ngo, Namjae Jeon, Steve French,
	Sergey Senozhatsky, Carlos Maiolino, John Johansen, Paul Moore,
	James Morris, Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek,
	Mateusz Guzik, Lorenzo Stoakes, Stefan Berger, Darrick J. Wong,
	linux-kernel, netfs, ecryptfs, linux-nfs, linux-unionfs,
	linux-cifs, linux-xfs, linux-security-module, selinux
In-Reply-To: <20251113002050.676694-1-neilb@ownmail.net>

From: NeilBrown <neil@brown.name>

By not using the generic end_creating() name here we are free to use it
more globally for a more generic function.
This should have been done when start_creating() was renamed.

For consistency, also rename failed_creating().

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: NeilBrown <neil@brown.name>
---
 fs/debugfs/inode.c | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
index 661a99a7dfbe..f241b9df642a 100644
--- a/fs/debugfs/inode.c
+++ b/fs/debugfs/inode.c
@@ -403,7 +403,7 @@ static struct dentry *debugfs_start_creating(const char *name,
 	return dentry;
 }
 
-static struct dentry *failed_creating(struct dentry *dentry)
+static struct dentry *debugfs_failed_creating(struct dentry *dentry)
 {
 	inode_unlock(d_inode(dentry->d_parent));
 	dput(dentry);
@@ -411,7 +411,7 @@ static struct dentry *failed_creating(struct dentry *dentry)
 	return ERR_PTR(-ENOMEM);
 }
 
-static struct dentry *end_creating(struct dentry *dentry)
+static struct dentry *debugfs_end_creating(struct dentry *dentry)
 {
 	inode_unlock(d_inode(dentry->d_parent));
 	return dentry;
@@ -435,7 +435,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
 		return dentry;
 
 	if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
-		failed_creating(dentry);
+		debugfs_failed_creating(dentry);
 		return ERR_PTR(-EPERM);
 	}
 
@@ -443,7 +443,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
 	if (unlikely(!inode)) {
 		pr_err("out of free dentries, can not create file '%s'\n",
 		       name);
-		return failed_creating(dentry);
+		return debugfs_failed_creating(dentry);
 	}
 
 	inode->i_mode = mode;
@@ -458,7 +458,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
 
 	d_instantiate(dentry, inode);
 	fsnotify_create(d_inode(dentry->d_parent), dentry);
-	return end_creating(dentry);
+	return debugfs_end_creating(dentry);
 }
 
 struct dentry *debugfs_create_file_full(const char *name, umode_t mode,
@@ -585,7 +585,7 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
 		return dentry;
 
 	if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
-		failed_creating(dentry);
+		debugfs_failed_creating(dentry);
 		return ERR_PTR(-EPERM);
 	}
 
@@ -593,7 +593,7 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
 	if (unlikely(!inode)) {
 		pr_err("out of free dentries, can not create directory '%s'\n",
 		       name);
-		return failed_creating(dentry);
+		return debugfs_failed_creating(dentry);
 	}
 
 	inode->i_mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO;
@@ -605,7 +605,7 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
 	d_instantiate(dentry, inode);
 	inc_nlink(d_inode(dentry->d_parent));
 	fsnotify_mkdir(d_inode(dentry->d_parent), dentry);
-	return end_creating(dentry);
+	return debugfs_end_creating(dentry);
 }
 EXPORT_SYMBOL_GPL(debugfs_create_dir);
 
@@ -632,7 +632,7 @@ struct dentry *debugfs_create_automount(const char *name,
 		return dentry;
 
 	if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
-		failed_creating(dentry);
+		debugfs_failed_creating(dentry);
 		return ERR_PTR(-EPERM);
 	}
 
@@ -640,7 +640,7 @@ struct dentry *debugfs_create_automount(const char *name,
 	if (unlikely(!inode)) {
 		pr_err("out of free dentries, can not create automount '%s'\n",
 		       name);
-		return failed_creating(dentry);
+		return debugfs_failed_creating(dentry);
 	}
 
 	make_empty_dir_inode(inode);
@@ -652,7 +652,7 @@ struct dentry *debugfs_create_automount(const char *name,
 	d_instantiate(dentry, inode);
 	inc_nlink(d_inode(dentry->d_parent));
 	fsnotify_mkdir(d_inode(dentry->d_parent), dentry);
-	return end_creating(dentry);
+	return debugfs_end_creating(dentry);
 }
 EXPORT_SYMBOL(debugfs_create_automount);
 
@@ -699,13 +699,13 @@ struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent,
 		pr_err("out of free dentries, can not create symlink '%s'\n",
 		       name);
 		kfree(link);
-		return failed_creating(dentry);
+		return debugfs_failed_creating(dentry);
 	}
 	inode->i_mode = S_IFLNK | S_IRWXUGO;
 	inode->i_op = &debugfs_symlink_inode_operations;
 	inode->i_link = link;
 	d_instantiate(dentry, inode);
-	return end_creating(dentry);
+	return debugfs_end_creating(dentry);
 }
 EXPORT_SYMBOL_GPL(debugfs_create_symlink);
 
-- 
2.50.0.107.gf914562f5916.dirty


^ permalink raw reply related

* [PATCH v6 00/15] Create and use APIs to centralise locking for directory ops.
From: NeilBrown @ 2025-11-13  0:18 UTC (permalink / raw)
  To: Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Jeff Layton, Chris Mason, David Sterba,
	David Howells, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Tyler Hicks, Miklos Szeredi, Chuck Lever,
	Olga Kornievskaia, Dai Ngo, Namjae Jeon, Steve French,
	Sergey Senozhatsky, Carlos Maiolino, John Johansen, Paul Moore,
	James Morris, Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek,
	Mateusz Guzik, Lorenzo Stoakes, Stefan Berger, Darrick J. Wong,
	linux-kernel, netfs, ecryptfs, linux-nfs, linux-unionfs,
	linux-cifs, linux-xfs, linux-security-module, selinux

Following is a new version of this series:
 - fixed a bug found by syzbot
 - cleanup suggested by Stephen Smalley
 - added patch for missing updates in smb/server - thanks Jeff Layton
 - various s-o-b


Previous description:

 this series is the next part of my effort to change directory-op
 locking to allow multiple concurrent ops in a directory.  Ultimately we
 will (in my plan) lock the target dentry(s) rather than the whole
 parent directory.

 To help with changing the locking protocol, this series centralises
 locking and lookup in some helpers.  The various helpers are introduced
 and then used in the same patch - roughly one patch per helper though
 with various exceptions.

 I haven't introduced these helpers into the various filesystems that
 Al's tree-in-dcache series is changing.  That series introduces and
 uses similar helpers tuned to the specific needs of that set of
 filesystems.  Ultimately all the helpers will use the same backends
 which can then be adjusted when it is time to change the locking
 protocol.

 One change that deserves highlighting is in patch 13 where vfs_mkdir()
 is changed to unlock the parent on failure, as well as the current
 behaviour of dput()ing the dentry on failure.  Once this change is in
 place, the final step of both create and an remove sequences only
 requires the target dentry, not the parent.  So e.g.  end_creating() is
 only given the dentry (which may be IS_ERR() after vfs_mkdir()).  This
 helps establish the pattern that it is the dentry that is being locked
 and unlocked (the lock is currently held on dentry->d_parent->d_inode,
 but that can change).

 Please review the changes I've made to your respective code areas and
 let us know of any problems.

Thanks,
NeilBrown


 [PATCH v6 01/15] debugfs: rename end_creating() to
 [PATCH v6 02/15] VFS: introduce start_dirop() and end_dirop()
 [PATCH v6 03/15] VFS: tidy up do_unlinkat()
 [PATCH v6 04/15] VFS/nfsd/cachefiles/ovl: add start_creating() and
 [PATCH v6 05/15] VFS/nfsd/cachefiles/ovl: introduce start_removing()
 [PATCH v6 06/15] VFS: introduce start_creating_noperm() and
 [PATCH v6 07/15] smb/server: use end_removing_noperm for for target
 [PATCH v6 08/15] VFS: introduce start_removing_dentry()
 [PATCH v6 09/15] VFS: add start_creating_killable() and
 [PATCH v6 10/15] VFS/nfsd/ovl: introduce start_renaming() and
 [PATCH v6 11/15] VFS/ovl/smb: introduce start_renaming_dentry()
 [PATCH v6 12/15] Add start_renaming_two_dentries()
 [PATCH v6 13/15] ecryptfs: use new start_creating/start_removing APIs
 [PATCH v6 14/15] VFS: change vfs_mkdir() to unlock on failure.
 [PATCH v6 15/15] VFS: introduce end_creating_keep()

^ permalink raw reply

* Re: [PATCH v5 05/14] VFS/nfsd/cachefiles/ovl: introduce start_removing() and end_removing()
From: NeilBrown @ 2025-11-12 23:51 UTC (permalink / raw)
  To: Jeff Layton
  Cc: Alexander Viro, Christian Brauner, Amir Goldstein, Jan Kara,
	linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <230c32bf9997f26b0cfe8c1a4c9309cf91c5071c.camel@kernel.org>

On Thu, 13 Nov 2025, Jeff Layton wrote:
> On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> > From: NeilBrown <neil@brown.name>
> > 
> > start_removing() is similar to start_creating() but will only return a
> > positive dentry with the expectation that it will be removed.  This is
> > used by nfsd, cachefiles, and overlayfs.  They are changed to also use
> > end_removing() to terminate the action begun by start_removing().  This
> > is a simple alias for end_dirop().
> > 
> > Apart from changes to the error paths, as we no longer need to unlock on
> > a lookup error, an effect on callers is that they don't need to test if
> > the found dentry is positive or negative - they can be sure it is
> > positive.
> > 
> > Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> > Signed-off-by: NeilBrown <neil@brown.name>
> > ---
> >  fs/cachefiles/namei.c    | 32 ++++++++++++++------------------
> >  fs/namei.c               | 27 +++++++++++++++++++++++++++
> >  fs/nfsd/nfs4recover.c    | 18 +++++-------------
> >  fs/nfsd/vfs.c            | 26 ++++++++++----------------
> >  fs/overlayfs/dir.c       | 15 +++++++--------
> >  fs/overlayfs/overlayfs.h |  8 ++++++++
> >  include/linux/namei.h    | 18 ++++++++++++++++++
> >  7 files changed, 89 insertions(+), 55 deletions(-)
> > 
> > diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
> > index 0a136eb434da..c7f0c6ab9b88 100644
> > --- a/fs/cachefiles/namei.c
> > +++ b/fs/cachefiles/namei.c
> > @@ -260,6 +260,7 @@ static int cachefiles_unlink(struct cachefiles_cache *cache,
> >   * - File backed objects are unlinked
> >   * - Directory backed objects are stuffed into the graveyard for userspace to
> >   *   delete
> > + * On entry dir must be locked.  It will be unlocked on exit.
> >   */
> >  int cachefiles_bury_object(struct cachefiles_cache *cache,
> >  			   struct cachefiles_object *object,
> > @@ -274,28 +275,30 @@ int cachefiles_bury_object(struct cachefiles_cache *cache,
> >  
> >  	_enter(",'%pd','%pd'", dir, rep);
> >  
> > +	/* end_removing() will dput() @rep but we need to keep
> > +	 * a ref, so take one now.  This also stops the dentry
> > +	 * being negated when unlinked which we need.
> > +	 */
> > +	dget(rep);
> > +
> >  	if (rep->d_parent != dir) {
> > -		inode_unlock(d_inode(dir));
> > +		end_removing(rep);
> >  		_leave(" = -ESTALE");
> >  		return -ESTALE;
> >  	}
> >  
> >  	/* non-directories can just be unlinked */
> >  	if (!d_is_dir(rep)) {
> > -		dget(rep); /* Stop the dentry being negated if it's only pinned
> > -			    * by a file struct.
> > -			    */
> >  		ret = cachefiles_unlink(cache, object, dir, rep, why);
> > -		dput(rep);
> > +		end_removing(rep);
> >  
> > -		inode_unlock(d_inode(dir));
> >  		_leave(" = %d", ret);
> >  		return ret;
> >  	}
> >  
> >  	/* directories have to be moved to the graveyard */
> >  	_debug("move stale object to graveyard");
> > -	inode_unlock(d_inode(dir));
> > +	end_removing(rep);
> >  
> >  try_again:
> >  	/* first step is to make up a grave dentry in the graveyard */
> > @@ -749,26 +752,20 @@ static struct dentry *cachefiles_lookup_for_cull(struct cachefiles_cache *cache,
> >  	struct dentry *victim;
> >  	int ret = -ENOENT;
> >  
> > -	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
> > +	victim = start_removing(&nop_mnt_idmap, dir, &QSTR(filename));
> >  
> > -	victim = lookup_one(&nop_mnt_idmap, &QSTR(filename), dir);
> >  	if (IS_ERR(victim))
> >  		goto lookup_error;
> > -	if (d_is_negative(victim))
> > -		goto lookup_put;
> >  	if (d_inode(victim)->i_flags & S_KERNEL_FILE)
> >  		goto lookup_busy;
> >  	return victim;
> >  
> >  lookup_busy:
> >  	ret = -EBUSY;
> > -lookup_put:
> > -	inode_unlock(d_inode(dir));
> > -	dput(victim);
> > +	end_removing(victim);
> >  	return ERR_PTR(ret);
> >  
> >  lookup_error:
> > -	inode_unlock(d_inode(dir));
> >  	ret = PTR_ERR(victim);
> >  	if (ret == -ENOENT)
> >  		return ERR_PTR(-ESTALE); /* Probably got retired by the netfs */
> > @@ -816,18 +813,17 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
> >  
> >  	ret = cachefiles_bury_object(cache, NULL, dir, victim,
> >  				     FSCACHE_OBJECT_WAS_CULLED);
> > +	dput(victim);
> >  	if (ret < 0)
> >  		goto error;
> >  
> >  	fscache_count_culled();
> > -	dput(victim);
> >  	_leave(" = 0");
> >  	return 0;
> >  
> >  error_unlock:
> > -	inode_unlock(d_inode(dir));
> > +	end_removing(victim);
> >  error:
> > -	dput(victim);
> >  	if (ret == -ENOENT)
> >  		return -ESTALE; /* Probably got retired by the netfs */
> >  
> > diff --git a/fs/namei.c b/fs/namei.c
> > index 8873ad0f05b0..38dda29552f6 100644
> > --- a/fs/namei.c
> > +++ b/fs/namei.c
> > @@ -3248,6 +3248,33 @@ struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
> >  }
> >  EXPORT_SYMBOL(start_creating);
> >  
> > +/**
> > + * start_removing - prepare to remove a given name with permission checking
> > + * @idmap:  idmap of the mount
> > + * @parent: directory in which to find the name
> > + * @name:   the name to be removed
> > + *
> > + * Locks are taken and a lookup in performed prior to removing
> > + * an object from a directory.  Permission checking (MAY_EXEC) is performed
> > + * against @idmap.
> > + *
> > + * If the name doesn't exist, an error is returned.
> > + *
> > + * end_removing() should be called when removal is complete, or aborted.
> > + *
> > + * Returns: a positive dentry, or an error.
> > + */
> > +struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
> > +			      struct qstr *name)
> > +{
> > +	int err = lookup_one_common(idmap, name, parent);
> > +
> > +	if (err)
> > +		return ERR_PTR(err);
> > +	return start_dirop(parent, name, 0);
> > +}
> > +EXPORT_SYMBOL(start_removing);
> > +
> >  #ifdef CONFIG_UNIX98_PTYS
> >  int path_pts(struct path *path)
> >  {
> > diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
> > index c247a7c3291c..3eefaa2202e3 100644
> > --- a/fs/nfsd/nfs4recover.c
> > +++ b/fs/nfsd/nfs4recover.c
> > @@ -324,20 +324,12 @@ nfsd4_unlink_clid_dir(char *name, struct nfsd_net *nn)
> >  	dprintk("NFSD: nfsd4_unlink_clid_dir. name %s\n", name);
> >  
> >  	dir = nn->rec_file->f_path.dentry;
> > -	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
> > -	dentry = lookup_one(&nop_mnt_idmap, &QSTR(name), dir);
> > -	if (IS_ERR(dentry)) {
> > -		status = PTR_ERR(dentry);
> > -		goto out_unlock;
> > -	}
> > -	status = -ENOENT;
> > -	if (d_really_is_negative(dentry))
> > -		goto out;
> > +	dentry = start_removing(&nop_mnt_idmap, dir, &QSTR(name));
> > +	if (IS_ERR(dentry))
> > +		return PTR_ERR(dentry);
> > +
> >  	status = vfs_rmdir(&nop_mnt_idmap, d_inode(dir), dentry);
> > -out:
> > -	dput(dentry);
> > -out_unlock:
> > -	inode_unlock(d_inode(dir));
> > +	end_removing(dentry);
> >  	return status;
> >  }
> >  
> > diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> > index 24e501abad0e..6291c371caa7 100644
> > --- a/fs/nfsd/vfs.c
> > +++ b/fs/nfsd/vfs.c
> > @@ -2044,7 +2044,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
> >  {
> >  	struct dentry	*dentry, *rdentry;
> >  	struct inode	*dirp;
> > -	struct inode	*rinode;
> > +	struct inode	*rinode = NULL;
> >  	__be32		err;
> >  	int		host_err;
> >  
> > @@ -2063,24 +2063,21 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
> >  
> >  	dentry = fhp->fh_dentry;
> >  	dirp = d_inode(dentry);
> > -	inode_lock_nested(dirp, I_MUTEX_PARENT);
> >  
> > -	rdentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), dentry);
> > +	rdentry = start_removing(&nop_mnt_idmap, dentry, &QSTR_LEN(fname, flen));
> > +
> >  	host_err = PTR_ERR(rdentry);
> >  	if (IS_ERR(rdentry))
> > -		goto out_unlock;
> > +		goto out_drop_write;
> >  
> > -	if (d_really_is_negative(rdentry)) {
> > -		dput(rdentry);
> > -		host_err = -ENOENT;
> > -		goto out_unlock;
> > -	}
> > -	rinode = d_inode(rdentry);
> >  	err = fh_fill_pre_attrs(fhp);
> >  	if (err != nfs_ok)
> >  		goto out_unlock;
> >  
> > +	rinode = d_inode(rdentry);
> > +	/* Prevent truncation until after locks dropped */
> >  	ihold(rinode);
> > +
> >  	if (!type)
> >  		type = d_inode(rdentry)->i_mode & S_IFMT;
> >  
> > @@ -2102,10 +2099,10 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
> >  	}
> >  	fh_fill_post_attrs(fhp);
> >  
> > -	inode_unlock(dirp);
> > -	if (!host_err)
> > +out_unlock:
> > +	end_removing(rdentry);
> > +	if (!err && !host_err)
> >  		host_err = commit_metadata(fhp);
> > -	dput(rdentry);
> >  	iput(rinode);    /* truncate the inode here */
> >  
> >  out_drop_write:
> > @@ -2123,9 +2120,6 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
> >  	}
> >  out:
> >  	return err != nfs_ok ? err : nfserrno(host_err);
> > -out_unlock:
> > -	inode_unlock(dirp);
> > -	goto out_drop_write;
> >  }
> >  
> >  /*
> > diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> > index b9160fefbd00..20682afdbd20 100644
> > --- a/fs/overlayfs/dir.c
> > +++ b/fs/overlayfs/dir.c
> > @@ -866,17 +866,17 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
> >  			goto out;
> >  	}
> >  
> > -	inode_lock_nested(dir, I_MUTEX_PARENT);
> > -	upper = ovl_lookup_upper(ofs, dentry->d_name.name, upperdir,
> > -				 dentry->d_name.len);
> > +	upper = ovl_start_removing_upper(ofs, upperdir,
> > +					 &QSTR_LEN(dentry->d_name.name,
> > +						   dentry->d_name.len));
> >  	err = PTR_ERR(upper);
> >  	if (IS_ERR(upper))
> > -		goto out_unlock;
> > +		goto out_dput;
> >  
> >  	err = -ESTALE;
> >  	if ((opaquedir && upper != opaquedir) ||
> >  	    (!opaquedir && !ovl_matches_upper(dentry, upper)))
> > -		goto out_dput_upper;
> > +		goto out_unlock;
> >  
> >  	if (is_dir)
> >  		err = ovl_do_rmdir(ofs, dir, upper);
> > @@ -892,10 +892,9 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
> >  	 */
> >  	if (!err)
> >  		d_drop(dentry);
> > -out_dput_upper:
> > -	dput(upper);
> >  out_unlock:
> > -	inode_unlock(dir);
> > +	end_removing(upper);
> > +out_dput:
> >  	dput(opaquedir);
> >  out:
> >  	return err;
> > diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> > index beeba96cfcb2..49ad65f829dc 100644
> > --- a/fs/overlayfs/overlayfs.h
> > +++ b/fs/overlayfs/overlayfs.h
> > @@ -423,6 +423,14 @@ static inline struct dentry *ovl_start_creating_upper(struct ovl_fs *ofs,
> >  			      parent, name);
> >  }
> >  
> > +static inline struct dentry *ovl_start_removing_upper(struct ovl_fs *ofs,
> > +						      struct dentry *parent,
> > +						      struct qstr *name)
> > +{
> > +	return start_removing(ovl_upper_mnt_idmap(ofs),
> > +			      parent, name);
> > +}
> > +
> >  static inline bool ovl_open_flags_need_copy_up(int flags)
> >  {
> >  	if (!flags)
> > diff --git a/include/linux/namei.h b/include/linux/namei.h
> > index 37b72f4a64f0..6d1069f93ebf 100644
> > --- a/include/linux/namei.h
> > +++ b/include/linux/namei.h
> > @@ -91,6 +91,8 @@ struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap,
> >  
> >  struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
> >  			      struct qstr *name);
> > +struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
> > +			      struct qstr *name);
> >  
> >  /**
> >   * end_creating - finish action started with start_creating
> > @@ -122,6 +124,22 @@ static inline void end_creating(struct dentry *child, struct dentry *parent)
> >  		end_dirop(child);
> >  }
> >  
> > +/**
> > + * end_removing - finish action started with start_removing
> > + * @child:  dentry returned by start_removing()
> > + * @parent: dentry given to start_removing()
> > + *
> > + * Unlock and release the child.
> > + *
> > + * This is identical to end_dirop().  It can be passed the result of
> > + * start_removing() whether that was successful or not, but it not needed
> > + * if start_removing() failed.
> > + */
> > +static inline void end_removing(struct dentry *child)
> > +{
> > +	end_dirop(child);
> > +}
> > +
> >  extern int follow_down_one(struct path *);
> >  extern int follow_down(struct path *path, unsigned int flags);
> >  extern int follow_up(struct path *);
> 
> This looks fine to me (particularly the knfsd parts), but doesn't ksmbd
> need to be similarly converted?

ksmbd removes names in two places.

ksmbd_vfs_unlink() is changed to use start_removing_dentry() in the next
patch as you noticed when reviewing it.

ksmbd_vfs_remove_file() is called after ksmbd_vfs_kern_path_locked()
which is only used there.  I should possible renamed to
ksmbd_vfs_kern_path_start_removing() and then the "do_lock" flag to
__ksmbd_vfs_kern_path() and ksmbd_vfs_path_lookup() should become
"for_remove" and then the lock/lookup in the "if (do_lock)" branch
(which will become "if (for_remove)") should use start_removing.

I'll insert a patch to do this.

> 
> Reviewed-by: Jeff Layton <jlayton@kernel.org>

Thanks,
NeilBrown



^ permalink raw reply

* Re: [PATCH v5 11/14] Add start_renaming_two_dentries()
From: NeilBrown @ 2025-11-12 23:37 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: Alexander Viro, Christian Brauner, Amir Goldstein, Jan Kara,
	linux-fsdevel, Jeff Layton, Chris Mason, David Sterba,
	David Howells, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Tyler Hicks, Miklos Szeredi, Chuck Lever,
	Olga Kornievskaia, Dai Ngo, Namjae Jeon, Steve French,
	Sergey Senozhatsky, Carlos Maiolino, John Johansen, Paul Moore,
	James Morris, Serge E. Hallyn, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <CAEjxPJ528Ou4dvRwHo+kXjWreGicda8BOXkQRvq3vMED6JQKOQ@mail.gmail.com>

On Tue, 11 Nov 2025, Stephen Smalley wrote:
> On Wed, Nov 5, 2025 at 7:56 PM NeilBrown <neilb@ownmail.net> wrote:
> >
> > From: NeilBrown <neil@brown.name>
> >
> > A few callers want to lock for a rename and already have both dentries.
> > Also debugfs does want to perform a lookup but doesn't want permission
> > checking, so start_renaming_dentry() cannot be used.
> >
> > This patch introduces start_renaming_two_dentries() which is given both
> > dentries.  debugfs performs one lookup itself.  As it will only continue
> > with a negative dentry and as those cannot be renamed or unlinked, it is
> > safe to do the lookup before getting the rename locks.
> >
> > overlayfs uses start_renaming_two_dentries() in three places and  selinux
> > uses it twice in sel_make_policy_nodes().
> >
> > In sel_make_policy_nodes() we now lock for rename twice instead of just
> > once so the combined operation is no longer atomic w.r.t the parent
> > directory locks.  As selinux_state.policy_mutex is held across the whole
> > operation this does open up any interesting races.
> >
> > Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> > Signed-off-by: NeilBrown <neil@brown.name>
> >
> > ---
> > changes since v3:
> >  added missing assignment to rd.mnt_idmap in ovl_cleanup_and_whiteout
> > ---
> 
> > diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> > index 232e087bce3e..a224ef9bb831 100644
> > --- a/security/selinux/selinuxfs.c
> > +++ b/security/selinux/selinuxfs.c
> > @@ -539,22 +540,30 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
> >         if (ret)
> >                 goto out;
> >
> > -       lock_rename(tmp_parent, fsi->sb->s_root);
> > +       rd.old_parent = tmp_parent;
> > +       rd.new_parent = fsi->sb->s_root;
> >
> >         /* booleans */
> > -       d_exchange(tmp_bool_dir, fsi->bool_dir);
> > +       ret = start_renaming_two_dentries(&rd, tmp_bool_dir, fsi->bool_dir);
> > +       if (!ret) {
> > +               d_exchange(tmp_bool_dir, fsi->bool_dir);
> 
> I would recommend an immediate goto out if ret != 0; we don't want to
> silently fall through and possibly reset ret on the next
> start_renaming_two_dentries() call, thereby ultimately returning 0 to
> the caller and acting as if nothing bad happened.

Yes, that is much cleaner - thanks!

and I've added the missing "NOT" in the commit message.

Thanks,
NeilBrown

^ permalink raw reply

* Re: [syzbot ci] Re: Create and use APIs to centralise locking for directory ops.
From: NeilBrown @ 2025-11-12 22:50 UTC (permalink / raw)
  To: syzbot ci
  Cc: amir73il, brauner, cem, chuck.lever, clm, code, dai.ngo, dakr,
	dhowells, djwong, dsterba, ecryptfs, gregkh, jack, jlayton,
	jmorris, john.johansen, linkinjeon, linux-cifs, linux-fsdevel,
	linux-kernel, linux-nfs, linux-security-module, linux-unionfs,
	linux-xfs, lorenzo.stoakes, miklos, mjguzik, netfs, okorniev,
	omosnace, paul, rafael, selinux, senozhatsky, serge, smfrench,
	stefanb, stephen.smalley.work, viro, syzbot, syzkaller-bugs
In-Reply-To: <690c6437.050a0220.baf87.0083.GAE@google.com>

On Thu, 06 Nov 2025, syzbot ci wrote:
> syzbot ci has tested the following series
> 
> [v5] Create and use APIs to centralise locking for directory ops.
> https://lore.kernel.org/all/20251106005333.956321-1-neilb@ownmail.net
> * [PATCH v5 01/14] debugfs: rename end_creating() to debugfs_end_creating()
> * [PATCH v5 02/14] VFS: introduce start_dirop() and end_dirop()
> * [PATCH v5 03/14] VFS: tidy up do_unlinkat()
> * [PATCH v5 04/14] VFS/nfsd/cachefiles/ovl: add start_creating() and end_creating()
> * [PATCH v5 05/14] VFS/nfsd/cachefiles/ovl: introduce start_removing() and end_removing()
> * [PATCH v5 06/14] VFS: introduce start_creating_noperm() and start_removing_noperm()
> * [PATCH v5 07/14] VFS: introduce start_removing_dentry()
> * [PATCH v5 08/14] VFS: add start_creating_killable() and start_removing_killable()
> * [PATCH v5 09/14] VFS/nfsd/ovl: introduce start_renaming() and end_renaming()
> * [PATCH v5 10/14] VFS/ovl/smb: introduce start_renaming_dentry()
> * [PATCH v5 11/14] Add start_renaming_two_dentries()
> * [PATCH v5 12/14] ecryptfs: use new start_creating/start_removing APIs
> * [PATCH v5 13/14] VFS: change vfs_mkdir() to unlock on failure.
> * [PATCH v5 14/14] VFS: introduce end_creating_keep()
> 
> and found the following issues:
> * WARNING: lock held when returning to user space in start_creating
> * possible deadlock in mnt_want_write
> 
> Full report is available here:
> https://ci.syzbot.org/series/4f406e4d-6aba-457a-b9c1-21f4407176a0
> 
> ***
> 
> WARNING: lock held when returning to user space in start_creating

I think this was due to a bug in 
   VFS: change vfs_mkdir() to unlock on failure.
in ovl_create_real()

That patch removed a end_creating() call that was after
ovl_create_real() returned failure, but didn't add end_creating() in
ovl_create_real() on failure.  So it could exit with the lock still
held.

This patch should fix it, particularly the second hunk.

Thanks,
NeilBrown

diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index a4a0dc261310..739f974dc258 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -187,7 +187,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
 			if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) {
 				pr_warn_ratelimited("wrong inherited casefold (%pd2)\n",
 						    newdentry);
-				dput(newdentry);
+				end_creating(newdentry);
 				err = -EINVAL;
 			}
 			break;
@@ -237,8 +237,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
 	}
 out:
 	if (err) {
-		if (!IS_ERR(newdentry))
-			dput(newdentry);
+		end_creating(newdentry);
 		return ERR_PTR(err);
 	}
 	return newdentry;

^ permalink raw reply related

* Re: [PATCH v5 14/14] VFS: introduce end_creating_keep()
From: Jeff Layton @ 2025-11-12 19:46 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-15-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> Occasionally the caller of end_creating() wants to keep using the dentry.
> Rather then requiring them to dget() the dentry (when not an error)
> before calling end_creating(), provide end_creating_keep() which does
> this.
> 
> cachefiles and overlayfs make use of this.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
>  fs/cachefiles/namei.c |  3 +--
>  fs/overlayfs/dir.c    |  8 ++------
>  fs/overlayfs/super.c  | 11 +++--------
>  include/linux/namei.h | 22 ++++++++++++++++++++++
>  4 files changed, 28 insertions(+), 16 deletions(-)
> 
> diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
> index 59327618ac42..ef22ac19545b 100644
> --- a/fs/cachefiles/namei.c
> +++ b/fs/cachefiles/namei.c
> @@ -155,8 +155,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
>  
>  	/* Tell rmdir() it's not allowed to delete the subdir */
>  	inode_lock(d_inode(subdir));
> -	dget(subdir);
> -	end_creating(subdir);
> +	end_creating_keep(subdir);
>  
>  	if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {
>  		pr_notice("cachefiles: Inode already in use: %pd (B=%lx)\n",
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index a4a0dc261310..50717ff8cac7 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -252,10 +252,7 @@ struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
>  	if (IS_ERR(ret))
>  		return ret;
>  	ret = ovl_create_real(ofs, workdir, ret, attr);
> -	if (!IS_ERR(ret))
> -		dget(ret);
> -	end_creating(ret);
> -	return ret;
> +	return end_creating_keep(ret);
>  }
>  
>  static int ovl_set_opaque_xerr(struct dentry *dentry, struct dentry *upper,
> @@ -365,8 +362,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
>  	if (IS_ERR(newdentry))
>  		return PTR_ERR(newdentry);
>  
> -	dget(newdentry);
> -	end_creating(newdentry);
> +	end_creating_keep(newdentry);
>  
>  	if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry) &&
>  	    !ovl_allow_offline_changes(ofs)) {
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 3acda985c8a3..7b8fc1cab6eb 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -319,8 +319,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
>  		};
>  
>  		if (work->d_inode) {
> -			dget(work);
> -			end_creating(work);
> +			end_creating_keep(work);
>  			if (persist)
>  				return work;
>  			err = -EEXIST;
> @@ -336,9 +335,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
>  		}
>  
>  		work = ovl_do_mkdir(ofs, dir, work, attr.ia_mode);
> -		if (!IS_ERR(work))
> -			dget(work);
> -		end_creating(work);
> +		end_creating_keep(work);
>  		err = PTR_ERR(work);
>  		if (IS_ERR(work))
>  			goto out_err;
> @@ -630,9 +627,7 @@ static struct dentry *ovl_lookup_or_create(struct ovl_fs *ofs,
>  		if (!child->d_inode)
>  			child = ovl_create_real(ofs, parent, child,
>  						OVL_CATTR(mode));
> -		if (!IS_ERR(child))
> -			dget(child);
> -		end_creating(child);
> +		end_creating_keep(child);
>  	}
>  	dput(parent);
>  
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index b4d95b79b5a8..58600cf234bc 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -126,6 +126,28 @@ static inline void end_creating(struct dentry *child)
>  	end_dirop(child);
>  }
>  
> +/* end_creating_keep - finish action started with start_creating() and return result
> + * @child: dentry returned by start_creating() or vfs_mkdir()
> + *
> + * Unlock and return the child. This can be called after
> + * start_creating() whether that function succeeded or not,
> + * but it is not needed on failure.
> + *
> + * If vfs_mkdir() was called then the value returned from that function
> + * should be given for @child rather than the original dentry, as vfs_mkdir()
> + * may have provided a new dentry.
> + *
> + * Returns: @child, which may be a dentry or an error.
> + *
> + */
> +static inline struct dentry *end_creating_keep(struct dentry *child)
> +{
> +	if (!IS_ERR(child))
> +		dget(child);
> +	end_dirop(child);
> +	return child;
> +}
> +
>  /**
>   * end_removing - finish action started with start_removing
>   * @child:  dentry returned by start_removing()

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 13/14] VFS: change vfs_mkdir() to unlock on failure.
From: Jeff Layton @ 2025-11-12 19:45 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-14-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> vfs_mkdir() already drops the reference to the dentry on failure but it
> leaves the parent locked.
> This complicates end_creating() which needs to unlock the parent even
> though the dentry is no longer available.
> 
> If we change vfs_mkdir() to unlock on failure as well as releasing the
> dentry, we can remove the "parent" arg from end_creating() and simplify
> the rules for calling it.
> 
> Note that cachefiles_get_directory() can choose to substitute an error
> instead of actually calling vfs_mkdir(), for fault injection.  In that
> case it needs to call end_creating(), just as vfs_mkdir() now does on
> error.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> 
> --
> changes since v2:
>  - extra {} in if() branch in cachefiles_get_directory() to
>    match the new extra {} in the else branch.
>  - filesystems/porting.rst updated.
> ---
>  Documentation/filesystems/porting.rst | 13 +++++++++++++
>  fs/btrfs/ioctl.c                      |  2 +-
>  fs/cachefiles/namei.c                 | 16 ++++++++-------
>  fs/ecryptfs/inode.c                   |  8 ++++----
>  fs/namei.c                            |  4 ++--
>  fs/nfsd/nfs3proc.c                    |  2 +-
>  fs/nfsd/nfs4proc.c                    |  2 +-
>  fs/nfsd/nfs4recover.c                 |  2 +-
>  fs/nfsd/nfsproc.c                     |  2 +-
>  fs/nfsd/vfs.c                         |  8 ++++----
>  fs/overlayfs/copy_up.c                |  4 ++--
>  fs/overlayfs/dir.c                    | 13 ++++++-------
>  fs/overlayfs/super.c                  |  6 +++---
>  fs/xfs/scrub/orphanage.c              |  2 +-
>  include/linux/namei.h                 | 28 +++++++++------------------
>  ipc/mqueue.c                          |  2 +-
>  16 files changed, 59 insertions(+), 55 deletions(-)
> 
> diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
> index 35f027981b21..d33429294252 100644
> --- a/Documentation/filesystems/porting.rst
> +++ b/Documentation/filesystems/porting.rst
> @@ -1309,3 +1309,16 @@ a different length, use
>  	vfs_parse_fs_qstr(fc, key, &QSTR_LEN(value, len))
>  
>  instead.
> +
> +---
> +
> +**mandatory**
> +
> +vfs_mkdir() now returns a dentry - the one returned by ->mkdir().  If
> +that dentry is different from the dentry passed in, including if it is
> +an IS_ERR() dentry pointer, the original dentry is dput().
> +
> +When vfs_mkdir() returns an error, and so both dputs() the original
> +dentry and doesn't provide a replacement, it also unlocks the parent.
> +Consequently the return value from vfs_mkdir() can be passed to
> +end_creating() and the parent will be unlocked precisely when necessary.
> diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
> index d0c3bb0423bb..b138120feba3 100644
> --- a/fs/btrfs/ioctl.c
> +++ b/fs/btrfs/ioctl.c
> @@ -935,7 +935,7 @@ static noinline int btrfs_mksubvol(struct dentry *parent,
>  out_up_read:
>  	up_read(&fs_info->subvol_sem);
>  out_dput:
> -	end_creating(dentry, parent);
> +	end_creating(dentry);
>  	return ret;
>  }
>  
> diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
> index 0104ac00485d..59327618ac42 100644
> --- a/fs/cachefiles/namei.c
> +++ b/fs/cachefiles/namei.c
> @@ -128,10 +128,12 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
>  		if (ret < 0)
>  			goto mkdir_error;
>  		ret = cachefiles_inject_write_error();
> -		if (ret == 0)
> +		if (ret == 0) {
>  			subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700);
> -		else
> +		} else {
> +			end_creating(subdir);
>  			subdir = ERR_PTR(ret);
> +		}
>  		if (IS_ERR(subdir)) {
>  			trace_cachefiles_vfs_error(NULL, d_inode(dir), ret,
>  						   cachefiles_trace_mkdir_error);
> @@ -140,7 +142,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
>  		trace_cachefiles_mkdir(dir, subdir);
>  
>  		if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) {
> -			end_creating(subdir, dir);
> +			end_creating(subdir);
>  			goto retry;
>  		}
>  		ASSERT(d_backing_inode(subdir));
> @@ -154,7 +156,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
>  	/* Tell rmdir() it's not allowed to delete the subdir */
>  	inode_lock(d_inode(subdir));
>  	dget(subdir);
> -	end_creating(subdir, dir);
> +	end_creating(subdir);
>  
>  	if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {
>  		pr_notice("cachefiles: Inode already in use: %pd (B=%lx)\n",
> @@ -196,7 +198,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
>  	return ERR_PTR(-EBUSY);
>  
>  mkdir_error:
> -	end_creating(subdir, dir);
> +	end_creating(subdir);
>  	pr_err("mkdir %s failed with error %d\n", dirname, ret);
>  	return ERR_PTR(ret);
>  
> @@ -699,7 +701,7 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
>  		if (ret < 0)
>  			goto out_end;
>  
> -		end_creating(dentry, fan);
> +		end_creating(dentry);
>  
>  		ret = cachefiles_inject_read_error();
>  		if (ret == 0)
> @@ -733,7 +735,7 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
>  	}
>  
>  out_end:
> -	end_creating(dentry, fan);
> +	end_creating(dentry);
>  out:
>  	_leave(" = %u", success);
>  	return success;
> diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
> index 37d6293600c7..c951e723f24d 100644
> --- a/fs/ecryptfs/inode.c
> +++ b/fs/ecryptfs/inode.c
> @@ -211,7 +211,7 @@ ecryptfs_do_create(struct inode *directory_inode,
>  	fsstack_copy_attr_times(directory_inode, lower_dir);
>  	fsstack_copy_inode_size(directory_inode, lower_dir);
>  out_lock:
> -	end_creating(lower_dentry, NULL);
> +	end_creating(lower_dentry);
>  	return inode;
>  }
>  
> @@ -456,7 +456,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
>  		  ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink);
>  	i_size_write(d_inode(new_dentry), file_size_save);
>  out_lock:
> -	end_creating(lower_new_dentry, NULL);
> +	end_creating(lower_new_dentry);
>  	return rc;
>  }
>  
> @@ -500,7 +500,7 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
>  	fsstack_copy_attr_times(dir, lower_dir);
>  	fsstack_copy_inode_size(dir, lower_dir);
>  out_lock:
> -	end_creating(lower_dentry, NULL);
> +	end_creating(lower_dentry);
>  	if (d_really_is_negative(dentry))
>  		d_drop(dentry);
>  	return rc;
> @@ -534,7 +534,7 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
>  	fsstack_copy_inode_size(dir, lower_dir);
>  	set_nlink(dir, lower_dir->i_nlink);
>  out:
> -	end_creating(lower_dentry, lower_dir_dentry);
> +	end_creating(lower_dentry);
>  	if (d_really_is_negative(dentry))
>  		d_drop(dentry);
>  	return ERR_PTR(rc);
> diff --git a/fs/namei.c b/fs/namei.c
> index 2444c7ddb926..e834486acff1 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -4832,7 +4832,7 @@ EXPORT_SYMBOL(start_creating_path);
>   */
>  void end_creating_path(const struct path *path, struct dentry *dentry)
>  {
> -	end_creating(dentry, path->dentry);
> +	end_creating(dentry);
>  	mnt_drop_write(path->mnt);
>  	path_put(path);
>  }
> @@ -5034,7 +5034,7 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
>  	return dentry;
>  
>  err:
> -	dput(dentry);
> +	end_creating(dentry);
>  	return ERR_PTR(error);
>  }
>  EXPORT_SYMBOL(vfs_mkdir);
> diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
> index e2aac0def2cb..6b39e4aff959 100644
> --- a/fs/nfsd/nfs3proc.c
> +++ b/fs/nfsd/nfs3proc.c
> @@ -364,7 +364,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs);
>  
>  out:
> -	end_creating(child, parent);
> +	end_creating(child);
>  out_write:
>  	fh_drop_write(fhp);
>  	return status;
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index b2c95e8e7c68..524cb07a477c 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -376,7 +376,7 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	if (attrs.na_aclerr)
>  		open->op_bmval[0] &= ~FATTR4_WORD0_ACL;
>  out:
> -	end_creating(child, parent);
> +	end_creating(child);
>  	nfsd_attrs_free(&attrs);
>  out_write:
>  	fh_drop_write(fhp);
> diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
> index 3eefaa2202e3..18c08395b273 100644
> --- a/fs/nfsd/nfs4recover.c
> +++ b/fs/nfsd/nfs4recover.c
> @@ -215,7 +215,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
>  	if (IS_ERR(dentry))
>  		status = PTR_ERR(dentry);
>  out_end:
> -	end_creating(dentry, dir);
> +	end_creating(dentry);
>  out:
>  	if (status == 0) {
>  		if (nn->in_grace)
> diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
> index ee1b16e921fd..28f03a6a3cc3 100644
> --- a/fs/nfsd/nfsproc.c
> +++ b/fs/nfsd/nfsproc.c
> @@ -421,7 +421,7 @@ nfsd_proc_create(struct svc_rqst *rqstp)
>  	}
>  
>  out_unlock:
> -	end_creating(dchild, dirfhp->fh_dentry);
> +	end_creating(dchild);
>  out_write:
>  	fh_drop_write(dirfhp);
>  done:
> diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> index a993f1e54182..145f1c8d124d 100644
> --- a/fs/nfsd/vfs.c
> +++ b/fs/nfsd/vfs.c
> @@ -1589,7 +1589,7 @@ nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  out:
>  	if (!err)
>  		fh_fill_post_attrs(fhp);
> -	end_creating(dchild, dentry);
> +	end_creating(dchild);
>  	return err;
>  
>  out_nfserr:
> @@ -1646,7 +1646,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  	return err;
>  
>  out_unlock:
> -	end_creating(dchild, dentry);
> +	end_creating(dchild);
>  	return err;
>  }
>  
> @@ -1747,7 +1747,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
>  		nfsd_create_setattr(rqstp, fhp, resfhp, attrs);
>  	fh_fill_post_attrs(fhp);
>  out_unlock:
> -	end_creating(dnew, dentry);
> +	end_creating(dnew);
>  	if (!err)
>  		err = nfserrno(commit_metadata(fhp));
>  	if (!err)
> @@ -1824,7 +1824,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
>  	host_err = vfs_link(dold, &nop_mnt_idmap, dirp, dnew, NULL);
>  	fh_fill_post_attrs(ffhp);
>  out_unlock:
> -	end_creating(dnew, ddir);
> +	end_creating(dnew);
>  	if (!host_err) {
>  		host_err = commit_metadata(ffhp);
>  		if (!host_err)
> diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
> index 9911a346b477..23216ed01325 100644
> --- a/fs/overlayfs/copy_up.c
> +++ b/fs/overlayfs/copy_up.c
> @@ -624,7 +624,7 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
>  			ovl_dentry_set_upper_alias(c->dentry);
>  			ovl_dentry_update_reval(c->dentry, upper);
>  		}
> -		end_creating(upper, upperdir);
> +		end_creating(upper);
>  	}
>  	if (err)
>  		goto out;
> @@ -891,7 +891,7 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
>  	err = PTR_ERR(upper);
>  	if (!IS_ERR(upper)) {
>  		err = ovl_do_link(ofs, temp, udir, upper);
> -		end_creating(upper, c->destdir);
> +		end_creating(upper);
>  	}
>  
>  	if (err)
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index 61e9484e4ab8..a4a0dc261310 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -91,7 +91,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
>  		err = ovl_do_whiteout(ofs, wdir, whiteout);
>  		if (!err)
>  			ofs->whiteout = dget(whiteout);
> -		end_creating(whiteout, workdir);
> +		end_creating(whiteout);
>  		if (err)
>  			return ERR_PTR(err);
>  	}
> @@ -103,7 +103,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
>  		err = ovl_do_link(ofs, ofs->whiteout, wdir, link);
>  		if (!err)
>  			whiteout = dget(link);
> -		end_creating(link, workdir);
> +		end_creating(link);
>  		if (!err)
>  			return whiteout;;
>  
> @@ -254,7 +254,7 @@ struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
>  	ret = ovl_create_real(ofs, workdir, ret, attr);
>  	if (!IS_ERR(ret))
>  		dget(ret);
> -	end_creating(ret, workdir);
> +	end_creating(ret);
>  	return ret;
>  }
>  
> @@ -362,12 +362,11 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
>  	if (IS_ERR(newdentry))
>  		return PTR_ERR(newdentry);
>  	newdentry = ovl_create_real(ofs, upperdir, newdentry, attr);
> -	if (IS_ERR(newdentry)) {
> -		end_creating(newdentry, upperdir);
> +	if (IS_ERR(newdentry))
>  		return PTR_ERR(newdentry);
> -	}
> +
>  	dget(newdentry);
> -	end_creating(newdentry, upperdir);
> +	end_creating(newdentry);
>  
>  	if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry) &&
>  	    !ovl_allow_offline_changes(ofs)) {
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index a721ef2b90e8..3acda985c8a3 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -320,7 +320,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
>  
>  		if (work->d_inode) {
>  			dget(work);
> -			end_creating(work, ofs->workbasedir);
> +			end_creating(work);
>  			if (persist)
>  				return work;
>  			err = -EEXIST;
> @@ -338,7 +338,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
>  		work = ovl_do_mkdir(ofs, dir, work, attr.ia_mode);
>  		if (!IS_ERR(work))
>  			dget(work);
> -		end_creating(work, ofs->workbasedir);
> +		end_creating(work);
>  		err = PTR_ERR(work);
>  		if (IS_ERR(work))
>  			goto out_err;
> @@ -632,7 +632,7 @@ static struct dentry *ovl_lookup_or_create(struct ovl_fs *ofs,
>  						OVL_CATTR(mode));
>  		if (!IS_ERR(child))
>  			dget(child);
> -		end_creating(child, parent);
> +		end_creating(child);
>  	}
>  	dput(parent);
>  
> diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c
> index e732605924a1..b77c2b6b6d44 100644
> --- a/fs/xfs/scrub/orphanage.c
> +++ b/fs/xfs/scrub/orphanage.c
> @@ -199,7 +199,7 @@ xrep_orphanage_create(
>  	sc->orphanage_ilock_flags = 0;
>  
>  out_dput_orphanage:
> -	end_creating(orphanage_dentry, root_dentry);
> +	end_creating(orphanage_dentry);
>  out_dput_root:
>  	dput(root_dentry);
>  out:
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index 0e6b1b9afc26..b4d95b79b5a8 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -106,34 +106,24 @@ struct dentry *start_creating_dentry(struct dentry *parent,
>  struct dentry *start_removing_dentry(struct dentry *parent,
>  				     struct dentry *child);
>  
> -/**
> - * end_creating - finish action started with start_creating
> - * @child:  dentry returned by start_creating() or vfs_mkdir()
> - * @parent: dentry given to start_creating(),
> - *
> - * Unlock and release the child.
> +/* end_creating - finish action started with start_creating
> + * @child: dentry returned by start_creating() or vfs_mkdir()
>   *
> - * Unlike end_dirop() this can only be called if start_creating() succeeded.
> - * It handles @child being and error as vfs_mkdir() might have converted the
> - * dentry to an error - in that case the parent still needs to be unlocked.
> + * Unlock and release the child. This can be called after
> + * start_creating() whether that function succeeded or not,
> + * but it is not needed on failure.
>   *
>   * If vfs_mkdir() was called then the value returned from that function
>   * should be given for @child rather than the original dentry, as vfs_mkdir()
> - * may have provided a new dentry.  Even if vfs_mkdir() returns an error
> - * it must be given to end_creating().
> + * may have provided a new dentry.
> + *
>   *
>   * If vfs_mkdir() was not called, then @child will be a valid dentry and
>   * @parent will be ignored.
>   */
> -static inline void end_creating(struct dentry *child, struct dentry *parent)
> +static inline void end_creating(struct dentry *child)
>  {
> -	if (IS_ERR(child))
> -		/* The parent is still locked despite the error from
> -		 * vfs_mkdir() - must unlock it.
> -		 */
> -		inode_unlock(parent->d_inode);
> -	else
> -		end_dirop(child);
> +	end_dirop(child);
>  }
>  
>  /**
> diff --git a/ipc/mqueue.c b/ipc/mqueue.c
> index 6d7610310003..83d9466710d6 100644
> --- a/ipc/mqueue.c
> +++ b/ipc/mqueue.c
> @@ -932,7 +932,7 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
>  		put_unused_fd(fd);
>  		fd = error;
>  	}
> -	end_creating(path.dentry, root);
> +	end_creating(path.dentry);
>  	if (!ro)
>  		mnt_drop_write(mnt);
>  out_putname:

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 12/14] ecryptfs: use new start_creating/start_removing APIs
From: Jeff Layton @ 2025-11-12 19:41 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-13-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> This requires the addition of start_creating_dentry() which is given the
> dentry which has already been found, and asks for it to be locked and
> its parent validated.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> 
> ---
> changes since v4
> - two places in ecryptfs uses dget_parent(dentry->d_parent),
>   thus incorrectly. getting grandparent.  Changed to
>   dget_parent(dentry).
> ---
>  fs/ecryptfs/inode.c   | 153 ++++++++++++++++++++----------------------
>  fs/namei.c            |  33 +++++++++
>  include/linux/namei.h |   2 +
>  3 files changed, 107 insertions(+), 81 deletions(-)
> 
> diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
> index fc6d37419753..37d6293600c7 100644
> --- a/fs/ecryptfs/inode.c
> +++ b/fs/ecryptfs/inode.c
> @@ -24,18 +24,26 @@
>  #include <linux/unaligned.h>
>  #include "ecryptfs_kernel.h"
>  
> -static int lock_parent(struct dentry *dentry,
> -		       struct dentry **lower_dentry,
> -		       struct inode **lower_dir)
> +static struct dentry *ecryptfs_start_creating_dentry(struct dentry *dentry)
>  {
> -	struct dentry *lower_dir_dentry;
> +	struct dentry *parent = dget_parent(dentry);
> +	struct dentry *ret;
>  
> -	lower_dir_dentry = ecryptfs_dentry_to_lower(dentry->d_parent);
> -	*lower_dir = d_inode(lower_dir_dentry);
> -	*lower_dentry = ecryptfs_dentry_to_lower(dentry);
> +	ret = start_creating_dentry(ecryptfs_dentry_to_lower(parent),
> +				    ecryptfs_dentry_to_lower(dentry));
> +	dput(parent);
> +	return ret;
> +}
>  
> -	inode_lock_nested(*lower_dir, I_MUTEX_PARENT);
> -	return (*lower_dentry)->d_parent == lower_dir_dentry ? 0 : -EINVAL;
> +static struct dentry *ecryptfs_start_removing_dentry(struct dentry *dentry)
> +{
> +	struct dentry *parent = dget_parent(dentry);
> +	struct dentry *ret;
> +
> +	ret = start_removing_dentry(ecryptfs_dentry_to_lower(parent),
> +				    ecryptfs_dentry_to_lower(dentry));
> +	dput(parent);
> +	return ret;
>  }
>  
>  static int ecryptfs_inode_test(struct inode *inode, void *lower_inode)
> @@ -141,15 +149,12 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry,
>  	struct inode *lower_dir;
>  	int rc;
>  
> -	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
> -	dget(lower_dentry);	// don't even try to make the lower negative
> -	if (!rc) {
> -		if (d_unhashed(lower_dentry))
> -			rc = -EINVAL;
> -		else
> -			rc = vfs_unlink(&nop_mnt_idmap, lower_dir, lower_dentry,
> -					NULL);
> -	}
> +	lower_dentry = ecryptfs_start_removing_dentry(dentry);
> +	if (IS_ERR(lower_dentry))
> +		return PTR_ERR(lower_dentry);
> +
> +	lower_dir = lower_dentry->d_parent->d_inode;
> +	rc = vfs_unlink(&nop_mnt_idmap, lower_dir, lower_dentry, NULL);
>  	if (rc) {
>  		printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc);
>  		goto out_unlock;
> @@ -158,8 +163,7 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry,
>  	set_nlink(inode, ecryptfs_inode_to_lower(inode)->i_nlink);
>  	inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
>  out_unlock:
> -	dput(lower_dentry);
> -	inode_unlock(lower_dir);
> +	end_removing(lower_dentry);
>  	if (!rc)
>  		d_drop(dentry);
>  	return rc;
> @@ -186,10 +190,12 @@ ecryptfs_do_create(struct inode *directory_inode,
>  	struct inode *lower_dir;
>  	struct inode *inode;
>  
> -	rc = lock_parent(ecryptfs_dentry, &lower_dentry, &lower_dir);
> -	if (!rc)
> -		rc = vfs_create(&nop_mnt_idmap, lower_dir,
> -				lower_dentry, mode, true);
> +	lower_dentry = ecryptfs_start_creating_dentry(ecryptfs_dentry);
> +	if (IS_ERR(lower_dentry))
> +		return ERR_CAST(lower_dentry);
> +	lower_dir = lower_dentry->d_parent->d_inode;
> +	rc = vfs_create(&nop_mnt_idmap, lower_dir,
> +			lower_dentry, mode, true);
>  	if (rc) {
>  		printk(KERN_ERR "%s: Failure to create dentry in lower fs; "
>  		       "rc = [%d]\n", __func__, rc);
> @@ -205,7 +211,7 @@ ecryptfs_do_create(struct inode *directory_inode,
>  	fsstack_copy_attr_times(directory_inode, lower_dir);
>  	fsstack_copy_inode_size(directory_inode, lower_dir);
>  out_lock:
> -	inode_unlock(lower_dir);
> +	end_creating(lower_dentry, NULL);
>  	return inode;
>  }
>  
> @@ -433,10 +439,12 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
>  
>  	file_size_save = i_size_read(d_inode(old_dentry));
>  	lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
> -	rc = lock_parent(new_dentry, &lower_new_dentry, &lower_dir);
> -	if (!rc)
> -		rc = vfs_link(lower_old_dentry, &nop_mnt_idmap, lower_dir,
> -			      lower_new_dentry, NULL);
> +	lower_new_dentry = ecryptfs_start_creating_dentry(new_dentry);
> +	if (IS_ERR(lower_new_dentry))
> +		return PTR_ERR(lower_new_dentry);
> +	lower_dir = lower_new_dentry->d_parent->d_inode;
> +	rc = vfs_link(lower_old_dentry, &nop_mnt_idmap, lower_dir,
> +		      lower_new_dentry, NULL);
>  	if (rc || d_really_is_negative(lower_new_dentry))
>  		goto out_lock;
>  	rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb);
> @@ -448,7 +456,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
>  		  ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink);
>  	i_size_write(d_inode(new_dentry), file_size_save);
>  out_lock:
> -	inode_unlock(lower_dir);
> +	end_creating(lower_new_dentry, NULL);
>  	return rc;
>  }
>  
> @@ -468,9 +476,11 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
>  	size_t encoded_symlen;
>  	struct ecryptfs_mount_crypt_stat *mount_crypt_stat = NULL;
>  
> -	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
> -	if (rc)
> -		goto out_lock;
> +	lower_dentry = ecryptfs_start_creating_dentry(dentry);
> +	if (IS_ERR(lower_dentry))
> +		return PTR_ERR(lower_dentry);
> +	lower_dir = lower_dentry->d_parent->d_inode;
> +
>  	mount_crypt_stat = &ecryptfs_superblock_to_private(
>  		dir->i_sb)->mount_crypt_stat;
>  	rc = ecryptfs_encrypt_and_encode_filename(&encoded_symname,
> @@ -490,7 +500,7 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
>  	fsstack_copy_attr_times(dir, lower_dir);
>  	fsstack_copy_inode_size(dir, lower_dir);
>  out_lock:
> -	inode_unlock(lower_dir);
> +	end_creating(lower_dentry, NULL);
>  	if (d_really_is_negative(dentry))
>  		d_drop(dentry);
>  	return rc;
> @@ -501,12 +511,14 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
>  {
>  	int rc;
>  	struct dentry *lower_dentry;
> +	struct dentry *lower_dir_dentry;
>  	struct inode *lower_dir;
>  
> -	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
> -	if (rc)
> -		goto out;
> -
> +	lower_dentry = ecryptfs_start_creating_dentry(dentry);
> +	if (IS_ERR(lower_dentry))
> +		return lower_dentry;
> +	lower_dir_dentry = dget(lower_dentry->d_parent);
> +	lower_dir = lower_dir_dentry->d_inode;
>  	lower_dentry = vfs_mkdir(&nop_mnt_idmap, lower_dir,
>  				 lower_dentry, mode);
>  	rc = PTR_ERR(lower_dentry);
> @@ -522,7 +534,7 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
>  	fsstack_copy_inode_size(dir, lower_dir);
>  	set_nlink(dir, lower_dir->i_nlink);
>  out:
> -	inode_unlock(lower_dir);
> +	end_creating(lower_dentry, lower_dir_dentry);
>  	if (d_really_is_negative(dentry))
>  		d_drop(dentry);
>  	return ERR_PTR(rc);
> @@ -534,21 +546,18 @@ static int ecryptfs_rmdir(struct inode *dir, struct dentry *dentry)
>  	struct inode *lower_dir;
>  	int rc;
>  
> -	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
> -	dget(lower_dentry);	// don't even try to make the lower negative
> -	if (!rc) {
> -		if (d_unhashed(lower_dentry))
> -			rc = -EINVAL;
> -		else
> -			rc = vfs_rmdir(&nop_mnt_idmap, lower_dir, lower_dentry);
> -	}
> +	lower_dentry = ecryptfs_start_removing_dentry(dentry);
> +	if (IS_ERR(lower_dentry))
> +		return PTR_ERR(lower_dentry);
> +	lower_dir = lower_dentry->d_parent->d_inode;
> +
> +	rc = vfs_rmdir(&nop_mnt_idmap, lower_dir, lower_dentry);
>  	if (!rc) {
>  		clear_nlink(d_inode(dentry));
>  		fsstack_copy_attr_times(dir, lower_dir);
>  		set_nlink(dir, lower_dir->i_nlink);
>  	}
> -	dput(lower_dentry);
> -	inode_unlock(lower_dir);
> +	end_removing(lower_dentry);
>  	if (!rc)
>  		d_drop(dentry);
>  	return rc;
> @@ -562,10 +571,12 @@ ecryptfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
>  	struct dentry *lower_dentry;
>  	struct inode *lower_dir;
>  
> -	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
> -	if (!rc)
> -		rc = vfs_mknod(&nop_mnt_idmap, lower_dir,
> -			       lower_dentry, mode, dev);
> +	lower_dentry = ecryptfs_start_creating_dentry(dentry);
> +	if (IS_ERR(lower_dentry))
> +		return PTR_ERR(lower_dentry);
> +	lower_dir = lower_dentry->d_parent->d_inode;
> +
> +	rc = vfs_mknod(&nop_mnt_idmap, lower_dir, lower_dentry, mode, dev);
>  	if (rc || d_really_is_negative(lower_dentry))
>  		goto out;
>  	rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb);
> @@ -574,7 +585,7 @@ ecryptfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
>  	fsstack_copy_attr_times(dir, lower_dir);
>  	fsstack_copy_inode_size(dir, lower_dir);
>  out:
> -	inode_unlock(lower_dir);
> +	end_removing(lower_dentry);
>  	if (d_really_is_negative(dentry))
>  		d_drop(dentry);
>  	return rc;
> @@ -590,7 +601,6 @@ ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
>  	struct dentry *lower_new_dentry;
>  	struct dentry *lower_old_dir_dentry;
>  	struct dentry *lower_new_dir_dentry;
> -	struct dentry *trap;
>  	struct inode *target_inode;
>  	struct renamedata rd = {};
>  
> @@ -605,31 +615,13 @@ ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
>  
>  	target_inode = d_inode(new_dentry);
>  
> -	trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
> -	if (IS_ERR(trap))
> -		return PTR_ERR(trap);
> -	dget(lower_new_dentry);
> -	rc = -EINVAL;
> -	if (lower_old_dentry->d_parent != lower_old_dir_dentry)
> -		goto out_lock;
> -	if (lower_new_dentry->d_parent != lower_new_dir_dentry)
> -		goto out_lock;
> -	if (d_unhashed(lower_old_dentry) || d_unhashed(lower_new_dentry))
> -		goto out_lock;
> -	/* source should not be ancestor of target */
> -	if (trap == lower_old_dentry)
> -		goto out_lock;
> -	/* target should not be ancestor of source */
> -	if (trap == lower_new_dentry) {
> -		rc = -ENOTEMPTY;
> -		goto out_lock;
> -	}
> +	rd.mnt_idmap  = &nop_mnt_idmap;
> +	rd.old_parent = lower_old_dir_dentry;
> +	rd.new_parent = lower_new_dir_dentry;
> +	rc = start_renaming_two_dentries(&rd, lower_old_dentry, lower_new_dentry);
> +	if (rc)
> +		return rc;
>  
> -	rd.mnt_idmap		= &nop_mnt_idmap;
> -	rd.old_parent		= lower_old_dir_dentry;
> -	rd.old_dentry		= lower_old_dentry;
> -	rd.new_parent		= lower_new_dir_dentry;
> -	rd.new_dentry		= lower_new_dentry;
>  	rc = vfs_rename(&rd);
>  	if (rc)
>  		goto out_lock;
> @@ -640,8 +632,7 @@ ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
>  	if (new_dir != old_dir)
>  		fsstack_copy_attr_all(old_dir, d_inode(lower_old_dir_dentry));
>  out_lock:
> -	dput(lower_new_dentry);
> -	unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
> +	end_renaming(&rd);
>  	return rc;
>  }
>  
> diff --git a/fs/namei.c b/fs/namei.c
> index 7f0384ceb976..2444c7ddb926 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3397,6 +3397,39 @@ struct dentry *start_removing_noperm(struct dentry *parent,
>  }
>  EXPORT_SYMBOL(start_removing_noperm);
>  
> +/**
> + * start_creating_dentry - prepare to create a given dentry
> + * @parent: directory from which dentry should be removed
> + * @child:  the dentry to be removed
> + *
> + * A lock is taken to protect the dentry again other dirops and
> + * the validity of the dentry is checked: correct parent and still hashed.
> + *
> + * If the dentry is valid and negative a reference is taken and
> + * returned.  If not an error is returned.
> + *
> + * end_creating() should be called when creation is complete, or aborted.
> + *
> + * Returns: the valid dentry, or an error.
> + */
> +struct dentry *start_creating_dentry(struct dentry *parent,
> +				     struct dentry *child)
> +{
> +	inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
> +	if (unlikely(IS_DEADDIR(parent->d_inode) ||
> +		     child->d_parent != parent ||
> +		     d_unhashed(child))) {
> +		inode_unlock(parent->d_inode);
> +		return ERR_PTR(-EINVAL);
> +	}
> +	if (d_is_positive(child)) {
> +		inode_unlock(parent->d_inode);
> +		return ERR_PTR(-EEXIST);
> +	}
> +	return dget(child);
> +}
> +EXPORT_SYMBOL(start_creating_dentry);
> +
>  /**
>   * start_removing_dentry - prepare to remove a given dentry
>   * @parent: directory from which dentry should be removed
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index 9104c7104191..0e6b1b9afc26 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -101,6 +101,8 @@ struct dentry *start_removing_killable(struct mnt_idmap *idmap,
>  				       struct qstr *name);
>  struct dentry *start_creating_noperm(struct dentry *parent, struct qstr *name);
>  struct dentry *start_removing_noperm(struct dentry *parent, struct qstr *name);
> +struct dentry *start_creating_dentry(struct dentry *parent,
> +				     struct dentry *child);
>  struct dentry *start_removing_dentry(struct dentry *parent,
>  				     struct dentry *child);
>  

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 11/14] Add start_renaming_two_dentries()
From: Jeff Layton @ 2025-11-12 19:38 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-12-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> A few callers want to lock for a rename and already have both dentries.
> Also debugfs does want to perform a lookup but doesn't want permission
> checking, so start_renaming_dentry() cannot be used.
> 
> This patch introduces start_renaming_two_dentries() which is given both
> dentries.  debugfs performs one lookup itself.  As it will only continue
> with a negative dentry and as those cannot be renamed or unlinked, it is
> safe to do the lookup before getting the rename locks.
> 
> overlayfs uses start_renaming_two_dentries() in three places and  selinux
> uses it twice in sel_make_policy_nodes().
> 
> In sel_make_policy_nodes() we now lock for rename twice instead of just
> once so the combined operation is no longer atomic w.r.t the parent
> directory locks.  As selinux_state.policy_mutex is held across the whole
> operation this does open up any interesting races.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> 
> ---
> changes since v3:
>  added missing assignment to rd.mnt_idmap in ovl_cleanup_and_whiteout
> ---
>  fs/debugfs/inode.c           | 48 ++++++++++++--------------
>  fs/namei.c                   | 65 ++++++++++++++++++++++++++++++++++++
>  fs/overlayfs/dir.c           | 43 ++++++++++++++++--------
>  include/linux/namei.h        |  2 ++
>  security/selinux/selinuxfs.c | 27 ++++++++++-----
>  5 files changed, 136 insertions(+), 49 deletions(-)
> 
> diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
> index f241b9df642a..532bd7c46baf 100644
> --- a/fs/debugfs/inode.c
> +++ b/fs/debugfs/inode.c
> @@ -842,7 +842,8 @@ int __printf(2, 3) debugfs_change_name(struct dentry *dentry, const char *fmt, .
>  	int error = 0;
>  	const char *new_name;
>  	struct name_snapshot old_name;
> -	struct dentry *parent, *target;
> +	struct dentry *target;
> +	struct renamedata rd = {};
>  	struct inode *dir;
>  	va_list ap;
>  
> @@ -855,36 +856,31 @@ int __printf(2, 3) debugfs_change_name(struct dentry *dentry, const char *fmt, .
>  	if (!new_name)
>  		return -ENOMEM;
>  
> -	parent = dget_parent(dentry);
> -	dir = d_inode(parent);
> -	inode_lock(dir);
> +	rd.old_parent = dget_parent(dentry);
> +	rd.new_parent = rd.old_parent;
> +	rd.flags = RENAME_NOREPLACE;
> +	target = lookup_noperm_unlocked(&QSTR(new_name), rd.new_parent);
> +	if (IS_ERR(target))
> +		return PTR_ERR(target);
>  
> -	take_dentry_name_snapshot(&old_name, dentry);
> -
> -	if (WARN_ON_ONCE(dentry->d_parent != parent)) {
> -		error = -EINVAL;
> -		goto out;
> -	}
> -	if (strcmp(old_name.name.name, new_name) == 0)
> -		goto out;
> -	target = lookup_noperm(&QSTR(new_name), parent);
> -	if (IS_ERR(target)) {
> -		error = PTR_ERR(target);
> -		goto out;
> -	}
> -	if (d_really_is_positive(target)) {
> -		dput(target);
> -		error = -EINVAL;
> +	error = start_renaming_two_dentries(&rd, dentry, target);
> +	if (error) {
> +		if (error == -EEXIST && target == dentry)
> +			/* it isn't an error to rename a thing to itself */
> +			error = 0;
>  		goto out;
>  	}
> -	simple_rename_timestamp(dir, dentry, dir, target);
> -	d_move(dentry, target);
> -	dput(target);
> +
> +	dir = d_inode(rd.old_parent);
> +	take_dentry_name_snapshot(&old_name, dentry);
> +	simple_rename_timestamp(dir, dentry, dir, rd.new_dentry);
> +	d_move(dentry, rd.new_dentry);
>  	fsnotify_move(dir, dir, &old_name.name, d_is_dir(dentry), NULL, dentry);
> -out:
>  	release_dentry_name_snapshot(&old_name);
> -	inode_unlock(dir);
> -	dput(parent);
> +	end_renaming(&rd);
> +out:
> +	dput(rd.old_parent);
> +	dput(target);
>  	kfree_const(new_name);
>  	return error;
>  }
> diff --git a/fs/namei.c b/fs/namei.c
> index 4b740048df97..7f0384ceb976 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3877,6 +3877,71 @@ int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
>  }
>  EXPORT_SYMBOL(start_renaming_dentry);
>  
> +/**
> + * start_renaming_two_dentries - Lock to dentries in given parents for rename
> + * @rd:           rename data containing parent
> + * @old_dentry:   dentry of name to move
> + * @new_dentry:   dentry to move to
> + *
> + * Ensure locks are in place for rename and check parentage is still correct.
> + *
> + * On success the two dentries are stored in @rd.old_dentry and
> + * @rd.new_dentry and @rd.old_parent and @rd.new_parent are confirmed to
> + * be the parents of the dentries.
> + *
> + * References and the lock can be dropped with end_renaming()
> + *
> + * Returns: zero or an error.
> + */
> +int
> +start_renaming_two_dentries(struct renamedata *rd,
> +			    struct dentry *old_dentry, struct dentry *new_dentry)
> +{
> +	struct dentry *trap;
> +	int err;
> +
> +	/* Already have the dentry - need to be sure to lock the correct parent */
> +	trap = lock_rename_child(old_dentry, rd->new_parent);
> +	if (IS_ERR(trap))
> +		return PTR_ERR(trap);
> +	err = -EINVAL;
> +	if (d_unhashed(old_dentry) ||
> +	    (rd->old_parent && rd->old_parent != old_dentry->d_parent))
> +		/* old_dentry was removed, or moved and explicit parent requested */
> +		goto out_unlock;
> +	if (d_unhashed(new_dentry) ||
> +	    rd->new_parent != new_dentry->d_parent)
> +		/* new_dentry was removed or moved */
> +		goto out_unlock;
> +
> +	if (old_dentry == trap)
> +		/* source is an ancestor of target */
> +		goto out_unlock;
> +
> +	if (new_dentry == trap) {
> +		/* target is an ancestor of source */
> +		if (rd->flags & RENAME_EXCHANGE)
> +			err = -EINVAL;
> +		else
> +			err = -ENOTEMPTY;
> +		goto out_unlock;
> +	}
> +
> +	err = -EEXIST;
> +	if (d_is_positive(new_dentry) && (rd->flags & RENAME_NOREPLACE))
> +		goto out_unlock;
> +
> +	rd->old_dentry = dget(old_dentry);
> +	rd->new_dentry = dget(new_dentry);
> +	rd->old_parent = dget(old_dentry->d_parent);
> +	return 0;
> +
> +out_unlock:
> +	unlock_rename(old_dentry->d_parent, rd->new_parent);
> +	return err;
> +}
> +EXPORT_SYMBOL(start_renaming_two_dentries);
> +
>  void end_renaming(struct renamedata *rd)
>  {
>  	unlock_rename(rd->old_parent, rd->new_parent);
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index 6b2f88edb497..61e9484e4ab8 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -123,6 +123,7 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
>  			     struct dentry *dentry)
>  {
>  	struct dentry *whiteout;
> +	struct renamedata rd = {};
>  	int err;
>  	int flags = 0;
>  
> @@ -134,10 +135,14 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
>  	if (d_is_dir(dentry))
>  		flags = RENAME_EXCHANGE;
>  
> -	err = ovl_lock_rename_workdir(ofs->workdir, whiteout, dir, dentry);
> +	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
> +	rd.old_parent = ofs->workdir;
> +	rd.new_parent = dir;
> +	rd.flags = flags;
> +	err = start_renaming_two_dentries(&rd, whiteout, dentry);
>  	if (!err) {
> -		err = ovl_do_rename(ofs, ofs->workdir, whiteout, dir, dentry, flags);
> -		unlock_rename(ofs->workdir, dir);
> +		err = ovl_do_rename_rd(&rd);
> +		end_renaming(&rd);
>  	}
>  	if (err)
>  		goto kill_whiteout;
> @@ -388,6 +393,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
>  	struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
>  	struct dentry *workdir = ovl_workdir(dentry);
>  	struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
> +	struct renamedata rd = {};
>  	struct path upperpath;
>  	struct dentry *upper;
>  	struct dentry *opaquedir;
> @@ -413,7 +419,11 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
>  	if (IS_ERR(opaquedir))
>  		goto out;
>  
> -	err = ovl_lock_rename_workdir(workdir, opaquedir, upperdir, upper);
> +	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
> +	rd.old_parent = workdir;
> +	rd.new_parent = upperdir;
> +	rd.flags = RENAME_EXCHANGE;
> +	err = start_renaming_two_dentries(&rd, opaquedir, upper);
>  	if (err)
>  		goto out_cleanup_unlocked;
>  
> @@ -431,8 +441,8 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
>  	if (err)
>  		goto out_cleanup;
>  
> -	err = ovl_do_rename(ofs, workdir, opaquedir, upperdir, upper, RENAME_EXCHANGE);
> -	unlock_rename(workdir, upperdir);
> +	err = ovl_do_rename_rd(&rd);
> +	end_renaming(&rd);
>  	if (err)
>  		goto out_cleanup_unlocked;
>  
> @@ -445,7 +455,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
>  	return opaquedir;
>  
>  out_cleanup:
> -	unlock_rename(workdir, upperdir);
> +	end_renaming(&rd);
>  out_cleanup_unlocked:
>  	ovl_cleanup(ofs, workdir, opaquedir);
>  	dput(opaquedir);
> @@ -468,6 +478,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
>  	struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
>  	struct dentry *workdir = ovl_workdir(dentry);
>  	struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
> +	struct renamedata rd = {};
>  	struct dentry *upper;
>  	struct dentry *newdentry;
>  	int err;
> @@ -499,7 +510,11 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
>  	if (IS_ERR(newdentry))
>  		goto out_dput;
>  
> -	err = ovl_lock_rename_workdir(workdir, newdentry, upperdir, upper);
> +	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
> +	rd.old_parent = workdir;
> +	rd.new_parent = upperdir;
> +	rd.flags = 0;
> +	err = start_renaming_two_dentries(&rd, newdentry, upper);
>  	if (err)
>  		goto out_cleanup_unlocked;
>  
> @@ -536,16 +551,16 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
>  		if (err)
>  			goto out_cleanup;
>  
> -		err = ovl_do_rename(ofs, workdir, newdentry, upperdir, upper,
> -				    RENAME_EXCHANGE);
> -		unlock_rename(workdir, upperdir);
> +		rd.flags = RENAME_EXCHANGE;
> +		err = ovl_do_rename_rd(&rd);
> +		end_renaming(&rd);
>  		if (err)
>  			goto out_cleanup_unlocked;
>  
>  		ovl_cleanup(ofs, workdir, upper);
>  	} else {
> -		err = ovl_do_rename(ofs, workdir, newdentry, upperdir, upper, 0);
> -		unlock_rename(workdir, upperdir);
> +		err = ovl_do_rename_rd(&rd);
> +		end_renaming(&rd);
>  		if (err)
>  			goto out_cleanup_unlocked;
>  	}
> @@ -565,7 +580,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
>  	return err;
>  
>  out_cleanup:
> -	unlock_rename(workdir, upperdir);
> +	end_renaming(&rd);
>  out_cleanup_unlocked:
>  	ovl_cleanup(ofs, workdir, newdentry);
>  	dput(newdentry);
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index c47713e9867c..9104c7104191 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -161,6 +161,8 @@ int start_renaming(struct renamedata *rd, int lookup_flags,
>  		   struct qstr *old_last, struct qstr *new_last);
>  int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
>  			  struct dentry *old_dentry, struct qstr *new_last);
> +int start_renaming_two_dentries(struct renamedata *rd,
> +				struct dentry *old_dentry, struct dentry *new_dentry);
>  void end_renaming(struct renamedata *rd);
>  
>  /**
> diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
> index 232e087bce3e..a224ef9bb831 100644
> --- a/security/selinux/selinuxfs.c
> +++ b/security/selinux/selinuxfs.c
> @@ -506,6 +506,7 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
>  {
>  	int ret = 0;
>  	struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir;
> +	struct renamedata rd = {};
>  	unsigned int bool_num = 0;
>  	char **bool_names = NULL;
>  	int *bool_values = NULL;
> @@ -539,22 +540,30 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
>  	if (ret)
>  		goto out;
>  
> -	lock_rename(tmp_parent, fsi->sb->s_root);
> +	rd.old_parent = tmp_parent;
> +	rd.new_parent = fsi->sb->s_root;
>  
>  	/* booleans */
> -	d_exchange(tmp_bool_dir, fsi->bool_dir);
> +	ret = start_renaming_two_dentries(&rd, tmp_bool_dir, fsi->bool_dir);
> +	if (!ret) {
> +		d_exchange(tmp_bool_dir, fsi->bool_dir);
>  
> -	swap(fsi->bool_num, bool_num);
> -	swap(fsi->bool_pending_names, bool_names);
> -	swap(fsi->bool_pending_values, bool_values);
> +		swap(fsi->bool_num, bool_num);
> +		swap(fsi->bool_pending_names, bool_names);
> +		swap(fsi->bool_pending_values, bool_values);
>  
> -	fsi->bool_dir = tmp_bool_dir;
> +		fsi->bool_dir = tmp_bool_dir;
> +		end_renaming(&rd);
> +	}
>  
>  	/* classes */
> -	d_exchange(tmp_class_dir, fsi->class_dir);
> -	fsi->class_dir = tmp_class_dir;
> +	ret = start_renaming_two_dentries(&rd, tmp_class_dir, fsi->class_dir);
> +	if (ret == 0) {
> +		d_exchange(tmp_class_dir, fsi->class_dir);
> +		fsi->class_dir = tmp_class_dir;
>  
> -	unlock_rename(tmp_parent, fsi->sb->s_root);
> +		end_renaming(&rd);
> +	}
>  
>  out:
>  	sel_remove_old_bool_data(bool_num, bool_names, bool_values);

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 10/14] VFS/ovl/smb: introduce start_renaming_dentry()
From: Jeff Layton @ 2025-11-12 19:36 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-11-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> Several callers perform a rename on a dentry they already have, and only
> require lookup for the target name.  This includes smb/server and a few
> different places in overlayfs.
> 
> start_renaming_dentry() performs the required lookup and takes the
> required lock using lock_rename_child()
> 
> It is used in three places in overlayfs and in ksmbd_vfs_rename().
> 
> In the ksmbd case, the parent of the source is not important - the
> source must be renamed from wherever it is.  So start_renaming_dentry()
> allows rd->old_parent to be NULL and only checks it if it is non-NULL.
> On success rd->old_parent will be the parent of old_dentry with an extra
> reference taken.  Other start_renaming function also now take the extra
> reference and end_renaming() now drops this reference as well.
> 
> ovl_lookup_temp(), ovl_parent_lock(), and ovl_parent_unlock() are
> all removed as they are no longer needed.
> 
> OVL_TEMPNAME_SIZE and ovl_tempname() are now declared in overlayfs.h so
> that ovl_check_rename_whiteout() can access them.
> 
> ovl_copy_up_workdir() now always cleans up on error.
> 
> Reviewed-by: Namjae Jeon <linkinjeon@kernel.org> (for ksmbd part)
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
>  fs/namei.c               | 108 ++++++++++++++++++++++++++++++++++++---
>  fs/overlayfs/copy_up.c   |  54 +++++++++-----------
>  fs/overlayfs/dir.c       |  19 +------
>  fs/overlayfs/overlayfs.h |   8 +--
>  fs/overlayfs/super.c     |  22 ++++----
>  fs/overlayfs/util.c      |  11 ----
>  fs/smb/server/vfs.c      |  60 ++++------------------
>  include/linux/namei.h    |   2 +
>  8 files changed, 150 insertions(+), 134 deletions(-)
> 
> diff --git a/fs/namei.c b/fs/namei.c
> index bad6c9d540f9..4b740048df97 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3669,7 +3669,7 @@ EXPORT_SYMBOL(unlock_rename);
>  
>  /**
>   * __start_renaming - lookup and lock names for rename
> - * @rd:           rename data containing parent and flags, and
> + * @rd:           rename data containing parents and flags, and
>   *                for receiving found dentries
>   * @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
>   *                LOOKUP_NO_SYMLINKS etc).
> @@ -3680,8 +3680,8 @@ EXPORT_SYMBOL(unlock_rename);
>   * rename.
>   *
>   * On success the found dentries are stored in @rd.old_dentry,
> - * @rd.new_dentry.  These references and the lock are dropped by
> - * end_renaming().
> + * @rd.new_dentry and an extra ref is taken on @rd.old_parent.
> + * These references and the lock are dropped by end_renaming().
>   *
>   * The passed in qstrs must have the hash calculated, and no permission
>   * checking is performed.
> @@ -3735,6 +3735,7 @@ __start_renaming(struct renamedata *rd, int lookup_flags,
>  
>  	rd->old_dentry = d1;
>  	rd->new_dentry = d2;
> +	dget(rd->old_parent);
>  	return 0;
>  
>  out_dput_d2:
> @@ -3748,7 +3749,7 @@ __start_renaming(struct renamedata *rd, int lookup_flags,
>  
>  /**
>   * start_renaming - lookup and lock names for rename with permission checking
> - * @rd:           rename data containing parent and flags, and
> + * @rd:           rename data containing parents and flags, and
>   *                for receiving found dentries
>   * @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
>   *                LOOKUP_NO_SYMLINKS etc).
> @@ -3759,8 +3760,8 @@ __start_renaming(struct renamedata *rd, int lookup_flags,
>   * rename.
>   *
>   * On success the found dentries are stored in @rd.old_dentry,
> - * @rd.new_dentry.  These references and the lock are dropped by
> - * end_renaming().
> + * @rd.new_dentry.  Also the refcount on @rd->old_parent is increased.
> + * These references and the lock are dropped by end_renaming().
>   *
>   * The passed in qstrs need not have the hash calculated, and basic
>   * eXecute permission checking is performed against @rd.mnt_idmap.
> @@ -3782,11 +3783,106 @@ int start_renaming(struct renamedata *rd, int lookup_flags,
>  }
>  EXPORT_SYMBOL(start_renaming);
>  
> +static int
> +__start_renaming_dentry(struct renamedata *rd, int lookup_flags,
> +			struct dentry *old_dentry, struct qstr *new_last)
> +{
> +	struct dentry *trap;
> +	struct dentry *d2;
> +	int target_flags = LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
> +	int err;
> +
> +	if (rd->flags & RENAME_EXCHANGE)
> +		target_flags = 0;
> +	if (rd->flags & RENAME_NOREPLACE)
> +		target_flags |= LOOKUP_EXCL;
> +
> +	/* Already have the dentry - need to be sure to lock the correct parent */
> +	trap = lock_rename_child(old_dentry, rd->new_parent);
> +	if (IS_ERR(trap))
> +		return PTR_ERR(trap);
> +	if (d_unhashed(old_dentry) ||
> +	    (rd->old_parent && rd->old_parent != old_dentry->d_parent)) {
> +		/* dentry was removed, or moved and explicit parent requested */
> +		err = -EINVAL;
> +		goto out_unlock;
> +	}
> +
> +	d2 = lookup_one_qstr_excl(new_last, rd->new_parent,
> +				  lookup_flags | target_flags);
> +	err = PTR_ERR(d2);
> +	if (IS_ERR(d2))
> +		goto out_unlock;
> +
> +	if (old_dentry == trap) {
> +		/* source is an ancestor of target */
> +		err = -EINVAL;
> +		goto out_dput_d2;
> +	}
> +
> +	if (d2 == trap) {
> +		/* target is an ancestor of source */
> +		if (rd->flags & RENAME_EXCHANGE)
> +			err = -EINVAL;
> +		else
> +			err = -ENOTEMPTY;
> +		goto out_dput_d2;
> +	}
> +
> +	rd->old_dentry = dget(old_dentry);
> +	rd->new_dentry = d2;
> +	rd->old_parent = dget(old_dentry->d_parent);
> +	return 0;
> +
> +out_dput_d2:
> +	dput(d2);
> +out_unlock:
> +	unlock_rename(old_dentry->d_parent, rd->new_parent);
> +	return err;
> +}
> +
> +/**
> + * start_renaming_dentry - lookup and lock name for rename with permission checking
> + * @rd:           rename data containing parents and flags, and
> + *                for receiving found dentries
> + * @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
> + *                LOOKUP_NO_SYMLINKS etc).
> + * @old_dentry:   dentry of name to move
> + * @new_last:     name of target in @rd.new_parent
> + *
> + * Look up target name and ensure locks are in place for
> + * rename.
> + *
> + * On success the found dentry is stored in @rd.new_dentry and
> + * @rd.old_parent is confirmed to be the parent of @old_dentry.  If it
> + * was originally %NULL, it is set.  In either case a reference is taken
> + * so that end_renaming() can have a stable reference to unlock.
> + *
> + * References and the lock can be dropped with end_renaming()
> + *
> + * The passed in qstr need not have the hash calculated, and basic
> + * eXecute permission checking is performed against @rd.mnt_idmap.
> + *
> + * Returns: zero or an error.
> + */
> +int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
> +			  struct dentry *old_dentry, struct qstr *new_last)
> +{
> +	int err;
> +
> +	err = lookup_one_common(rd->mnt_idmap, new_last, rd->new_parent);
> +	if (err)
> +		return err;
> +	return __start_renaming_dentry(rd, lookup_flags, old_dentry, new_last);
> +}
> +EXPORT_SYMBOL(start_renaming_dentry);
> +
>  void end_renaming(struct renamedata *rd)
>  {
>  	unlock_rename(rd->old_parent, rd->new_parent);
>  	dput(rd->old_dentry);
>  	dput(rd->new_dentry);
> +	dput(rd->old_parent);
>  }
>  EXPORT_SYMBOL(end_renaming);
>  
> diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
> index e2bdac4317e7..9911a346b477 100644
> --- a/fs/overlayfs/copy_up.c
> +++ b/fs/overlayfs/copy_up.c
> @@ -523,8 +523,8 @@ static int ovl_create_index(struct dentry *dentry, const struct ovl_fh *fh,
>  {
>  	struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
>  	struct dentry *indexdir = ovl_indexdir(dentry->d_sb);
> -	struct dentry *index = NULL;
>  	struct dentry *temp = NULL;
> +	struct renamedata rd = {};
>  	struct qstr name = { };
>  	int err;
>  
> @@ -556,17 +556,15 @@ static int ovl_create_index(struct dentry *dentry, const struct ovl_fh *fh,
>  	if (err)
>  		goto out;
>  
> -	err = ovl_parent_lock(indexdir, temp);
> +	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
> +	rd.old_parent = indexdir;
> +	rd.new_parent = indexdir;
> +	err = start_renaming_dentry(&rd, 0, temp, &name);
>  	if (err)
>  		goto out;
> -	index = ovl_lookup_upper(ofs, name.name, indexdir, name.len);
> -	if (IS_ERR(index)) {
> -		err = PTR_ERR(index);
> -	} else {
> -		err = ovl_do_rename(ofs, indexdir, temp, indexdir, index, 0);
> -		dput(index);
> -	}
> -	ovl_parent_unlock(indexdir);
> +
> +	err = ovl_do_rename_rd(&rd);
> +	end_renaming(&rd);
>  out:
>  	if (err)
>  		ovl_cleanup(ofs, indexdir, temp);
> @@ -763,7 +761,8 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
>  	struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
>  	struct inode *inode;
>  	struct path path = { .mnt = ovl_upper_mnt(ofs) };
> -	struct dentry *temp, *upper, *trap;
> +	struct renamedata rd = {};
> +	struct dentry *temp;
>  	struct ovl_cu_creds cc;
>  	int err;
>  	struct ovl_cattr cattr = {
> @@ -807,29 +806,24 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
>  	 * ovl_copy_up_data(), so lock workdir and destdir and make sure that
>  	 * temp wasn't moved before copy up completion or cleanup.
>  	 */
> -	trap = lock_rename(c->workdir, c->destdir);
> -	if (trap || temp->d_parent != c->workdir) {
> -		/* temp or workdir moved underneath us? abort without cleanup */
> -		dput(temp);
> +	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
> +	rd.old_parent = c->workdir;
> +	rd.new_parent = c->destdir;
> +	rd.flags = 0;
> +	err = start_renaming_dentry(&rd, 0, temp,
> +				    &QSTR_LEN(c->destname.name, c->destname.len));
> +	if (err) {
> +		/* temp or workdir moved underneath us? map to -EIO */
>  		err = -EIO;
> -		if (!IS_ERR(trap))
> -			unlock_rename(c->workdir, c->destdir);
> -		goto out;
>  	}
> -
> -	err = ovl_copy_up_metadata(c, temp);
>  	if (err)
> -		goto cleanup;
> +		goto cleanup_unlocked;
>  
> -	upper = ovl_lookup_upper(ofs, c->destname.name, c->destdir,
> -				 c->destname.len);
> -	err = PTR_ERR(upper);
> -	if (IS_ERR(upper))
> -		goto cleanup;
> +	err = ovl_copy_up_metadata(c, temp);
> +	if (!err)
> +		err = ovl_do_rename_rd(&rd);
> +	end_renaming(&rd);
>  
> -	err = ovl_do_rename(ofs, c->workdir, temp, c->destdir, upper, 0);
> -	unlock_rename(c->workdir, c->destdir);
> -	dput(upper);
>  	if (err)
>  		goto cleanup_unlocked;
>  
> @@ -850,8 +844,6 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
>  
>  	return err;
>  
> -cleanup:
> -	unlock_rename(c->workdir, c->destdir);
>  cleanup_unlocked:
>  	ovl_cleanup(ofs, c->workdir, temp);
>  	dput(temp);
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index cf6fc48459f3..6b2f88edb497 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -57,8 +57,7 @@ int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir,
>  	return 0;
>  }
>  
> -#define OVL_TEMPNAME_SIZE 20
> -static void ovl_tempname(char name[OVL_TEMPNAME_SIZE])
> +void ovl_tempname(char name[OVL_TEMPNAME_SIZE])
>  {
>  	static atomic_t temp_id = ATOMIC_INIT(0);
>  
> @@ -66,22 +65,6 @@ static void ovl_tempname(char name[OVL_TEMPNAME_SIZE])
>  	snprintf(name, OVL_TEMPNAME_SIZE, "#%x", atomic_inc_return(&temp_id));
>  }
>  
> -struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir)
> -{
> -	struct dentry *temp;
> -	char name[OVL_TEMPNAME_SIZE];
> -
> -	ovl_tempname(name);
> -	temp = ovl_lookup_upper(ofs, name, workdir, strlen(name));
> -	if (!IS_ERR(temp) && temp->d_inode) {
> -		pr_err("workdir/%s already exists\n", name);
> -		dput(temp);
> -		temp = ERR_PTR(-EIO);
> -	}
> -
> -	return temp;
> -}
> -
>  static struct dentry *ovl_start_creating_temp(struct ovl_fs *ofs,
>  					      struct dentry *workdir)
>  {
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index 3cc85a893b5c..746bc4ad7b37 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -447,11 +447,6 @@ static inline bool ovl_open_flags_need_copy_up(int flags)
>  }
>  
>  /* util.c */
> -int ovl_parent_lock(struct dentry *parent, struct dentry *child);
> -static inline void ovl_parent_unlock(struct dentry *parent)
> -{
> -	inode_unlock(parent->d_inode);
> -}
>  int ovl_get_write_access(struct dentry *dentry);
>  void ovl_put_write_access(struct dentry *dentry);
>  void ovl_start_write(struct dentry *dentry);
> @@ -888,7 +883,8 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs,
>  			       struct dentry *parent, struct dentry *newdentry,
>  			       struct ovl_cattr *attr);
>  int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir, struct dentry *dentry);
> -struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir);
> +#define OVL_TEMPNAME_SIZE 20
> +void ovl_tempname(char name[OVL_TEMPNAME_SIZE]);
>  struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
>  			       struct ovl_cattr *attr);
>  
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 6e0816c1147a..a721ef2b90e8 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -566,9 +566,10 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
>  {
>  	struct dentry *workdir = ofs->workdir;
>  	struct dentry *temp;
> -	struct dentry *dest;
>  	struct dentry *whiteout;
>  	struct name_snapshot name;
> +	struct renamedata rd = {};
> +	char name2[OVL_TEMPNAME_SIZE];
>  	int err;
>  
>  	temp = ovl_create_temp(ofs, workdir, OVL_CATTR(S_IFREG | 0));
> @@ -576,23 +577,21 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
>  	if (IS_ERR(temp))
>  		return err;
>  
> -	err = ovl_parent_lock(workdir, temp);
> +	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
> +	rd.old_parent = workdir;
> +	rd.new_parent = workdir;
> +	rd.flags = RENAME_WHITEOUT;
> +	ovl_tempname(name2);
> +	err = start_renaming_dentry(&rd, 0, temp, &QSTR(name2));
>  	if (err) {
>  		dput(temp);
>  		return err;
>  	}
> -	dest = ovl_lookup_temp(ofs, workdir);
> -	err = PTR_ERR(dest);
> -	if (IS_ERR(dest)) {
> -		dput(temp);
> -		ovl_parent_unlock(workdir);
> -		return err;
> -	}
>  
>  	/* Name is inline and stable - using snapshot as a copy helper */
>  	take_dentry_name_snapshot(&name, temp);
> -	err = ovl_do_rename(ofs, workdir, temp, workdir, dest, RENAME_WHITEOUT);
> -	ovl_parent_unlock(workdir);
> +	err = ovl_do_rename_rd(&rd);
> +	end_renaming(&rd);
>  	if (err) {
>  		if (err == -EINVAL)
>  			err = 0;
> @@ -616,7 +615,6 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
>  	ovl_cleanup(ofs, workdir, temp);
>  	release_dentry_name_snapshot(&name);
>  	dput(temp);
> -	dput(dest);
>  
>  	return err;
>  }
> diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
> index 2da1c035f716..fffc22859211 100644
> --- a/fs/overlayfs/util.c
> +++ b/fs/overlayfs/util.c
> @@ -1548,14 +1548,3 @@ void ovl_copyattr(struct inode *inode)
>  	i_size_write(inode, i_size_read(realinode));
>  	spin_unlock(&inode->i_lock);
>  }
> -
> -int ovl_parent_lock(struct dentry *parent, struct dentry *child)
> -{
> -	inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
> -	if (!child ||
> -	    (!d_unhashed(child) && child->d_parent == parent))
> -		return 0;
> -
> -	inode_unlock(parent->d_inode);
> -	return -EINVAL;
> -}
> diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
> index 7c4ddc43ab39..f54b5b0aaba2 100644
> --- a/fs/smb/server/vfs.c
> +++ b/fs/smb/server/vfs.c
> @@ -663,7 +663,6 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname,
>  int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
>  		     char *newname, int flags)
>  {
> -	struct dentry *old_parent, *new_dentry, *trap;
>  	struct dentry *old_child = old_path->dentry;
>  	struct path new_path;
>  	struct qstr new_last;
> @@ -673,7 +672,6 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
>  	struct ksmbd_file *parent_fp;
>  	int new_type;
>  	int err, lookup_flags = LOOKUP_NO_SYMLINKS;
> -	int target_lookup_flags = LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
>  
>  	if (ksmbd_override_fsids(work))
>  		return -ENOMEM;
> @@ -684,14 +682,6 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
>  		goto revert_fsids;
>  	}
>  
> -	/*
> -	 * explicitly handle file overwrite case, for compatibility with
> -	 * filesystems that may not support rename flags (e.g: fuse)
> -	 */
> -	if (flags & RENAME_NOREPLACE)
> -		target_lookup_flags |= LOOKUP_EXCL;
> -	flags &= ~(RENAME_NOREPLACE);
> -
>  retry:
>  	err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH,
>  				     &new_path, &new_last, &new_type,
> @@ -708,17 +698,14 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
>  	if (err)
>  		goto out2;
>  
> -	trap = lock_rename_child(old_child, new_path.dentry);
> -	if (IS_ERR(trap)) {
> -		err = PTR_ERR(trap);
> +	rd.mnt_idmap		= mnt_idmap(old_path->mnt);
> +	rd.old_parent		= NULL;
> +	rd.new_parent		= new_path.dentry;
> +	rd.flags		= flags;
> +	rd.delegated_inode	= NULL,
> +	err = start_renaming_dentry(&rd, lookup_flags, old_child, &new_last);
> +	if (err)
>  		goto out_drop_write;
> -	}
> -
> -	old_parent = dget(old_child->d_parent);
> -	if (d_unhashed(old_child)) {
> -		err = -EINVAL;
> -		goto out3;
> -	}
>  
>  	parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent);
>  	if (parent_fp) {
> @@ -731,44 +718,17 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
>  		ksmbd_fd_put(work, parent_fp);
>  	}
>  
> -	new_dentry = lookup_one_qstr_excl(&new_last, new_path.dentry,
> -					  lookup_flags | target_lookup_flags);
> -	if (IS_ERR(new_dentry)) {
> -		err = PTR_ERR(new_dentry);
> -		goto out3;
> -	}
> -
> -	if (d_is_symlink(new_dentry)) {
> +	if (d_is_symlink(rd.new_dentry)) {
>  		err = -EACCES;
> -		goto out4;
> -	}
> -
> -	if (old_child == trap) {
> -		err = -EINVAL;
> -		goto out4;
> -	}
> -
> -	if (new_dentry == trap) {
> -		err = -ENOTEMPTY;
> -		goto out4;
> +		goto out3;
>  	}
>  
> -	rd.mnt_idmap		= mnt_idmap(old_path->mnt),
> -	rd.old_parent		= old_parent,
> -	rd.old_dentry		= old_child,
> -	rd.new_parent		= new_path.dentry,
> -	rd.new_dentry		= new_dentry,
> -	rd.flags		= flags,
> -	rd.delegated_inode	= NULL,
>  	err = vfs_rename(&rd);
>  	if (err)
>  		ksmbd_debug(VFS, "vfs_rename failed err %d\n", err);
>  
> -out4:
> -	dput(new_dentry);
>  out3:
> -	dput(old_parent);
> -	unlock_rename(old_parent, new_path.dentry);
> +	end_renaming(&rd);
>  out_drop_write:
>  	mnt_drop_write(old_path->mnt);
>  out2:
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index 7fdd9fdcbd2b..c47713e9867c 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -159,6 +159,8 @@ extern struct dentry *lock_rename_child(struct dentry *, struct dentry *);
>  extern void unlock_rename(struct dentry *, struct dentry *);
>  int start_renaming(struct renamedata *rd, int lookup_flags,
>  		   struct qstr *old_last, struct qstr *new_last);
> +int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
> +			  struct dentry *old_dentry, struct qstr *new_last);
>  void end_renaming(struct renamedata *rd);
>  
>  /**

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* [PATCH] keys: Replace memcpy with strscpy in proc_keys_show
From: Thorsten Blum @ 2025-11-12 17:26 UTC (permalink / raw)
  To: David Howells, Jarkko Sakkinen, Paul Moore, James Morris,
	Serge E. Hallyn
  Cc: Thorsten Blum, keyrings, linux-security-module, linux-kernel

Use strscpy() to copy strings into the fixed-size buffer 'xbuf' instead
of hardcoding the number of bytes to copy. This improves maintainability
and ensures the buffer is always NUL-terminated.

Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
---
 security/keys/proc.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/security/keys/proc.c b/security/keys/proc.c
index 4f4e2c1824f1..1d380766f45d 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -10,6 +10,7 @@
 #include <linux/fs.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
+#include <linux/string.h>
 #include <asm/errno.h>
 #include "internal.h"
 
@@ -199,9 +200,9 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	/* come up with a suitable timeout value */
 	expiry = READ_ONCE(key->expiry);
 	if (expiry == TIME64_MAX) {
-		memcpy(xbuf, "perm", 5);
+		strscpy(xbuf, "perm");
 	} else if (now >= expiry) {
-		memcpy(xbuf, "expd", 5);
+		strscpy(xbuf, "expd");
 	} else {
 		timo = expiry - now;
 
-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH] Documentation/landlock: Make docs in cred.h and domain.h visible.
From: Mickaël Salaün @ 2025-11-12 17:04 UTC (permalink / raw)
  To: Tingmao Wang; +Cc: Günther Noack, linux-security-module
In-Reply-To: <6050e764c2679cba01715653e5f1f4f17091d8f8.1759103277.git.m@maowtm.org>

On Mon, Sep 29, 2025 at 12:49:49AM +0100, Tingmao Wang wrote:
> Currently even though the structures in these files have documentation,
> they are not shown in the "Landlock LSM: kernel documentation" page.
> 
> Signed-off-by: Tingmao Wang <m@maowtm.org>

Thanks! I'll merge this patch in -next.


> ---
>  Documentation/security/landlock.rst | 9 +++++++++
>  1 file changed, 9 insertions(+)
> 
> diff --git a/Documentation/security/landlock.rst b/Documentation/security/landlock.rst
> index e0fc54aff09e..5066f8c3091e 100644
> --- a/Documentation/security/landlock.rst
> +++ b/Documentation/security/landlock.rst
> @@ -110,6 +110,12 @@ Filesystem
>  .. kernel-doc:: security/landlock/fs.h
>      :identifiers:
>  
> +Process credential
> +------------------
> +
> +.. kernel-doc:: security/landlock/cred.h
> +    :identifiers:
> +
>  Ruleset and domain
>  ------------------
>  
> @@ -128,6 +134,9 @@ makes the reasoning much easier and helps avoid pitfalls.
>  .. kernel-doc:: security/landlock/ruleset.h
>      :identifiers:
>  
> +.. kernel-doc:: security/landlock/domain.h
> +    :identifiers:
> +
>  Additional documentation
>  ========================
>  
> 
> base-commit: f83ec76bf285bea5727f478a68b894f5543ca76e
> -- 
> 2.51.0
> 
> 

^ permalink raw reply

* Re: [PATCH] selftests/landlock: Fix makefile header list
From: Mickaël Salaün @ 2025-11-12 17:03 UTC (permalink / raw)
  To: Matthieu Buffet; +Cc: Günther Noack, linux-security-module
In-Reply-To: <20251027011440.1838514-1-matthieu@buffet.re>

On Mon, Oct 27, 2025 at 02:14:40AM +0100, Matthieu Buffet wrote:
> Make all headers part of make's dependencies computations.
> Otherwise, updating audit.h, common.h, scoped_base_variants.h,
> scoped_common.h, scoped_multiple_domain_variants.h, or wrappers.h,
> re-running make and running selftests could lead to testing stale headers.
> 
> Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")
> Fixes: fefcf0f7cf47 ("selftests/landlock: Test abstract UNIX socket scoping")
> Fixes: 5147779d5e1b ("selftests/landlock: Add wrappers.h")
> Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
> ---
> I was troubleshooting why some auditing selftests kept failing at random
> (recvfrom() sometimes returning EAGAIN in audit_recv()). I did not
> troubleshoot it yet, but I did find this first step while trying to edit
> audit.h in vain:

Thanks! I'll merge this patch in -next.

> 
>  tools/testing/selftests/landlock/Makefile | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
> index a3f449914bf9..044b83bde16e 100644
> --- a/tools/testing/selftests/landlock/Makefile
> +++ b/tools/testing/selftests/landlock/Makefile
> @@ -4,7 +4,7 @@
>  
>  CFLAGS += -Wall -O2 $(KHDR_INCLUDES)
>  
> -LOCAL_HDRS += common.h
> +LOCAL_HDRS += $(wildcard *.h)
>  
>  src_test := $(wildcard *_test.c)
>  
> 
> base-commit: 6dde339a3df80a57ac3d780d8cfc14d9262e2acd
> -- 
> 2.47.2
> 
> 

^ permalink raw reply

* Re: [PATCH v5 09/14] VFS/nfsd/ovl: introduce start_renaming() and end_renaming()
From: Jeff Layton @ 2025-11-12 15:06 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-10-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> start_renaming() combines name lookup and locking to prepare for rename.
> It is used when two names need to be looked up as in nfsd and overlayfs -
> cases where one or both dentries are already available will be handled
> separately.
> 
> __start_renaming() avoids the inode_permission check and hash
> calculation and is suitable after filename_parentat() in do_renameat2().
> It subsumes quite a bit of code from that function.
> 
> start_renaming() does calculate the hash and check X permission and is
> suitable elsewhere:
> - nfsd_rename()
> - ovl_rename()
> 
> In ovl, ovl_do_rename_rd() is factored out of ovl_do_rename(), which
> itself will be gone by the end of the series.
> 
> Acked-by: Chuck Lever <chuck.lever@oracle.com> (for nfsd parts)
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> 
> --
> Changes since v3:
>  - added missig dput() in ovl_rename when "whiteout" is not-NULL.
> 
> Changes since v2:
>  - in __start_renaming() some label have been renamed, and err
>    is always set before a "goto out_foo" rather than passing the
>    error in a dentry*.
>  - ovl_do_rename() changed to call the new ovl_do_rename_rd() rather
>    than keeping duplicate code
>  - code around ovl_cleanup() call in ovl_rename() restructured.
> ---
>  fs/namei.c               | 197 ++++++++++++++++++++++++++++-----------
>  fs/nfsd/vfs.c            |  73 +++++----------
>  fs/overlayfs/dir.c       |  74 +++++++--------
>  fs/overlayfs/overlayfs.h |  23 +++--
>  include/linux/namei.h    |   3 +
>  5 files changed, 218 insertions(+), 152 deletions(-)
> 
> diff --git a/fs/namei.c b/fs/namei.c
> index e70d056b9543..bad6c9d540f9 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3667,6 +3667,129 @@ void unlock_rename(struct dentry *p1, struct dentry *p2)
>  }
>  EXPORT_SYMBOL(unlock_rename);
>  
> +/**
> + * __start_renaming - lookup and lock names for rename
> + * @rd:           rename data containing parent and flags, and
> + *                for receiving found dentries
> + * @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
> + *                LOOKUP_NO_SYMLINKS etc).
> + * @old_last:     name of object in @rd.old_parent
> + * @new_last:     name of object in @rd.new_parent
> + *
> + * Look up two names and ensure locks are in place for
> + * rename.
> + *
> + * On success the found dentries are stored in @rd.old_dentry,
> + * @rd.new_dentry.  These references and the lock are dropped by
> + * end_renaming().
> + *
> + * The passed in qstrs must have the hash calculated, and no permission
> + * checking is performed.
> + *
> + * Returns: zero or an error.
> + */
> +static int
> +__start_renaming(struct renamedata *rd, int lookup_flags,
> +		 struct qstr *old_last, struct qstr *new_last)
> +{
> +	struct dentry *trap;
> +	struct dentry *d1, *d2;
> +	int target_flags = LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
> +	int err;
> +
> +	if (rd->flags & RENAME_EXCHANGE)
> +		target_flags = 0;
> +	if (rd->flags & RENAME_NOREPLACE)
> +		target_flags |= LOOKUP_EXCL;
> +
> +	trap = lock_rename(rd->old_parent, rd->new_parent);
> +	if (IS_ERR(trap))
> +		return PTR_ERR(trap);
> +
> +	d1 = lookup_one_qstr_excl(old_last, rd->old_parent,
> +				  lookup_flags);
> +	err = PTR_ERR(d1);
> +	if (IS_ERR(d1))
> +		goto out_unlock;
> +
> +	d2 = lookup_one_qstr_excl(new_last, rd->new_parent,
> +				  lookup_flags | target_flags);
> +	err = PTR_ERR(d2);
> +	if (IS_ERR(d2))
> +		goto out_dput_d1;
> +
> +	if (d1 == trap) {
> +		/* source is an ancestor of target */
> +		err = -EINVAL;
> +		goto out_dput_d2;
> +	}
> +
> +	if (d2 == trap) {
> +		/* target is an ancestor of source */
> +		if (rd->flags & RENAME_EXCHANGE)
> +			err = -EINVAL;
> +		else
> +			err = -ENOTEMPTY;
> +		goto out_dput_d2;
> +	}
> +
> +	rd->old_dentry = d1;
> +	rd->new_dentry = d2;
> +	return 0;
> +
> +out_dput_d2:
> +	dput(d2);
> +out_dput_d1:
> +	dput(d1);
> +out_unlock:
> +	unlock_rename(rd->old_parent, rd->new_parent);
> +	return err;
> +}
> +
> +/**
> + * start_renaming - lookup and lock names for rename with permission checking
> + * @rd:           rename data containing parent and flags, and
> + *                for receiving found dentries
> + * @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
> + *                LOOKUP_NO_SYMLINKS etc).
> + * @old_last:     name of object in @rd.old_parent
> + * @new_last:     name of object in @rd.new_parent
> + *
> + * Look up two names and ensure locks are in place for
> + * rename.
> + *
> + * On success the found dentries are stored in @rd.old_dentry,
> + * @rd.new_dentry.  These references and the lock are dropped by
> + * end_renaming().
> + *
> + * The passed in qstrs need not have the hash calculated, and basic
> + * eXecute permission checking is performed against @rd.mnt_idmap.
> + *
> + * Returns: zero or an error.
> + */
> +int start_renaming(struct renamedata *rd, int lookup_flags,
> +		   struct qstr *old_last, struct qstr *new_last)
> +{
> +	int err;
> +
> +	err = lookup_one_common(rd->mnt_idmap, old_last, rd->old_parent);
> +	if (err)
> +		return err;
> +	err = lookup_one_common(rd->mnt_idmap, new_last, rd->new_parent);
> +	if (err)
> +		return err;
> +	return __start_renaming(rd, lookup_flags, old_last, new_last);
> +}
> +EXPORT_SYMBOL(start_renaming);
> +
> +void end_renaming(struct renamedata *rd)
> +{
> +	unlock_rename(rd->old_parent, rd->new_parent);
> +	dput(rd->old_dentry);
> +	dput(rd->new_dentry);
> +}
> +EXPORT_SYMBOL(end_renaming);
> +
>  /**
>   * vfs_prepare_mode - prepare the mode to be used for a new inode
>   * @idmap:	idmap of the mount the inode was found from
> @@ -5504,14 +5627,11 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
>  		 struct filename *to, unsigned int flags)
>  {
>  	struct renamedata rd;
> -	struct dentry *old_dentry, *new_dentry;
> -	struct dentry *trap;
>  	struct path old_path, new_path;
>  	struct qstr old_last, new_last;
>  	int old_type, new_type;
>  	struct inode *delegated_inode = NULL;
> -	unsigned int lookup_flags = 0, target_flags =
> -		LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
> +	unsigned int lookup_flags = 0;
>  	bool should_retry = false;
>  	int error = -EINVAL;
>  
> @@ -5522,11 +5642,6 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
>  	    (flags & RENAME_EXCHANGE))
>  		goto put_names;
>  
> -	if (flags & RENAME_EXCHANGE)
> -		target_flags = 0;
> -	if (flags & RENAME_NOREPLACE)
> -		target_flags |= LOOKUP_EXCL;
> -
>  retry:
>  	error = filename_parentat(olddfd, from, lookup_flags, &old_path,
>  				  &old_last, &old_type);
> @@ -5556,66 +5671,40 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
>  		goto exit2;
>  
>  retry_deleg:
> -	trap = lock_rename(new_path.dentry, old_path.dentry);
> -	if (IS_ERR(trap)) {
> -		error = PTR_ERR(trap);
> +	rd.old_parent	   = old_path.dentry;
> +	rd.mnt_idmap	   = mnt_idmap(old_path.mnt);
> +	rd.new_parent	   = new_path.dentry;
> +	rd.delegated_inode = &delegated_inode;
> +	rd.flags	   = flags;
> +
> +	error = __start_renaming(&rd, lookup_flags, &old_last, &new_last);
> +	if (error)
>  		goto exit_lock_rename;
> -	}
>  
> -	old_dentry = lookup_one_qstr_excl(&old_last, old_path.dentry,
> -					  lookup_flags);
> -	error = PTR_ERR(old_dentry);
> -	if (IS_ERR(old_dentry))
> -		goto exit3;
> -	new_dentry = lookup_one_qstr_excl(&new_last, new_path.dentry,
> -					  lookup_flags | target_flags);
> -	error = PTR_ERR(new_dentry);
> -	if (IS_ERR(new_dentry))
> -		goto exit4;
>  	if (flags & RENAME_EXCHANGE) {
> -		if (!d_is_dir(new_dentry)) {
> +		if (!d_is_dir(rd.new_dentry)) {
>  			error = -ENOTDIR;
>  			if (new_last.name[new_last.len])
> -				goto exit5;
> +				goto exit_unlock;
>  		}
>  	}
>  	/* unless the source is a directory trailing slashes give -ENOTDIR */
> -	if (!d_is_dir(old_dentry)) {
> +	if (!d_is_dir(rd.old_dentry)) {
>  		error = -ENOTDIR;
>  		if (old_last.name[old_last.len])
> -			goto exit5;
> +			goto exit_unlock;
>  		if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len])
> -			goto exit5;
> -	}
> -	/* source should not be ancestor of target */
> -	error = -EINVAL;
> -	if (old_dentry == trap)
> -		goto exit5;
> -	/* target should not be an ancestor of source */
> -	if (!(flags & RENAME_EXCHANGE))
> -		error = -ENOTEMPTY;
> -	if (new_dentry == trap)
> -		goto exit5;
> +			goto exit_unlock;
> +	}
>  
> -	error = security_path_rename(&old_path, old_dentry,
> -				     &new_path, new_dentry, flags);
> +	error = security_path_rename(&old_path, rd.old_dentry,
> +				     &new_path, rd.new_dentry, flags);
>  	if (error)
> -		goto exit5;
> +		goto exit_unlock;
>  
> -	rd.old_parent	   = old_path.dentry;
> -	rd.old_dentry	   = old_dentry;
> -	rd.mnt_idmap	   = mnt_idmap(old_path.mnt);
> -	rd.new_parent	   = new_path.dentry;
> -	rd.new_dentry	   = new_dentry;
> -	rd.delegated_inode = &delegated_inode;
> -	rd.flags	   = flags;
>  	error = vfs_rename(&rd);
> -exit5:
> -	dput(new_dentry);
> -exit4:
> -	dput(old_dentry);
> -exit3:
> -	unlock_rename(new_path.dentry, old_path.dentry);
> +exit_unlock:
> +	end_renaming(&rd);
>  exit_lock_rename:
>  	if (delegated_inode) {
>  		error = break_deleg_wait(&delegated_inode);
> diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> index 6291c371caa7..a993f1e54182 100644
> --- a/fs/nfsd/vfs.c
> +++ b/fs/nfsd/vfs.c
> @@ -1885,11 +1885,12 @@ __be32
>  nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  			    struct svc_fh *tfhp, char *tname, int tlen)
>  {
> -	struct dentry	*fdentry, *tdentry, *odentry, *ndentry, *trap;
> +	struct dentry	*fdentry, *tdentry;
>  	int		type = S_IFDIR;
> +	struct renamedata rd = {};
>  	__be32		err;
>  	int		host_err;
> -	bool		close_cached = false;
> +	struct dentry	*close_cached;
>  
>  	trace_nfsd_vfs_rename(rqstp, ffhp, tfhp, fname, flen, tname, tlen);
>  
> @@ -1915,15 +1916,22 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  		goto out;
>  
>  retry:
> +	close_cached = NULL;
>  	host_err = fh_want_write(ffhp);
>  	if (host_err) {
>  		err = nfserrno(host_err);
>  		goto out;
>  	}
>  
> -	trap = lock_rename(tdentry, fdentry);
> -	if (IS_ERR(trap)) {
> -		err = nfserr_xdev;
> +	rd.mnt_idmap	= &nop_mnt_idmap;
> +	rd.old_parent	= fdentry;
> +	rd.new_parent	= tdentry;
> +
> +	host_err = start_renaming(&rd, 0, &QSTR_LEN(fname, flen),
> +				  &QSTR_LEN(tname, tlen));
> +
> +	if (host_err) {
> +		err = nfserrno(host_err);
>  		goto out_want_write;
>  	}
>  	err = fh_fill_pre_attrs(ffhp);
> @@ -1933,48 +1941,23 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  	if (err != nfs_ok)
>  		goto out_unlock;
>  
> -	odentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), fdentry);
> -	host_err = PTR_ERR(odentry);
> -	if (IS_ERR(odentry))
> -		goto out_nfserr;
> +	type = d_inode(rd.old_dentry)->i_mode & S_IFMT;
> +
> +	if (d_inode(rd.new_dentry))
> +		type = d_inode(rd.new_dentry)->i_mode & S_IFMT;
>  
> -	host_err = -ENOENT;
> -	if (d_really_is_negative(odentry))
> -		goto out_dput_old;
> -	host_err = -EINVAL;
> -	if (odentry == trap)
> -		goto out_dput_old;
> -	type = d_inode(odentry)->i_mode & S_IFMT;
> -
> -	ndentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(tname, tlen), tdentry);
> -	host_err = PTR_ERR(ndentry);
> -	if (IS_ERR(ndentry))
> -		goto out_dput_old;
> -	if (d_inode(ndentry))
> -		type = d_inode(ndentry)->i_mode & S_IFMT;
> -	host_err = -ENOTEMPTY;
> -	if (ndentry == trap)
> -		goto out_dput_new;
> -
> -	if ((ndentry->d_sb->s_export_op->flags & EXPORT_OP_CLOSE_BEFORE_UNLINK) &&
> -	    nfsd_has_cached_files(ndentry)) {
> -		close_cached = true;
> -		goto out_dput_old;
> +	if ((rd.new_dentry->d_sb->s_export_op->flags & EXPORT_OP_CLOSE_BEFORE_UNLINK) &&
> +	    nfsd_has_cached_files(rd.new_dentry)) {
> +		close_cached = dget(rd.new_dentry);
> +		goto out_unlock;
>  	} else {
> -		struct renamedata rd = {
> -			.mnt_idmap	= &nop_mnt_idmap,
> -			.old_parent	= fdentry,
> -			.old_dentry	= odentry,
> -			.new_parent	= tdentry,
> -			.new_dentry	= ndentry,
> -		};
>  		int retries;
>  
>  		for (retries = 1;;) {
>  			host_err = vfs_rename(&rd);
>  			if (host_err != -EAGAIN || !retries--)
>  				break;
> -			if (!nfsd_wait_for_delegreturn(rqstp, d_inode(odentry)))
> +			if (!nfsd_wait_for_delegreturn(rqstp, d_inode(rd.old_dentry)))
>  				break;
>  		}
>  		if (!host_err) {
> @@ -1983,11 +1966,6 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  				host_err = commit_metadata(ffhp);
>  		}
>  	}
> - out_dput_new:
> -	dput(ndentry);
> - out_dput_old:
> -	dput(odentry);
> - out_nfserr:
>  	if (host_err == -EBUSY) {
>  		/*
>  		 * See RFC 8881 Section 18.26.4 para 1-3: NFSv4 RENAME
> @@ -2006,7 +1984,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  		fh_fill_post_attrs(tfhp);
>  	}
>  out_unlock:
> -	unlock_rename(tdentry, fdentry);
> +	end_renaming(&rd);
>  out_want_write:
>  	fh_drop_write(ffhp);
>  
> @@ -2017,9 +1995,8 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
>  	 * until this point and then reattempt the whole shebang.
>  	 */
>  	if (close_cached) {
> -		close_cached = false;
> -		nfsd_close_cached_files(ndentry);
> -		dput(ndentry);
> +		nfsd_close_cached_files(close_cached);
> +		dput(close_cached);
>  		goto retry;
>  	}
>  out:
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index 6d1d0e94e287..cf6fc48459f3 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -1124,9 +1124,7 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
>  	int err;
>  	struct dentry *old_upperdir;
>  	struct dentry *new_upperdir;
> -	struct dentry *olddentry = NULL;
> -	struct dentry *newdentry = NULL;
> -	struct dentry *trap, *de;
> +	struct renamedata rd = {};
>  	bool old_opaque;
>  	bool new_opaque;
>  	bool cleanup_whiteout = false;
> @@ -1136,6 +1134,7 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
>  	bool new_is_dir = d_is_dir(new);
>  	bool samedir = olddir == newdir;
>  	struct dentry *opaquedir = NULL;
> +	struct dentry *whiteout = NULL;
>  	const struct cred *old_cred = NULL;
>  	struct ovl_fs *ofs = OVL_FS(old->d_sb);
>  	LIST_HEAD(list);
> @@ -1233,29 +1232,21 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
>  		}
>  	}
>  
> -	trap = lock_rename(new_upperdir, old_upperdir);
> -	if (IS_ERR(trap)) {
> -		err = PTR_ERR(trap);
> -		goto out_revert_creds;
> -	}
> +	rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
> +	rd.old_parent = old_upperdir;
> +	rd.new_parent = new_upperdir;
> +	rd.flags = flags;
>  
> -	de = ovl_lookup_upper(ofs, old->d_name.name, old_upperdir,
> -			      old->d_name.len);
> -	err = PTR_ERR(de);
> -	if (IS_ERR(de))
> -		goto out_unlock;
> -	olddentry = de;
> +	err = start_renaming(&rd, 0,
> +			     &QSTR_LEN(old->d_name.name, old->d_name.len),
> +			     &QSTR_LEN(new->d_name.name, new->d_name.len));
>  
> -	err = -ESTALE;
> -	if (!ovl_matches_upper(old, olddentry))
> -		goto out_unlock;
> +	if (err)
> +		goto out_revert_creds;
>  
> -	de = ovl_lookup_upper(ofs, new->d_name.name, new_upperdir,
> -			      new->d_name.len);
> -	err = PTR_ERR(de);
> -	if (IS_ERR(de))
> +	err = -ESTALE;
> +	if (!ovl_matches_upper(old, rd.old_dentry))
>  		goto out_unlock;
> -	newdentry = de;
>  
>  	old_opaque = ovl_dentry_is_opaque(old);
>  	new_opaque = ovl_dentry_is_opaque(new);
> @@ -1263,15 +1254,15 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
>  	err = -ESTALE;
>  	if (d_inode(new) && ovl_dentry_upper(new)) {
>  		if (opaquedir) {
> -			if (newdentry != opaquedir)
> +			if (rd.new_dentry != opaquedir)
>  				goto out_unlock;
>  		} else {
> -			if (!ovl_matches_upper(new, newdentry))
> +			if (!ovl_matches_upper(new, rd.new_dentry))
>  				goto out_unlock;
>  		}
>  	} else {
> -		if (!d_is_negative(newdentry)) {
> -			if (!new_opaque || !ovl_upper_is_whiteout(ofs, newdentry))
> +		if (!d_is_negative(rd.new_dentry)) {
> +			if (!new_opaque || !ovl_upper_is_whiteout(ofs, rd.new_dentry))
>  				goto out_unlock;
>  		} else {
>  			if (flags & RENAME_EXCHANGE)
> @@ -1279,19 +1270,14 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
>  		}
>  	}
>  
> -	if (olddentry == trap)
> -		goto out_unlock;
> -	if (newdentry == trap)
> -		goto out_unlock;
> -
> -	if (olddentry->d_inode == newdentry->d_inode)
> +	if (rd.old_dentry->d_inode == rd.new_dentry->d_inode)
>  		goto out_unlock;
>  
>  	err = 0;
>  	if (ovl_type_merge_or_lower(old))
>  		err = ovl_set_redirect(old, samedir);
>  	else if (is_dir && !old_opaque && ovl_type_merge(new->d_parent))
> -		err = ovl_set_opaque_xerr(old, olddentry, -EXDEV);
> +		err = ovl_set_opaque_xerr(old, rd.old_dentry, -EXDEV);
>  	if (err)
>  		goto out_unlock;
>  
> @@ -1299,18 +1285,24 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
>  		err = ovl_set_redirect(new, samedir);
>  	else if (!overwrite && new_is_dir && !new_opaque &&
>  		 ovl_type_merge(old->d_parent))
> -		err = ovl_set_opaque_xerr(new, newdentry, -EXDEV);
> +		err = ovl_set_opaque_xerr(new, rd.new_dentry, -EXDEV);
>  	if (err)
>  		goto out_unlock;
>  
> -	err = ovl_do_rename(ofs, old_upperdir, olddentry,
> -			    new_upperdir, newdentry, flags);
> -	unlock_rename(new_upperdir, old_upperdir);
> +	err = ovl_do_rename_rd(&rd);
> +
> +	if (!err && cleanup_whiteout)
> +		whiteout = dget(rd.new_dentry);
> +
> +	end_renaming(&rd);
> +
>  	if (err)
>  		goto out_revert_creds;
>  
> -	if (cleanup_whiteout)
> -		ovl_cleanup(ofs, old_upperdir, newdentry);
> +	if (whiteout) {
> +		ovl_cleanup(ofs, old_upperdir, whiteout);
> +		dput(whiteout);
> +	}
>  
>  	if (overwrite && d_inode(new)) {
>  		if (new_is_dir)
> @@ -1336,14 +1328,12 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
>  	else
>  		ovl_drop_write(old);
>  out:
> -	dput(newdentry);
> -	dput(olddentry);
>  	dput(opaquedir);
>  	ovl_cache_free(&list);
>  	return err;
>  
>  out_unlock:
> -	unlock_rename(new_upperdir, old_upperdir);
> +	end_renaming(&rd);
>  	goto out_revert_creds;
>  }
>  
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index 49ad65f829dc..3cc85a893b5c 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -355,11 +355,24 @@ static inline int ovl_do_remove_acl(struct ovl_fs *ofs, struct dentry *dentry,
>  	return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), dentry, acl_name);
>  }
>  
> +static inline int ovl_do_rename_rd(struct renamedata *rd)
> +{
> +	int err;
> +
> +	pr_debug("rename(%pd2, %pd2, 0x%x)\n", rd->old_dentry, rd->new_dentry,
> +		 rd->flags);
> +	err = vfs_rename(rd);
> +	if (err) {
> +		pr_debug("...rename(%pd2, %pd2, ...) = %i\n",
> +			 rd->old_dentry, rd->new_dentry, err);
> +	}
> +	return err;
> +}
> +
>  static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir,
>  				struct dentry *olddentry, struct dentry *newdir,
>  				struct dentry *newdentry, unsigned int flags)
>  {
> -	int err;
>  	struct renamedata rd = {
>  		.mnt_idmap	= ovl_upper_mnt_idmap(ofs),
>  		.old_parent	= olddir,
> @@ -369,13 +382,7 @@ static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir,
>  		.flags		= flags,
>  	};
>  
> -	pr_debug("rename(%pd2, %pd2, 0x%x)\n", olddentry, newdentry, flags);
> -	err = vfs_rename(&rd);
> -	if (err) {
> -		pr_debug("...rename(%pd2, %pd2, ...) = %i\n",
> -			 olddentry, newdentry, err);
> -	}
> -	return err;
> +	return ovl_do_rename_rd(&rd);
>  }
>  
>  static inline int ovl_do_whiteout(struct ovl_fs *ofs,
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index 196c66156a8a..7fdd9fdcbd2b 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -157,6 +157,9 @@ extern int follow_up(struct path *);
>  extern struct dentry *lock_rename(struct dentry *, struct dentry *);
>  extern struct dentry *lock_rename_child(struct dentry *, struct dentry *);
>  extern void unlock_rename(struct dentry *, struct dentry *);
> +int start_renaming(struct renamedata *rd, int lookup_flags,
> +		   struct qstr *old_last, struct qstr *new_last);
> +void end_renaming(struct renamedata *rd);
>  
>  /**
>   * mode_strip_umask - handle vfs umask stripping


Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 08/14] VFS: add start_creating_killable() and start_removing_killable()
From: Jeff Layton @ 2025-11-12 15:01 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-9-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> These are similar to start_creating() and start_removing(), but allow a
> fatal signal to abort waiting for the lock.
> 
> They are used in btrfs for subvol creation and removal.
> 
> btrfs_may_create() no longer needs IS_DEADDIR() and
> start_creating_killable() includes that check.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
>  fs/btrfs/ioctl.c      | 41 +++++++---------------
>  fs/namei.c            | 80 +++++++++++++++++++++++++++++++++++++++++--
>  include/linux/namei.h |  6 ++++
>  3 files changed, 95 insertions(+), 32 deletions(-)
> 
> diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
> index 8cb7d5a462ef..d0c3bb0423bb 100644
> --- a/fs/btrfs/ioctl.c
> +++ b/fs/btrfs/ioctl.c
> @@ -904,14 +904,9 @@ static noinline int btrfs_mksubvol(struct dentry *parent,
>  	struct fscrypt_str name_str = FSTR_INIT((char *)qname->name, qname->len);
>  	int ret;
>  
> -	ret = down_write_killable_nested(&dir->i_rwsem, I_MUTEX_PARENT);
> -	if (ret == -EINTR)
> -		return ret;
> -
> -	dentry = lookup_one(idmap, qname, parent);
> -	ret = PTR_ERR(dentry);
> +	dentry = start_creating_killable(idmap, parent, qname);
>  	if (IS_ERR(dentry))
> -		goto out_unlock;
> +		return PTR_ERR(dentry);
>  
>  	ret = btrfs_may_create(idmap, dir, dentry);
>  	if (ret)
> @@ -940,9 +935,7 @@ static noinline int btrfs_mksubvol(struct dentry *parent,
>  out_up_read:
>  	up_read(&fs_info->subvol_sem);
>  out_dput:
> -	dput(dentry);
> -out_unlock:
> -	btrfs_inode_unlock(BTRFS_I(dir), 0);
> +	end_creating(dentry, parent);
>  	return ret;
>  }
>  
> @@ -2417,18 +2410,10 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
>  		goto free_subvol_name;
>  	}
>  
> -	ret = down_write_killable_nested(&dir->i_rwsem, I_MUTEX_PARENT);
> -	if (ret == -EINTR)
> -		goto free_subvol_name;
> -	dentry = lookup_one(idmap, &QSTR(subvol_name), parent);
> +	dentry = start_removing_killable(idmap, parent, &QSTR(subvol_name));
>  	if (IS_ERR(dentry)) {
>  		ret = PTR_ERR(dentry);
> -		goto out_unlock_dir;
> -	}
> -
> -	if (d_really_is_negative(dentry)) {
> -		ret = -ENOENT;
> -		goto out_dput;
> +		goto out_end_removing;
>  	}
>  
>  	inode = d_inode(dentry);
> @@ -2449,7 +2434,7 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
>  		 */
>  		ret = -EPERM;
>  		if (!btrfs_test_opt(fs_info, USER_SUBVOL_RM_ALLOWED))
> -			goto out_dput;
> +			goto out_end_removing;
>  
>  		/*
>  		 * Do not allow deletion if the parent dir is the same
> @@ -2460,21 +2445,21 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
>  		 */
>  		ret = -EINVAL;
>  		if (root == dest)
> -			goto out_dput;
> +			goto out_end_removing;
>  
>  		ret = inode_permission(idmap, inode, MAY_WRITE | MAY_EXEC);
>  		if (ret)
> -			goto out_dput;
> +			goto out_end_removing;
>  	}
>  
>  	/* check if subvolume may be deleted by a user */
>  	ret = btrfs_may_delete(idmap, dir, dentry, 1);
>  	if (ret)
> -		goto out_dput;
> +		goto out_end_removing;
>  
>  	if (btrfs_ino(BTRFS_I(inode)) != BTRFS_FIRST_FREE_OBJECTID) {
>  		ret = -EINVAL;
> -		goto out_dput;
> +		goto out_end_removing;
>  	}
>  
>  	btrfs_inode_lock(BTRFS_I(inode), 0);
> @@ -2483,10 +2468,8 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
>  	if (!ret)
>  		d_delete_notify(dir, dentry);
>  
> -out_dput:
> -	dput(dentry);
> -out_unlock_dir:
> -	btrfs_inode_unlock(BTRFS_I(dir), 0);
> +out_end_removing:
> +	end_removing(dentry);
>  free_subvol_name:
>  	kfree(subvol_name_ptr);
>  free_parent:
> diff --git a/fs/namei.c b/fs/namei.c
> index 729b42fb143b..e70d056b9543 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -2778,19 +2778,33 @@ static int filename_parentat(int dfd, struct filename *name,
>   * Returns: a locked dentry, or an error.
>   *
>   */
> -struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
> -			   unsigned int lookup_flags)
> +static struct dentry *__start_dirop(struct dentry *parent, struct qstr *name,
> +				    unsigned int lookup_flags,
> +				    unsigned int state)
>  {
>  	struct dentry *dentry;
>  	struct inode *dir = d_inode(parent);
>  
> -	inode_lock_nested(dir, I_MUTEX_PARENT);
> +	if (state == TASK_KILLABLE) {
> +		int ret = down_write_killable_nested(&dir->i_rwsem,
> +						     I_MUTEX_PARENT);
> +		if (ret)
> +			return ERR_PTR(ret);
> +	} else {
> +		inode_lock_nested(dir, I_MUTEX_PARENT);
> +	}
>  	dentry = lookup_one_qstr_excl(name, parent, lookup_flags);
>  	if (IS_ERR(dentry))
>  		inode_unlock(dir);
>  	return dentry;
>  }
>  
> +struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
> +			   unsigned int lookup_flags)
> +{
> +	return __start_dirop(parent, name, lookup_flags, TASK_NORMAL);
> +}
> +
>  /**
>   * end_dirop - signal completion of a dirop
>   * @de: the dentry which was returned by start_dirop or similar.
> @@ -3275,6 +3289,66 @@ struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
>  }
>  EXPORT_SYMBOL(start_removing);
>  
> +/**
> + * start_creating_killable - prepare to create a given name with permission checking
> + * @idmap:  idmap of the mount
> + * @parent: directory in which to prepare to create the name
> + * @name:   the name to be created
> + *
> + * Locks are taken and a lookup in performed prior to creating
> + * an object in a directory.  Permission checking (MAY_EXEC) is performed
> + * against @idmap.
> + *
> + * If the name already exists, a positive dentry is returned.
> + *
> + * If a signal is received or was already pending, the function aborts
> + * with -EINTR;
> + *
> + * Returns: a negative or positive dentry, or an error.
> + */
> +struct dentry *start_creating_killable(struct mnt_idmap *idmap,
> +				       struct dentry *parent,
> +				       struct qstr *name)
> +{
> +	int err = lookup_one_common(idmap, name, parent);
> +
> +	if (err)
> +		return ERR_PTR(err);
> +	return __start_dirop(parent, name, LOOKUP_CREATE, TASK_KILLABLE);
> +}
> +EXPORT_SYMBOL(start_creating_killable);
> +
> +/**
> + * start_removing_killable - prepare to remove a given name with permission checking
> + * @idmap:  idmap of the mount
> + * @parent: directory in which to find the name
> + * @name:   the name to be removed
> + *
> + * Locks are taken and a lookup in performed prior to removing
> + * an object from a directory.  Permission checking (MAY_EXEC) is performed
> + * against @idmap.
> + *
> + * If the name doesn't exist, an error is returned.
> + *
> + * end_removing() should be called when removal is complete, or aborted.
> + *
> + * If a signal is received or was already pending, the function aborts
> + * with -EINTR;
> + *
> + * Returns: a positive dentry, or an error.
> + */
> +struct dentry *start_removing_killable(struct mnt_idmap *idmap,
> +				       struct dentry *parent,
> +				       struct qstr *name)
> +{
> +	int err = lookup_one_common(idmap, name, parent);
> +
> +	if (err)
> +		return ERR_PTR(err);
> +	return __start_dirop(parent, name, 0, TASK_KILLABLE);
> +}
> +EXPORT_SYMBOL(start_removing_killable);
> +
>  /**
>   * start_creating_noperm - prepare to create a given name without permission checking
>   * @parent: directory in which to prepare to create the name
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index d089e4e8fdd0..196c66156a8a 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -93,6 +93,12 @@ struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
>  			      struct qstr *name);
>  struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
>  			      struct qstr *name);
> +struct dentry *start_creating_killable(struct mnt_idmap *idmap,
> +				       struct dentry *parent,
> +				       struct qstr *name);
> +struct dentry *start_removing_killable(struct mnt_idmap *idmap,
> +				       struct dentry *parent,
> +				       struct qstr *name);
>  struct dentry *start_creating_noperm(struct dentry *parent, struct qstr *name);
>  struct dentry *start_removing_noperm(struct dentry *parent, struct qstr *name);
>  struct dentry *start_removing_dentry(struct dentry *parent,

Nice. Maybe we can start using the killable versions in more places
this way!

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 07/14] VFS: introduce start_removing_dentry()
From: Jeff Layton @ 2025-11-12 14:58 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-8-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> start_removing_dentry() is similar to start_removing() but instead of
> providing a name for lookup, the target dentry is given.
> 
> start_removing_dentry() checks that the dentry is still hashed and in
> the parent, and if so it locks and increases the refcount so that
> end_removing() can be used to finish the operation.
> 
> This is used in cachefiles, overlayfs, smb/server, and apparmor.
> 
> There will be other users including ecryptfs.
> 
> As start_removing_dentry() takes an extra reference to the dentry (to be
> put by end_removing()), there is no need to explicitly take an extra
> reference to stop d_delete() from using dentry_unlink_inode() to negate
> the dentry - as in cachefiles_delete_object(), and ksmbd_vfs_unlink().
> 
> cachefiles_bury_object() now gets an extra ref to the victim, which is
> drops.  As it includes the needed end_removing() calls, the caller
> doesn't need them.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> 
> ---
> Changes since v4
> Callers of cachefiles_bury_object() were incorrectly calling
> end_removing() after that call.  The dput() was needed, the unlock
> wasn't.  The dput() has been effectively moved into
> cachefiles_bury_object() by removing a dget() which is now not needed.
> ---
>  fs/cachefiles/interface.c      | 11 +++++++----
>  fs/cachefiles/namei.c          | 30 ++++++++++++++----------------
>  fs/cachefiles/volume.c         |  9 ++++++---
>  fs/namei.c                     | 33 +++++++++++++++++++++++++++++++++
>  fs/overlayfs/dir.c             | 10 ++++------
>  fs/overlayfs/readdir.c         |  8 ++++----
>  fs/smb/server/vfs.c            | 27 ++++-----------------------
>  include/linux/namei.h          |  2 ++
>  security/apparmor/apparmorfs.c |  8 ++++----
>  9 files changed, 78 insertions(+), 60 deletions(-)
> 
> diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c
> index 3e63cfe15874..a08250d244ea 100644
> --- a/fs/cachefiles/interface.c
> +++ b/fs/cachefiles/interface.c
> @@ -9,6 +9,7 @@
>  #include <linux/mount.h>
>  #include <linux/xattr.h>
>  #include <linux/file.h>
> +#include <linux/namei.h>
>  #include <linux/falloc.h>
>  #include <trace/events/fscache.h>
>  #include "internal.h"
> @@ -428,11 +429,13 @@ static bool cachefiles_invalidate_cookie(struct fscache_cookie *cookie)
>  		if (!old_tmpfile) {
>  			struct cachefiles_volume *volume = object->volume;
>  			struct dentry *fan = volume->fanout[(u8)cookie->key_hash];
> +			struct dentry *obj;
>  
> -			inode_lock_nested(d_inode(fan), I_MUTEX_PARENT);
> -			cachefiles_bury_object(volume->cache, object, fan,
> -					       old_file->f_path.dentry,
> -					       FSCACHE_OBJECT_INVALIDATED);
> +			obj = start_removing_dentry(fan, old_file->f_path.dentry);
> +			if (!IS_ERR(obj))
> +				cachefiles_bury_object(volume->cache, object,
> +						       fan, obj,
> +						       FSCACHE_OBJECT_INVALIDATED);
>  		}
>  		fput(old_file);
>  	}
> diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
> index c7f0c6ab9b88..0104ac00485d 100644
> --- a/fs/cachefiles/namei.c
> +++ b/fs/cachefiles/namei.c
> @@ -261,6 +261,7 @@ static int cachefiles_unlink(struct cachefiles_cache *cache,
>   * - Directory backed objects are stuffed into the graveyard for userspace to
>   *   delete
>   * On entry dir must be locked.  It will be unlocked on exit.
> + * On entry there must be at least 2 refs on rep, one will be dropped on exit.
>   */
>  int cachefiles_bury_object(struct cachefiles_cache *cache,
>  			   struct cachefiles_object *object,
> @@ -275,12 +276,6 @@ int cachefiles_bury_object(struct cachefiles_cache *cache,
>  
>  	_enter(",'%pd','%pd'", dir, rep);
>  
> -	/* end_removing() will dput() @rep but we need to keep
> -	 * a ref, so take one now.  This also stops the dentry
> -	 * being negated when unlinked which we need.
> -	 */
> -	dget(rep);
> -
>  	if (rep->d_parent != dir) {
>  		end_removing(rep);
>  		_leave(" = -ESTALE");
> @@ -425,13 +420,12 @@ int cachefiles_delete_object(struct cachefiles_object *object,
>  
>  	_enter(",OBJ%x{%pD}", object->debug_id, object->file);
>  
> -	/* Stop the dentry being negated if it's only pinned by a file struct. */
> -	dget(dentry);
> -
> -	inode_lock_nested(d_backing_inode(fan), I_MUTEX_PARENT);
> -	ret = cachefiles_unlink(volume->cache, object, fan, dentry, why);
> -	inode_unlock(d_backing_inode(fan));
> -	dput(dentry);
> +	dentry = start_removing_dentry(fan, dentry);
> +	if (IS_ERR(dentry))
> +		ret = PTR_ERR(dentry);
> +	else
> +		ret = cachefiles_unlink(volume->cache, object, fan, dentry, why);
> +	end_removing(dentry);
>  	return ret;
>  }
>  
> @@ -644,9 +638,13 @@ bool cachefiles_look_up_object(struct cachefiles_object *object)
>  
>  	if (!d_is_reg(dentry)) {
>  		pr_err("%pd is not a file\n", dentry);
> -		inode_lock_nested(d_inode(fan), I_MUTEX_PARENT);
> -		ret = cachefiles_bury_object(volume->cache, object, fan, dentry,
> -					     FSCACHE_OBJECT_IS_WEIRD);
> +		struct dentry *de = start_removing_dentry(fan, dentry);
> +		if (IS_ERR(de))
> +			ret = PTR_ERR(de);
> +		else
> +			ret = cachefiles_bury_object(volume->cache, object,
> +						     fan, de,
> +						     FSCACHE_OBJECT_IS_WEIRD);
>  		dput(dentry);
>  		if (ret < 0)
>  			return false;
> diff --git a/fs/cachefiles/volume.c b/fs/cachefiles/volume.c
> index 781aac4ef274..90ba926f488e 100644
> --- a/fs/cachefiles/volume.c
> +++ b/fs/cachefiles/volume.c
> @@ -7,6 +7,7 @@
>  
>  #include <linux/fs.h>
>  #include <linux/slab.h>
> +#include <linux/namei.h>
>  #include "internal.h"
>  #include <trace/events/fscache.h>
>  
> @@ -58,9 +59,11 @@ void cachefiles_acquire_volume(struct fscache_volume *vcookie)
>  		if (ret < 0) {
>  			if (ret != -ESTALE)
>  				goto error_dir;
> -			inode_lock_nested(d_inode(cache->store), I_MUTEX_PARENT);
> -			cachefiles_bury_object(cache, NULL, cache->store, vdentry,
> -					       FSCACHE_VOLUME_IS_WEIRD);
> +			vdentry = start_removing_dentry(cache->store, vdentry);
> +			if (!IS_ERR(vdentry))
> +				cachefiles_bury_object(cache, NULL, cache->store,
> +						       vdentry,
> +						       FSCACHE_VOLUME_IS_WEIRD);
>  			cachefiles_put_directory(volume->dentry);
>  			cond_resched();
>  			goto retry;
> diff --git a/fs/namei.c b/fs/namei.c
> index da01b828ede6..729b42fb143b 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3323,6 +3323,39 @@ struct dentry *start_removing_noperm(struct dentry *parent,
>  }
>  EXPORT_SYMBOL(start_removing_noperm);
>  
> +/**
> + * start_removing_dentry - prepare to remove a given dentry
> + * @parent: directory from which dentry should be removed
> + * @child:  the dentry to be removed
> + *
> + * A lock is taken to protect the dentry again other dirops and
> + * the validity of the dentry is checked: correct parent and still hashed.
> + *
> + * If the dentry is valid and positive, a reference is taken and
> + * returned.  If not an error is returned.
> + *
> + * end_removing() should be called when removal is complete, or aborted.
> + *
> + * Returns: the valid dentry, or an error.
> + */
> +struct dentry *start_removing_dentry(struct dentry *parent,
> +				     struct dentry *child)
> +{
> +	inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
> +	if (unlikely(IS_DEADDIR(parent->d_inode) ||
> +		     child->d_parent != parent ||
> +		     d_unhashed(child))) {
> +		inode_unlock(parent->d_inode);
> +		return ERR_PTR(-EINVAL);
> +	}
> +	if (d_is_negative(child)) {
> +		inode_unlock(parent->d_inode);
> +		return ERR_PTR(-ENOENT);
> +	}
> +	return dget(child);
> +}
> +EXPORT_SYMBOL(start_removing_dentry);
> +
>  #ifdef CONFIG_UNIX98_PTYS
>  int path_pts(struct path *path)
>  {
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index 20682afdbd20..6d1d0e94e287 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -47,14 +47,12 @@ static int ovl_cleanup_locked(struct ovl_fs *ofs, struct inode *wdir,
>  int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir,
>  		struct dentry *wdentry)
>  {
> -	int err;
> -
> -	err = ovl_parent_lock(workdir, wdentry);
> -	if (err)
> -		return err;
> +	wdentry = start_removing_dentry(workdir, wdentry);
> +	if (IS_ERR(wdentry))
> +		return PTR_ERR(wdentry);
>  
>  	ovl_cleanup_locked(ofs, workdir->d_inode, wdentry);
> -	ovl_parent_unlock(workdir);
> +	end_removing(wdentry);
>  
>  	return 0;
>  }
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> index 1e9792cc557b..77ecc39fc33a 100644
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -1242,11 +1242,11 @@ int ovl_workdir_cleanup(struct ovl_fs *ofs, struct dentry *parent,
>  	if (!d_is_dir(dentry) || level > 1)
>  		return ovl_cleanup(ofs, parent, dentry);
>  
> -	err = ovl_parent_lock(parent, dentry);
> -	if (err)
> -		return err;
> +	dentry = start_removing_dentry(parent, dentry);
> +	if (IS_ERR(dentry))
> +		return PTR_ERR(dentry);
>  	err = ovl_do_rmdir(ofs, parent->d_inode, dentry);
> -	ovl_parent_unlock(parent);
> +	end_removing(dentry);
>  	if (err) {
>  		struct path path = { .mnt = mnt, .dentry = dentry };
>  
> diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
> index 891ed2dc2b73..7c4ddc43ab39 100644
> --- a/fs/smb/server/vfs.c
> +++ b/fs/smb/server/vfs.c
> @@ -49,24 +49,6 @@ static void ksmbd_vfs_inherit_owner(struct ksmbd_work *work,
>  	i_uid_write(inode, i_uid_read(parent_inode));
>  }
>  
> -/**
> - * ksmbd_vfs_lock_parent() - lock parent dentry if it is stable
> - * @parent: parent dentry
> - * @child: child dentry
> - *
> - * Returns: %0 on success, %-ENOENT if the parent dentry is not stable
> - */
> -int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child)
> -{
> -	inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
> -	if (child->d_parent != parent) {
> -		inode_unlock(d_inode(parent));
> -		return -ENOENT;
> -	}
> -
> -	return 0;
> -}
> -
>  static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
>  				 char *pathname, unsigned int flags,
>  				 struct path *path, bool do_lock)
> @@ -1084,18 +1066,17 @@ int ksmbd_vfs_unlink(struct file *filp)
>  		return err;
>  
>  	dir = dget_parent(dentry);
> -	err = ksmbd_vfs_lock_parent(dir, dentry);
> -	if (err)
> +	dentry = start_removing_dentry(dir, dentry);
> +	err = PTR_ERR(dentry);
> +	if (IS_ERR(dentry))
>  		goto out;
> -	dget(dentry);
>  
>  	if (S_ISDIR(d_inode(dentry)->i_mode))
>  		err = vfs_rmdir(idmap, d_inode(dir), dentry);
>  	else
>  		err = vfs_unlink(idmap, d_inode(dir), dentry, NULL);
>  
> -	dput(dentry);
> -	inode_unlock(d_inode(dir));
> +	end_removing(dentry);
>  	if (err)
>  		ksmbd_debug(VFS, "failed to delete, err %d\n", err);
>  out:

I guess this answers my earlier question.

> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index 0441f5921f87..d089e4e8fdd0 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -95,6 +95,8 @@ struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
>  			      struct qstr *name);
>  struct dentry *start_creating_noperm(struct dentry *parent, struct qstr *name);
>  struct dentry *start_removing_noperm(struct dentry *parent, struct qstr *name);
> +struct dentry *start_removing_dentry(struct dentry *parent,
> +				     struct dentry *child);
>  
>  /**
>   * end_creating - finish action started with start_creating
> diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
> index 391a586d0557..9d08d103f142 100644
> --- a/security/apparmor/apparmorfs.c
> +++ b/security/apparmor/apparmorfs.c
> @@ -355,17 +355,17 @@ static void aafs_remove(struct dentry *dentry)
>  	if (!dentry || IS_ERR(dentry))
>  		return;
>  
> +	/* ->d_parent is stable as rename is not supported */
>  	dir = d_inode(dentry->d_parent);
> -	inode_lock(dir);
> -	if (simple_positive(dentry)) {
> +	dentry = start_removing_dentry(dentry->d_parent, dentry);
> +	if (!IS_ERR(dentry) && simple_positive(dentry)) {
>  		if (d_is_dir(dentry))
>  			simple_rmdir(dir, dentry);
>  		else
>  			simple_unlink(dir, dentry);
>  		d_delete(dentry);
> -		dput(dentry);
>  	}
> -	inode_unlock(dir);
> +	end_removing(dentry);
>  	simple_release_fs(&aafs_mnt, &aafs_count);
>  }
>  

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 05/14] VFS/nfsd/cachefiles/ovl: introduce start_removing() and end_removing()
From: Jeff Layton @ 2025-11-12 14:51 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-6-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> start_removing() is similar to start_creating() but will only return a
> positive dentry with the expectation that it will be removed.  This is
> used by nfsd, cachefiles, and overlayfs.  They are changed to also use
> end_removing() to terminate the action begun by start_removing().  This
> is a simple alias for end_dirop().
> 
> Apart from changes to the error paths, as we no longer need to unlock on
> a lookup error, an effect on callers is that they don't need to test if
> the found dentry is positive or negative - they can be sure it is
> positive.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
>  fs/cachefiles/namei.c    | 32 ++++++++++++++------------------
>  fs/namei.c               | 27 +++++++++++++++++++++++++++
>  fs/nfsd/nfs4recover.c    | 18 +++++-------------
>  fs/nfsd/vfs.c            | 26 ++++++++++----------------
>  fs/overlayfs/dir.c       | 15 +++++++--------
>  fs/overlayfs/overlayfs.h |  8 ++++++++
>  include/linux/namei.h    | 18 ++++++++++++++++++
>  7 files changed, 89 insertions(+), 55 deletions(-)
> 
> diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
> index 0a136eb434da..c7f0c6ab9b88 100644
> --- a/fs/cachefiles/namei.c
> +++ b/fs/cachefiles/namei.c
> @@ -260,6 +260,7 @@ static int cachefiles_unlink(struct cachefiles_cache *cache,
>   * - File backed objects are unlinked
>   * - Directory backed objects are stuffed into the graveyard for userspace to
>   *   delete
> + * On entry dir must be locked.  It will be unlocked on exit.
>   */
>  int cachefiles_bury_object(struct cachefiles_cache *cache,
>  			   struct cachefiles_object *object,
> @@ -274,28 +275,30 @@ int cachefiles_bury_object(struct cachefiles_cache *cache,
>  
>  	_enter(",'%pd','%pd'", dir, rep);
>  
> +	/* end_removing() will dput() @rep but we need to keep
> +	 * a ref, so take one now.  This also stops the dentry
> +	 * being negated when unlinked which we need.
> +	 */
> +	dget(rep);
> +
>  	if (rep->d_parent != dir) {
> -		inode_unlock(d_inode(dir));
> +		end_removing(rep);
>  		_leave(" = -ESTALE");
>  		return -ESTALE;
>  	}
>  
>  	/* non-directories can just be unlinked */
>  	if (!d_is_dir(rep)) {
> -		dget(rep); /* Stop the dentry being negated if it's only pinned
> -			    * by a file struct.
> -			    */
>  		ret = cachefiles_unlink(cache, object, dir, rep, why);
> -		dput(rep);
> +		end_removing(rep);
>  
> -		inode_unlock(d_inode(dir));
>  		_leave(" = %d", ret);
>  		return ret;
>  	}
>  
>  	/* directories have to be moved to the graveyard */
>  	_debug("move stale object to graveyard");
> -	inode_unlock(d_inode(dir));
> +	end_removing(rep);
>  
>  try_again:
>  	/* first step is to make up a grave dentry in the graveyard */
> @@ -749,26 +752,20 @@ static struct dentry *cachefiles_lookup_for_cull(struct cachefiles_cache *cache,
>  	struct dentry *victim;
>  	int ret = -ENOENT;
>  
> -	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
> +	victim = start_removing(&nop_mnt_idmap, dir, &QSTR(filename));
>  
> -	victim = lookup_one(&nop_mnt_idmap, &QSTR(filename), dir);
>  	if (IS_ERR(victim))
>  		goto lookup_error;
> -	if (d_is_negative(victim))
> -		goto lookup_put;
>  	if (d_inode(victim)->i_flags & S_KERNEL_FILE)
>  		goto lookup_busy;
>  	return victim;
>  
>  lookup_busy:
>  	ret = -EBUSY;
> -lookup_put:
> -	inode_unlock(d_inode(dir));
> -	dput(victim);
> +	end_removing(victim);
>  	return ERR_PTR(ret);
>  
>  lookup_error:
> -	inode_unlock(d_inode(dir));
>  	ret = PTR_ERR(victim);
>  	if (ret == -ENOENT)
>  		return ERR_PTR(-ESTALE); /* Probably got retired by the netfs */
> @@ -816,18 +813,17 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
>  
>  	ret = cachefiles_bury_object(cache, NULL, dir, victim,
>  				     FSCACHE_OBJECT_WAS_CULLED);
> +	dput(victim);
>  	if (ret < 0)
>  		goto error;
>  
>  	fscache_count_culled();
> -	dput(victim);
>  	_leave(" = 0");
>  	return 0;
>  
>  error_unlock:
> -	inode_unlock(d_inode(dir));
> +	end_removing(victim);
>  error:
> -	dput(victim);
>  	if (ret == -ENOENT)
>  		return -ESTALE; /* Probably got retired by the netfs */
>  
> diff --git a/fs/namei.c b/fs/namei.c
> index 8873ad0f05b0..38dda29552f6 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3248,6 +3248,33 @@ struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
>  }
>  EXPORT_SYMBOL(start_creating);
>  
> +/**
> + * start_removing - prepare to remove a given name with permission checking
> + * @idmap:  idmap of the mount
> + * @parent: directory in which to find the name
> + * @name:   the name to be removed
> + *
> + * Locks are taken and a lookup in performed prior to removing
> + * an object from a directory.  Permission checking (MAY_EXEC) is performed
> + * against @idmap.
> + *
> + * If the name doesn't exist, an error is returned.
> + *
> + * end_removing() should be called when removal is complete, or aborted.
> + *
> + * Returns: a positive dentry, or an error.
> + */
> +struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
> +			      struct qstr *name)
> +{
> +	int err = lookup_one_common(idmap, name, parent);
> +
> +	if (err)
> +		return ERR_PTR(err);
> +	return start_dirop(parent, name, 0);
> +}
> +EXPORT_SYMBOL(start_removing);
> +
>  #ifdef CONFIG_UNIX98_PTYS
>  int path_pts(struct path *path)
>  {
> diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
> index c247a7c3291c..3eefaa2202e3 100644
> --- a/fs/nfsd/nfs4recover.c
> +++ b/fs/nfsd/nfs4recover.c
> @@ -324,20 +324,12 @@ nfsd4_unlink_clid_dir(char *name, struct nfsd_net *nn)
>  	dprintk("NFSD: nfsd4_unlink_clid_dir. name %s\n", name);
>  
>  	dir = nn->rec_file->f_path.dentry;
> -	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
> -	dentry = lookup_one(&nop_mnt_idmap, &QSTR(name), dir);
> -	if (IS_ERR(dentry)) {
> -		status = PTR_ERR(dentry);
> -		goto out_unlock;
> -	}
> -	status = -ENOENT;
> -	if (d_really_is_negative(dentry))
> -		goto out;
> +	dentry = start_removing(&nop_mnt_idmap, dir, &QSTR(name));
> +	if (IS_ERR(dentry))
> +		return PTR_ERR(dentry);
> +
>  	status = vfs_rmdir(&nop_mnt_idmap, d_inode(dir), dentry);
> -out:
> -	dput(dentry);
> -out_unlock:
> -	inode_unlock(d_inode(dir));
> +	end_removing(dentry);
>  	return status;
>  }
>  
> diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> index 24e501abad0e..6291c371caa7 100644
> --- a/fs/nfsd/vfs.c
> +++ b/fs/nfsd/vfs.c
> @@ -2044,7 +2044,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  {
>  	struct dentry	*dentry, *rdentry;
>  	struct inode	*dirp;
> -	struct inode	*rinode;
> +	struct inode	*rinode = NULL;
>  	__be32		err;
>  	int		host_err;
>  
> @@ -2063,24 +2063,21 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  
>  	dentry = fhp->fh_dentry;
>  	dirp = d_inode(dentry);
> -	inode_lock_nested(dirp, I_MUTEX_PARENT);
>  
> -	rdentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), dentry);
> +	rdentry = start_removing(&nop_mnt_idmap, dentry, &QSTR_LEN(fname, flen));
> +
>  	host_err = PTR_ERR(rdentry);
>  	if (IS_ERR(rdentry))
> -		goto out_unlock;
> +		goto out_drop_write;
>  
> -	if (d_really_is_negative(rdentry)) {
> -		dput(rdentry);
> -		host_err = -ENOENT;
> -		goto out_unlock;
> -	}
> -	rinode = d_inode(rdentry);
>  	err = fh_fill_pre_attrs(fhp);
>  	if (err != nfs_ok)
>  		goto out_unlock;
>  
> +	rinode = d_inode(rdentry);
> +	/* Prevent truncation until after locks dropped */
>  	ihold(rinode);
> +
>  	if (!type)
>  		type = d_inode(rdentry)->i_mode & S_IFMT;
>  
> @@ -2102,10 +2099,10 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  	}
>  	fh_fill_post_attrs(fhp);
>  
> -	inode_unlock(dirp);
> -	if (!host_err)
> +out_unlock:
> +	end_removing(rdentry);
> +	if (!err && !host_err)
>  		host_err = commit_metadata(fhp);
> -	dput(rdentry);
>  	iput(rinode);    /* truncate the inode here */
>  
>  out_drop_write:
> @@ -2123,9 +2120,6 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  	}
>  out:
>  	return err != nfs_ok ? err : nfserrno(host_err);
> -out_unlock:
> -	inode_unlock(dirp);
> -	goto out_drop_write;
>  }
>  
>  /*
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index b9160fefbd00..20682afdbd20 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -866,17 +866,17 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
>  			goto out;
>  	}
>  
> -	inode_lock_nested(dir, I_MUTEX_PARENT);
> -	upper = ovl_lookup_upper(ofs, dentry->d_name.name, upperdir,
> -				 dentry->d_name.len);
> +	upper = ovl_start_removing_upper(ofs, upperdir,
> +					 &QSTR_LEN(dentry->d_name.name,
> +						   dentry->d_name.len));
>  	err = PTR_ERR(upper);
>  	if (IS_ERR(upper))
> -		goto out_unlock;
> +		goto out_dput;
>  
>  	err = -ESTALE;
>  	if ((opaquedir && upper != opaquedir) ||
>  	    (!opaquedir && !ovl_matches_upper(dentry, upper)))
> -		goto out_dput_upper;
> +		goto out_unlock;
>  
>  	if (is_dir)
>  		err = ovl_do_rmdir(ofs, dir, upper);
> @@ -892,10 +892,9 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
>  	 */
>  	if (!err)
>  		d_drop(dentry);
> -out_dput_upper:
> -	dput(upper);
>  out_unlock:
> -	inode_unlock(dir);
> +	end_removing(upper);
> +out_dput:
>  	dput(opaquedir);
>  out:
>  	return err;
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index beeba96cfcb2..49ad65f829dc 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -423,6 +423,14 @@ static inline struct dentry *ovl_start_creating_upper(struct ovl_fs *ofs,
>  			      parent, name);
>  }
>  
> +static inline struct dentry *ovl_start_removing_upper(struct ovl_fs *ofs,
> +						      struct dentry *parent,
> +						      struct qstr *name)
> +{
> +	return start_removing(ovl_upper_mnt_idmap(ofs),
> +			      parent, name);
> +}
> +
>  static inline bool ovl_open_flags_need_copy_up(int flags)
>  {
>  	if (!flags)
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index 37b72f4a64f0..6d1069f93ebf 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -91,6 +91,8 @@ struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap,
>  
>  struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
>  			      struct qstr *name);
> +struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
> +			      struct qstr *name);
>  
>  /**
>   * end_creating - finish action started with start_creating
> @@ -122,6 +124,22 @@ static inline void end_creating(struct dentry *child, struct dentry *parent)
>  		end_dirop(child);
>  }
>  
> +/**
> + * end_removing - finish action started with start_removing
> + * @child:  dentry returned by start_removing()
> + * @parent: dentry given to start_removing()
> + *
> + * Unlock and release the child.
> + *
> + * This is identical to end_dirop().  It can be passed the result of
> + * start_removing() whether that was successful or not, but it not needed
> + * if start_removing() failed.
> + */
> +static inline void end_removing(struct dentry *child)
> +{
> +	end_dirop(child);
> +}
> +
>  extern int follow_down_one(struct path *);
>  extern int follow_down(struct path *path, unsigned int flags);
>  extern int follow_up(struct path *);

This looks fine to me (particularly the knfsd parts), but doesn't ksmbd
need to be similarly converted?

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 03/14] VFS: tidy up do_unlinkat()
From: Jeff Layton @ 2025-11-12 14:47 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-4-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> The simplification of locking in the previous patch opens up some room
> for tidying up do_unlinkat()
> 
> - change all "exit" labels to describe what will happen at the label.
> - always goto an exit label on an error - unwrap the "if (!IS_ERR())" branch.
> - Move the "slashes" handing inline, but mark it as unlikely()
> - simplify use of the "inode" variable - we no longer need to test for NULL.
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
>  fs/namei.c | 55 ++++++++++++++++++++++++++----------------------------
>  1 file changed, 26 insertions(+), 29 deletions(-)
> 
> diff --git a/fs/namei.c b/fs/namei.c
> index 231e1ffd4b8d..93c5fce2d814 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -4755,65 +4755,62 @@ int do_unlinkat(int dfd, struct filename *name)
>  	struct path path;
>  	struct qstr last;
>  	int type;
> -	struct inode *inode = NULL;
> +	struct inode *inode;
>  	struct inode *delegated_inode = NULL;
>  	unsigned int lookup_flags = 0;
>  retry:
>  	error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
>  	if (error)
> -		goto exit1;
> +		goto exit_putname;
>  
>  	error = -EISDIR;
>  	if (type != LAST_NORM)
> -		goto exit2;
> +		goto exit_path_put;
>  
>  	error = mnt_want_write(path.mnt);
>  	if (error)
> -		goto exit2;
> +		goto exit_path_put;
>  retry_deleg:
>  	dentry = start_dirop(path.dentry, &last, lookup_flags);
>  	error = PTR_ERR(dentry);
> -	if (!IS_ERR(dentry)) {
> +	if (IS_ERR(dentry))
> +		goto exit_drop_write;
>  
> -		/* Why not before? Because we want correct error value */
> -		if (last.name[last.len])
> -			goto slashes;
> -		inode = dentry->d_inode;
> -		ihold(inode);
> -		error = security_path_unlink(&path, dentry);
> -		if (error)
> -			goto exit3;
> -		error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
> -				   dentry, &delegated_inode);
> -exit3:
> +	/* Why not before? Because we want correct error value */
> +	if (unlikely(last.name[last.len])) {
> +		if (d_is_dir(dentry))
> +			error = -EISDIR;
> +		else
> +			error = -ENOTDIR;
>  		end_dirop(dentry);
> +		goto exit_drop_write;
>  	}
> -	if (inode)
> -		iput(inode);	/* truncate the inode here */
> -	inode = NULL;
> +	inode = dentry->d_inode;
> +	ihold(inode);
> +	error = security_path_unlink(&path, dentry);
> +	if (error)
> +		goto exit_end_dirop;
> +	error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
> +			   dentry, &delegated_inode);
> +exit_end_dirop:
> +	end_dirop(dentry);
> +	iput(inode);	/* truncate the inode here */
>  	if (delegated_inode) {
>  		error = break_deleg_wait(&delegated_inode);
>  		if (!error)
>  			goto retry_deleg;
>  	}
> +exit_drop_write:
>  	mnt_drop_write(path.mnt);
> -exit2:
> +exit_path_put:
>  	path_put(&path);
>  	if (retry_estale(error, lookup_flags)) {
>  		lookup_flags |= LOOKUP_REVAL;
> -		inode = NULL;
>  		goto retry;
>  	}
> -exit1:
> +exit_putname:
>  	putname(name);
>  	return error;
> -
> -slashes:
> -	if (d_is_dir(dentry))
> -		error = -EISDIR;
> -	else
> -		error = -ENOTDIR;
> -	goto exit3;
>  }
>  
>  SYSCALL_DEFINE3(unlinkat, int, dfd, const char __user *, pathname, int, flag)

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH v5 02/14] VFS: introduce start_dirop() and end_dirop()
From: Jeff Layton @ 2025-11-12 14:46 UTC (permalink / raw)
  To: NeilBrown, Alexander Viro, Christian Brauner, Amir Goldstein
  Cc: Jan Kara, linux-fsdevel, Chris Mason, David Sterba, David Howells,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tyler Hicks, Miklos Szeredi, Chuck Lever, Olga Kornievskaia,
	Dai Ngo, Namjae Jeon, Steve French, Sergey Senozhatsky,
	Carlos Maiolino, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Stephen Smalley, Ondrej Mosnacek, Mateusz Guzik,
	Lorenzo Stoakes, Stefan Berger, Darrick J. Wong, linux-kernel,
	netfs, ecryptfs, linux-nfs, linux-unionfs, linux-cifs, linux-xfs,
	linux-security-module, selinux
In-Reply-To: <20251106005333.956321-3-neilb@ownmail.net>

On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
> 
> The fact that directory operations (create,remove,rename) are protected
> by a lock on the parent is known widely throughout the kernel.
> In order to change this - to instead lock the target dentry  - it is
> best to centralise this knowledge so it can be changed in one place.
> 
> This patch introduces start_dirop() which is local to VFS code.
> It performs the required locking for create and remove.  Rename
> will be handled separately.
> 
> Various functions with names like start_creating() or start_removing_path(),
> some of which already exist, will export this functionality beyond the VFS.
> 
> end_dirop() is the partner of start_dirop().  It drops the lock and
> releases the reference on the dentry.
> It *is* exported so that various end_creating etc functions can be inline.
> 
> As vfs_mkdir() drops the dentry on error we cannot use end_dirop() as
> that won't unlock when the dentry IS_ERR().  For now we need an explicit
> unlock when dentry IS_ERR().  I hope to change vfs_mkdir() to unlock
> when it drops a dentry so that explicit unlock can go away.
> 
> end_dirop() can always be called on the result of start_dirop(), but not
> after vfs_mkdir().  After a vfs_mkdir() we still may need the explicit
> unlock as seen in end_creating_path().
> 
> As well as adding start_dirop() and end_dirop()
> this patch uses them in:
>  - simple_start_creating (which requires sharing lookup_noperm_common()
>         with libfs.c)
>  - start_removing_path / start_removing_user_path_at
>  - filename_create / end_creating_path()
>  - do_rmdir(), do_unlinkat()
> 
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
>  fs/internal.h      |  3 ++
>  fs/libfs.c         | 36 ++++++++---------
>  fs/namei.c         | 98 ++++++++++++++++++++++++++++++++++------------
>  include/linux/fs.h |  2 +
>  4 files changed, 95 insertions(+), 44 deletions(-)
> 
> diff --git a/fs/internal.h b/fs/internal.h
> index 9b2b4d116880..d08d5e2235e9 100644
> --- a/fs/internal.h
> +++ b/fs/internal.h
> @@ -67,6 +67,9 @@ int vfs_tmpfile(struct mnt_idmap *idmap,
>  		const struct path *parentpath,
>  		struct file *file, umode_t mode);
>  struct dentry *d_hash_and_lookup(struct dentry *, struct qstr *);
> +struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
> +			   unsigned int lookup_flags);
> +int lookup_noperm_common(struct qstr *qname, struct dentry *base);
>  
>  /*
>   * namespace.c
> diff --git a/fs/libfs.c b/fs/libfs.c
> index 1661dcb7d983..2d6657947abd 100644
> --- a/fs/libfs.c
> +++ b/fs/libfs.c
> @@ -2290,27 +2290,25 @@ void stashed_dentry_prune(struct dentry *dentry)
>  	cmpxchg(stashed, dentry, NULL);
>  }
>  
> -/* parent must be held exclusive */
> +/**
> + * simple_start_creating - prepare to create a given name
> + * @parent: directory in which to prepare to create the name
> + * @name:   the name to be created
> + *
> + * Required lock is taken and a lookup in performed prior to creating an
> + * object in a directory.  No permission checking is performed.
> + *
> + * Returns: a negative dentry on which vfs_create() or similar may
> + *  be attempted, or an error.
> + */
>  struct dentry *simple_start_creating(struct dentry *parent, const char *name)
>  {
> -	struct dentry *dentry;
> -	struct inode *dir = d_inode(parent);
> +	struct qstr qname = QSTR(name);
> +	int err;
>  
> -	inode_lock(dir);
> -	if (unlikely(IS_DEADDIR(dir))) {
> -		inode_unlock(dir);
> -		return ERR_PTR(-ENOENT);
> -	}
> -	dentry = lookup_noperm(&QSTR(name), parent);
> -	if (IS_ERR(dentry)) {
> -		inode_unlock(dir);
> -		return dentry;
> -	}
> -	if (dentry->d_inode) {
> -		dput(dentry);
> -		inode_unlock(dir);
> -		return ERR_PTR(-EEXIST);
> -	}
> -	return dentry;
> +	err = lookup_noperm_common(&qname, parent);
> +	if (err)
> +		return ERR_PTR(err);
> +	return start_dirop(parent, &qname, LOOKUP_CREATE | LOOKUP_EXCL);
>  }
>  EXPORT_SYMBOL(simple_start_creating);
> diff --git a/fs/namei.c b/fs/namei.c
> index 39c4d52f5b54..231e1ffd4b8d 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -2765,6 +2765,48 @@ static int filename_parentat(int dfd, struct filename *name,
>  	return __filename_parentat(dfd, name, flags, parent, last, type, NULL);
>  }
>  
> +/**
> + * start_dirop - begin a create or remove dirop, performing locking and lookup
> + * @parent:       the dentry of the parent in which the operation will occur
> + * @name:         a qstr holding the name within that parent
> + * @lookup_flags: intent and other lookup flags.
> + *
> + * The lookup is performed and necessary locks are taken so that, on success,
> + * the returned dentry can be operated on safely.
> + * The qstr must already have the hash value calculated.
> + *
> + * Returns: a locked dentry, or an error.
> + *
> + */
> +struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
> +			   unsigned int lookup_flags)
> +{
> +	struct dentry *dentry;
> +	struct inode *dir = d_inode(parent);
> +
> +	inode_lock_nested(dir, I_MUTEX_PARENT);
> +	dentry = lookup_one_qstr_excl(name, parent, lookup_flags);
> +	if (IS_ERR(dentry))
> +		inode_unlock(dir);
> +	return dentry;
> +}
> +
> +/**
> + * end_dirop - signal completion of a dirop
> + * @de: the dentry which was returned by start_dirop or similar.
> + *
> + * If the de is an error, nothing happens. Otherwise any lock taken to
> + * protect the dentry is dropped and the dentry itself is release (dput()).
> + */
> +void end_dirop(struct dentry *de)
> +{
> +	if (!IS_ERR(de)) {
> +		inode_unlock(de->d_parent->d_inode);
> +		dput(de);
> +	}
> +}
> +EXPORT_SYMBOL(end_dirop);
> +
>  /* does lookup, returns the object with parent locked */
>  static struct dentry *__start_removing_path(int dfd, struct filename *name,
>  					   struct path *path)
> @@ -2781,10 +2823,9 @@ static struct dentry *__start_removing_path(int dfd, struct filename *name,
>  		return ERR_PTR(-EINVAL);
>  	/* don't fail immediately if it's r/o, at least try to report other errors */
>  	error = mnt_want_write(parent_path.mnt);
> -	inode_lock_nested(parent_path.dentry->d_inode, I_MUTEX_PARENT);
> -	d = lookup_one_qstr_excl(&last, parent_path.dentry, 0);
> +	d = start_dirop(parent_path.dentry, &last, 0);
>  	if (IS_ERR(d))
> -		goto unlock;
> +		goto drop;
>  	if (error)
>  		goto fail;
>  	path->dentry = no_free_ptr(parent_path.dentry);
> @@ -2792,10 +2833,9 @@ static struct dentry *__start_removing_path(int dfd, struct filename *name,
>  	return d;
>  
>  fail:
> -	dput(d);
> +	end_dirop(d);
>  	d = ERR_PTR(error);
> -unlock:
> -	inode_unlock(parent_path.dentry->d_inode);
> +drop:
>  	if (!error)
>  		mnt_drop_write(parent_path.mnt);
>  	return d;
> @@ -2910,7 +2950,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
>  }
>  EXPORT_SYMBOL(vfs_path_lookup);
>  
> -static int lookup_noperm_common(struct qstr *qname, struct dentry *base)
> +int lookup_noperm_common(struct qstr *qname, struct dentry *base)
>  {
>  	const char *name = qname->name;
>  	u32 len = qname->len;
> @@ -4223,21 +4263,18 @@ static struct dentry *filename_create(int dfd, struct filename *name,
>  	 */
>  	if (last.name[last.len] && !want_dir)
>  		create_flags &= ~LOOKUP_CREATE;
> -	inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
> -	dentry = lookup_one_qstr_excl(&last, path->dentry,
> -				      reval_flag | create_flags);
> +	dentry = start_dirop(path->dentry, &last, reval_flag | create_flags);
>  	if (IS_ERR(dentry))
> -		goto unlock;
> +		goto out_drop_write;
>  
>  	if (unlikely(error))
>  		goto fail;
>  
>  	return dentry;
>  fail:
> -	dput(dentry);
> +	end_dirop(dentry);
>  	dentry = ERR_PTR(error);
> -unlock:
> -	inode_unlock(path->dentry->d_inode);
> +out_drop_write:
>  	if (!error)
>  		mnt_drop_write(path->mnt);
>  out:
> @@ -4256,11 +4293,26 @@ struct dentry *start_creating_path(int dfd, const char *pathname,
>  }
>  EXPORT_SYMBOL(start_creating_path);
>  
> +/**
> + * end_creating_path - finish a code section started by start_creating_path()
> + * @path: the path instantiated by start_creating_path()
> + * @dentry: the dentry returned by start_creating_path()
> + *
> + * end_creating_path() will unlock and locks taken by start_creating_path()
> + * and drop an references that were taken.  It should only be called
> + * if start_creating_path() returned a non-error.
> + * If vfs_mkdir() was called and it returned an error, that error *should*
> + * be passed to end_creating_path() together with the path.
> + */
>  void end_creating_path(const struct path *path, struct dentry *dentry)
>  {
> -	if (!IS_ERR(dentry))
> -		dput(dentry);
> -	inode_unlock(path->dentry->d_inode);
> +	if (IS_ERR(dentry))
> +		/* The parent is still locked despite the error from
> +		 * vfs_mkdir() - must unlock it.
> +		 */
> +		inode_unlock(path->dentry->d_inode);
> +	else
> +		end_dirop(dentry);
>  	mnt_drop_write(path->mnt);
>  	path_put(path);
>  }
> @@ -4592,8 +4644,7 @@ int do_rmdir(int dfd, struct filename *name)
>  	if (error)
>  		goto exit2;
>  
> -	inode_lock_nested(path.dentry->d_inode, I_MUTEX_PARENT);
> -	dentry = lookup_one_qstr_excl(&last, path.dentry, lookup_flags);
> +	dentry = start_dirop(path.dentry, &last, lookup_flags);
>  	error = PTR_ERR(dentry);
>  	if (IS_ERR(dentry))
>  		goto exit3;
> @@ -4602,9 +4653,8 @@ int do_rmdir(int dfd, struct filename *name)
>  		goto exit4;
>  	error = vfs_rmdir(mnt_idmap(path.mnt), path.dentry->d_inode, dentry);
>  exit4:
> -	dput(dentry);
> +	end_dirop(dentry);
>  exit3:
> -	inode_unlock(path.dentry->d_inode);
>  	mnt_drop_write(path.mnt);
>  exit2:
>  	path_put(&path);
> @@ -4721,8 +4771,7 @@ int do_unlinkat(int dfd, struct filename *name)
>  	if (error)
>  		goto exit2;
>  retry_deleg:
> -	inode_lock_nested(path.dentry->d_inode, I_MUTEX_PARENT);
> -	dentry = lookup_one_qstr_excl(&last, path.dentry, lookup_flags);
> +	dentry = start_dirop(path.dentry, &last, lookup_flags);
>  	error = PTR_ERR(dentry);
>  	if (!IS_ERR(dentry)) {
>  
> @@ -4737,9 +4786,8 @@ int do_unlinkat(int dfd, struct filename *name)
>  		error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
>  				   dentry, &delegated_inode);
>  exit3:
> -		dput(dentry);
> +		end_dirop(dentry);
>  	}
> -	inode_unlock(path.dentry->d_inode);
>  	if (inode)
>  		iput(inode);	/* truncate the inode here */
>  	inode = NULL;
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 03e450dd5211..9e7556e79d19 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -3196,6 +3196,8 @@ extern void iterate_supers_type(struct file_system_type *,
>  void filesystems_freeze(void);
>  void filesystems_thaw(void);
>  
> +void end_dirop(struct dentry *de);
> +
>  extern int dcache_dir_open(struct inode *, struct file *);
>  extern int dcache_dir_close(struct inode *, struct file *);
>  extern loff_t dcache_dir_lseek(struct file *, loff_t, int);

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply

* Re: [PATCH] security: sctp: Format type and permission checks tables
From: Bagas Sanjaya @ 2025-11-12 12:07 UTC (permalink / raw)
  To: Paul Moore
  Cc: Linux Kernel Mailing List, Linux Documentation,
	Linux Security Module, Jonathan Corbet, Jarkko Sakkinen,
	Christian Brauner, Jeff Layton, Kees Cook,
	Mickaël Salaün, Stuart Yoder, Randy Dunlap
In-Reply-To: <CAHC9VhQeghqosexgOQO3==poNwsf_6mNiOqkUTUOdYnzRYzKmQ@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 594 bytes --]

On Tue, Nov 11, 2025 at 07:50:56PM -0500, Paul Moore wrote:
> On Mon, Nov 10, 2025 at 9:35 PM Bagas Sanjaya <bagasdotme@gmail.com> wrote:
> >
> > On Mon, Nov 03, 2025 at 06:39:23PM +0700, Bagas Sanjaya wrote:
> > > Use reST grid tables for both type and permission checks tables.
> >
> > review ping
> 
> You don't need to 'ping' for a review, your patch is in my review
> queue, but code changes take priority at this point in the dev cycle
> as I'm okay with merging documentation changes fairly late.

OK, thanks!

-- 
An old man doll... just what I always wanted! - Clara

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH v17] exec: Fix dead-lock in de_thread with ptrace_attach
From: Oleg Nesterov @ 2025-11-12  9:52 UTC (permalink / raw)
  To: Bernd Edlinger
  Cc: Christian Brauner, Alexander Viro, Alexey Dobriyan, Kees Cook,
	Andy Lutomirski, Will Drewry, Andrew Morton, Michal Hocko,
	Serge Hallyn, James Morris, Randy Dunlap, Suren Baghdasaryan,
	Yafang Shao, Helge Deller, Eric W. Biederman, Adrian Reber,
	Thomas Gleixner, Jens Axboe, Alexei Starovoitov,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest, linux-mm, linux-security-module, tiozhang,
	Luis Chamberlain, Paulo Alcantara (SUSE), Sergey Senozhatsky,
	Frederic Weisbecker, YueHaibing, Paul Moore, Aleksa Sarai,
	Stefan Roesch, Chao Yu, xu xin, Jeff Layton, Jan Kara,
	David Hildenbrand, Dave Chinner, Shuah Khan, Elena Reshetova,
	David Windsor, Mateusz Guzik, Ard Biesheuvel,
	Joel Fernandes (Google), Matthew Wilcox (Oracle),
	Hans Liljestrand, Penglei Jiang, Lorenzo Stoakes, Adrian Ratiu,
	Ingo Molnar, Peter Zijlstra (Intel), Cyrill Gorcunov,
	Eric Dumazet
In-Reply-To: <GV2PPF74270EBEE16FE36CF873C5C2309A9E4CFA@GV2PPF74270EBEE.EURP195.PROD.OUTLOOK.COM>

On 11/11, Bernd Edlinger wrote:
>
> On 11/11/25 14:12, Oleg Nesterov wrote:
> > On 11/11, Bernd Edlinger wrote:
> >>
> >> Well when this is absolutely not acceptable then I would have to change
> >> all security engines to be aware of the current and the new credentials.
> >
> > Hmm... even if we find another way to avoid the deadlock? Say, the patches
> > I sent...
> >
>
> Maybe, but it looks almost too simple ;-)
>
>    164          sleep(2);
>    165          /* deadlock may happen here */
>    166          k = ptrace(PTRACE_ATTACH, thread2_tid, 0L, 0L);
>
> what happens if you change the test expectation here, that the
> ptrace may fail instead of succeed?
>
> What signals does the debugger receive after that point?
> Is the debugger notified that the debugged process continues,
> has the same PID, and is no longer ptraced?

Ah, but this is another thing... OK, you dislike 3/3 and I have to agree.

Yes, de_thread() silently untraces/reaps the old leader and after 3/3 debugger
can't rely on PTRACE_EVENT_EXIT, so unless the debugger has already attached to
all sub-threads (at least to execing thread) it looks as if the leader was just
untraced somehow.

OK, this is probably too bad, we need another solution...

Oleg.


^ permalink raw reply

* Re: [PATCH 1/2] fs: add iput_not_last()
From: Christian Brauner @ 2025-11-12  9:48 UTC (permalink / raw)
  To: Mateusz Guzik
  Cc: mic, linux-security-module, linux-fsdevel, viro, eadavis, gnoack,
	jack, jannh, max.kellermann, m, syzbot+12479ae15958fc3f54ec
In-Reply-To: <CAGudoHF_9_7cEgwtX=huvSf1q-FF0gSwTn2imXHmszYoa2xPZA@mail.gmail.com>

On Tue, Nov 11, 2025 at 12:53:53PM +0100, Mateusz Guzik wrote:
> On Tue, Nov 11, 2025 at 12:46 PM Christian Brauner <brauner@kernel.org> wrote:
> >
> > On Wed, 05 Nov 2025 22:20:24 +0100, Mateusz Guzik wrote:
> > >
> >
> >
> > Applied to the vfs-6.19.inode branch of the vfs/vfs.git tree.
> > Patches in the vfs-6.19.inode branch should appear in linux-next soon.
> >
> 
> That might_sleep in iput is already in master slated for 6.18, so this
> should land in vfs.fixes instead.

Done, thanks.

^ permalink raw reply

* Re: [PATCH v1] fs: Move might_sleep() annotation to iput_final()
From: kernel test robot @ 2025-11-12  5:40 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: oe-lkp, lkp, Christian Brauner, Edward Adam Davis,
	Günther Noack, Hillf Danton, Jan Kara, Jann Horn,
	Mateusz Guzik, Max Kellermann, Tingmao Wang, linux-fsdevel,
	Mickaël Salaün, linux-security-module, Al Viro,
	syzbot+12479ae15958fc3f54ec, oliver.sang
In-Reply-To: <20251105193800.2340868-1-mic@digikod.net>



Hello,

kernel test robot noticed "BUG:sleeping_function_called_from_invalid_context_at_fs/inode.c" on:

commit: 29fb8368dfb5d1f784fd936cec578c9601d77325 ("[PATCH v1] fs: Move might_sleep() annotation to iput_final()")
url: https://github.com/intel-lab-lkp/linux/commits/Micka-l-Sala-n/fs-Move-might_sleep-annotation-to-iput_final/20251106-060704
patch link: https://lore.kernel.org/all/20251105193800.2340868-1-mic@digikod.net/
patch subject: [PATCH v1] fs: Move might_sleep() annotation to iput_final()

in testcase: boot

config: x86_64-rhel-9.4-rust
compiler: clang-20
test machine: qemu-system-x86_64 -enable-kvm -cpu SandyBridge -smp 2 -m 32G

(please refer to attached dmesg/kmsg for entire log/backtrace)


If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <oliver.sang@intel.com>
| Closes: https://lore.kernel.org/oe-lkp/202511121304.5e522f7b-lkp@intel.com


[   18.387422][    T1] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   18.389429][    T1] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 1, name: init
[   18.390340][    T1] preempt_count: 1, expected: 0
[   18.390892][    T1] RCU nest depth: 0, expected: 0
[   18.391438][    T1] CPU: 1 UID: 0 PID: 1 Comm: init Not tainted 6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   18.391441][    T1] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   18.391443][    T1] Call Trace:
[   18.392215][    T1]  <TASK>
[   18.392219][    T1]  dump_stack_lvl (lib/dump_stack.c:123)
[   18.392228][    T1]  __might_resched (kernel/sched/core.c:8838)
[   18.393064][    T1]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   18.393072][    T1]  __dentry_kill (fs/dcache.c:?)
[   18.393075][    T1]  dput (fs/dcache.c:912)
[   18.393077][    T1]  __fput (fs/file_table.c:477)
[   18.393081][    T1]  __x64_sys_close (fs/open.c:1591)
[   18.393082][    T1]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   18.393086][    T1]  ? __x64_sys_connect (net/socket.c:2131 net/socket.c:2128 net/socket.c:2128)
[   18.393098][    T1]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   18.393099][    T1]  ? kmem_cache_alloc_noprof (include/linux/kernel.h:?)
[   18.393104][    T1]  ? alloc_empty_file (fs/file_table.c:238)
[   18.393105][    T1]  ? init_file (fs/file_table.c:174)
[   18.393107][    T1]  ? file_init_path (fs/file_table.c:326)
[   18.393108][    T1]  ? alloc_file_pseudo (fs/file_table.c:?)
[   18.393110][    T1]  ? sock_alloc_file (net/socket.c:?)
[   18.393113][    T1]  ? __sys_socket (net/socket.c:?)
[   18.393115][    T1]  ? __x64_sys_socket (net/socket.c:1765 net/socket.c:1763 net/socket.c:1763)
[   18.393117][    T1]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   18.393119][    T1]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   18.393121][    T1] RIP: 0033:0x7fcfd14c3040
[   18.393126][    T1] Code: 40 75 0b 31 c0 48 83 c4 08 e9 0c ff ff ff 48 8d 3d c5 99 09 00 e8 a0 3f 02 00 83 3d 9d 71 2d 00 00 75 10 b8 03 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 31 c3 48 83 ec 08 e8 9e b1 01 00 48 89 04 24
All code
========
   0:	40 75 0b             	rex jne 0xe
   3:	31 c0                	xor    %eax,%eax
   5:	48 83 c4 08          	add    $0x8,%rsp
   9:	e9 0c ff ff ff       	jmp    0xffffffffffffff1a
   e:	48 8d 3d c5 99 09 00 	lea    0x999c5(%rip),%rdi        # 0x999da
  15:	e8 a0 3f 02 00       	call   0x23fba
  1a:	83 3d 9d 71 2d 00 00 	cmpl   $0x0,0x2d719d(%rip)        # 0x2d71be
  21:	75 10                	jne    0x33
  23:	b8 03 00 00 00       	mov    $0x3,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax		<-- trapping instruction
  30:	73 31                	jae    0x63
  32:	c3                   	ret
  33:	48 83 ec 08          	sub    $0x8,%rsp
  37:	e8 9e b1 01 00       	call   0x1b1da
  3c:	48 89 04 24          	mov    %rax,(%rsp)

Code starting with the faulting instruction
===========================================
   0:	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax
   6:	73 31                	jae    0x39
   8:	c3                   	ret
   9:	48 83 ec 08          	sub    $0x8,%rsp
   d:	e8 9e b1 01 00       	call   0x1b1b0
  12:	48 89 04 24          	mov    %rax,(%rsp)
[   18.393128][    T1] RSP: 002b:00007ffd4cbc7398 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[   18.393131][    T1] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007fcfd14c3040
[   18.393132][    T1] RDX: 00007fcfd1526f2c RSI: 0000000000000000 RDI: 0000000000000008
[   18.393133][    T1] RBP: 0000000000000008 R08: 0000000000000003 R09: 0000000000000000
[   18.393134][    T1] R10: 0000000000000000 R11: 0000000000000246 R12: 00007fcfd221b6a0
[   18.393135][    T1] R13: 00007fcfd15563cb R14: 00000000ffffffff R15: 0000000000000000
[   18.393137][    T1]  </TASK>
LKP: ttyS0: 86: skip deploy intel ucode as no ucode is specified
LKP: ttyS0: 86: Kernel tests: Boot OK!
LKP: ttyS0: 86: HOSTNAME vm-snb, MAC 52:54:00:12:34:56, kernel 6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 1
[   18.589452][  T181] udevd[181]: starting version 175
[   18.598118][  T107] is_virt=true
[   18.598124][  T107]
[   18.603531][  T107] lkp: kernel tainted state: 512
[   18.603537][  T107]
[   18.612979][  T107] LKP: stdout: 86: Kernel tests: Boot OK!
[   18.612986][  T107]
LKP: ttyS0: 86:  /lkp/lkp/src/bin/run-lkp /lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml
[   18.661802][  T107] LKP: stdout: 86: HOSTNAME vm-snb, MAC 52:54:00:12:34:56, kernel 6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 1
[   18.661809][  T107]
[   18.676637][  T107] NO_NETWORK=
[   18.676643][  T107]
[   18.680228][  T200] piix4_smbus 0000:00:01.3: SMBus Host Controller at 0x700, revision 0
[   18.690939][  T107] INFO: lkp CACHE_DIR is /tmp/cache
[   18.690945][  T107]
[   18.694448][  T200] i2c i2c-0: Memory type 0x07 not supported yet, not instantiating SPD
[   18.761028][  T107] LKP: stdout: 86:  /lkp/lkp/src/bin/run-lkp /lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml
[   18.761038][  T107]
[   18.781039][  T107] RESULT_ROOT=/result/boot/1/vm-snb/quantal-x86_64-core-20190426.cgz/x86_64-rhel-9.4-rust/clang-20/29fb8368dfb5d1f784fd936cec578c9601d77325/0
[   18.781047][  T107]
[   18.805586][  T107] job=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml
[   18.805592][  T107]
[   18.857299][  T201] libata version 3.00 loaded.
[   18.872519][  T107] result_service: raw_upload, RESULT_MNT: /internal-lkp-server/result, RESULT_ROOT: /internal-lkp-server/result/boot/1/vm-snb/quantal-x86_64-core-20190426.cgz/x86_64-rhel-9.4-rust/clang-20/29fb8368dfb5d1f784fd936cec578c9601d77325/0, TMP_RESULT_ROOT: /tmp/lkp/result
[   18.872526][  T107]
[   18.880780][  T201] scsi host0: ata_piix
[   18.899515][  T107] run-job /lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml
[   18.899520][  T107]
[   18.908385][  T201] scsi host1: ata_piix
[   18.910926][  T201] ata1: PATA max MWDMA2 cmd 0x1f0 ctl 0x3f6 bmdma 0xc140 irq 14 lpm-pol 0
[   18.911964][  T201] ata2: PATA max MWDMA2 cmd 0x170 ctl 0x376 bmdma 0xc148 irq 15 lpm-pol 0
[   18.932209][  T107] /usr/bin/wget -nv --timeout=3600 --tries=1 --local-encoding=UTF-8 http://internal-lkp-server:80/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&job_state=running -O /dev/null
[   18.932216][  T107]
[   18.974405][  T212] ACPI: bus type drm_connector registered
[   18.978482][    T1] init: failsafe main process (320) killed by TERM signal
[   19.074983][  T307] ata2: found unknown device (class 0)
[   19.076067][  T307] ata2.00: ATAPI: QEMU DVD-ROM, 2.5+, max UDMA/100
[   19.077908][   T12] scsi 1:0:0:0: CD-ROM            QEMU     QEMU DVD-ROM     2.5+ PQ: 0 ANSI: 5
[   19.099276][  T312] parport_pc 00:03: reported by Plug and Play ACPI
[   19.104906][  T312] parport0: PC-style at 0x378, irq 7 [PCSPP,TRISTATE]
[   19.190656][  T384] sr 1:0:0:0: [sr0] scsi3-mmc drive: 4x/4x cd/rw xa/form2 tray
[   19.191608][  T384] cdrom: Uniform CD-ROM driver Revision: 3.20
[   19.201172][  T212] bochs-drm 0000:00:02.0: vgaarb: deactivate vga console
[   19.206902][  T212] Console: switching to colour dummy device 80x25
[   19.207699][  T212] [drm] Found bochs VGA, ID 0xb0c5.
[   19.208107][  T212] [drm] Framebuffer size 16384 kB @ 0xfd000000, mmio @ 0xfebf0000.
[   19.209140][  T212] [drm] Initialized bochs-drm 1.0.0 for 0000:00:02.0 on minor 0
[   19.216585][  T212] fbcon: bochs-drmdrmfb (fb0) is primary device
[   19.233924][  T384] sr 1:0:0:0: Attached scsi CD-ROM sr0
[   19.236779][  T212] Console: switching to colour frame buffer device 160x50
[   19.296502][  T212] bochs-drm 0000:00:02.0: [drm] fb0: bochs-drmdrmfb frame buffer device
[   19.300750][  T391] ppdev: user-space parallel port driver
[   19.386935][   T82] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   19.387879][   T82] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 82, name: plymouthd
[   19.388607][   T82] preempt_count: 1, expected: 0
[   19.389718][   T82] RCU nest depth: 0, expected: 0
[   19.391578][   T82] CPU: 1 UID: 0 PID: 82 Comm: plymouthd Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   19.391582][   T82] Tainted: [W]=WARN
[   19.391583][   T82] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   19.391584][   T82] Call Trace:
[   19.391587][   T82]  <TASK>
[   19.391590][   T82]  dump_stack_lvl (lib/dump_stack.c:123)
[   19.391599][   T82]  __might_resched (kernel/sched/core.c:8838)
[   19.391605][   T82]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   19.391609][   T82]  __dentry_kill (fs/dcache.c:?)
[   19.391613][   T82]  dput (fs/dcache.c:912)
[   19.391615][   T82]  __fput (fs/file_table.c:477)
[   19.391625][   T82]  __x64_sys_close (fs/open.c:1591)
[   19.391627][   T82]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   19.391631][   T82]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   19.391633][   T82]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   19.391635][   T82]  ? __x64_sys_sendto (net/socket.c:2255 net/socket.c:2251 net/socket.c:2251)
[   19.391645][   T82]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   19.391647][   T82]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   19.391650][   T82] RIP: 0033:0x7ff9cf9f9040
[   19.391654][   T82] Code: 40 75 0b 31 c0 48 83 c4 08 e9 0c ff ff ff 48 8d 3d c5 99 09 00 e8 a0 3f 02 00 83 3d 9d 71 2d 00 00 75 10 b8 03 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 31 c3 48 83 ec 08 e8 9e b1 01 00 48 89 04 24
All code
========
   0:	40 75 0b             	rex jne 0xe
   3:	31 c0                	xor    %eax,%eax
   5:	48 83 c4 08          	add    $0x8,%rsp
   9:	e9 0c ff ff ff       	jmp    0xffffffffffffff1a
   e:	48 8d 3d c5 99 09 00 	lea    0x999c5(%rip),%rdi        # 0x999da
  15:	e8 a0 3f 02 00       	call   0x23fba
  1a:	83 3d 9d 71 2d 00 00 	cmpl   $0x0,0x2d719d(%rip)        # 0x2d71be
  21:	75 10                	jne    0x33
  23:	b8 03 00 00 00       	mov    $0x3,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax		<-- trapping instruction
  30:	73 31                	jae    0x63
  32:	c3                   	ret
  33:	48 83 ec 08          	sub    $0x8,%rsp
  37:	e8 9e b1 01 00       	call   0x1b1da
  3c:	48 89 04 24          	mov    %rax,(%rsp)

Code starting with the faulting instruction
===========================================
   0:	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax
   6:	73 31                	jae    0x39
   8:	c3                   	ret
   9:	48 83 ec 08          	sub    $0x8,%rsp
   d:	e8 9e b1 01 00       	call   0x1b1b0
  12:	48 89 04 24          	mov    %rax,(%rsp)
[   19.391656][   T82] RSP: 002b:00007ffd01a06c28 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[   19.391659][   T82] RAX: ffffffffffffffda RBX: 0000000034e212e0 RCX: 00007ff9cf9f9040
[   19.391661][   T82] RDX: 00007ffd01a06cbc RSI: 0000000034e212e0 RDI: 0000000000000007
[   19.391662][   T82] RBP: 0000000034e20f50 R08: 00000000118a0360 R09: 7fffffffffffffff
[   19.391664][   T82] R10: 3fffffffffffffff R11: 0000000000000246 R12: 0000000034e215a0
[   19.391668][   T82] R13: 0000000034e21570 R14: 0000000000000058 R15: 0000000000000000
[   19.391670][   T82]  </TASK>
[   19.684798][    C0] hrtimer: interrupt took 7335062 ns
[   19.697123][  T109] 2025-11-10 18:26:34 URL:http://internal-lkp-server/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&job_state=running [0/0] -> "/dev/null" [1]
[   19.773587][  T109]
[   19.827552][  T107] target ucode:
[   19.827558][  T107]
[   19.854980][  T107] check_nr_cpu
[   19.872576][  T107]
[   19.875184][  T107] CPU(s):                2
[   19.877090][  T107]
[   19.888546][  T107] On-line CPU(s) list:   0,1
[   19.888553][  T107]
[   19.922264][  T107] Thread(s) per core:    1
[   19.922272][  T107]
[   19.925376][  T107] Core(s) per socket:    2
[   19.925383][  T107]
[   19.942720][  T107] Socket(s):             1
[   19.942727][  T107]
[   19.966452][  T107] NUMA node(s):          1
[   19.966460][  T107]
[   19.981930][  T107] NUMA node0 CPU(s):     0,1
[   19.981942][  T107]
[   20.557619][  T497] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   20.558839][  T497] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 497, name: udevd
[   20.559927][  T497] preempt_count: 1, expected: 0
[   20.562617][  T497] RCU nest depth: 0, expected: 0
[   20.563969][  T497] CPU: 0 UID: 0 PID: 497 Comm: udevd Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   20.563978][  T497] Tainted: [W]=WARN
[   20.563979][  T497] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   20.563981][  T497] Call Trace:
[   20.563986][  T497]  <TASK>
[   20.563989][  T497]  dump_stack_lvl (lib/dump_stack.c:123)
[   20.563999][  T497]  __might_resched (kernel/sched/core.c:8838)
[   20.564004][  T497]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   20.564008][  T497]  __dentry_kill (fs/dcache.c:?)
[   20.564012][  T497]  shrink_dentry_list (fs/dcache.c:1114)
[   20.564015][  T497]  shrink_dcache_parent (fs/dcache.c:1550)
[   20.564019][  T497]  d_invalidate (fs/dcache.c:1660)
[   20.564022][  T497]  proc_invalidate_siblings_dcache (fs/proc/inode.c:143)
[   20.564026][  T497]  release_task (kernel/exit.c:292)
[   20.564030][  T497]  wait_consider_task (kernel/exit.c:1276)
[   20.564033][  T497]  ? do_wait (kernel/exit.c:1714)
[   20.564035][  T497]  __do_wait (kernel/exit.c:1640 kernel/exit.c:1674)
[   20.564037][  T497]  ? do_wait (kernel/exit.c:1714)
[   20.564039][  T497]  do_wait (kernel/exit.c:1716)
[   20.564041][  T497]  kernel_wait4 (kernel/exit.c:1874)
[   20.564043][  T497]  ? get_task_struct (kernel/exit.c:1599)
[   20.564045][  T497]  __x64_sys_wait4 (kernel/exit.c:1902 kernel/exit.c:1898 kernel/exit.c:1898)
[   20.564048][  T497]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   20.564052][  T497]  ? vfs_read (fs/read_write.c:492)
[   20.564055][  T497]  ? __x64_sys_read (fs/read_write.c:?)
[   20.564056][  T497]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   20.564058][  T497]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   20.564061][  T497]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   20.564064][  T497] RIP: 0033:0x7f0bbd443c3e
[   20.564068][  T497] Code: 00 f7 d8 64 89 02 48 89 f8 eb cc 90 48 83 ec 28 8b 05 aa e5 2f 00 85 c0 75 1d 45 31 d2 48 63 d2 48 63 ff b8 3d 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 51 48 83 c4 28 c3 89 54 24 08 48 89 74 24 10
All code
========
   0:	00 f7                	add    %dh,%bh
   2:	d8 64 89 02          	fsubs  0x2(%rcx,%rcx,4)
   6:	48 89 f8             	mov    %rdi,%rax
   9:	eb cc                	jmp    0xffffffffffffffd7
   b:	90                   	nop
   c:	48 83 ec 28          	sub    $0x28,%rsp
  10:	8b 05 aa e5 2f 00    	mov    0x2fe5aa(%rip),%eax        # 0x2fe5c0
  16:	85 c0                	test   %eax,%eax
  18:	75 1d                	jne    0x37
  1a:	45 31 d2             	xor    %r10d,%r10d
  1d:	48 63 d2             	movslq %edx,%rdx
  20:	48 63 ff             	movslq %edi,%rdi
  23:	b8 3d 00 00 00       	mov    $0x3d,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax		<-- trapping instruction
  30:	77 51                	ja     0x83
  32:	48 83 c4 28          	add    $0x28,%rsp
  36:	c3                   	ret
  37:	89 54 24 08          	mov    %edx,0x8(%rsp)
  3b:	48 89 74 24 10       	mov    %rsi,0x10(%rsp)

Code starting with the faulting instruction
===========================================
   0:	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax
   6:	77 51                	ja     0x59
   8:	48 83 c4 28          	add    $0x28,%rsp
   c:	c3                   	ret
   d:	89 54 24 08          	mov    %edx,0x8(%rsp)
  11:	48 89 74 24 10       	mov    %rsi,0x10(%rsp)
[   20.564071][  T497] RSP: 002b:00007fff65aecee0 EFLAGS: 00000246 ORIG_RAX: 000000000000003d
[   20.564074][  T497] RAX: ffffffffffffffda RBX: 0000556a4924f6d0 RCX: 00007f0bbd443c3e
[   20.564076][  T497] RDX: 0000000000000001 RSI: 00007fff65aedfe4 RDI: 00000000000001f2
[   20.564077][  T497] RBP: 0000000000000000 R08: 000000001bb5db01 R09: 7fffffffffffffff
[   20.564079][  T497] R10: 0000000000000000 R11: 0000000000000246 R12: 00007fff65af3948
[   20.564080][  T497] R13: 00000000000001f2 R14: 0000556a49215250 R15: 00000000000003e8
[   20.564083][  T497]  </TASK>
[   20.689253][    T1] init: networking main process (512) terminated with status 1
[   21.005572][  T107] sleep started
[   21.005581][  T107]
[   22.006385][  T210] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   22.007457][  T210] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 210, name: run-lkp
[   22.008482][  T210] preempt_count: 1, expected: 0
[   22.009261][  T210] RCU nest depth: 0, expected: 0
[   22.009992][  T210] CPU: 1 UID: 0 PID: 210 Comm: run-lkp Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   22.009996][  T210] Tainted: [W]=WARN
[   22.009997][  T210] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   22.009999][  T210] Call Trace:
[   22.010003][  T210]  <TASK>
[   22.010005][  T210]  dump_stack_lvl (lib/dump_stack.c:123)
[   22.010015][  T210]  __might_resched (kernel/sched/core.c:8838)
[   22.010021][  T210]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   22.010026][  T210]  __dentry_kill (fs/dcache.c:?)
[   22.010030][  T210]  shrink_dentry_list (fs/dcache.c:1114)
[   22.010034][  T210]  shrink_dcache_parent (fs/dcache.c:1550)
[   22.010037][  T210]  d_invalidate (fs/dcache.c:1660)
[   22.010040][  T210]  proc_invalidate_siblings_dcache (fs/proc/inode.c:143)
[   22.010046][  T210]  release_task (kernel/exit.c:292)
[   22.010051][  T210]  wait_consider_task (kernel/exit.c:1276)
[   22.010057][  T210]  __do_wait (kernel/exit.c:1565 kernel/exit.c:1681)
[   22.010059][  T210]  ? do_wait (kernel/exit.c:1714)
[   22.010061][  T210]  do_wait (kernel/exit.c:1716)
[   22.010063][  T210]  kernel_wait4 (kernel/exit.c:1874)
[   22.010066][  T210]  ? get_task_struct (kernel/exit.c:1599)
[   22.010069][  T210]  __x64_sys_wait4 (kernel/exit.c:1902 kernel/exit.c:1898 kernel/exit.c:1898)
[   22.010071][  T210]  ? count_memcg_events (mm/memcontrol.c:? mm/memcontrol.c:847)
[   22.010073][  T210]  ? handle_mm_fault (mm/memory.c:6423)
[   22.010078][  T210]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   22.010082][  T210]  ? do_user_addr_fault (arch/x86/mm/fault.c:1337)
[   22.010086][  T210]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   22.010089][  T210]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   22.010093][  T210] RIP: 0033:0x7fe001e18c3e
[   22.010097][  T210] Code: 00 f7 d8 64 89 02 48 89 f8 eb cc 90 48 83 ec 28 8b 05 aa e5 2f 00 85 c0 75 1d 45 31 d2 48 63 d2 48 63 ff b8 3d 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 51 48 83 c4 28 c3 89 54 24 08 48 89 74 24 10
All code
========
   0:	00 f7                	add    %dh,%bh
   2:	d8 64 89 02          	fsubs  0x2(%rcx,%rcx,4)
   6:	48 89 f8             	mov    %rdi,%rax
   9:	eb cc                	jmp    0xffffffffffffffd7
   b:	90                   	nop
   c:	48 83 ec 28          	sub    $0x28,%rsp
  10:	8b 05 aa e5 2f 00    	mov    0x2fe5aa(%rip),%eax        # 0x2fe5c0
  16:	85 c0                	test   %eax,%eax
  18:	75 1d                	jne    0x37
  1a:	45 31 d2             	xor    %r10d,%r10d
  1d:	48 63 d2             	movslq %edx,%rdx
  20:	48 63 ff             	movslq %edi,%rdi
  23:	b8 3d 00 00 00       	mov    $0x3d,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax		<-- trapping instruction
  30:	77 51                	ja     0x83
  32:	48 83 c4 28          	add    $0x28,%rsp
  36:	c3                   	ret
  37:	89 54 24 08          	mov    %edx,0x8(%rsp)
  3b:	48 89 74 24 10       	mov    %rsi,0x10(%rsp)

Code starting with the faulting instruction
===========================================
   0:	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax
   6:	77 51                	ja     0x59
   8:	48 83 c4 28          	add    $0x28,%rsp
   c:	c3                   	ret
   d:	89 54 24 08          	mov    %edx,0x8(%rsp)
  11:	48 89 74 24 10       	mov    %rsi,0x10(%rsp)
[   22.010099][  T210] RSP: 002b:00007ffc8644ef50 EFLAGS: 00000246 ORIG_RAX: 000000000000003d
[   22.010102][  T210] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00007fe001e18c3e
[   22.010104][  T210] RDX: 0000000000000000 RSI: 00007ffc8644efb8 RDI: ffffffffffffffff
[   22.010105][  T210] RBP: 000000003db55300 R08: 000000003db553c8 R09: 0000000000000001
[   22.010107][  T210] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
[   22.010108][  T210] R13: 0000000000000000 R14: 0000000000000000 R15: 00000000ffffffff
[   22.010110][  T210]  </TASK>
[   22.147235][  T107] /usr/bin/wget -nv --timeout=3600 --tries=1 --local-encoding=UTF-8 http://internal-lkp-server:80/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&job_state=post_run -O /dev/null
[   22.147244][  T107]
[   22.859171][  T109] 2025-11-10 18:26:38 URL:http://internal-lkp-server/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&job_state=post_run [0/0] -> "/dev/null" [1]
[   22.859180][  T109]
[   23.872028][  T539] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   23.873181][  T539] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 539, name: post-run
[   23.875374][  T539] preempt_count: 1, expected: 0
[   23.876396][  T539] RCU nest depth: 0, expected: 0
[   23.877455][  T539] CPU: 1 UID: 0 PID: 539 Comm: post-run Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   23.877459][  T539] Tainted: [W]=WARN
[   23.877460][  T539] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   23.877461][  T539] Call Trace:
[   23.877465][  T539]  <TASK>
[   23.877467][  T539]  dump_stack_lvl (lib/dump_stack.c:123)
[   23.877475][  T539]  __might_resched (kernel/sched/core.c:8838)
[   23.877480][  T539]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   23.877484][  T539]  __dentry_kill (fs/dcache.c:?)
[   23.877488][  T539]  dput (fs/dcache.c:912)
[   23.877490][  T539]  __fput (fs/file_table.c:477)
[   23.877494][  T539]  __x64_sys_close (fs/open.c:1591)
[   23.877496][  T539]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   23.877499][  T539]  ? handle_mm_fault (mm/memory.c:6423)
[   23.877504][  T539]  ? do_user_addr_fault (arch/x86/mm/fault.c:1337)
[   23.877508][  T539]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   23.877510][  T539]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   23.877513][  T539]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   23.877516][  T539] RIP: 0033:0x7f1031cde040
[   23.877519][  T539] Code: 40 75 0b 31 c0 48 83 c4 08 e9 0c ff ff ff 48 8d 3d c5 99 09 00 e8 a0 3f 02 00 83 3d 9d 71 2d 00 00 75 10 b8 03 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 31 c3 48 83 ec 08 e8 9e b1 01 00 48 89 04 24
All code
========
   0:	40 75 0b             	rex jne 0xe
   3:	31 c0                	xor    %eax,%eax
   5:	48 83 c4 08          	add    $0x8,%rsp
   9:	e9 0c ff ff ff       	jmp    0xffffffffffffff1a
   e:	48 8d 3d c5 99 09 00 	lea    0x999c5(%rip),%rdi        # 0x999da
  15:	e8 a0 3f 02 00       	call   0x23fba
  1a:	83 3d 9d 71 2d 00 00 	cmpl   $0x0,0x2d719d(%rip)        # 0x2d71be
  21:	75 10                	jne    0x33
  23:	b8 03 00 00 00       	mov    $0x3,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax		<-- trapping instruction
  30:	73 31                	jae    0x63
  32:	c3                   	ret
  33:	48 83 ec 08          	sub    $0x8,%rsp
  37:	e8 9e b1 01 00       	call   0x1b1da
  3c:	48 89 04 24          	mov    %rax,(%rsp)

Code starting with the faulting instruction
===========================================
   0:	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax
   6:	73 31                	jae    0x39
   8:	c3                   	ret
   9:	48 83 ec 08          	sub    $0x8,%rsp
   d:	e8 9e b1 01 00       	call   0x1b1b0
  12:	48 89 04 24          	mov    %rax,(%rsp)
[   23.877521][  T539] RSP: 002b:00007fff879e0888 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[   23.877524][  T539] RAX: ffffffffffffffda RBX: 0000000000000004 RCX: 00007f1031cde040
[   23.877525][  T539] RDX: 0000000000000001 RSI: 0000000000000002 RDI: 0000000000000003
[   23.877526][  T539] RBP: 0000000000000000 R08: 000000000000000a R09: 0000000000000000
[   23.877527][  T539] R10: 0000000000000000 R11: 0000000000000246 R12: 00007fff879e08e4
[   23.877528][  T539] R13: 0000000000000080 R14: 000000000000000a R15: 000000001dc63a08
[   23.877530][  T539]  </TASK>
[   23.937654][  T107] kill 429 vmstat -n 10
[   23.937662][  T107]
[   23.944123][  T107] kill 425 cat /proc/kmsg
[   23.944131][  T107]
[   23.962000][  T107] wait for background processes: 435 432 oom-killer meminfo
[   23.962008][  T107]
[   24.972654][  T561] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   24.973813][  T561] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 561, name: post-run
[   24.974943][  T561] preempt_count: 1, expected: 0
[   24.975752][  T561] RCU nest depth: 0, expected: 0
[   24.976454][  T561] CPU: 1 UID: 0 PID: 561 Comm: post-run Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   24.976459][  T561] Tainted: [W]=WARN
[   24.976459][  T561] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   24.976461][  T561] Call Trace:
[   24.976464][  T561]  <TASK>
[   24.976466][  T561]  dump_stack_lvl (lib/dump_stack.c:123)
[   24.976476][  T561]  __might_resched (kernel/sched/core.c:8838)
[   24.976485][  T561]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   24.976489][  T561]  __dentry_kill (fs/dcache.c:?)
[   24.976492][  T561]  shrink_dentry_list (fs/dcache.c:1114)
[   24.976495][  T561]  shrink_dcache_parent (fs/dcache.c:1550)
[   24.976498][  T561]  d_invalidate (fs/dcache.c:1660)
[   24.976502][  T561]  proc_invalidate_siblings_dcache (fs/proc/inode.c:143)
[   24.976507][  T561]  release_task (kernel/exit.c:292)
[   24.976510][  T561]  wait_consider_task (kernel/exit.c:1276)
[   24.976513][  T561]  __do_wait (kernel/exit.c:1565 kernel/exit.c:1681)
[   24.976515][  T561]  ? do_wait (kernel/exit.c:1714)
[   24.976517][  T561]  do_wait (kernel/exit.c:1716)
[   24.976519][  T561]  kernel_wait4 (kernel/exit.c:1874)
[   24.976522][  T561]  ? get_task_struct (kernel/exit.c:1599)
[   24.976524][  T561]  __x64_sys_wait4 (kernel/exit.c:1902 kernel/exit.c:1898 kernel/exit.c:1898)
[   24.976526][  T561]  ? _copy_to_user (arch/x86/include/asm/uaccess_64.h:126 arch/x86/include/asm/uaccess_64.h:134 arch/x86/include/asm/uaccess_64.h:147 include/linux/uaccess.h:204 lib/usercopy.c:26)
[   24.976530][  T561]  ? __x64_sys_rt_sigaction (include/linux/uaccess.h:232 kernel/signal.c:4648 kernel/signal.c:4629 kernel/signal.c:4629)
[   24.976535][  T561]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   24.976538][  T561]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   24.976539][  T561]  ? _copy_from_user (arch/x86/include/asm/uaccess_64.h:126 arch/x86/include/asm/uaccess_64.h:141 include/linux/uaccess.h:185 lib/usercopy.c:18)
[   24.976541][  T561]  ? __x64_sys_rt_sigprocmask (kernel/signal.c:3340)
[   24.976544][  T561]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   24.976546][  T561]  ? do_user_addr_fault (arch/x86/mm/fault.c:1337)
[   24.976549][  T561]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   24.976553][  T561]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   24.976556][  T561] RIP: 0033:0x7f1031cb6c3e
[   24.976560][  T561] Code: 00 f7 d8 64 89 02 48 89 f8 eb cc 90 48 83 ec 28 8b 05 aa e5 2f 00 85 c0 75 1d 45 31 d2 48 63 d2 48 63 ff b8 3d 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 51 48 83 c4 28 c3 89 54 24 08 48 89 74 24 10
All code
========
   0:	00 f7                	add    %dh,%bh
   2:	d8 64 89 02          	fsubs  0x2(%rcx,%rcx,4)
   6:	48 89 f8             	mov    %rdi,%rax
   9:	eb cc                	jmp    0xffffffffffffffd7
   b:	90                   	nop
   c:	48 83 ec 28          	sub    $0x28,%rsp
  10:	8b 05 aa e5 2f 00    	mov    0x2fe5aa(%rip),%eax        # 0x2fe5c0
  16:	85 c0                	test   %eax,%eax
  18:	75 1d                	jne    0x37
  1a:	45 31 d2             	xor    %r10d,%r10d
  1d:	48 63 d2             	movslq %edx,%rdx
  20:	48 63 ff             	movslq %edi,%rdi
  23:	b8 3d 00 00 00       	mov    $0x3d,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax		<-- trapping instruction
  30:	77 51                	ja     0x83
  32:	48 83 c4 28          	add    $0x28,%rsp
  36:	c3                   	ret
  37:	89 54 24 08          	mov    %edx,0x8(%rsp)
  3b:	48 89 74 24 10       	mov    %rsi,0x10(%rsp)

Code starting with the faulting instruction
===========================================
   0:	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax
   6:	77 51                	ja     0x59
   8:	48 83 c4 28          	add    $0x28,%rsp
   c:	c3                   	ret
   d:	89 54 24 08          	mov    %edx,0x8(%rsp)
  11:	48 89 74 24 10       	mov    %rsi,0x10(%rsp)
[   24.976562][  T561] RSP: 002b:00007fff879dfb10 EFLAGS: 00000246 ORIG_RAX: 000000000000003d
[   24.976564][  T561] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f1031cb6c3e
[   24.976566][  T561] RDX: 0000000000000000 RSI: 00007fff879dfb78 RDI: ffffffffffffffff
[   24.976567][  T561] RBP: 000000001dc65f00 R08: 000000001dc65f48 R09: 0000000000000000
[   24.976568][  T561] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
[   24.976570][  T561] R13: 0000000000000000 R14: 0000000000000000 R15: 00000000ffffffff
[   24.976572][  T561]  </TASK>
[   26.014722][  T539] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   26.015763][  T539] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 539, name: post-run
[   26.016818][  T539] preempt_count: 1, expected: 0
[   26.017515][  T539] RCU nest depth: 0, expected: 0
[   26.018261][  T539] CPU: 0 UID: 0 PID: 539 Comm: post-run Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   26.018266][  T539] Tainted: [W]=WARN
[   26.018266][  T539] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   26.018268][  T539] Call Trace:
[   26.018271][  T539]  <TASK>
[   26.018274][  T539]  dump_stack_lvl (lib/dump_stack.c:123)
[   26.018284][  T539]  __might_resched (kernel/sched/core.c:8838)
[   26.018289][  T539]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   26.018293][  T539]  __dentry_kill (fs/dcache.c:?)
[   26.018296][  T539]  dput (fs/dcache.c:912)
[   26.018299][  T539]  __fput (fs/file_table.c:477)
[   26.018302][  T539]  __x64_sys_close (fs/open.c:1591)
[   26.018305][  T539]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   26.018309][  T539]  ? mutex_lock (arch/x86/include/asm/current.h:25 kernel/locking/mutex.c:152 kernel/locking/mutex.c:273)
[   26.018311][  T539]  ? anon_pipe_read (fs/pipe.c:404)
[   26.018313][  T539]  ? arch_exit_to_user_mode_prepare (arch/x86/include/asm/entry-common.h:?)
[   26.018317][  T539]  ? vfs_read (fs/read_write.c:492)
[   26.018319][  T539]  ? __x64_sys_read (fs/read_write.c:?)
[   26.018321][  T539]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   26.018323][  T539]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   26.018324][  T539]  ? do_user_addr_fault (arch/x86/mm/fault.c:1337)
[   26.018328][  T539]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   26.018332][  T539]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   26.018334][  T539] RIP: 0033:0x7f1031cde040
[   26.018338][  T539] Code: 40 75 0b 31 c0 48 83 c4 08 e9 0c ff ff ff 48 8d 3d c5 99 09 00 e8 a0 3f 02 00 83 3d 9d 71 2d 00 00 75 10 b8 03 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 31 c3 48 83 ec 08 e8 9e b1 01 00 48 89 04 24
All code
========
   0:	40 75 0b             	rex jne 0xe
   3:	31 c0                	xor    %eax,%eax
   5:	48 83 c4 08          	add    $0x8,%rsp
   9:	e9 0c ff ff ff       	jmp    0xffffffffffffff1a
   e:	48 8d 3d c5 99 09 00 	lea    0x999c5(%rip),%rdi        # 0x999da
  15:	e8 a0 3f 02 00       	call   0x23fba
  1a:	83 3d 9d 71 2d 00 00 	cmpl   $0x0,0x2d719d(%rip)        # 0x2d71be
  21:	75 10                	jne    0x33
  23:	b8 03 00 00 00       	mov    $0x3,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax		<-- trapping instruction
  30:	73 31                	jae    0x63
  32:	c3                   	ret
  33:	48 83 ec 08          	sub    $0x8,%rsp
  37:	e8 9e b1 01 00       	call   0x1b1da
  3c:	48 89 04 24          	mov    %rax,(%rsp)

Code starting with the faulting instruction
===========================================
   0:	48 3d 01 f0 ff ff    	cmp    $0xfffffffffffff001,%rax
   6:	73 31                	jae    0x39
   8:	c3                   	ret
   9:	48 83 ec 08          	sub    $0x8,%rsp
   d:	e8 9e b1 01 00       	call   0x1b1b0
  12:	48 89 04 24          	mov    %rax,(%rsp)
[   26.018340][  T539] RSP: 002b:00007fff879e0888 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[   26.018343][  T539] RAX: ffffffffffffffda RBX: 0000000000000004 RCX: 00007f1031cde040
[   26.018345][  T539] RDX: 0000000000000001 RSI: 0000000000000002 RDI: 0000000000000003
[   26.018346][  T539] RBP: 0000000000000000 R08: 000000000000000a R09: 0000000000000000
[   26.018347][  T539] R10: 0000000000000000 R11: 0000000000000246 R12: 00007fff879e08e4
[   26.018348][  T539] R13: 0000000000000080 R14: 000000000000000a R15: 000000001dc65a08
[   26.018350][  T539]  </TASK>
[   27.079314][  T577] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   27.080408][  T577] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 577, name: grep
[   27.081456][  T577] preempt_count: 1, expected: 0
[   27.082233][  T577] RCU nest depth: 0, expected: 0
[   27.082945][  T577] CPU: 0 UID: 0 PID: 577 Comm: grep Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   27.082949][  T577] Tainted: [W]=WARN
[   27.082950][  T577] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   27.082951][  T577] Call Trace:
[   27.082954][  T577]  <TASK>
[   27.082957][  T577]  dump_stack_lvl (lib/dump_stack.c:123)
[   27.082966][  T577]  __might_resched (kernel/sched/core.c:8838)
[   27.082972][  T577]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   27.082979][  T577]  __dentry_kill (fs/dcache.c:?)
[   27.082983][  T577]  dput (fs/dcache.c:912)
[   27.082986][  T577]  __fput (fs/file_table.c:477)
[   27.082989][  T577]  task_work_run (kernel/task_work.c:235)
[   27.082994][  T577]  do_exit (kernel/exit.c:971)
[   27.082997][  T577]  do_group_exit (kernel/exit.c:1111)
[   27.083000][  T577]  __x64_sys_exit_group (kernel/exit.c:1122)
[   27.083001][  T577]  x64_sys_call (??:?)
[   27.083004][  T577]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   27.083008][  T577]  ? __x64_sys_close (fs/open.c:1591)
[   27.083010][  T577]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   27.083011][  T577]  ? do_user_addr_fault (arch/x86/mm/fault.c:1337)
[   27.083015][  T577]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   27.083018][  T577]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   27.083021][  T577] RIP: 0033:0x7f5f03de3408
[   27.083024][  T577] Code: Unable to access opcode bytes at 0x7f5f03de33de.

Code starting with the faulting instruction
===========================================
[   27.083025][  T577] RSP: 002b:00007ffd05f705b8 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
[   27.083028][  T577] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f5f03de3408
[   27.083029][  T577] RDX: 0000000000000000 RSI: 000000000000003c RDI: 0000000000000000
[   27.083031][  T577] RBP: 00007f5f040d7820 R08: 00000000000000e7 R09: ffffffffffffffa0
[   27.083032][  T577] R10: 00007f5f040ddb80 R11: 0000000000000246 R12: 00007f5f040d7820
[   27.083033][  T577] R13: 0000000000000001 R14: 000000002f100438 R15: 000000000000000a
[   27.083036][  T577]  </TASK>
[   28.126767][  T582] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   28.127828][  T582] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 582, name: grep
[   28.128876][  T582] preempt_count: 1, expected: 0
[   28.129633][  T582] RCU nest depth: 0, expected: 0
[   28.130368][  T582] CPU: 1 UID: 0 PID: 582 Comm: grep Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   28.130373][  T582] Tainted: [W]=WARN
[   28.130374][  T582] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   28.130375][  T582] Call Trace:
[   28.130379][  T582]  <TASK>
[   28.130381][  T582]  dump_stack_lvl (lib/dump_stack.c:123)
[   28.130391][  T582]  __might_resched (kernel/sched/core.c:8838)
[   28.130396][  T582]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   28.130400][  T582]  __dentry_kill (fs/dcache.c:?)
[   28.130404][  T582]  dput (fs/dcache.c:912)
[   28.130407][  T582]  __fput (fs/file_table.c:477)
[   28.130410][  T582]  task_work_run (kernel/task_work.c:235)
[   28.130414][  T582]  do_exit (kernel/exit.c:971)
[   28.130418][  T582]  ? get_page_from_freelist (mm/page_alloc.c:?)
[   28.130421][  T582]  do_group_exit (kernel/exit.c:1111)
[   28.130423][  T582]  __x64_sys_exit_group (kernel/exit.c:1122)
[   28.130425][  T582]  x64_sys_call (??:?)
[   28.130428][  T582]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   28.130431][  T582]  ? __x64_sys_close (fs/open.c:1591)
[   28.130433][  T582]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   28.130435][  T582]  ? __x64_sys_close (fs/open.c:1591)
[   28.130436][  T582]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   28.130438][  T582]  ? alloc_pages_mpol (mm/mempolicy.c:2481)
[   28.130443][  T582]  ? update_curr (kernel/sched/fair.c:1224)
[   28.130445][  T582]  ? place_entity (kernel/sched/fair.c:?)
[   28.130447][  T582]  ? kvm_sched_clock_read (arch/x86/kernel/kvmclock.c:91)
[   28.130450][  T582]  ? __smp_call_single_queue (kernel/smp.c:117)
[   28.130454][  T582]  ? native_smp_send_reschedule (arch/x86/kernel/apic/ipi.c:78)
[   28.130457][  T582]  ? ttwu_queue_wakelist (kernel/sched/core.c:? kernel/sched/core.c:3880)
[   28.130459][  T582]  ? try_to_wake_up (kernel/sched/core.c:4224)
[   28.130460][  T582]  ? tick_setup_sched_timer (kernel/time/tick-sched.c:307)
[   28.130464][  T582]  ? swake_up_one (include/linux/list.h:226 include/linux/list.h:295 kernel/sched/swait.c:31 kernel/sched/swait.c:53)
[   28.130467][  T582]  ? kvm_sched_clock_read (arch/x86/kernel/kvmclock.c:91)
[   28.130469][  T582]  ? sched_clock (arch/x86/include/asm/preempt.h:95 arch/x86/kernel/tsc.c:289)
[   28.130477][  T582]  ? sched_clock_cpu (kernel/sched/clock.c:397)
[   28.130478][  T582]  ? irqtime_account_irq (kernel/sched/cputime.c:67)
[   28.130481][  T582]  ? handle_softirqs (arch/x86/include/asm/preempt.h:27 kernel/softirq.c:407 kernel/softirq.c:468 kernel/softirq.c:654)
[   28.130483][  T582]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   28.130486][  T582]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   28.130488][  T582] RIP: 0033:0x7fe98a926408
[   28.130491][  T582] Code: Unable to access opcode bytes at 0x7fe98a9263de.

Code starting with the faulting instruction
===========================================
[   28.130493][  T582] RSP: 002b:00007ffc82246da8 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
[   28.130496][  T582] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007fe98a926408
[   28.130497][  T582] RDX: 0000000000000000 RSI: 000000000000003c RDI: 0000000000000000
[   28.130498][  T582] RBP: 00007fe98ac1a820 R08: 00000000000000e7 R09: ffffffffffffffa0
[   28.130499][  T582] R10: 00007fe98ac20b80 R11: 0000000000000246 R12: 00007fe98ac1a820
[   28.130501][  T582] R13: 0000000000000001 R14: 0000000012c71014 R15: 000000000000000a
[   28.130503][  T582]  </TASK>
[   28.839638][  T107] /usr/bin/wget -nv --timeout=3600 --tries=1 --local-encoding=UTF-8 http://internal-lkp-server:80/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&loadavg=0.78%200.19%200.06%201/108%20594&start_time=1762799196&end_time=1762799197&version=/lkp/lkp/.src-20251109-171750:1aad5493ad31-dirty:35b842bfeaee-dirty& -O /dev/null
[   28.839648][  T107]
[   29.442824][  T614] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   29.443836][  T614] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 614, name: wget
[   29.444824][  T614] preempt_count: 1, expected: 0
[   29.445535][  T614] RCU nest depth: 0, expected: 0
[   29.446242][  T614] CPU: 1 UID: 0 PID: 614 Comm: wget Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   29.446246][  T614] Tainted: [W]=WARN
[   29.446247][  T614] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   29.446249][  T614] Call Trace:
[   29.446252][  T614]  <TASK>
[   29.446255][  T614]  dump_stack_lvl (lib/dump_stack.c:123)
[   29.446265][  T614]  __might_resched (kernel/sched/core.c:8838)
[   29.446270][  T614]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   29.446274][  T614]  __dentry_kill (fs/dcache.c:?)
[   29.446278][  T614]  dput (fs/dcache.c:912)
[   29.446280][  T614]  __fput (fs/file_table.c:477)
[   29.446283][  T614]  task_work_run (kernel/task_work.c:235)
[   29.446287][  T614]  do_exit (kernel/exit.c:971)
[   29.446290][  T614]  ? __lruvec_stat_mod_folio (include/linux/rcupdate.h:899 mm/memcontrol.c:798)
[   29.446292][  T614]  do_group_exit (kernel/exit.c:1111)
[   29.446294][  T614]  __x64_sys_exit_group (kernel/exit.c:1122)
[   29.446296][  T614]  x64_sys_call (??:?)
[   29.446298][  T614]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   29.446300][  T614]  ? filemap_map_pages (mm/filemap.c:3935)
[   29.446307][  T614]  ? count_memcg_events (mm/memcontrol.c:? mm/memcontrol.c:847)
[   29.446308][  T614]  ? handle_mm_fault (mm/memory.c:6423)
[   29.446317][  T614]  ? do_user_addr_fault (arch/x86/mm/fault.c:1337)
[   29.446321][  T614]  ? __x64_sys_close (fs/open.c:1591)
[   29.446323][  T614]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   29.446326][  T614]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   29.446329][  T614] RIP: 0033:0x7f69b74d2408
[   29.446331][  T614] Code: Unable to access opcode bytes at 0x7f69b74d23de.

Code starting with the faulting instruction
===========================================
[   29.446332][  T614] RSP: 002b:00007ffcd55f1d78 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
[   29.446335][  T614] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f69b74d2408
[   29.446337][  T614] RDX: 0000000000000000 RSI: 000000000000003c RDI: 0000000000000000
[   29.446338][  T614] RBP: 00007f69b77c6820 R08: 00000000000000e7 R09: ffffffffffffffa0
[   29.446339][  T614] R10: 00007f69b77cdfa8 R11: 0000000000000246 R12: 00007f69b77c6820
[   29.446340][  T614] R13: 0000000000000001 R14: 0000000000000000 R15: 0000000000000000
[   29.446343][  T614]  </TASK>
[   29.519252][  T107] /usr/bin/wget -nv --timeout=3600 --tries=1 --local-encoding=UTF-8 http://internal-lkp-server:80/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&job_state=finished -O /dev/null
[   29.519260][  T107]
[   29.555346][  T109] 2025-11-10 18:26:44 URL:http://internal-lkp-server/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&loadavg=0.78%200.19%200.06%201/108%20594&start_time=1762799196&end_time=1762799197&version=/lkp/lkp/.src-20251109-171750:1aad5493ad31-dirty:35b842bfeaee-dirty& [0/0] -> "/dev/null" [1]
[   29.555355][  T109]
[   30.276210][  T109] 2025-11-10 18:26:45 URL:http://internal-lkp-server/~lkp/cgi-bin/lkp-jobfile-append-var?job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml&job_state=finished [0/0] -> "/dev/null" [1]
LKP: ttyS0: 86: LKP: rebooting forcely
[   30.276218][  T109]
[   30.285962][  T107] LKP: stdout: 86: LKP: rebooting forcely
[   30.285968][  T107]
[   30.324585][  T107] /usr/bin/wget -nv --timeout=3600 --tries=1 --local-encoding=UTF-8 http://internal-lkp-server:80/~lkp/cgi-bin/lkp-wtmp?tbox_name=vm-snb&tbox_state=rebooting&job_file=/lkp/jobs/scheduled/vm-meta-119/boot-1-quantal-x86_64-core-20190426.cgz-29fb8368dfb5-20251110-11844-dgg7hi-0.yaml -O /dev/null
[   30.324593][  T107]
[   31.152096][  T641] BUG: sleeping function called from invalid context at fs/inode.c:1920
[   31.153357][  T641] in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 641, name: wget
[   31.154471][  T641] preempt_count: 1, expected: 0
[   31.155280][  T641] RCU nest depth: 0, expected: 0
[   31.156362][  T641] CPU: 1 UID: 0 PID: 641 Comm: wget Tainted: G        W           6.18.0-rc4-next-20251105-00001-g29fb8368dfb5 #1 PREEMPT(voluntary)
[   31.156367][  T641] Tainted: [W]=WARN
[   31.156367][  T641] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   31.156369][  T641] Call Trace:
[   31.156392][  T641]  <TASK>
[   31.156395][  T641]  dump_stack_lvl (lib/dump_stack.c:123)
[   31.156408][  T641]  __might_resched (kernel/sched/core.c:8838)
[   31.156415][  T641]  iput (include/linux/kernel.h:61 fs/inode.c:1920 fs/inode.c:2010)
[   31.156420][  T641]  __dentry_kill (fs/dcache.c:?)
[   31.156423][  T641]  dput (fs/dcache.c:912)
[   31.156429][  T641]  __fput (fs/file_table.c:477)
[   31.156432][  T641]  task_work_run (kernel/task_work.c:235)
[   31.156436][  T641]  do_exit (kernel/exit.c:971)
[   31.156440][  T641]  do_group_exit (kernel/exit.c:1111)
[   31.156442][  T641]  __x64_sys_exit_group (kernel/exit.c:1122)
[   31.156444][  T641]  x64_sys_call (??:?)
[   31.156447][  T641]  do_syscall_64 (arch/x86/entry/syscall_64.c:?)
[   31.156451][  T641]  ? count_memcg_events (mm/memcontrol.c:? mm/memcontrol.c:847)
[   31.156453][  T641]  ? handle_mm_fault (mm/memory.c:6423)
[   31.156458][  T641]  ? do_user_addr_fault (arch/x86/mm/fault.c:1337)
[   31.156462][  T641]  ? do_syscall_64 (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/unwind_deferred.h:37 include/linux/irq-entry-common.h:300 include/linux/entry-common.h:196 arch/x86/entry/syscall_64.c:100)
[   31.156463][  T641]  ? irqentry_exit (include/linux/rseq_entry.h:576 include/linux/irq-entry-common.h:271 include/linux/irq-entry-common.h:339 kernel/entry/common.c:196)
[   31.156466][  T641]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
[   31.156468][  T641] RIP: 0033:0x7f07d32bf408
[   31.156471][  T641] Code: Unable to access opcode bytes at 0x7f07d32bf3de.

Code starting with the faulting instruction
===========================================
[   31.156472][  T641] RSP: 002b:00007ffc164c7eb8 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
[   31.156475][  T641] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f07d32bf408
[   31.156477][  T641] RDX: 0000000000000000 RSI: 000000000000003c RDI: 0000000000000000
[   31.156478][  T641] RBP: 00007f07d35b3820 R08: 00000000000000e7 R09: ffffffffffffffa0
[   31.156479][  T641] R10: 00007f07d35bafa8 R11: 0000000000000246 R12: 00007f07d35b3820
[   31.156480][  T641] R13: 0000000000000001 R14: 0000000000000000 R15: 0000000000000000
[   31.156482][  T641]  </TASK>



The kernel config and materials to reproduce are available at:
https://download.01.org/0day-ci/archive/20251112/202511121304.5e522f7b-lkp@intel.com



-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply


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