Message ID | 20241009040316.GY4017910@ZenIV (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | [RFC] getname_maybe_null() - the third variant of pathname copy-in | expand |
On Wed, Oct 09, 2024 at 05:03:16AM +0100, Al Viro wrote: > [ > in #work.getname; if nobody objects, I'm going to make #work.xattr pull that. > IMO it's saner than hacks around vfs_empty_path() and it does very similar > logics - with simpler handling on the caller side. > ] > > 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_FLAGS 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> > --- Looks good. Fyi, I'm using your #base.getname as a base for some other work that I'm currently doing. So please don't rebase #base.getname anymore. ;) Since you have your #work.xattr and #work.stat using it as base it seems pretty unlikely anyway but I just thought I mention explicitly that I'm relying on that #base.getname branch.
On Tue, Oct 15, 2024 at 04:05:15PM +0200, Christian Brauner wrote: > Looks good. > > Fyi, I'm using your #base.getname as a base for some other work that I'm > currently doing. So please don't rebase #base.getname anymore. ;) > > Since you have your #work.xattr and #work.stat using it as base it seems > pretty unlikely anyway but I just thought I mention explicitly that I'm > relying on that #base.getname branch. FWIW, I see a problem with that sucker. The trouble is in the combination AT_FDCWD, "", AT_EMPTY_PATH. vfs_empty_path() returns false on that, so fstatat() in mainline falls back to vfs_statx() and does the right thing. This variant does _not_. Note that your variant of xattr series also ended up failing on e.g. getxattrat() with such combination: if (at_flags & AT_EMPTY_PATH && vfs_empty_path(dfd, pathname)) { CLASS(fd, f)(dfd); if (!f.file) return -EBADF; audit_file(f.file); return getxattr(file_mnt_idmap(f.file), file_dentry(f.file), name, value, size); } lookup_flags = (at_flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW; retry: error = user_path_at(dfd, pathname, lookup_flags, &path); ended up calling user_path_at() with empty pathname and nothing like LOOKUP_EMPTY in lookup_flags. Which bails out with -ENOENT, since getname() in there does so. My variant bails out with -EBADF and I'd argue that neither is correct. Not sure what's the sane solution here, need to think for a while...
On Wed, Oct 16, 2024 at 06:09:08AM +0100, Al Viro wrote: > On Tue, Oct 15, 2024 at 04:05:15PM +0200, Christian Brauner wrote: > > Looks good. > > > > Fyi, I'm using your #base.getname as a base for some other work that I'm > > currently doing. So please don't rebase #base.getname anymore. ;) > > > > Since you have your #work.xattr and #work.stat using it as base it seems > > pretty unlikely anyway but I just thought I mention explicitly that I'm > > relying on that #base.getname branch. > > FWIW, I see a problem with that sucker. The trouble is in the > combination AT_FDCWD, "", AT_EMPTY_PATH. vfs_empty_path() returns Yeah, we're aware. > false on that, so fstatat() in mainline falls back to vfs_statx() and > does the right thing. This variant does _not_. > > Note that your variant of xattr series also ended up failing on e.g. > getxattrat() with such combination: > if (at_flags & AT_EMPTY_PATH && vfs_empty_path(dfd, pathname)) { > CLASS(fd, f)(dfd); > if (!f.file) > return -EBADF; > audit_file(f.file); > return getxattr(file_mnt_idmap(f.file), file_dentry(f.file), > name, value, size); > } > > lookup_flags = (at_flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW; > > retry: > error = user_path_at(dfd, pathname, lookup_flags, &path); > > ended up calling user_path_at() with empty pathname and nothing like LOOKUP_EMPTY > in lookup_flags. Which bails out with -ENOENT, since getname() in there does > so. My variant bails out with -EBADF and I'd argue that neither is correct. > > Not sure what's the sane solution here, need to think for a while... Fwiw, in the other thread we concluded to just not care about AT_FDCWD with "". And so far I agree with that.
On Wed, Oct 16, 2024 at 10:32:16AM +0200, Christian Brauner wrote: > > ended up calling user_path_at() with empty pathname and nothing like LOOKUP_EMPTY > > in lookup_flags. Which bails out with -ENOENT, since getname() in there does > > so. My variant bails out with -EBADF and I'd argue that neither is correct. > > > > Not sure what's the sane solution here, need to think for a while... > > Fwiw, in the other thread we concluded to just not care about AT_FDCWD with "". > And so far I agree with that. Subject:?
On Wed, Oct 16, 2024 at 03:00:50PM +0100, Al Viro wrote: > On Wed, Oct 16, 2024 at 10:32:16AM +0200, Christian Brauner wrote: > > > > ended up calling user_path_at() with empty pathname and nothing like LOOKUP_EMPTY > > > in lookup_flags. Which bails out with -ENOENT, since getname() in there does > > > so. My variant bails out with -EBADF and I'd argue that neither is correct. > > > > > > Not sure what's the sane solution here, need to think for a while... > > > > Fwiw, in the other thread we concluded to just not care about AT_FDCWD with "". > > And so far I agree with that. > > Subject:? https://lore.kernel.org/r/CAGudoHHdccL5Lh8zAO-0swqqRCW4GXMSXhq4jQGoVj=UdBK-Lg@mail.gmail.com Hm, this only speaks about the NULL case. I just looked through codesearch on github and on debian and the only example I found was https://sources.debian.org/src/snapd/2.65.3-1/tests/main/seccomp-statx/test-snapd-statx/bin/statx.py/?hl=71#L71 So really, just special-case it for statx() imho instead of spreading that ugliness everywhere?
On Wed, Oct 16, 2024 at 04:49:48PM +0200, Christian Brauner wrote: > On Wed, Oct 16, 2024 at 03:00:50PM +0100, Al Viro wrote: > > On Wed, Oct 16, 2024 at 10:32:16AM +0200, Christian Brauner wrote: > > > > > > ended up calling user_path_at() with empty pathname and nothing like LOOKUP_EMPTY > > > > in lookup_flags. Which bails out with -ENOENT, since getname() in there does > > > > so. My variant bails out with -EBADF and I'd argue that neither is correct. > > > > > > > > Not sure what's the sane solution here, need to think for a while... > > > > > > Fwiw, in the other thread we concluded to just not care about AT_FDCWD with "". > > > And so far I agree with that. > > > > Subject:? > > https://lore.kernel.org/r/CAGudoHHdccL5Lh8zAO-0swqqRCW4GXMSXhq4jQGoVj=UdBK-Lg@mail.gmail.com > > Hm, this only speaks about the NULL case. > > > I just looked through codesearch on github and on debian and the only > example I found was > https://sources.debian.org/src/snapd/2.65.3-1/tests/main/seccomp-statx/test-snapd-statx/bin/statx.py/?hl=71#L71 > > So really, just special-case it for statx() imho instead of spreading > that ugliness everywhere? Not sure, TBH. I wonder if it would be simpler to have filename_lookup() accept NULL for pathname and have it treat that as "". Look: all it takes is the following trick * add const char *pathname to struct nameidata * in __set_nameidata() add p->pathname = likely(name) ? name->name : ""; * in path_init() replace const char *s = nd->name->name; with const char *s = nd->pathname; and we are done. Oh, and teach putname() to treat NULL as no-op. With such changes in fs/namei.c we could do struct filename *name = getname_maybe_null(filename, flags); if (!name && dfd != AT_FDCWD) return do_statx_fd(dfd, flags & ~AT_NO_AUTOMOUNT, mask, buffer); ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS); putname(name); return ret; in statx(2) and similar in vfs_fstatat(). With that I'm not even sure we want to bother with separate vfs_statx_fd() - the overhead might very well turn out to be tolerable. It is non-zero, but that's a fairly straight trip through filename_lookup() machinery. Would require some profiling, though...
--- a/fs/namei.c +++ b/fs/namei.c @@ -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; diff --git a/fs/stat.c b/fs/stat.c index 41e598376d7e..aa5bfc41a669 100644 --- 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) 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); @@ -775,7 +768,7 @@ SYSCALL_DEFINE5(statx, { int ret; unsigned lflags; - struct filename *name; + struct filename *name = getname_maybe_null(filename, flags); /* * Short-circuit handling of NULL and "" paths. @@ -788,10 +781,9 @@ SYSCALL_DEFINE5(statx, * 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) 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); diff --git a/include/linux/fs.h b/include/linux/fs.h index e3c603d01337..403258ac2ea2 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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,
[ in #work.getname; if nobody objects, I'm going to make #work.xattr pull that. IMO it's saner than hacks around vfs_empty_path() and it does very similar logics - with simpler handling on the caller side. ] 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_FLAGS 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> --- diff --git a/fs/namei.c b/fs/namei.c index 4a4a22a08ac2..27eb0a81d9b8 100644