]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
getname_maybe_null() - the third variant of pathname copy-in
authorAl Viro <viro@zeniv.linux.org.uk>
Tue, 8 Oct 2024 03:08:34 +0000 (23:08 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Sun, 20 Oct 2024 00:33:34 +0000 (20:33 -0400)
Semantics used by statx(2) (and later *xattrat(2)): without AT_EMPTY_PATH
it's standard getname() (i.e. ERR_PTR(-ENOENT) on empty string,
ERR_PTR(-EFAULT) on NULL), with AT_EMPTY_PATH both empty string and
NULL are accepted.

Calling conventions: getname_maybe_null(user_pointer, flags) returns
* pointer to struct filename when non-empty string had been
successfully read
* ERR_PTR(...) on error
* NULL if an empty string or NULL pointer had been given
with AT_EMPTY_PATH in the flags argument.

It tries to avoid allocation in the last case; it's not always
able to do so, in which case the temporary struct filename instance
is freed and NULL returned anyway.

Fast path is inlined.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namei.c
fs/stat.c
include/linux/fs.h

index aaf3cd6c802f5a20ff935a577aa9091c4189290a..2bfe476c3bd043f092a852111bab0c1f8924a432 100644 (file)
@@ -211,22 +211,38 @@ getname_flags(const char __user *filename, int flags)
        return result;
 }
 
-struct filename *
-getname_uflags(const char __user *filename, int uflags)
+struct filename *getname_uflags(const char __user *filename, int uflags)
 {
        int flags = (uflags & AT_EMPTY_PATH) ? LOOKUP_EMPTY : 0;
 
        return getname_flags(filename, flags);
 }
 
-struct filename *
-getname(const char __user * filename)
+struct filename *getname(const char __user * filename)
 {
        return getname_flags(filename, 0);
 }
 
-struct filename *
-getname_kernel(const char * filename)
+struct filename *__getname_maybe_null(const char __user *pathname)
+{
+       struct filename *name;
+       char c;
+
+       /* try to save on allocations; loss on um, though */
+       if (get_user(c, pathname))
+               return ERR_PTR(-EFAULT);
+       if (!c)
+               return NULL;
+
+       name = getname_flags(pathname, LOOKUP_EMPTY);
+       if (!IS_ERR(name) && !(name->name[0])) {
+               putname(name);
+               name = NULL;
+       }
+       return name;
+}
+
+struct filename *getname_kernel(const char * filename)
 {
        struct filename *result;
        int len = strlen(filename) + 1;
@@ -264,7 +280,7 @@ EXPORT_SYMBOL(getname_kernel);
 
 void putname(struct filename *name)
 {
-       if (IS_ERR(name))
+       if (IS_ERR_OR_NULL(name))
                return;
 
        if (WARN_ON_ONCE(!atomic_read(&name->refcnt)))
index 41e598376d7e3d1a43641d0dd5ad467154e34364..b74831dc7ae635efaf55f83d621b306995ae7e0d 100644 (file)
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -326,18 +326,11 @@ int vfs_fstatat(int dfd, const char __user *filename,
 {
        int ret;
        int statx_flags = flags | AT_NO_AUTOMOUNT;
-       struct filename *name;
+       struct filename *name = getname_maybe_null(filename, flags);
 
-       /*
-        * Work around glibc turning fstat() into fstatat(AT_EMPTY_PATH)
-        *
-        * If AT_EMPTY_PATH is set, we expect the common case to be that
-        * empty path, and avoid doing all the extra pathname work.
-        */
-       if (flags == AT_EMPTY_PATH && vfs_empty_path(dfd, filename))
+       if (!name && dfd >= 0)
                return vfs_fstat(dfd, stat);
 
-       name = getname_flags(filename, getname_statx_lookup_flags(statx_flags));
        ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS);
        putname(name);
 
@@ -774,24 +767,11 @@ SYSCALL_DEFINE5(statx,
                struct statx __user *, buffer)
 {
        int ret;
-       unsigned lflags;
-       struct filename *name;
+       struct filename *name = getname_maybe_null(filename, flags);
 
-       /*
-        * Short-circuit handling of NULL and "" paths.
-        *
-        * For a NULL path we require and accept only the AT_EMPTY_PATH flag
-        * (possibly |'d with AT_STATX flags).
-        *
-        * However, glibc on 32-bit architectures implements fstatat as statx
-        * with the "" pathname and AT_NO_AUTOMOUNT | AT_EMPTY_PATH flags.
-        * Supporting this results in the uglification below.
-        */
-       lflags = flags & ~(AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE);
-       if (lflags == AT_EMPTY_PATH && vfs_empty_path(dfd, filename))
+       if (!name && dfd >= 0)
                return do_statx_fd(dfd, flags & ~AT_NO_AUTOMOUNT, mask, buffer);
 
-       name = getname_flags(filename, getname_statx_lookup_flags(flags));
        ret = do_statx(dfd, name, flags, mask, buffer);
        putname(name);
 
index e3c603d01337650d562405500013f5c4cfed8eb6..403258ac2ea2d7da91989b7896d61cae87137f38 100644 (file)
@@ -2766,6 +2766,16 @@ extern struct filename *getname_flags(const char __user *, int);
 extern struct filename *getname_uflags(const char __user *, int);
 extern struct filename *getname(const char __user *);
 extern struct filename *getname_kernel(const char *);
+extern struct filename *__getname_maybe_null(const char __user *);
+static inline struct filename *getname_maybe_null(const char __user *name, int flags)
+{
+       if (!(flags & AT_EMPTY_PATH))
+               return getname(name);
+
+       if (!name)
+               return NULL;
+       return __getname_maybe_null(name);
+}
 extern void putname(struct filename *name);
 
 extern int finish_open(struct file *file, struct dentry *dentry,