Skip to content
14 changes: 12 additions & 2 deletions fs/overlayfs/copy_up.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new)
{
ssize_t list_size, size, value_size = 0;
char *buf, *name, *value = NULL;
int uninitialized_var(error);
int error = 0;
size_t slen;

if (!old->d_inode->i_op->getxattr ||
!new->d_inode->i_op->getxattr)
Expand All @@ -47,7 +48,16 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new)
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))
continue;
retry:
Expand Down
86 changes: 60 additions & 26 deletions fs/overlayfs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <linux/xattr.h>
#include <linux/security.h>
#include <linux/cred.h>
#include <linux/atomic.h>
#include "overlayfs.h"

void ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
Expand All @@ -35,8 +36,11 @@ struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry)
{
struct dentry *temp;
char name[20];
static atomic_t temp_id = ATOMIC_INIT(0);

/* counter is allowed to wrap, since temp dentries are ephemeral */
snprintf(name, sizeof(name), "#%x", atomic_inc_return(&temp_id));

snprintf(name, sizeof(name), "#%lx", (unsigned long) dentry);

temp = lookup_one_len(name, workdir, strlen(name));
if (!IS_ERR(temp) && temp->d_inode) {
Expand Down Expand Up @@ -416,8 +420,17 @@ static int ovl_create_or_link(struct dentry *dentry, int mode, dev_t rdev,
cap_raise(override_cred->cap_effective, CAP_FOWNER);
old_cred = override_creds(override_cred);

err = ovl_create_over_whiteout(dentry, inode, &stat, link,
hardlink);
err = -ENOMEM;
override_cred = prepare_creds();
if (override_cred) {
override_cred->fsuid = old_cred->fsuid;
override_cred->fsgid = old_cred->fsgid;
put_cred(override_creds(override_cred));
put_cred(override_cred);

err = ovl_create_over_whiteout(dentry, inode, &stat,
link, hardlink);
}

revert_creds(old_cred);
put_cred(override_cred);
Expand Down Expand Up @@ -567,21 +580,25 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir)
{
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct inode *dir = upperdir->d_inode;
struct dentry *upper = ovl_dentry_upper(dentry);
struct dentry *upper;
int err;

mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
upper = lookup_one_len(dentry->d_name.name, upperdir,
dentry->d_name.len);
err = PTR_ERR(upper);
if (IS_ERR(upper))
goto out_unlock;

err = -ESTALE;
if (upper->d_parent == upperdir) {
/* Don't let d_delete() think it can reset d_inode */
dget(upper);
if (upper == ovl_dentry_upper(dentry)) {
if (is_dir)
err = vfs_rmdir(dir, upper);
else
err = vfs_unlink(dir, upper, NULL);
dput(upper);
ovl_dentry_version_inc(dentry->d_parent);
}
dput(upper);

/*
* Keeping this dentry hashed would mean having to release
Expand All @@ -591,6 +608,7 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir)
*/
if (!err)
d_drop(dentry);
out_unlock:
mutex_unlock(&dir->i_mutex);

return err;
Expand Down Expand Up @@ -811,29 +829,43 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old,

trap = lock_rename(new_upperdir, old_upperdir);

olddentry = ovl_dentry_upper(old);
newdentry = ovl_dentry_upper(new);
if (newdentry) {

olddentry = lookup_one_len(old->d_name.name, old_upperdir,
old->d_name.len);
err = PTR_ERR(olddentry);
if (IS_ERR(olddentry))
goto out_unlock;

err = -ESTALE;
if (olddentry != ovl_dentry_upper(old))
goto out_dput_old;

newdentry = lookup_one_len(new->d_name.name, new_upperdir,
new->d_name.len);
err = PTR_ERR(newdentry);
if (IS_ERR(newdentry))
goto out_dput_old;

err = -ESTALE;
if (ovl_dentry_upper(new)) {
if (opaquedir) {
newdentry = opaquedir;
opaquedir = NULL;
if (newdentry != opaquedir)
goto out_dput;
} else {
dget(newdentry);
if (newdentry != ovl_dentry_upper(new))
goto out_dput;
}
} else {
new_create = true;
newdentry = lookup_one_len(new->d_name.name, new_upperdir,
new->d_name.len);
err = PTR_ERR(newdentry);
if (IS_ERR(newdentry))
goto out_unlock;
new_create = true;
if (!d_is_negative(newdentry)) {
if (!new_opaque || !ovl_is_whiteout(newdentry))
goto out_dput;
} else {
if (flags & RENAME_EXCHANGE)
goto out_dput;
}
}

err = -ESTALE;
if (olddentry->d_parent != old_upperdir)
goto out_dput;
if (newdentry->d_parent != new_upperdir)
goto out_dput;
if (olddentry == trap)
goto out_dput;
if (newdentry == trap)
Expand Down Expand Up @@ -889,6 +921,8 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old,

out_dput:
dput(newdentry);
out_dput_old:
dput(olddentry);
out_unlock:
unlock_rename(new_upperdir, old_upperdir);
out_revert_creds:
Expand Down
6 changes: 5 additions & 1 deletion fs/overlayfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ static bool ovl_can_list(const char *s)
return true;

/* Never list trusted.overlay, list other trusted for superuser only */
return !ovl_is_private_xattr(s) && capable(CAP_SYS_ADMIN);
return !ovl_is_private_xattr(s) &&
has_capability_noaudit(current, CAP_SYS_ADMIN);
}

ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)
Expand Down Expand Up @@ -377,6 +378,9 @@ struct inode *ovl_d_select_inode(struct dentry *dentry, unsigned file_flags)
ovl_path_upper(dentry, &realpath);
}

if (realpath.dentry->d_flags & DCACHE_OP_SELECT_INODE)
return realpath.dentry->d_op->d_select_inode(realpath.dentry, file_flags);

return d_backing_inode(realpath.dentry);
}

Expand Down
18 changes: 11 additions & 7 deletions fs/overlayfs/readdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct ovl_dir_cache {

struct ovl_readdir_data {
struct dir_context ctx;
bool is_merge;
bool is_lowest;
struct rb_root root;
struct list_head *list;
struct list_head middle;
Expand Down Expand Up @@ -133,7 +133,7 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
return 0;
}

static int ovl_fill_lower(struct ovl_readdir_data *rdd,
static int ovl_fill_lowest(struct ovl_readdir_data *rdd,
const char *name, int namelen,
loff_t offset, u64 ino, unsigned int d_type)
{
Expand Down Expand Up @@ -186,10 +186,10 @@ static int ovl_fill_merge(void *buf, const char *name, int namelen,
struct ovl_readdir_data *rdd = buf;

rdd->count++;
if (!rdd->is_merge)
if (!rdd->is_lowest)
return ovl_cache_entry_add_rb(rdd, name, namelen, ino, d_type);
else
return ovl_fill_lower(rdd, name, namelen, offset, ino, d_type);
return ovl_fill_lowest(rdd, name, namelen, offset, ino, d_type);
}

static inline int ovl_dir_read(struct path *realpath,
Expand Down Expand Up @@ -283,7 +283,7 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list)
.ctx.actor = ovl_fill_merge,
.list = list,
.root = RB_ROOT,
.is_merge = false,
.is_lowest = false,
};

ovl_path_lower(dentry, &lowerpath);
Expand All @@ -306,7 +306,7 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list)
* offsets to be reasonably constant
*/
list_add(&rdd.middle, rdd.list);
rdd.is_merge = true;
rdd.is_lowest = true;
err = ovl_dir_read(&lowerpath, &rdd);
list_del(&rdd.middle);
}
Expand Down Expand Up @@ -447,10 +447,14 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,
struct dentry *dentry = file->f_path.dentry;
struct file *realfile = od->realfile;

/* Nothing to sync for lower */
if (ovl_path_type(dentry) != OVL_PATH_UPPER)
return 0;

/*
* Need to check if we started out being a lower dir, but got copied up
*/
if (!od->is_upper && ovl_path_type(dentry) != OVL_PATH_LOWER) {
if (!od->is_upper) {
struct inode *inode = file_inode(file);

realfile = lockless_dereference(od->upperfile);
Expand Down
1 change: 1 addition & 0 deletions kernel/capability.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ bool has_capability_noaudit(struct task_struct *t, int cap)
{
return has_ns_capability_noaudit(t, &init_user_ns, cap);
}
EXPORT_SYMBOL_GPL(has_capability_noaudit);

/**
* ns_capable - Determine if the current task has a superior capability in effect
Expand Down