337 lines
10 KiB
Diff
337 lines
10 KiB
Diff
|
From 3fe6e52f062643676eb4518d68cee3bc1272091b Mon Sep 17 00:00:00 2001
|
||
|
From: Antonio Murdaca <amurdaca@redhat.com>
|
||
|
Date: Thu, 7 Apr 2016 15:48:25 +0200
|
||
|
Subject: [PATCH] ovl: override creds with the ones from the superblock mounter
|
||
|
|
||
|
In user namespace the whiteout creation fails with -EPERM because the
|
||
|
current process isn't capable(CAP_SYS_ADMIN) when setting xattr.
|
||
|
|
||
|
A simple reproducer:
|
||
|
|
||
|
$ mkdir upper lower work merged lower/dir
|
||
|
$ sudo mount -t overlay overlay -olowerdir=lower,upperdir=upper,workdir=work merged
|
||
|
$ unshare -m -p -f -U -r bash
|
||
|
|
||
|
Now as root in the user namespace:
|
||
|
|
||
|
\# touch merged/dir/{1,2,3} # this will force a copy up of lower/dir
|
||
|
\# rm -fR merged/*
|
||
|
|
||
|
This ends up failing with -EPERM after the files in dir has been
|
||
|
correctly deleted:
|
||
|
|
||
|
unlinkat(4, "2", 0) = 0
|
||
|
unlinkat(4, "1", 0) = 0
|
||
|
unlinkat(4, "3", 0) = 0
|
||
|
close(4) = 0
|
||
|
unlinkat(AT_FDCWD, "merged/dir", AT_REMOVEDIR) = -1 EPERM (Operation not
|
||
|
permitted)
|
||
|
|
||
|
Interestingly, if you don't place files in merged/dir you can remove it,
|
||
|
meaning if upper/dir does not exist, creating the char device file works
|
||
|
properly in that same location.
|
||
|
|
||
|
This patch uses ovl_sb_creator_cred() to get the cred struct from the
|
||
|
superblock mounter and override the old cred with these new ones so that
|
||
|
the whiteout creation is possible because overlay is wrong in assuming that
|
||
|
the creds it will get with prepare_creds will be in the initial user
|
||
|
namespace. The old cap_raise game is removed in favor of just overriding
|
||
|
the old cred struct.
|
||
|
|
||
|
This patch also drops from ovl_copy_up_one() the following two lines:
|
||
|
|
||
|
override_cred->fsuid = stat->uid;
|
||
|
override_cred->fsgid = stat->gid;
|
||
|
|
||
|
This is because the correct uid and gid are taken directly with the stat
|
||
|
struct and correctly set with ovl_set_attr().
|
||
|
|
||
|
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
|
||
|
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
|
||
|
---
|
||
|
fs/overlayfs/copy_up.c | 26 +------------------
|
||
|
fs/overlayfs/dir.c | 67 ++++--------------------------------------------
|
||
|
fs/overlayfs/overlayfs.h | 1 +
|
||
|
fs/overlayfs/readdir.c | 14 +++-------
|
||
|
fs/overlayfs/super.c | 18 ++++++++++++-
|
||
|
5 files changed, 27 insertions(+), 99 deletions(-)
|
||
|
|
||
|
--- a/fs/overlayfs/copy_up.c
|
||
|
+++ b/fs/overlayfs/copy_up.c
|
||
|
@@ -303,7 +303,6 @@ int ovl_copy_up_one(struct dentry *paren
|
||
|
struct dentry *upperdir;
|
||
|
struct dentry *upperdentry;
|
||
|
const struct cred *old_cred;
|
||
|
- struct cred *override_cred;
|
||
|
char *link = NULL;
|
||
|
|
||
|
if (WARN_ON(!workdir))
|
||
|
@@ -322,28 +321,7 @@ int ovl_copy_up_one(struct dentry *paren
|
||
|
return PTR_ERR(link);
|
||
|
}
|
||
|
|
||
|
- err = -ENOMEM;
|
||
|
- override_cred = prepare_creds();
|
||
|
- if (!override_cred)
|
||
|
- goto out_free_link;
|
||
|
-
|
||
|
- override_cred->fsuid = stat->uid;
|
||
|
- override_cred->fsgid = stat->gid;
|
||
|
- /*
|
||
|
- * CAP_SYS_ADMIN for copying up extended attributes
|
||
|
- * CAP_DAC_OVERRIDE for create
|
||
|
- * CAP_FOWNER for chmod, timestamp update
|
||
|
- * CAP_FSETID for chmod
|
||
|
- * CAP_CHOWN for chown
|
||
|
- * CAP_MKNOD for mknod
|
||
|
- */
|
||
|
- cap_raise(override_cred->cap_effective, CAP_SYS_ADMIN);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_FOWNER);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_FSETID);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_CHOWN);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_MKNOD);
|
||
|
- old_cred = override_creds(override_cred);
|
||
|
+ old_cred = ovl_override_creds(dentry->d_sb);
|
||
|
|
||
|
err = -EIO;
|
||
|
if (lock_rename(workdir, upperdir) != NULL) {
|
||
|
@@ -366,9 +344,7 @@ int ovl_copy_up_one(struct dentry *paren
|
||
|
out_unlock:
|
||
|
unlock_rename(workdir, upperdir);
|
||
|
revert_creds(old_cred);
|
||
|
- put_cred(override_cred);
|
||
|
|
||
|
-out_free_link:
|
||
|
if (link)
|
||
|
free_page((unsigned long) link);
|
||
|
|
||
|
--- a/fs/overlayfs/dir.c
|
||
|
+++ b/fs/overlayfs/dir.c
|
||
|
@@ -405,28 +405,13 @@ static int ovl_create_or_link(struct den
|
||
|
err = ovl_create_upper(dentry, inode, &stat, link, hardlink);
|
||
|
} else {
|
||
|
const struct cred *old_cred;
|
||
|
- struct cred *override_cred;
|
||
|
|
||
|
- err = -ENOMEM;
|
||
|
- override_cred = prepare_creds();
|
||
|
- if (!override_cred)
|
||
|
- goto out_iput;
|
||
|
-
|
||
|
- /*
|
||
|
- * CAP_SYS_ADMIN for setting opaque xattr
|
||
|
- * CAP_DAC_OVERRIDE for create in workdir, rename
|
||
|
- * CAP_FOWNER for removing whiteout from sticky dir
|
||
|
- */
|
||
|
- cap_raise(override_cred->cap_effective, CAP_SYS_ADMIN);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_FOWNER);
|
||
|
- old_cred = override_creds(override_cred);
|
||
|
+ old_cred = ovl_override_creds(dentry->d_sb);
|
||
|
|
||
|
err = ovl_create_over_whiteout(dentry, inode, &stat, link,
|
||
|
hardlink);
|
||
|
|
||
|
revert_creds(old_cred);
|
||
|
- put_cred(override_cred);
|
||
|
}
|
||
|
|
||
|
if (!err)
|
||
|
@@ -656,32 +641,11 @@ static int ovl_do_remove(struct dentry *
|
||
|
if (OVL_TYPE_PURE_UPPER(type)) {
|
||
|
err = ovl_remove_upper(dentry, is_dir);
|
||
|
} else {
|
||
|
- const struct cred *old_cred;
|
||
|
- struct cred *override_cred;
|
||
|
-
|
||
|
- err = -ENOMEM;
|
||
|
- override_cred = prepare_creds();
|
||
|
- if (!override_cred)
|
||
|
- goto out_drop_write;
|
||
|
-
|
||
|
- /*
|
||
|
- * CAP_SYS_ADMIN for setting xattr on whiteout, opaque dir
|
||
|
- * CAP_DAC_OVERRIDE for create in workdir, rename
|
||
|
- * CAP_FOWNER for removing whiteout from sticky dir
|
||
|
- * CAP_FSETID for chmod of opaque dir
|
||
|
- * CAP_CHOWN for chown of opaque dir
|
||
|
- */
|
||
|
- cap_raise(override_cred->cap_effective, CAP_SYS_ADMIN);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_FOWNER);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_FSETID);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_CHOWN);
|
||
|
- old_cred = override_creds(override_cred);
|
||
|
+ const struct cred *old_cred = ovl_override_creds(dentry->d_sb);
|
||
|
|
||
|
err = ovl_remove_and_whiteout(dentry, is_dir);
|
||
|
|
||
|
revert_creds(old_cred);
|
||
|
- put_cred(override_cred);
|
||
|
}
|
||
|
out_drop_write:
|
||
|
ovl_drop_write(dentry);
|
||
|
@@ -720,7 +684,6 @@ static int ovl_rename2(struct inode *old
|
||
|
bool new_is_dir = false;
|
||
|
struct dentry *opaquedir = NULL;
|
||
|
const struct cred *old_cred = NULL;
|
||
|
- struct cred *override_cred = NULL;
|
||
|
|
||
|
err = -EINVAL;
|
||
|
if (flags & ~(RENAME_EXCHANGE | RENAME_NOREPLACE))
|
||
|
@@ -789,26 +752,8 @@ static int ovl_rename2(struct inode *old
|
||
|
old_opaque = !OVL_TYPE_PURE_UPPER(old_type);
|
||
|
new_opaque = !OVL_TYPE_PURE_UPPER(new_type);
|
||
|
|
||
|
- if (old_opaque || new_opaque) {
|
||
|
- err = -ENOMEM;
|
||
|
- override_cred = prepare_creds();
|
||
|
- if (!override_cred)
|
||
|
- goto out_drop_write;
|
||
|
-
|
||
|
- /*
|
||
|
- * CAP_SYS_ADMIN for setting xattr on whiteout, opaque dir
|
||
|
- * CAP_DAC_OVERRIDE for create in workdir
|
||
|
- * CAP_FOWNER for removing whiteout from sticky dir
|
||
|
- * CAP_FSETID for chmod of opaque dir
|
||
|
- * CAP_CHOWN for chown of opaque dir
|
||
|
- */
|
||
|
- cap_raise(override_cred->cap_effective, CAP_SYS_ADMIN);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_FOWNER);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_FSETID);
|
||
|
- cap_raise(override_cred->cap_effective, CAP_CHOWN);
|
||
|
- old_cred = override_creds(override_cred);
|
||
|
- }
|
||
|
+ if (old_opaque || new_opaque)
|
||
|
+ old_cred = ovl_override_creds(old->d_sb);
|
||
|
|
||
|
if (overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) {
|
||
|
opaquedir = ovl_check_empty_and_clear(new);
|
||
|
@@ -939,10 +884,8 @@ out_dput_old:
|
||
|
out_unlock:
|
||
|
unlock_rename(new_upperdir, old_upperdir);
|
||
|
out_revert_creds:
|
||
|
- if (old_opaque || new_opaque) {
|
||
|
+ if (old_opaque || new_opaque)
|
||
|
revert_creds(old_cred);
|
||
|
- put_cred(override_cred);
|
||
|
- }
|
||
|
out_drop_write:
|
||
|
ovl_drop_write(old);
|
||
|
out:
|
||
|
--- a/fs/overlayfs/overlayfs.h
|
||
|
+++ b/fs/overlayfs/overlayfs.h
|
||
|
@@ -150,6 +150,7 @@ void ovl_drop_write(struct dentry *dentr
|
||
|
bool ovl_dentry_is_opaque(struct dentry *dentry);
|
||
|
void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque);
|
||
|
bool ovl_is_whiteout(struct dentry *dentry);
|
||
|
+const struct cred *ovl_override_creds(struct super_block *sb);
|
||
|
void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry);
|
||
|
struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
||
|
unsigned int flags);
|
||
|
--- a/fs/overlayfs/readdir.c
|
||
|
+++ b/fs/overlayfs/readdir.c
|
||
|
@@ -36,6 +36,7 @@ struct ovl_dir_cache {
|
||
|
|
||
|
struct ovl_readdir_data {
|
||
|
struct dir_context ctx;
|
||
|
+ struct dentry *dentry;
|
||
|
bool is_lowest;
|
||
|
struct rb_root root;
|
||
|
struct list_head *list;
|
||
|
@@ -205,17 +206,8 @@ static int ovl_check_whiteouts(struct de
|
||
|
struct ovl_cache_entry *p;
|
||
|
struct dentry *dentry;
|
||
|
const struct cred *old_cred;
|
||
|
- struct cred *override_cred;
|
||
|
-
|
||
|
- override_cred = prepare_creds();
|
||
|
- if (!override_cred)
|
||
|
- return -ENOMEM;
|
||
|
|
||
|
- /*
|
||
|
- * CAP_DAC_OVERRIDE for lookup
|
||
|
- */
|
||
|
- cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
|
||
|
- old_cred = override_creds(override_cred);
|
||
|
+ old_cred = ovl_override_creds(rdd->dentry->d_sb);
|
||
|
|
||
|
err = mutex_lock_killable(&dir->d_inode->i_mutex);
|
||
|
if (!err) {
|
||
|
@@ -231,7 +223,6 @@ static int ovl_check_whiteouts(struct de
|
||
|
mutex_unlock(&dir->d_inode->i_mutex);
|
||
|
}
|
||
|
revert_creds(old_cred);
|
||
|
- put_cred(override_cred);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
@@ -287,6 +278,7 @@ static int ovl_dir_read_merged(struct de
|
||
|
struct path realpath;
|
||
|
struct ovl_readdir_data rdd = {
|
||
|
.ctx.actor = ovl_fill_merge,
|
||
|
+ .dentry = dentry,
|
||
|
.list = list,
|
||
|
.root = RB_ROOT,
|
||
|
.is_lowest = false,
|
||
|
--- a/fs/overlayfs/super.c
|
||
|
+++ b/fs/overlayfs/super.c
|
||
|
@@ -42,6 +42,8 @@ struct ovl_fs {
|
||
|
long lower_namelen;
|
||
|
/* pathnames of lower and upper dirs, for show_options */
|
||
|
struct ovl_config config;
|
||
|
+ /* creds of process who forced instantiation of super block */
|
||
|
+ const struct cred *creator_cred;
|
||
|
};
|
||
|
|
||
|
struct ovl_dir_cache;
|
||
|
@@ -246,6 +248,13 @@ bool ovl_is_whiteout(struct dentry *dent
|
||
|
return inode && IS_WHITEOUT(inode);
|
||
|
}
|
||
|
|
||
|
+const struct cred *ovl_override_creds(struct super_block *sb)
|
||
|
+{
|
||
|
+ struct ovl_fs *ofs = sb->s_fs_info;
|
||
|
+
|
||
|
+ return override_creds(ofs->creator_cred);
|
||
|
+}
|
||
|
+
|
||
|
static bool ovl_is_opaquedir(struct dentry *dentry)
|
||
|
{
|
||
|
int res;
|
||
|
@@ -587,6 +596,7 @@ static void ovl_put_super(struct super_b
|
||
|
kfree(ufs->config.lowerdir);
|
||
|
kfree(ufs->config.upperdir);
|
||
|
kfree(ufs->config.workdir);
|
||
|
+ put_cred(ufs->creator_cred);
|
||
|
kfree(ufs);
|
||
|
}
|
||
|
|
||
|
@@ -1068,10 +1078,14 @@ static int ovl_fill_super(struct super_b
|
||
|
else
|
||
|
sb->s_d_op = &ovl_dentry_operations;
|
||
|
|
||
|
+ ufs->creator_cred = prepare_creds();
|
||
|
+ if (!ufs->creator_cred)
|
||
|
+ goto out_put_lower_mnt;
|
||
|
+
|
||
|
err = -ENOMEM;
|
||
|
oe = ovl_alloc_entry(numlower);
|
||
|
if (!oe)
|
||
|
- goto out_put_lower_mnt;
|
||
|
+ goto out_put_cred;
|
||
|
|
||
|
root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR, oe));
|
||
|
if (!root_dentry)
|
||
|
@@ -1104,6 +1118,8 @@ static int ovl_fill_super(struct super_b
|
||
|
|
||
|
out_free_oe:
|
||
|
kfree(oe);
|
||
|
+out_put_cred:
|
||
|
+ put_cred(ufs->creator_cred);
|
||
|
out_put_lower_mnt:
|
||
|
for (i = 0; i < ufs->numlower; i++)
|
||
|
mntput(ufs->lower_mnt[i]);
|