diff options
-rw-r--r-- | fs/namei.c | 69 | ||||
-rw-r--r-- | include/linux/fs.h | 1 |
2 files changed, 50 insertions, 20 deletions
diff --git a/fs/namei.c b/fs/namei.c index 80b162b142f..d1895f30815 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -119,40 +119,69 @@ */ void final_putname(struct filename *name) { - __putname(name->name); - kfree(name); + if (name->separate) { + __putname(name->name); + kfree(name); + } else { + __putname(name); + } } +#define EMBEDDED_NAME_MAX (PATH_MAX - sizeof(struct filename)) + static struct filename * getname_flags(const char __user *filename, int flags, int *empty) { struct filename *result, *err; - char *kname; int len; + long max; + char *kname; result = audit_reusename(filename); if (result) return result; - /* FIXME: create dedicated slabcache? */ - result = kzalloc(sizeof(*result), GFP_KERNEL); + result = __getname(); if (unlikely(!result)) return ERR_PTR(-ENOMEM); - kname = __getname(); - if (unlikely(!kname)) { - err = ERR_PTR(-ENOMEM); - goto error_free_name; - } - + /* + * First, try to embed the struct filename inside the names_cache + * allocation + */ + kname = (char *)result + sizeof(*result); result->name = kname; - result->uptr = filename; - len = strncpy_from_user(kname, filename, PATH_MAX); + result->separate = false; + max = EMBEDDED_NAME_MAX; + +recopy: + len = strncpy_from_user(kname, filename, max); if (unlikely(len < 0)) { err = ERR_PTR(len); goto error; } + /* + * Uh-oh. We have a name that's approaching PATH_MAX. Allocate a + * separate struct filename so we can dedicate the entire + * names_cache allocation for the pathname, and re-do the copy from + * userland. + */ + if (len == EMBEDDED_NAME_MAX && max == EMBEDDED_NAME_MAX) { + kname = (char *)result; + + result = kzalloc(sizeof(*result), GFP_KERNEL); + if (!result) { + err = ERR_PTR(-ENOMEM); + result = (struct filename *)kname; + goto error; + } + result->name = kname; + result->separate = true; + max = PATH_MAX; + goto recopy; + } + /* The empty path is special. */ if (unlikely(!len)) { if (empty) @@ -163,15 +192,15 @@ getname_flags(const char __user *filename, int flags, int *empty) } err = ERR_PTR(-ENAMETOOLONG); - if (likely(len < PATH_MAX)) { - audit_getname(result); - return result; - } + if (unlikely(len >= PATH_MAX)) + goto error; + + result->uptr = filename; + audit_getname(result); + return result; error: - __putname(kname); -error_free_name: - kfree(result); + final_putname(result); return err; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 4aa7160a51c..65fbf571023 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2201,6 +2201,7 @@ struct filename { const char *name; /* pointer to actual string */ const __user char *uptr; /* original userland pointer */ struct audit_names *aname; + bool separate; /* should "name" be freed? */ }; extern int do_truncate(struct dentry *, loff_t start, unsigned int time_attrs, |