2021-09-08 00:06:12

by Stephen Brennan

[permalink] [raw]
Subject: [PATCH v3 0/4] namei: fix use-after-free and adjust calling conventions

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


2021-09-08 00:09:30

by Stephen Brennan

[permalink] [raw]
Subject: [PATCH v3 2/4] namei: Rename __filename_parentat()

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

Link: https://lore.kernel.org/linux-fsdevel/[email protected]/
Cc: Christoph Hellwig <[email protected]>
Cc: Al Viro <[email protected]>
Signed-off-by: Stephen Brennan <[email protected]>
Co-authored-by: Dmitry Kadashev <[email protected]>
---
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

2021-09-08 00:09:34

by Stephen Brennan

[permalink] [raw]
Subject: [PATCH v3 4/4] namei: Standardize callers of filename_create()

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/[email protected]/
Cc: Christoph Hellwig <[email protected]>
Cc: Al Viro <[email protected]>
Signed-off-by: Stephen Brennan <[email protected]>
---
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