Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a892b48 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
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
Loading
Loading
Loading
Loading
+25 −0
Original line number Original line Diff line number Diff line
@@ -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)
+20 −47
Original line number Original line Diff line number Diff line
@@ -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)
		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)
	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,
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,
		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,
	}
	}
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)
		dput(parent);
		dput(parent);
		dput(next);
		dput(next);
	}
	}
	revert_creds(old_cred);


	return err;
	return err;
}
}
+4 −1
Original line number Original line Diff line number Diff line
@@ -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)
{
{
	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) {
+10 −34
Original line number Original line Diff line number Diff line
@@ -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)
		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;


	old_cred = ovl_override_creds(dentry->d_sb);
	err = vfs_getattr(&lowerpath, &stat);
	if (!err) {
		stat.size = 0;
		stat.size = 0;
		err = ovl_copy_up_one(parent, dentry, &lowerpath, &stat);
		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,
				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 = {
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,
+22 −11
Original line number Original line Diff line number Diff line
@@ -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)
				  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,
			  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,
	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,
		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,
		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,
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,
		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;
Loading