|
@@ -117,18 +117,70 @@
|
|
|
* POSIX.1 2.4: an empty pathname is invalid (ENOENT).
|
|
|
* PATH_MAX includes the nul terminator --RR.
|
|
|
*/
|
|
|
-static char *getname_flags(const char __user *filename, int flags, int *empty)
|
|
|
+void final_putname(struct filename *name)
|
|
|
{
|
|
|
- char *result = __getname(), *err;
|
|
|
+ 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;
|
|
|
int len;
|
|
|
+ long max;
|
|
|
+ char *kname;
|
|
|
|
|
|
+ result = audit_reusename(filename);
|
|
|
+ if (result)
|
|
|
+ return result;
|
|
|
+
|
|
|
+ result = __getname();
|
|
|
if (unlikely(!result))
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
- len = strncpy_from_user(result, filename, PATH_MAX);
|
|
|
- err = ERR_PTR(len);
|
|
|
- if (unlikely(len < 0))
|
|
|
+ /*
|
|
|
+ * First, try to embed the struct filename inside the names_cache
|
|
|
+ * allocation
|
|
|
+ */
|
|
|
+ kname = (char *)result + sizeof(*result);
|
|
|
+ result->name = kname;
|
|
|
+ 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)) {
|
|
@@ -140,30 +192,32 @@ static char *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(result);
|
|
|
+ final_putname(result);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-char *getname(const char __user * filename)
|
|
|
+struct filename *
|
|
|
+getname(const char __user * filename)
|
|
|
{
|
|
|
return getname_flags(filename, 0, NULL);
|
|
|
}
|
|
|
+EXPORT_SYMBOL(getname);
|
|
|
|
|
|
#ifdef CONFIG_AUDITSYSCALL
|
|
|
-void putname(const char *name)
|
|
|
+void putname(struct filename *name)
|
|
|
{
|
|
|
if (unlikely(!audit_dummy_context()))
|
|
|
- audit_putname(name);
|
|
|
- else
|
|
|
- __putname(name);
|
|
|
+ return audit_putname(name);
|
|
|
+ final_putname(name);
|
|
|
}
|
|
|
-EXPORT_SYMBOL(putname);
|
|
|
#endif
|
|
|
|
|
|
static int check_acl(struct inode *inode, int mask)
|
|
@@ -1963,24 +2017,29 @@ static int path_lookupat(int dfd, const char *name,
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-static int do_path_lookup(int dfd, const char *name,
|
|
|
+static int filename_lookup(int dfd, struct filename *name,
|
|
|
unsigned int flags, struct nameidata *nd)
|
|
|
{
|
|
|
- int retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
|
|
|
+ int retval = path_lookupat(dfd, name->name, flags | LOOKUP_RCU, nd);
|
|
|
if (unlikely(retval == -ECHILD))
|
|
|
- retval = path_lookupat(dfd, name, flags, nd);
|
|
|
+ retval = path_lookupat(dfd, name->name, flags, nd);
|
|
|
if (unlikely(retval == -ESTALE))
|
|
|
- retval = path_lookupat(dfd, name, flags | LOOKUP_REVAL, nd);
|
|
|
+ retval = path_lookupat(dfd, name->name,
|
|
|
+ flags | LOOKUP_REVAL, nd);
|
|
|
|
|
|
- if (likely(!retval)) {
|
|
|
- if (unlikely(!audit_dummy_context())) {
|
|
|
- if (nd->path.dentry && nd->inode)
|
|
|
- audit_inode(name, nd->path.dentry);
|
|
|
- }
|
|
|
- }
|
|
|
+ if (likely(!retval))
|
|
|
+ audit_inode(name, nd->path.dentry, flags & LOOKUP_PARENT);
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
+static int do_path_lookup(int dfd, const char *name,
|
|
|
+ unsigned int flags, struct nameidata *nd)
|
|
|
+{
|
|
|
+ struct filename filename = { .name = name };
|
|
|
+
|
|
|
+ return filename_lookup(dfd, &filename, flags, nd);
|
|
|
+}
|
|
|
+
|
|
|
/* does lookup, returns the object with parent locked */
|
|
|
struct dentry *kern_path_locked(const char *name, struct path *path)
|
|
|
{
|
|
@@ -2098,13 +2157,13 @@ int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
|
|
|
struct path *path, int *empty)
|
|
|
{
|
|
|
struct nameidata nd;
|
|
|
- char *tmp = getname_flags(name, flags, empty);
|
|
|
+ struct filename *tmp = getname_flags(name, flags, empty);
|
|
|
int err = PTR_ERR(tmp);
|
|
|
if (!IS_ERR(tmp)) {
|
|
|
|
|
|
BUG_ON(flags & LOOKUP_PARENT);
|
|
|
|
|
|
- err = do_path_lookup(dfd, tmp, flags, &nd);
|
|
|
+ err = filename_lookup(dfd, tmp, flags, &nd);
|
|
|
putname(tmp);
|
|
|
if (!err)
|
|
|
*path = nd.path;
|
|
@@ -2118,22 +2177,28 @@ int user_path_at(int dfd, const char __user *name, unsigned flags,
|
|
|
return user_path_at_empty(dfd, name, flags, path, NULL);
|
|
|
}
|
|
|
|
|
|
-static int user_path_parent(int dfd, const char __user *path,
|
|
|
- struct nameidata *nd, char **name)
|
|
|
+/*
|
|
|
+ * NB: most callers don't do anything directly with the reference to the
|
|
|
+ * to struct filename, but the nd->last pointer points into the name string
|
|
|
+ * allocated by getname. So we must hold the reference to it until all
|
|
|
+ * path-walking is complete.
|
|
|
+ */
|
|
|
+static struct filename *
|
|
|
+user_path_parent(int dfd, const char __user *path, struct nameidata *nd)
|
|
|
{
|
|
|
- char *s = getname(path);
|
|
|
+ struct filename *s = getname(path);
|
|
|
int error;
|
|
|
|
|
|
if (IS_ERR(s))
|
|
|
- return PTR_ERR(s);
|
|
|
+ return s;
|
|
|
|
|
|
- error = do_path_lookup(dfd, s, LOOKUP_PARENT, nd);
|
|
|
- if (error)
|
|
|
+ error = filename_lookup(dfd, s, LOOKUP_PARENT, nd);
|
|
|
+ if (error) {
|
|
|
putname(s);
|
|
|
- else
|
|
|
- *name = s;
|
|
|
+ return ERR_PTR(error);
|
|
|
+ }
|
|
|
|
|
|
- return error;
|
|
|
+ return s;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -2180,7 +2245,7 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
|
|
|
return -ENOENT;
|
|
|
|
|
|
BUG_ON(victim->d_parent->d_inode != dir);
|
|
|
- audit_inode_child(victim, dir);
|
|
|
+ audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
|
|
|
|
|
|
error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
|
|
if (error)
|
|
@@ -2625,7 +2690,7 @@ out_dput:
|
|
|
*/
|
|
|
static int do_last(struct nameidata *nd, struct path *path,
|
|
|
struct file *file, const struct open_flags *op,
|
|
|
- int *opened, const char *pathname)
|
|
|
+ int *opened, struct filename *name)
|
|
|
{
|
|
|
struct dentry *dir = nd->path.dentry;
|
|
|
int open_flag = op->open_flag;
|
|
@@ -2652,7 +2717,7 @@ static int do_last(struct nameidata *nd, struct path *path,
|
|
|
error = complete_walk(nd);
|
|
|
if (error)
|
|
|
return error;
|
|
|
- audit_inode(pathname, nd->path.dentry);
|
|
|
+ audit_inode(name, nd->path.dentry, 0);
|
|
|
if (open_flag & O_CREAT) {
|
|
|
error = -EISDIR;
|
|
|
goto out;
|
|
@@ -2662,7 +2727,7 @@ static int do_last(struct nameidata *nd, struct path *path,
|
|
|
error = complete_walk(nd);
|
|
|
if (error)
|
|
|
return error;
|
|
|
- audit_inode(pathname, dir);
|
|
|
+ audit_inode(name, dir, 0);
|
|
|
goto finish_open;
|
|
|
}
|
|
|
|
|
@@ -2691,7 +2756,7 @@ static int do_last(struct nameidata *nd, struct path *path,
|
|
|
if (error)
|
|
|
return error;
|
|
|
|
|
|
- audit_inode(pathname, dir);
|
|
|
+ audit_inode(name, dir, 0);
|
|
|
error = -EISDIR;
|
|
|
/* trailing slashes? */
|
|
|
if (nd->last.name[nd->last.len])
|
|
@@ -2721,7 +2786,7 @@ retry_lookup:
|
|
|
!S_ISREG(file->f_path.dentry->d_inode->i_mode))
|
|
|
will_truncate = false;
|
|
|
|
|
|
- audit_inode(pathname, file->f_path.dentry);
|
|
|
+ audit_inode(name, file->f_path.dentry, 0);
|
|
|
goto opened;
|
|
|
}
|
|
|
|
|
@@ -2738,7 +2803,7 @@ retry_lookup:
|
|
|
* create/update audit record if it already exists.
|
|
|
*/
|
|
|
if (path->dentry->d_inode)
|
|
|
- audit_inode(pathname, path->dentry);
|
|
|
+ audit_inode(name, path->dentry, 0);
|
|
|
|
|
|
/*
|
|
|
* If atomic_open() acquired write access it is dropped now due to
|
|
@@ -2803,7 +2868,7 @@ finish_lookup:
|
|
|
error = -ENOTDIR;
|
|
|
if ((nd->flags & LOOKUP_DIRECTORY) && !nd->inode->i_op->lookup)
|
|
|
goto out;
|
|
|
- audit_inode(pathname, nd->path.dentry);
|
|
|
+ audit_inode(name, nd->path.dentry, 0);
|
|
|
finish_open:
|
|
|
if (!S_ISREG(nd->inode->i_mode))
|
|
|
will_truncate = false;
|
|
@@ -2871,7 +2936,7 @@ stale_open:
|
|
|
goto retry_lookup;
|
|
|
}
|
|
|
|
|
|
-static struct file *path_openat(int dfd, const char *pathname,
|
|
|
+static struct file *path_openat(int dfd, struct filename *pathname,
|
|
|
struct nameidata *nd, const struct open_flags *op, int flags)
|
|
|
{
|
|
|
struct file *base = NULL;
|
|
@@ -2886,12 +2951,12 @@ static struct file *path_openat(int dfd, const char *pathname,
|
|
|
|
|
|
file->f_flags = op->open_flag;
|
|
|
|
|
|
- error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);
|
|
|
+ error = path_init(dfd, pathname->name, flags | LOOKUP_PARENT, nd, &base);
|
|
|
if (unlikely(error))
|
|
|
goto out;
|
|
|
|
|
|
current->total_link_count = 0;
|
|
|
- error = link_path_walk(pathname, nd);
|
|
|
+ error = link_path_walk(pathname->name, nd);
|
|
|
if (unlikely(error))
|
|
|
goto out;
|
|
|
|
|
@@ -2937,7 +3002,7 @@ out:
|
|
|
return file;
|
|
|
}
|
|
|
|
|
|
-struct file *do_filp_open(int dfd, const char *pathname,
|
|
|
+struct file *do_filp_open(int dfd, struct filename *pathname,
|
|
|
const struct open_flags *op, int flags)
|
|
|
{
|
|
|
struct nameidata nd;
|
|
@@ -2956,6 +3021,7 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
|
|
|
{
|
|
|
struct nameidata nd;
|
|
|
struct file *file;
|
|
|
+ struct filename filename = { .name = name };
|
|
|
|
|
|
nd.root.mnt = mnt;
|
|
|
nd.root.dentry = dentry;
|
|
@@ -2965,11 +3031,11 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
|
|
|
if (dentry->d_inode->i_op->follow_link && op->intent & LOOKUP_OPEN)
|
|
|
return ERR_PTR(-ELOOP);
|
|
|
|
|
|
- file = path_openat(-1, name, &nd, op, flags | LOOKUP_RCU);
|
|
|
+ file = path_openat(-1, &filename, &nd, op, flags | LOOKUP_RCU);
|
|
|
if (unlikely(file == ERR_PTR(-ECHILD)))
|
|
|
- file = path_openat(-1, name, &nd, op, flags);
|
|
|
+ file = path_openat(-1, &filename, &nd, op, flags);
|
|
|
if (unlikely(file == ERR_PTR(-ESTALE)))
|
|
|
- file = path_openat(-1, name, &nd, op, flags | LOOKUP_REVAL);
|
|
|
+ file = path_openat(-1, &filename, &nd, op, flags | LOOKUP_REVAL);
|
|
|
return file;
|
|
|
}
|
|
|
|
|
@@ -3044,11 +3110,11 @@ EXPORT_SYMBOL(done_path_create);
|
|
|
|
|
|
struct dentry *user_path_create(int dfd, const char __user *pathname, struct path *path, int is_dir)
|
|
|
{
|
|
|
- char *tmp = getname(pathname);
|
|
|
+ struct filename *tmp = getname(pathname);
|
|
|
struct dentry *res;
|
|
|
if (IS_ERR(tmp))
|
|
|
return ERR_CAST(tmp);
|
|
|
- res = kern_path_create(dfd, tmp, path, is_dir);
|
|
|
+ res = kern_path_create(dfd, tmp->name, path, is_dir);
|
|
|
putname(tmp);
|
|
|
return res;
|
|
|
}
|
|
@@ -3253,13 +3319,13 @@ out:
|
|
|
static long do_rmdir(int dfd, const char __user *pathname)
|
|
|
{
|
|
|
int error = 0;
|
|
|
- char * name;
|
|
|
+ struct filename *name;
|
|
|
struct dentry *dentry;
|
|
|
struct nameidata nd;
|
|
|
|
|
|
- error = user_path_parent(dfd, pathname, &nd, &name);
|
|
|
- if (error)
|
|
|
- return error;
|
|
|
+ name = user_path_parent(dfd, pathname, &nd);
|
|
|
+ if (IS_ERR(name))
|
|
|
+ return PTR_ERR(name);
|
|
|
|
|
|
switch(nd.last_type) {
|
|
|
case LAST_DOTDOT:
|
|
@@ -3348,14 +3414,14 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry)
|
|
|
static long do_unlinkat(int dfd, const char __user *pathname)
|
|
|
{
|
|
|
int error;
|
|
|
- char *name;
|
|
|
+ struct filename *name;
|
|
|
struct dentry *dentry;
|
|
|
struct nameidata nd;
|
|
|
struct inode *inode = NULL;
|
|
|
|
|
|
- error = user_path_parent(dfd, pathname, &nd, &name);
|
|
|
- if (error)
|
|
|
- return error;
|
|
|
+ name = user_path_parent(dfd, pathname, &nd);
|
|
|
+ if (IS_ERR(name))
|
|
|
+ return PTR_ERR(name);
|
|
|
|
|
|
error = -EISDIR;
|
|
|
if (nd.last_type != LAST_NORM)
|
|
@@ -3439,7 +3505,7 @@ SYSCALL_DEFINE3(symlinkat, const char __user *, oldname,
|
|
|
int, newdfd, const char __user *, newname)
|
|
|
{
|
|
|
int error;
|
|
|
- char *from;
|
|
|
+ struct filename *from;
|
|
|
struct dentry *dentry;
|
|
|
struct path path;
|
|
|
|
|
@@ -3452,9 +3518,9 @@ SYSCALL_DEFINE3(symlinkat, const char __user *, oldname,
|
|
|
if (IS_ERR(dentry))
|
|
|
goto out_putname;
|
|
|
|
|
|
- error = security_path_symlink(&path, dentry, from);
|
|
|
+ error = security_path_symlink(&path, dentry, from->name);
|
|
|
if (!error)
|
|
|
- error = vfs_symlink(path.dentry->d_inode, dentry, from);
|
|
|
+ error = vfs_symlink(path.dentry->d_inode, dentry, from->name);
|
|
|
done_path_create(&path, dentry);
|
|
|
out_putname:
|
|
|
putname(from);
|
|
@@ -3734,17 +3800,21 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
|
|
struct dentry *old_dentry, *new_dentry;
|
|
|
struct dentry *trap;
|
|
|
struct nameidata oldnd, newnd;
|
|
|
- char *from;
|
|
|
- char *to;
|
|
|
+ struct filename *from;
|
|
|
+ struct filename *to;
|
|
|
int error;
|
|
|
|
|
|
- error = user_path_parent(olddfd, oldname, &oldnd, &from);
|
|
|
- if (error)
|
|
|
+ from = user_path_parent(olddfd, oldname, &oldnd);
|
|
|
+ if (IS_ERR(from)) {
|
|
|
+ error = PTR_ERR(from);
|
|
|
goto exit;
|
|
|
+ }
|
|
|
|
|
|
- error = user_path_parent(newdfd, newname, &newnd, &to);
|
|
|
- if (error)
|
|
|
+ to = user_path_parent(newdfd, newname, &newnd);
|
|
|
+ if (IS_ERR(to)) {
|
|
|
+ error = PTR_ERR(to);
|
|
|
goto exit1;
|
|
|
+ }
|
|
|
|
|
|
error = -EXDEV;
|
|
|
if (oldnd.path.mnt != newnd.path.mnt)
|
|
@@ -3968,7 +4038,6 @@ EXPORT_SYMBOL(follow_down_one);
|
|
|
EXPORT_SYMBOL(follow_down);
|
|
|
EXPORT_SYMBOL(follow_up);
|
|
|
EXPORT_SYMBOL(get_write_access); /* nfsd */
|
|
|
-EXPORT_SYMBOL(getname);
|
|
|
EXPORT_SYMBOL(lock_rename);
|
|
|
EXPORT_SYMBOL(lookup_one_len);
|
|
|
EXPORT_SYMBOL(page_follow_link_light);
|