LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH v3 0/4] namei: fix use-after-free and adjust calling conventions
@ 2021-09-08  0:04 Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 1/4] namei: Fix use after free in kern_path_locked Stephen Brennan
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Stephen Brennan @ 2021-09-08  0:04 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-fsdevel, linux-kernel, Stephen Brennan

Currently filename_parentat() is inherently buggy, and results in a
use after free bug. But removing it and renaming __filename_parentat()
destroys its symmetry with the other functions:

   __filename_create   filename_create()
   __filename_lookup   filename_lookup()

The ones with leading double-underscores do not free their struct
filename arguments, while the ones without double-underscores always do.

I looked at the callers of filename_create and filename_lookup. All are
small functions which are trivial to modify to include a putname(). It
seems to me that adding a few more lines to these functions is a good
traedoff for better clarity on lifetimes (as it's uncommon for functions
to drop references to their parameters) and better consistency.

This small series fixes the UAF and makes it so all three families of
functions have just one variation, which does *not* free its pathname
arguments.

v3: Split patch 1 into a fix and a rename
v2: Fix double getname in user_path_create()

Stephen Brennan (4):
  namei: Fix use after free in kern_path_locked
  namei: Rename __filename_parentat()
  namei: Standardize callers of filename_lookup()
  namei: Standardize callers of filename_create()

 fs/fs_parser.c |   1 -
 fs/namei.c     | 125 ++++++++++++++++++++++++++-----------------------
 2 files changed, 67 insertions(+), 59 deletions(-)

-- 
2.30.2


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/4] namei: Fix use after free in kern_path_locked
  2021-09-08  0:04 [PATCH v3 0/4] namei: fix use-after-free and adjust calling conventions Stephen Brennan
@ 2021-09-08  0:04 ` Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 2/4] namei: Rename __filename_parentat() Stephen Brennan
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Stephen Brennan @ 2021-09-08  0:04 UTC (permalink / raw)
  To: Alexander Viro, Jens Axboe, Dmitry Kadashev
  Cc: Stephen Brennan, linux-fsdevel, linux-kernel

In 0ee50b47532a ("namei: change filename_parentat() calling
conventions"), filename_parentat() was made to always call putname() on
the  filename before returning, and kern_path_locked() was migrated to
this calling convention. However, kern_path_locked() uses the "last"
parameter to lookup and potentially create a new dentry. The last
parameter contains the last component of the path and points within the
filename, which was recently freed at the end of filename_parentat().
Thus, when kern_path_locked() calls __lookup_hash(), it is using the
filename after it has already been freed.

Switch to using __filename_parentat() to avoid freeing the filename, and
wrap the function with a getname and putname instead. Remove
filename_parentat() as it is now unused, and it is inherently broken.

Fixes: 0ee50b47532a ("namei: change filename_parentat() calling conventions")
Link: https://lore.kernel.org/linux-fsdevel/YTfVE7IbbTV71Own@zeniv-ca.linux.org.uk/
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Reported-by: syzbot+fb0d60a179096e8c2731@syzkaller.appspotmail.com
Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 fs/namei.c | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index d049d3972695..d6340fabaab4 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2538,25 +2538,15 @@ static int __filename_parentat(int dfd, struct filename *name,
 	return retval;
 }
 
-static int filename_parentat(int dfd, struct filename *name,
-				unsigned int flags, struct path *parent,
-				struct qstr *last, int *type)
-{
-	int retval = __filename_parentat(dfd, name, flags, parent, last, type);
-
-	putname(name);
-	return retval;
-}
-
 /* does lookup, returns the object with parent locked */
-struct dentry *kern_path_locked(const char *name, struct path *path)
+static struct dentry *__kern_path_locked(struct filename *name,
+					 struct path *path)
 {
 	struct dentry *d;
 	struct qstr last;
 	int type, error;
 
-	error = filename_parentat(AT_FDCWD, getname_kernel(name), 0, path,
-				    &last, &type);
+	error = __filename_parentat(AT_FDCWD, name, 0, path, &last, &type);
 	if (error)
 		return ERR_PTR(error);
 	if (unlikely(type != LAST_NORM)) {
@@ -2572,6 +2562,15 @@ struct dentry *kern_path_locked(const char *name, struct path *path)
 	return d;
 }
 
+struct dentry *kern_path_locked(const char *name, struct path *path)
+{
+	struct filename *filename = getname_kernel(name);
+	struct dentry *res = __kern_path_locked(filename, path);
+
+	putname(filename);
+	return res;
+}
+
 int kern_path(const char *name, unsigned int flags, struct path *path)
 {
 	return filename_lookup(AT_FDCWD, getname_kernel(name),
-- 
2.30.2


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 2/4] namei: Rename __filename_parentat()
  2021-09-08  0:04 [PATCH v3 0/4] namei: fix use-after-free and adjust calling conventions Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 1/4] namei: Fix use after free in kern_path_locked Stephen Brennan
@ 2021-09-08  0:04 ` Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 3/4] namei: Standardize callers of filename_lookup() Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 4/4] namei: Standardize callers of filename_create() Stephen Brennan
  3 siblings, 0 replies; 5+ messages in thread
