Commit 1a892b48 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs

Pull overlayfs updates from Miklos Szeredi:
 "This update contains fixes to the "use mounter's permission to access
  underlying layers" area, and miscellaneous other fixes and cleanups.

  No new features this time"

* 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs:
  ovl: use vfs_get_link()
  vfs: add vfs_get_link() helper
  ovl: use generic_readlink
  ovl: explain error values when removing acl from workdir
  ovl: Fix info leak in ovl_lookup_temp()
  ovl: during copy up, switch to mounter's creds early
  ovl: lookup: do getxattr with mounter's permission
  ovl: copy_up_xattr(): use strnlen
parents 5d89d9f5 7764235b
...@@ -4668,6 +4668,31 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen) ...@@ -4668,6 +4668,31 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
} }
EXPORT_SYMBOL(generic_readlink); EXPORT_SYMBOL(generic_readlink);
/**
* vfs_get_link - get symlink body
* @dentry: dentry on which to get symbolic link
* @done: caller needs to free returned data with this
*
* Calls security hook and i_op->get_link() on the supplied inode.
*
* It does not touch atime. That's up to the caller if necessary.
*
* Does not work on "special" symlinks like /proc/$$/fd/N
*/
const char *vfs_get_link(struct dentry *dentry, struct delayed_call *done)
{
const char *res = ERR_PTR(-EINVAL);
struct inode *inode = d_inode(dentry);
if (d_is_symlink(dentry)) {
res = ERR_PTR(security_inode_readlink(dentry));
if (!res)
res = inode->i_op->get_link(dentry, inode, done);
}
return res;
}
EXPORT_SYMBOL(vfs_get_link);
/* get the link contents into pagecache */ /* get the link contents into pagecache */
const char *page_get_link(struct dentry *dentry, struct inode *inode, const char *page_get_link(struct dentry *dentry, struct inode *inode,
struct delayed_call *callback) struct delayed_call *callback)
......
...@@ -57,6 +57,7 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new) ...@@ -57,6 +57,7 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new)
ssize_t list_size, size, value_size = 0; ssize_t list_size, size, value_size = 0;
char *buf, *name, *value = NULL; char *buf, *name, *value = NULL;
int uninitialized_var(error); int uninitialized_var(error);
size_t slen;
if (!(old->d_inode->i_opflags & IOP_XATTR) || if (!(old->d_inode->i_opflags & IOP_XATTR) ||
!(new->d_inode->i_opflags & IOP_XATTR)) !(new->d_inode->i_opflags & IOP_XATTR))
...@@ -79,7 +80,16 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new) ...@@ -79,7 +80,16 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new)
goto out; goto out;
} }
for (name = buf; name < (buf + list_size); name += strlen(name) + 1) { for (name = buf; list_size; name += slen) {
slen = strnlen(name, list_size) + 1;
/* underlying fs providing us with an broken xattr list? */
if (WARN_ON(slen > list_size)) {
error = -EIO;
break;
}
list_size -= slen;
if (ovl_is_private_xattr(name)) if (ovl_is_private_xattr(name))
continue; continue;
retry: retry:
...@@ -174,40 +184,6 @@ static int ovl_copy_up_data(struct path *old, struct path *new, loff_t len) ...@@ -174,40 +184,6 @@ static int ovl_copy_up_data(struct path *old, struct path *new, loff_t len)
return error; return error;
} }
static char *ovl_read_symlink(struct dentry *realdentry)
{
int res;
char *buf;
struct inode *inode = realdentry->d_inode;
mm_segment_t old_fs;
res = -EINVAL;
if (!inode->i_op->readlink)
goto err;
res = -ENOMEM;
buf = (char *) __get_free_page(GFP_KERNEL);
if (!buf)
goto err;
old_fs = get_fs();
set_fs(get_ds());
/* The cast to a user pointer is valid due to the set_fs() */
res = inode->i_op->readlink(realdentry,
(char __user *)buf, PAGE_SIZE - 1);
set_fs(old_fs);
if (res < 0) {
free_page((unsigned long) buf);
goto err;
}
buf[res] = '\0';
return buf;
err:
return ERR_PTR(res);
}
static int ovl_set_timestamps(struct dentry *upperdentry, struct kstat *stat) static int ovl_set_timestamps(struct dentry *upperdentry, struct kstat *stat)
{ {
struct iattr attr = { struct iattr attr = {
...@@ -354,19 +330,20 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, ...@@ -354,19 +330,20 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
struct path *lowerpath, struct kstat *stat) struct path *lowerpath, struct kstat *stat)
{ {
DEFINE_DELAYED_CALL(done);
struct dentry *workdir = ovl_workdir(dentry); struct dentry *workdir = ovl_workdir(dentry);
int err; int err;
struct kstat pstat; struct kstat pstat;
struct path parentpath; struct path parentpath;
struct dentry *lowerdentry = lowerpath->dentry;
struct dentry *upperdir; struct dentry *upperdir;
struct dentry *upperdentry; struct dentry *upperdentry;
const struct cred *old_cred; const char *link = NULL;
char *link = NULL;
if (WARN_ON(!workdir)) if (WARN_ON(!workdir))
return -EROFS; return -EROFS;
ovl_do_check_copy_up(lowerpath->dentry); ovl_do_check_copy_up(lowerdentry);
ovl_path_upper(parent, &parentpath); ovl_path_upper(parent, &parentpath);
upperdir = parentpath.dentry; upperdir = parentpath.dentry;
...@@ -376,13 +353,11 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, ...@@ -376,13 +353,11 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
return err; return err;
if (S_ISLNK(stat->mode)) { if (S_ISLNK(stat->mode)) {
link = ovl_read_symlink(lowerpath->dentry); link = vfs_get_link(lowerdentry, &done);
if (IS_ERR(link)) if (IS_ERR(link))
return PTR_ERR(link); return PTR_ERR(link);
} }
old_cred = ovl_override_creds(dentry->d_sb);
err = -EIO; err = -EIO;
if (lock_rename(workdir, upperdir) != NULL) { if (lock_rename(workdir, upperdir) != NULL) {
pr_err("overlayfs: failed to lock workdir+upperdir\n"); pr_err("overlayfs: failed to lock workdir+upperdir\n");
...@@ -403,19 +378,16 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, ...@@ -403,19 +378,16 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
} }
out_unlock: out_unlock:
unlock_rename(workdir, upperdir); unlock_rename(workdir, upperdir);
revert_creds(old_cred); do_delayed_call(&done);
if (link)
free_page((unsigned long) link);
return err; return err;
} }
int ovl_copy_up(struct dentry *dentry) int ovl_copy_up(struct dentry *dentry)
{ {
int err; int err = 0;
const struct cred *old_cred = ovl_override_creds(dentry->d_sb);
err = 0;
while (!err) { while (!err) {
struct dentry *next; struct dentry *next;
struct dentry *parent; struct dentry *parent;
...@@ -447,6 +419,7 @@ int ovl_copy_up(struct dentry *dentry) ...@@ -447,6 +419,7 @@ int ovl_copy_up(struct dentry *dentry)
dput(parent); dput(parent);
dput(next); dput(next);
} }
revert_creds(old_cred);
return err; return err;
} }
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/cred.h> #include <linux/cred.h>
#include <linux/posix_acl.h> #include <linux/posix_acl.h>
#include <linux/posix_acl_xattr.h> #include <linux/posix_acl_xattr.h>
#include <linux/atomic.h>
#include "overlayfs.h" #include "overlayfs.h"
void ovl_cleanup(struct inode *wdir, struct dentry *wdentry) void ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
...@@ -37,8 +38,10 @@ struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry) ...@@ -37,8 +38,10 @@ struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry)
{ {
struct dentry *temp; struct dentry *temp;
char name[20]; char name[20];
static atomic_t temp_id = ATOMIC_INIT(0);
snprintf(name, sizeof(name), "#%lx", (unsigned long) dentry); /* counter is allowed to wrap, since temp dentries are ephemeral */
snprintf(name, sizeof(name), "#%x", atomic_inc_return(&temp_id));
temp = lookup_one_len(name, workdir, strlen(name)); temp = lookup_one_len(name, workdir, strlen(name));
if (!IS_ERR(temp) && temp->d_inode) { if (!IS_ERR(temp) && temp->d_inode) {
......
...@@ -19,6 +19,7 @@ static int ovl_copy_up_truncate(struct dentry *dentry) ...@@ -19,6 +19,7 @@ static int ovl_copy_up_truncate(struct dentry *dentry)
struct dentry *parent; struct dentry *parent;
struct kstat stat; struct kstat stat;
struct path lowerpath; struct path lowerpath;
const struct cred *old_cred;
parent = dget_parent(dentry); parent = dget_parent(dentry);
err = ovl_copy_up(parent); err = ovl_copy_up(parent);
...@@ -26,12 +27,14 @@ static int ovl_copy_up_truncate(struct dentry *dentry) ...@@ -26,12 +27,14 @@ static int ovl_copy_up_truncate(struct dentry *dentry)
goto out_dput_parent; goto out_dput_parent;
ovl_path_lower(dentry, &lowerpath); ovl_path_lower(dentry, &lowerpath);
err = vfs_getattr(&lowerpath, &stat);
if (err)
goto out_dput_parent;
stat.size = 0; old_cred = ovl_override_creds(dentry->d_sb);
err = ovl_copy_up_one(parent, dentry, &lowerpath, &stat); err = vfs_getattr(&lowerpath, &stat);
if (!err) {
stat.size = 0;
err = ovl_copy_up_one(parent, dentry, &lowerpath, &stat);
}
revert_creds(old_cred);
out_dput_parent: out_dput_parent:
dput(parent); dput(parent);
...@@ -153,45 +156,18 @@ static const char *ovl_get_link(struct dentry *dentry, ...@@ -153,45 +156,18 @@ static const char *ovl_get_link(struct dentry *dentry,
struct inode *inode, struct inode *inode,
struct delayed_call *done) struct delayed_call *done)
{ {
struct dentry *realdentry;
struct inode *realinode;
const struct cred *old_cred; const struct cred *old_cred;
const char *p; const char *p;
if (!dentry) if (!dentry)
return ERR_PTR(-ECHILD); return ERR_PTR(-ECHILD);
realdentry = ovl_dentry_real(dentry);
realinode = realdentry->d_inode;
if (WARN_ON(!realinode->i_op->get_link))
return ERR_PTR(-EPERM);
old_cred = ovl_override_creds(dentry->d_sb); old_cred = ovl_override_creds(dentry->d_sb);
p = realinode->i_op->get_link(realdentry, realinode, done); p = vfs_get_link(ovl_dentry_real(dentry), done);
revert_creds(old_cred); revert_creds(old_cred);
return p; return p;
} }
static int ovl_readlink(struct dentry *dentry, char __user *buf, int bufsiz)
{
struct path realpath;
struct inode *realinode;
const struct cred *old_cred;
int err;
ovl_path_real(dentry, &realpath);
realinode = realpath.dentry->d_inode;
if (!realinode->i_op->readlink)
return -EINVAL;
old_cred = ovl_override_creds(dentry->d_sb);
err = realinode->i_op->readlink(realpath.dentry, buf, bufsiz);
revert_creds(old_cred);
return err;
}
bool ovl_is_private_xattr(const char *name) bool ovl_is_private_xattr(const char *name)
{ {
return strncmp(name, OVL_XATTR_PREFIX, return strncmp(name, OVL_XATTR_PREFIX,
...@@ -375,7 +351,7 @@ static const struct inode_operations ovl_file_inode_operations = { ...@@ -375,7 +351,7 @@ static const struct inode_operations ovl_file_inode_operations = {
static const struct inode_operations ovl_symlink_inode_operations = { static const struct inode_operations ovl_symlink_inode_operations = {
.setattr = ovl_setattr, .setattr = ovl_setattr,
.get_link = ovl_get_link, .get_link = ovl_get_link,
.readlink = ovl_readlink, .readlink = generic_readlink,
.getattr = ovl_getattr, .getattr = ovl_getattr,
.listxattr = ovl_listxattr, .listxattr = ovl_listxattr,
.update_time = ovl_update_time, .update_time = ovl_update_time,
......
...@@ -273,12 +273,11 @@ static bool ovl_is_opaquedir(struct dentry *dentry) ...@@ -273,12 +273,11 @@ static bool ovl_is_opaquedir(struct dentry *dentry)
{ {
int res; int res;
char val; char val;
struct inode *inode = dentry->d_inode;
if (!S_ISDIR(inode->i_mode) || !(inode->i_opflags & IOP_XATTR)) if (!d_is_dir(dentry))
return false; return false;
res = __vfs_getxattr(dentry, inode, OVL_XATTR_OPAQUE, &val, 1); res = vfs_getxattr(dentry, OVL_XATTR_OPAQUE, &val, 1);
if (res == 1 && val == 'y') if (res == 1 && val == 'y')
return true; return true;
...@@ -419,16 +418,12 @@ static bool ovl_dentry_weird(struct dentry *dentry) ...@@ -419,16 +418,12 @@ static bool ovl_dentry_weird(struct dentry *dentry)
DCACHE_OP_COMPARE); DCACHE_OP_COMPARE);
} }
static inline struct dentry *ovl_lookup_real(struct super_block *ovl_sb, static inline struct dentry *ovl_lookup_real(struct dentry *dir,
struct dentry *dir,
const struct qstr *name) const struct qstr *name)
{ {
const struct cred *old_cred;
struct dentry *dentry; struct dentry *dentry;
old_cred = ovl_override_creds(ovl_sb);
dentry = lookup_one_len_unlocked(name->name, dir, name->len); dentry = lookup_one_len_unlocked(name->name, dir, name->len);
revert_creds(old_cred);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
if (PTR_ERR(dentry) == -ENOENT) if (PTR_ERR(dentry) == -ENOENT)
...@@ -469,6 +464,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ...@@ -469,6 +464,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags) unsigned int flags)
{ {
struct ovl_entry *oe; struct ovl_entry *oe;
const struct cred *old_cred;
struct ovl_entry *poe = dentry->d_parent->d_fsdata; struct ovl_entry *poe = dentry->d_parent->d_fsdata;
struct path *stack = NULL; struct path *stack = NULL;
struct dentry *upperdir, *upperdentry = NULL; struct dentry *upperdir, *upperdentry = NULL;
...@@ -479,9 +475,10 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ...@@ -479,9 +475,10 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int i; unsigned int i;
int err; int err;
old_cred = ovl_override_creds(dentry->d_sb);
upperdir = ovl_upperdentry_dereference(poe); upperdir = ovl_upperdentry_dereference(poe);
if (upperdir) { if (upperdir) {
this = ovl_lookup_real(dentry->d_sb, upperdir, &dentry->d_name); this = ovl_lookup_real(upperdir, &dentry->d_name);
err = PTR_ERR(this); err = PTR_ERR(this);
if (IS_ERR(this)) if (IS_ERR(this))
goto out; goto out;
...@@ -514,8 +511,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ...@@ -514,8 +511,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
bool opaque = false; bool opaque = false;
struct path lowerpath = poe->lowerstack[i]; struct path lowerpath = poe->lowerstack[i];
this = ovl_lookup_real(dentry->d_sb, this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name);
lowerpath.dentry, &dentry->d_name);
err = PTR_ERR(this); err = PTR_ERR(this);
if (IS_ERR(this)) { if (IS_ERR(this)) {
/* /*
...@@ -588,6 +584,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ...@@ -588,6 +584,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
ovl_copyattr(realdentry->d_inode, inode); ovl_copyattr(realdentry->d_inode, inode);
} }
revert_creds(old_cred);
oe->opaque = upperopaque; oe->opaque = upperopaque;
oe->__upperdentry = upperdentry; oe->__upperdentry = upperdentry;
memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr);
...@@ -606,6 +603,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ...@@ -606,6 +603,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
out_put_upper: out_put_upper:
dput(upperdentry); dput(upperdentry);
out: out:
revert_creds(old_cred);
return ERR_PTR(err); return ERR_PTR(err);
} }
...@@ -834,6 +832,19 @@ static struct dentry *ovl_workdir_create(struct vfsmount *mnt, ...@@ -834,6 +832,19 @@ static struct dentry *ovl_workdir_create(struct vfsmount *mnt,
if (err) if (err)
goto out_dput; goto out_dput;
/*
* Try to remove POSIX ACL xattrs from workdir. We are good if:
*
* a) success (there was a POSIX ACL xattr and was removed)
* b) -ENODATA (there was no POSIX ACL xattr)
* c) -EOPNOTSUPP (POSIX ACL xattrs are not supported)
*
* There are various other error values that could effectively
* mean that the xattr doesn't exist (e.g. -ERANGE is returned
* if the xattr name is too long), but the set of filesystems
* allowed as upper are limited to "normal" ones, where checking
* for the above two errors is sufficient.
*/
err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_DEFAULT); err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_DEFAULT);
if (err && err != -ENODATA && err != -EOPNOTSUPP) if (err && err != -ENODATA && err != -EOPNOTSUPP)
goto out_dput; goto out_dput;
......
...@@ -2934,6 +2934,7 @@ extern int vfs_stat(const char __user *, struct kstat *); ...@@ -2934,6 +2934,7 @@ extern int vfs_stat(const char __user *, struct kstat *);
extern int vfs_lstat(const char __user *, struct kstat *); extern int vfs_lstat(const char __user *, struct kstat *);
extern int vfs_fstat(unsigned int, struct kstat *); extern int vfs_fstat(unsigned int, struct kstat *);
extern int vfs_fstatat(int , const char __user *, struct kstat *, int); extern int vfs_fstatat(int , const char __user *, struct kstat *, int);
extern const char *vfs_get_link(struct dentry *, struct delayed_call *);
extern int __generic_block_fiemap(struct inode *inode, extern int __generic_block_fiemap(struct inode *inode,
struct fiemap_extent_info *fieinfo, struct fiemap_extent_info *fieinfo,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment