summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2025-11-01 20:29:06 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2026-01-13 15:18:07 -0500
commit9fa3ec84587c5eca7580eafc27eee332bc3a5a0e (patch)
tree0978f6966129e0d79a123e8bb5f9a81c27c4783a /fs
parenta9900a27dfe58e638690a6c3e8d477daa548d192 (diff)
allow incomplete imports of filenames
There are two filename-related problems in io_uring and its interplay with audit. Filenames are imported when request is submitted and used when it is processed. Unfortunately, the latter may very well happen in a different thread. In that case the reference to filename is put into the wrong audit_context - that of submitting thread, not the processing one. Audit logics is called by the latter, and it really wants to be able to find the names in audit_context current (== processing) thread. Another related problem is the headache with refcounts - normally all references to given struct filename are visible only to one thread (the one that uses that struct filename). io_uring violates that - an extra reference is stashed in audit_context of submitter. It gets dropped when submitter returns to userland, which can happen simultaneously with processing thread deciding to drop the reference it got. We paper over that by making refcount atomic, but that means pointless headache for everyone. Solution: the notion of partially imported filenames. Namely, already copied from userland, but *not* exposed to audit yet. io_uring can create that in submitter thread, and complete the import (obtaining the usual reference to struct filename) in processing thread. Object: struct delayed_filename. Primitives for working with it: delayed_getname(&delayed_filename, user_string) - copies the name from userland, returning 0 and stashing the address of (still incomplete) struct filename in delayed_filename on success and returning -E... on error. delayed_getname_uflags(&delayed_filename, user_string, atflags) - similar, in the same relation to delayed_getname() as getname_uflags() is to getname() complete_getname(&delayed_filename) - completes the import of filename stashed in delayed_filename and returns struct filename to caller, emptying delayed_filename. CLASS(filename_complete_delayed, name)(&delayed_filename) - variant of CLASS(filename) with complete_getname() for constructor. dismiss_delayed_filename(&delayed_filename) - destructor; drops whatever might be stashed in delayed_filename, emptying it. putname_to_delayed(&delayed_filename, name) - if name is shared, stashes its copy into delayed_filename and drops the reference to name, otherwise stashes the name itself in there. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs')
-rw-r--r--fs/namei.c66
1 files changed, 61 insertions, 5 deletions
diff --git a/fs/namei.c b/fs/namei.c
index f1a2161bd691..b76cc43fe89d 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -172,8 +172,8 @@ static int getname_long(struct filename *name, const char __user *filename)
return 0;
}
-struct filename *
-getname_flags(const char __user *filename, int flags)
+static struct filename *
+do_getname(const char __user *filename, int flags, bool incomplete)
{
struct filename *result;
char *kname;
@@ -214,10 +214,17 @@ getname_flags(const char __user *filename, int flags)
}
initname(result);
- audit_getname(result);
+ if (likely(!incomplete))
+ audit_getname(result);
return result;
}
+struct filename *
+getname_flags(const char __user *filename, int flags)
+{
+ return do_getname(filename, flags, false);
+}
+
struct filename *getname_uflags(const char __user *filename, int uflags)
{
int flags = (uflags & AT_EMPTY_PATH) ? LOOKUP_EMPTY : 0;
@@ -242,7 +249,7 @@ struct filename *__getname_maybe_null(const char __user *pathname)
return no_free_ptr(name);
}
-struct filename *getname_kernel(const char * filename)
+static struct filename *do_getname_kernel(const char *filename, bool incomplete)
{
struct filename *result;
int len = strlen(filename) + 1;
@@ -267,9 +274,15 @@ struct filename *getname_kernel(const char * filename)
}
result->name = p;
initname(result);
- audit_getname(result);
+ if (likely(!incomplete))
+ audit_getname(result);
return result;
}
+
+struct filename *getname_kernel(const char *filename)
+{
+ return do_getname_kernel(filename, false);
+}
EXPORT_SYMBOL(getname_kernel);
void putname(struct filename *name)
@@ -294,6 +307,49 @@ void putname(struct filename *name)
}
EXPORT_SYMBOL(putname);
+static inline int __delayed_getname(struct delayed_filename *v,
+ const char __user *string, int flags)
+{
+ v->__incomplete_filename = do_getname(string, flags, true);
+ return PTR_ERR_OR_ZERO(v->__incomplete_filename);
+}
+
+int delayed_getname(struct delayed_filename *v, const char __user *string)
+{
+ return __delayed_getname(v, string, 0);
+}
+
+int delayed_getname_uflags(struct delayed_filename *v, const char __user *string,
+ int uflags)
+{
+ int flags = (uflags & AT_EMPTY_PATH) ? LOOKUP_EMPTY : 0;
+ return __delayed_getname(v, string, flags);
+}
+
+int putname_to_delayed(struct delayed_filename *v, struct filename *name)
+{
+ if (likely(atomic_read(&name->refcnt) == 1)) {
+ v->__incomplete_filename = name;
+ return 0;
+ }
+ v->__incomplete_filename = do_getname_kernel(name->name, true);
+ putname(name);
+ return PTR_ERR_OR_ZERO(v->__incomplete_filename);
+}
+
+void dismiss_delayed_filename(struct delayed_filename *v)
+{
+ putname(no_free_ptr(v->__incomplete_filename));
+}
+
+struct filename *complete_getname(struct delayed_filename *v)
+{
+ struct filename *res = no_free_ptr(v->__incomplete_filename);
+ if (!IS_ERR(res))
+ audit_getname(res);
+ return res;
+}
+
/**
* check_acl - perform ACL permission checking
* @idmap: idmap of the mount the inode was found from