From: Stephen Brennan @ 2021-09-08  0:04 UTC (permalink / raw)
  To: Alexander Viro; +Cc: Stephen Brennan, linux-fsdevel, linux-kernel

Now that filename_parentat() is gone, rename __filename_parentat() to
the original name.

Link: https://lore.kernel.org/linux-fsdevel/YS9D4AlEsaCxLFV0@infradead.org/
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
Co-authored-by: Dmitry Kadashev <dkadashev@gmail.com>
---
 fs/namei.c | 23 ++++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index d6340fabaab4..33f60d1b3a87 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2514,9 +2514,10 @@ static int path_parentat(struct nameidata *nd, unsigned flags,
 	return err;
 }
 
-static int __filename_parentat(int dfd, struct filename *name,
-				unsigned int flags, struct path *parent,
-				struct qstr *last, int *type)
+/* Note: this does not consume "name" */
+static int filename_parentat(int dfd, struct filename *name,
+			     unsigned int flags, struct path *parent,
+			     struct qstr *last, int *type)
 {
 	int retval;
 	struct nameidata nd;
@@ -2546,7 +2547,7 @@ static struct dentry *__kern_path_locked(struct filename *name,
 	struct qstr last;
 	int type, error;
 
-	error = __filename_parentat(AT_FDCWD, name, 0, path, &last, &type);
+	error = filename_parentat(AT_FDCWD, name, 0, path, &last, &type);
 	if (error)
 		return ERR_PTR(error);
 	if (unlikely(type != LAST_NORM)) {
@@ -3633,7 +3634,7 @@ static struct dentry *__filename_create(int dfd, struct filename *name,
 	 */
 	lookup_flags &= LOOKUP_REVAL;
 
-	error = __filename_parentat(dfd, name, lookup_flags, path, &last, &type);
+	error = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
 	if (error)
 		return ERR_PTR(error);
 
@@ -3995,7 +3996,7 @@ int do_rmdir(int dfd, struct filename *name)
 	int type;
 	unsigned int lookup_flags = 0;
 retry:
-	error = __filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
+	error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
 	if (error)
 		goto exit1;
 
@@ -4134,7 +4135,7 @@ int do_unlinkat(int dfd, struct filename *name)
 	struct inode *delegated_inode = NULL;
 	unsigned int lookup_flags = 0;
 retry:
-	error = __filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
+	error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
 	if (error)
 		goto exit1;
 
@@ -4682,13 +4683,13 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
 		target_flags = 0;
 
 retry:
-	error = __filename_parentat(olddfd, from, lookup_flags, &old_path,
-					&old_last, &old_type);
+	error = filename_parentat(olddfd, from, lookup_flags, &old_path,
+				  &old_last, &old_type);
 	if (error)
 		goto put_names;
 
-	error = __filename_parentat(newdfd, to, lookup_flags, &new_path, &new_last,
-				&new_type);
+	error = filename_parentat(newdfd, to, lookup_flags, &new_path, &new_last,
+				  &new_type);
 	if (error)
 		goto exit1;
 
-- 
2.30.2


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 3/4] namei: Standardize callers of filename_lookup()
  2021-09-08  0:04 [PATCH v3 0/4] namei: fix use-after-free and adjust calling conventions Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 1/4] namei: Fix use after free in kern_path_locked Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 2/4] namei: Rename __filename_parentat() Stephen Brennan
@ 2021-09-08  0:04 ` Stephen Brennan
  2021-09-08  0:04 ` [PATCH v3 4/4] namei: Standardize callers of filename_create() Stephen Brennan
  3 siblings, 0 replies; 5+ messages in thread
From: Stephen Brennan @ 2021-09-08  0:04 UTC (permalink / raw)
  To: Alexander Viro; +Cc: Stephen Brennan, linux-fsdevel, linux-kernel

filename_lookup() has two variants, one which drops the caller's
reference to filename (filename_lookup), and one which does
not (__filename_lookup). This can be confusing as it's unusual to drop a
caller's reference. Remove filename_lookup, rename __filename_lookup
to filename_lookup, and convert all callers. The cost is a few slightly
longer functions, but the clarity is greater.

Link: https://lore.kernel.org/linux-fsdevel/YS+dstZ3xfcLxhoB@zeniv-ca.linux.org.uk/
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 fs/fs_parser.c |  1 -
 fs/namei.c     | 41 ++++++++++++++++++++++++-----------------
 2 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/fs/fs_parser.c b/fs/fs_parser.c
index 980d44fd3a36..3df07c0e32b3 100644
--- a/fs/fs_parser.c
+++ b/fs/fs_parser.c
@@ -165,7 +165,6 @@ int fs_lookup_param(struct fs_context *fc,
 		return invalf(fc, "%s: not usable as path", param->key);
 	}
 
-	f->refcnt++; /* filename_lookup() drops our ref. */
 	ret = filename_lookup(param->dirfd, f, flags, _path, NULL);
 	if (ret < 0) {
 		errorf(fc, "%s: Lookup failure for '%s'", param->key, f->name);
diff --git a/fs/namei.c b/fs/namei.c
index 33f60d1b3a87..7bd3d1c90e52 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2467,7 +2467,7 @@ static int path_lookupat(struct nameidata *nd, unsigned flags, struct path *path
 	return err;
 }
 
-static int __filename_lookup(int dfd, struct filename *name, unsigned flags,
+int filename_lookup(int dfd, struct filename *name, unsigned flags,
 		    struct path *path, struct path *root)
 {
 	int retval;
@@ -2488,15 +2488,6 @@ static int __filename_lookup(int dfd, struct filename *name, unsigned flags,
 	return retval;
 }
 
-int filename_lookup(int dfd, struct filename *name, unsigned flags,
-		    struct path *path, struct path *root)
-{
-	int retval = __filename_lookup(dfd, name, flags, path, root);
-
-	putname(name);
-	return retval;
-}
-
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
 static int path_parentat(struct nameidata *nd, unsigned flags,
 				struct path *parent)
@@ -2574,8 +2565,14 @@ struct dentry *kern_path_locked(const char *name, struct path *path)
 
 int kern_path(const char *name, unsigned int flags, struct path *path)
 {
-	return filename_lookup(AT_FDCWD, getname_kernel(name),
-			       flags, path, NULL);
+	struct filename *filename;
+	int ret;
+
+	filename = getname_kernel(name);
+	ret = filename_lookup(AT_FDCWD, filename, flags, path, NULL);
+	putname(filename);
+	return ret;
+
 }
 EXPORT_SYMBOL(kern_path);
 
@@ -2591,10 +2588,15 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 		    const char *name, unsigned int flags,
 		    struct path *path)
 {
+	struct filename *filename;
 	struct path root = {.mnt = mnt, .dentry = dentry};
+	int ret;
+
+	filename = getname_kernel(name);
 	/* the first argument of filename_lookup() is ignored with root */
-	return filename_lookup(AT_FDCWD, getname_kernel(name),
-			       flags , path, &root);
+	ret = filename_lookup(AT_FDCWD, filename, flags, path, &root);
+	putname(filename);
+	return ret;
 }
 EXPORT_SYMBOL(vfs_path_lookup);
 
@@ -2798,8 +2800,13 @@ int path_pts(struct path *path)
 int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
 		 struct path *path, int *empty)
 {
-	return filename_lookup(dfd, getname_flags(name, flags, empty),
-			       flags, path, NULL);
+	struct filename *filename;
+	int ret;
+
+	filename = getname_flags(name, flags, empty);
+	ret = filename_lookup(dfd, filename, flags, path, NULL);
+	putname(filename);
+	return ret;
 }
 EXPORT_SYMBOL(user_path_at_empty);
 
@@ -4424,7 +4431,7 @@ int do_linkat(int olddfd, struct filename *old, int newdfd,
 	if (flags & AT_SYMLINK_FOLLOW)
 		how |= LOOKUP_FOLLOW;
 retry:
-	error = __filename_lookup(olddfd, old, how, &old_path, NULL);
+	error = filename_lookup(olddfd, old, how, &old_path, NULL);
 	if (error)
 		goto out_putnames;
 
-- 
2.30.2


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 4/4] namei: Standardize callers of filename_create()
  2021-09-08  0:04 [PATCH v3 0/4] namei: fix use-after-free and adjust calling conventions Stephen Brennan
                   ` (2 preceding siblings ...)
  2021-09-08  0:04 ` [PATCH v3 3/4] namei: Standardize callers of filename_lookup() Stephen Brennan
@ 2021-09-08  0:04 ` Stephen Brennan
  3 siblings, 0 replies; 5+ messages in thread
From: Stephen Brennan @ 2021-09-08  0:04 UTC (permalink / raw)
  To: Alexander Viro; +Cc: Stephen Brennan, linux-fsdevel, linux-kernel

filename_create() has two variants, one which drops the caller's
reference to filename (filename_create) and one which does
not (__filename_create). This can be confusing as it's unusual to drop a
caller's reference. Remove filename_create, rename __filename_create
to filename_create, and convert all callers.

Link: https://lore.kernel.org/linux-fsdevel/f6238254-35bd-7e97-5b27-21050c745874@oracle.com/
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 fs/namei.c | 38 ++++++++++++++++++++------------------
 1 file changed, 20 insertions(+), 18 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 7bd3d1c90e52..88cdc379255c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3625,8 +3625,8 @@ struct file *do_file_open_root(const struct path *root,
 	return file;
 }
 
-static struct dentry *__filename_create(int dfd, struct filename *name,
-				struct path *path, unsigned int lookup_flags)
+static struct dentry *filename_create(int dfd, struct filename *name,
+				      struct path *path, unsigned int lookup_flags)
 {
 	struct dentry *dentry = ERR_PTR(-EEXIST);
 	struct qstr last;
@@ -3694,20 +3694,16 @@ static struct dentry *__filename_create(int dfd, struct filename *name,
 	return dentry;
 }
 
-static inline struct dentry *filename_create(int dfd, struct filename *name,
-				struct path *path, unsigned int lookup_flags)
-{
-	struct dentry *res = __filename_create(dfd, name, path, lookup_flags);
-
-	putname(name);
-	return res;
-}
-
 struct dentry *kern_path_create(int dfd, const char *pathname,
 				struct path *path, unsigned int lookup_flags)
 {
-	return filename_create(dfd, getname_kernel(pathname),
-				path, lookup_flags);
+	struct filename *filename;
+	struct dentry *dentry;
+
+	filename = getname_kernel(pathname);
+	dentry = filename_create(dfd, filename, path, lookup_flags);
+	putname(filename);
+	return dentry;
 }
 EXPORT_SYMBOL(kern_path_create);
 
@@ -3723,7 +3719,13 @@ EXPORT_SYMBOL(done_path_create);
 inline struct dentry *user_path_create(int dfd, const char __user *pathname,
 				struct path *path, unsigned int lookup_flags)
 {
-	return filename_create(dfd, getname(pathname), path, lookup_flags);
+	struct filename *filename;
+	struct dentry *dentry;
+
+	filename = getname(pathname);
+	dentry = filename_create(dfd, filename, path, lookup_flags);
+	putname(filename);
+	return dentry;
 }
 EXPORT_SYMBOL(user_path_create);
 
@@ -3804,7 +3806,7 @@ static int do_mknodat(int dfd, struct filename *name, umode_t mode,
 	if (error)
 		goto out1;
 retry:
-	dentry = __filename_create(dfd, name, &path, lookup_flags);
+	dentry = filename_create(dfd, name, &path, lookup_flags);
 	error = PTR_ERR(dentry);
 	if (IS_ERR(dentry))
 		goto out1;
@@ -3904,7 +3906,7 @@ int do_mkdirat(int dfd, struct filename *name, umode_t mode)
 	unsigned int lookup_flags = LOOKUP_DIRECTORY;
 
 retry:
-	dentry = __filename_create(dfd, name, &path, lookup_flags);
+	dentry = filename_create(dfd, name, &path, lookup_flags);
 	error = PTR_ERR(dentry);
 	if (IS_ERR(dentry))
 		goto out_putname;
@@ -4271,7 +4273,7 @@ int do_symlinkat(struct filename *from, int newdfd, struct filename *to)
 		goto out_putnames;
 	}
 retry:
-	dentry = __filename_create(newdfd, to, &path, lookup_flags);
+	dentry = filename_create(newdfd, to, &path, lookup_flags);
 	error = PTR_ERR(dentry);
 	if (IS_ERR(dentry))
 		goto out_putnames;
@@ -4435,7 +4437,7 @@ int do_linkat(int olddfd, struct filename *old, int newdfd,
 	if (error)
 		goto out_putnames;
 
-	new_dentry = __filename_create(newdfd, new, &new_path,
+	new_dentry = filename_create(newdfd, new, &new_path,
 					(how & LOOKUP_REVAL));
 	error = PTR_ERR(new_dentry);
 	if (IS_ERR(new_dentry))
-- 
2.30.2


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-09-08  0:04 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-08  0:04 [PATCH v3 0/4] namei: fix use-after-free and adjust calling conventions Stephen Brennan
2021-09-08  0:04 ` [PATCH v3 1/4] namei: Fix use after free in kern_path_locked Stephen Brennan
2021-09-08  0:04 ` [PATCH v3 2/4] namei: Rename __filename_parentat() Stephen Brennan
2021-09-08  0:04 ` [PATCH v3 3/4] namei: Standardize callers of filename_lookup() Stephen Brennan
2021-09-08  0:04 ` [PATCH v3 4/4] namei: Standardize callers of filename_create() Stephen Brennan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).