From 42f559ed70897a7b74dd3e6293b42e6d2e511eaa Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rafa=C5=82=20Mi=C5=82ecki?= Date: Tue, 6 Sep 2016 21:32:47 +0200 Subject: [PATCH] kernel: backport upstream overlayfs fixes MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit First two patches weren't marked for stable but are dependencies for laters ones. The rest of patches was marked for stable but most likely will be backported to 4.5+ only so we need to get them on our own. An important fix is eea2fb4851e9d ("ovl: proper cleanup of workdir") as it allows mounting overlayfs with dirty workdir, e.g. after power cut. Signed-off-by: Rafał Miłecki --- ...001-ovl-rename-is_merge-to-is_lowest.patch | 72 ++++ ...ds-with-the-ones-from-the-superblock.patch | 336 ++++++++++++++++++ ...51-0003-ovl-don-t-copy-up-opaqueness.patch | 73 ++++ ...emove-posix_acl_default-from-workdir.patch | 49 +++ ...1-0005-ovl-proper-cleanup-of-workdir.patch | 131 +++++++ .../051-0006-ovl-listxattr-use-strnlen.patch | 53 +++ .../051-0007-ovl-fix-workdir-creation.patch | 35 ++ 7 files changed, 749 insertions(+) create mode 100644 target/linux/generic/patches-4.4/051-0001-ovl-rename-is_merge-to-is_lowest.patch create mode 100644 target/linux/generic/patches-4.4/051-0002-ovl-override-creds-with-the-ones-from-the-superblock.patch create mode 100644 target/linux/generic/patches-4.4/051-0003-ovl-don-t-copy-up-opaqueness.patch create mode 100644 target/linux/generic/patches-4.4/051-0004-ovl-remove-posix_acl_default-from-workdir.patch create mode 100644 target/linux/generic/patches-4.4/051-0005-ovl-proper-cleanup-of-workdir.patch create mode 100644 target/linux/generic/patches-4.4/051-0006-ovl-listxattr-use-strnlen.patch create mode 100644 target/linux/generic/patches-4.4/051-0007-ovl-fix-workdir-creation.patch diff --git a/target/linux/generic/patches-4.4/051-0001-ovl-rename-is_merge-to-is_lowest.patch b/target/linux/generic/patches-4.4/051-0001-ovl-rename-is_merge-to-is_lowest.patch new file mode 100644 index 0000000000..79140b9008 --- /dev/null +++ b/target/linux/generic/patches-4.4/051-0001-ovl-rename-is_merge-to-is_lowest.patch @@ -0,0 +1,72 @@ +From 56656e960b555cb98bc414382566dcb59aae99a2 Mon Sep 17 00:00:00 2001 +From: Miklos Szeredi +Date: Mon, 21 Mar 2016 17:31:46 +0100 +Subject: [PATCH] ovl: rename is_merge to is_lowest + +The 'is_merge' is an historical naming from when only a single lower layer +could exist. With the introduction of multiple lower layers the meaning of +this flag was changed to mean only the "lowest layer" (while all lower +layers were being merged). + +So now 'is_merge' is inaccurate and hence renaming to 'is_lowest' + +Signed-off-by: Miklos Szeredi +--- + fs/overlayfs/readdir.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +--- a/fs/overlayfs/readdir.c ++++ b/fs/overlayfs/readdir.c +@@ -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; +@@ -139,9 +139,9 @@ static int ovl_cache_entry_add_rb(struct + return 0; + } + +-static int ovl_fill_lower(struct ovl_readdir_data *rdd, +- const char *name, int namelen, +- loff_t offset, u64 ino, unsigned int d_type) ++static int ovl_fill_lowest(struct ovl_readdir_data *rdd, ++ const char *name, int namelen, ++ loff_t offset, u64 ino, unsigned int d_type) + { + struct ovl_cache_entry *p; + +@@ -193,10 +193,10 @@ static int ovl_fill_merge(struct dir_con + container_of(ctx, struct ovl_readdir_data, ctx); + + 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 int ovl_check_whiteouts(struct dentry *dir, struct ovl_readdir_data *rdd) +@@ -289,7 +289,7 @@ static int ovl_dir_read_merged(struct de + .ctx.actor = ovl_fill_merge, + .list = list, + .root = RB_ROOT, +- .is_merge = false, ++ .is_lowest = false, + }; + int idx, next; + +@@ -306,7 +306,7 @@ static int ovl_dir_read_merged(struct de + * allows offsets to be reasonably constant + */ + list_add(&rdd.middle, rdd.list); +- rdd.is_merge = true; ++ rdd.is_lowest = true; + err = ovl_dir_read(&realpath, &rdd); + list_del(&rdd.middle); + } diff --git a/target/linux/generic/patches-4.4/051-0002-ovl-override-creds-with-the-ones-from-the-superblock.patch b/target/linux/generic/patches-4.4/051-0002-ovl-override-creds-with-the-ones-from-the-superblock.patch new file mode 100644 index 0000000000..8a7d00aeb0 --- /dev/null +++ b/target/linux/generic/patches-4.4/051-0002-ovl-override-creds-with-the-ones-from-the-superblock.patch @@ -0,0 +1,336 @@ +From 3fe6e52f062643676eb4518d68cee3bc1272091b Mon Sep 17 00:00:00 2001 +From: Antonio Murdaca +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 +Signed-off-by: Miklos Szeredi +--- + 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]); diff --git a/target/linux/generic/patches-4.4/051-0003-ovl-don-t-copy-up-opaqueness.patch b/target/linux/generic/patches-4.4/051-0003-ovl-don-t-copy-up-opaqueness.patch new file mode 100644 index 0000000000..da9e380a32 --- /dev/null +++ b/target/linux/generic/patches-4.4/051-0003-ovl-don-t-copy-up-opaqueness.patch @@ -0,0 +1,73 @@ +From 0956254a2d5b9e2141385514553aeef694dfe3b5 Mon Sep 17 00:00:00 2001 +From: Miklos Szeredi +Date: Mon, 8 Aug 2016 15:08:49 +0200 +Subject: [PATCH] ovl: don't copy up opaqueness + +When a copy up of a directory occurs which has the opaque xattr set, the +xattr remains in the upper directory. The immediate behavior with overlayfs +is that the upper directory is not treated as opaque, however after a +remount the opaque flag is used and upper directory is treated as opaque. +This causes files created in the lower layer to be hidden when using +multiple lower directories. + +Fix by not copying up the opaque flag. + +To reproduce: + + ----8<---------8<---------8<---------8<---------8<---------8<---- +mkdir -p l/d/s u v w mnt +mount -t overlay overlay -olowerdir=l,upperdir=u,workdir=w mnt +rm -rf mnt/d/ +mkdir -p mnt/d/n +umount mnt +mount -t overlay overlay -olowerdir=u:l,upperdir=v,workdir=w mnt +touch mnt/d/foo +umount mnt +mount -t overlay overlay -olowerdir=u:l,upperdir=v,workdir=w mnt +ls mnt/d + ----8<---------8<---------8<---------8<---------8<---------8<---- + +output should be: "foo n" + +Reported-by: Derek McGowan +Link: https://bugzilla.kernel.org/show_bug.cgi?id=151291 +Signed-off-by: Miklos Szeredi +Cc: +--- + fs/overlayfs/copy_up.c | 2 ++ + fs/overlayfs/inode.c | 2 +- + fs/overlayfs/overlayfs.h | 1 + + 3 files changed, 4 insertions(+), 1 deletion(-) + +--- a/fs/overlayfs/copy_up.c ++++ b/fs/overlayfs/copy_up.c +@@ -48,6 +48,8 @@ int ovl_copy_xattr(struct dentry *old, s + } + + for (name = buf; name < (buf + list_size); name += strlen(name) + 1) { ++ if (ovl_is_private_xattr(name)) ++ continue; + retry: + size = vfs_getxattr(old, name, value, value_size); + if (size == -ERANGE) +--- a/fs/overlayfs/inode.c ++++ b/fs/overlayfs/inode.c +@@ -219,7 +219,7 @@ static int ovl_readlink(struct dentry *d + } + + +-static bool ovl_is_private_xattr(const char *name) ++bool ovl_is_private_xattr(const char *name) + { + return strncmp(name, OVL_XATTR_PRE_NAME, OVL_XATTR_PRE_LEN) == 0; + } +--- a/fs/overlayfs/overlayfs.h ++++ b/fs/overlayfs/overlayfs.h +@@ -175,6 +175,7 @@ ssize_t ovl_getxattr(struct dentry *dent + ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size); + int ovl_removexattr(struct dentry *dentry, const char *name); + struct inode *ovl_d_select_inode(struct dentry *dentry, unsigned file_flags); ++bool ovl_is_private_xattr(const char *name); + + struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, + struct ovl_entry *oe); diff --git a/target/linux/generic/patches-4.4/051-0004-ovl-remove-posix_acl_default-from-workdir.patch b/target/linux/generic/patches-4.4/051-0004-ovl-remove-posix_acl_default-from-workdir.patch new file mode 100644 index 0000000000..1785c9ad55 --- /dev/null +++ b/target/linux/generic/patches-4.4/051-0004-ovl-remove-posix_acl_default-from-workdir.patch @@ -0,0 +1,49 @@ +From c11b9fdd6a612f376a5e886505f1c54c16d8c380 Mon Sep 17 00:00:00 2001 +From: Miklos Szeredi +Date: Thu, 1 Sep 2016 11:11:59 +0200 +Subject: [PATCH] ovl: remove posix_acl_default from workdir + +Clear out posix acl xattrs on workdir and also reset the mode after +creation so that an inherited sgid bit is cleared. + +Signed-off-by: Miklos Szeredi +Cc: +--- + fs/overlayfs/super.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +--- a/fs/overlayfs/super.c ++++ b/fs/overlayfs/super.c +@@ -773,6 +773,10 @@ retry: + struct kstat stat = { + .mode = S_IFDIR | 0, + }; ++ struct iattr attr = { ++ .ia_valid = ATTR_MODE, ++ .ia_mode = stat.mode, ++ }; + + if (work->d_inode) { + err = -EEXIST; +@@ -788,6 +792,21 @@ retry: + err = ovl_create_real(dir, work, &stat, NULL, NULL, true); + if (err) + goto out_dput; ++ ++ err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_DEFAULT); ++ if (err && err != -ENODATA) ++ goto out_dput; ++ ++ err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_ACCESS); ++ if (err && err != -ENODATA) ++ goto out_dput; ++ ++ /* Clear any inherited mode bits */ ++ mutex_lock(&work->d_inode->i_mutex); ++ err = notify_change(work, &attr, NULL); ++ mutex_unlock(&work->d_inode->i_mutex); ++ if (err) ++ goto out_dput; + } + out_unlock: + mutex_unlock(&dir->i_mutex); diff --git a/target/linux/generic/patches-4.4/051-0005-ovl-proper-cleanup-of-workdir.patch b/target/linux/generic/patches-4.4/051-0005-ovl-proper-cleanup-of-workdir.patch new file mode 100644 index 0000000000..eb095b7a2e --- /dev/null +++ b/target/linux/generic/patches-4.4/051-0005-ovl-proper-cleanup-of-workdir.patch @@ -0,0 +1,131 @@ +From eea2fb4851e9dcbab6b991aaf47e2e024f1f55a0 Mon Sep 17 00:00:00 2001 +From: Miklos Szeredi +Date: Thu, 1 Sep 2016 11:11:59 +0200 +Subject: [PATCH] ovl: proper cleanup of workdir + +When mounting overlayfs it needs a clean "work" directory under the +supplied workdir. + +Previously the mount code removed this directory if it already existed and +created a new one. If the removal failed (e.g. directory was not empty) +then it fell back to a read-only mount not using the workdir. + +While this has never been reported, it is possible to get a non-empty +"work" dir from a previous mount of overlayfs in case of crash in the +middle of an operation using the work directory. + +In this case the left over state should be discarded and the overlay +filesystem will be consistent, guaranteed by the atomicity of operations on +moving to/from the workdir to the upper layer. + +This patch implements cleaning out any files left in workdir. It is +implemented using real recursion for simplicity, but the depth is limited +to 2, because the worst case is that of a directory containing whiteouts +under "work". + +Signed-off-by: Miklos Szeredi +Cc: +--- + fs/overlayfs/overlayfs.h | 2 ++ + fs/overlayfs/readdir.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++- + fs/overlayfs/super.c | 2 +- + 3 files changed, 65 insertions(+), 2 deletions(-) + +--- a/fs/overlayfs/overlayfs.h ++++ b/fs/overlayfs/overlayfs.h +@@ -164,6 +164,8 @@ extern const struct file_operations ovl_ + int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list); + void ovl_cleanup_whiteouts(struct dentry *upper, struct list_head *list); + void ovl_cache_free(struct list_head *list); ++void ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, ++ struct dentry *dentry, int level); + + /* inode.c */ + int ovl_setattr(struct dentry *dentry, struct iattr *attr); +--- a/fs/overlayfs/readdir.c ++++ b/fs/overlayfs/readdir.c +@@ -247,7 +247,7 @@ static inline int ovl_dir_read(struct pa + err = rdd->err; + } while (!err && rdd->count); + +- if (!err && rdd->first_maybe_whiteout) ++ if (!err && rdd->first_maybe_whiteout && rdd->dentry) + err = ovl_check_whiteouts(realpath->dentry, rdd); + + fput(realfile); +@@ -569,3 +569,64 @@ void ovl_cleanup_whiteouts(struct dentry + } + mutex_unlock(&upper->d_inode->i_mutex); + } ++ ++static void ovl_workdir_cleanup_recurse(struct path *path, int level) ++{ ++ int err; ++ struct inode *dir = path->dentry->d_inode; ++ LIST_HEAD(list); ++ struct ovl_cache_entry *p; ++ struct ovl_readdir_data rdd = { ++ .ctx.actor = ovl_fill_merge, ++ .dentry = NULL, ++ .list = &list, ++ .root = RB_ROOT, ++ .is_lowest = false, ++ }; ++ ++ err = ovl_dir_read(path, &rdd); ++ if (err) ++ goto out; ++ ++ mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT); ++ list_for_each_entry(p, &list, l_node) { ++ struct dentry *dentry; ++ ++ if (p->name[0] == '.') { ++ if (p->len == 1) ++ continue; ++ if (p->len == 2 && p->name[1] == '.') ++ continue; ++ } ++ dentry = lookup_one_len(p->name, path->dentry, p->len); ++ if (IS_ERR(dentry)) ++ continue; ++ if (dentry->d_inode) ++ ovl_workdir_cleanup(dir, path->mnt, dentry, level); ++ dput(dentry); ++ } ++ mutex_unlock(&dir->i_mutex); ++out: ++ ovl_cache_free(&list); ++} ++ ++void ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, ++ struct dentry *dentry, int level) ++{ ++ int err; ++ ++ if (!d_is_dir(dentry) || level > 1) { ++ ovl_cleanup(dir, dentry); ++ return; ++ } ++ ++ err = ovl_do_rmdir(dir, dentry); ++ if (err) { ++ struct path path = { .mnt = mnt, .dentry = dentry }; ++ ++ mutex_unlock(&dir->i_mutex); ++ ovl_workdir_cleanup_recurse(&path, level + 1); ++ mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT); ++ ovl_cleanup(dir, dentry); ++ } ++} +--- a/fs/overlayfs/super.c ++++ b/fs/overlayfs/super.c +@@ -784,7 +784,7 @@ retry: + goto out_dput; + + retried = true; +- ovl_cleanup(dir, work); ++ ovl_workdir_cleanup(dir, mnt, work, 0); + dput(work); + goto retry; + } diff --git a/target/linux/generic/patches-4.4/051-0006-ovl-listxattr-use-strnlen.patch b/target/linux/generic/patches-4.4/051-0006-ovl-listxattr-use-strnlen.patch new file mode 100644 index 0000000000..82ad20db4c --- /dev/null +++ b/target/linux/generic/patches-4.4/051-0006-ovl-listxattr-use-strnlen.patch @@ -0,0 +1,53 @@ +From 7cb35119d067191ce9ebc380a599db0b03cbd9d9 Mon Sep 17 00:00:00 2001 +From: Miklos Szeredi +Date: Thu, 1 Sep 2016 11:12:00 +0200 +Subject: [PATCH] ovl: listxattr: use strnlen() + +Be defensive about what underlying fs provides us in the returned xattr +list buffer. If it's not properly null terminated, bail out with a warning +insead of BUG. + +Signed-off-by: Miklos Szeredi +Cc: +--- + fs/overlayfs/inode.c | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +--- a/fs/overlayfs/inode.c ++++ b/fs/overlayfs/inode.c +@@ -277,7 +277,8 @@ ssize_t ovl_listxattr(struct dentry *den + struct path realpath; + enum ovl_path_type type = ovl_path_real(dentry, &realpath); + ssize_t res; +- int off; ++ size_t len; ++ char *s; + + res = vfs_listxattr(realpath.dentry, list, size); + if (res <= 0 || size == 0) +@@ -287,17 +288,19 @@ ssize_t ovl_listxattr(struct dentry *den + return res; + + /* filter out private xattrs */ +- for (off = 0; off < res;) { +- char *s = list + off; +- size_t slen = strlen(s) + 1; ++ for (s = list, len = res; len;) { ++ size_t slen = strnlen(s, len) + 1; + +- BUG_ON(off + slen > res); ++ /* underlying fs providing us with an broken xattr list? */ ++ if (WARN_ON(slen > len)) ++ return -EIO; + ++ len -= slen; + if (ovl_is_private_xattr(s)) { + res -= slen; +- memmove(s, s + slen, res - off); ++ memmove(s, s + slen, len); + } else { +- off += slen; ++ s += slen; + } + } + diff --git a/target/linux/generic/patches-4.4/051-0007-ovl-fix-workdir-creation.patch b/target/linux/generic/patches-4.4/051-0007-ovl-fix-workdir-creation.patch new file mode 100644 index 0000000000..1f50170bfc --- /dev/null +++ b/target/linux/generic/patches-4.4/051-0007-ovl-fix-workdir-creation.patch @@ -0,0 +1,35 @@ +From e1ff3dd1ae52cef5b5373c8cc4ad949c2c25a71c Mon Sep 17 00:00:00 2001 +From: Miklos Szeredi +Date: Mon, 5 Sep 2016 13:55:20 +0200 +Subject: [PATCH] ovl: fix workdir creation + +Workdir creation fails in latest kernel. + +Fix by allowing EOPNOTSUPP as a valid return value from +vfs_removexattr(XATTR_NAME_POSIX_ACL_*). Upper filesystem may not support +ACL and still be perfectly able to support overlayfs. + +Reported-by: Martin Ziegler +Signed-off-by: Miklos Szeredi +Fixes: c11b9fdd6a61 ("ovl: remove posix_acl_default from workdir") +Cc: +--- + fs/overlayfs/super.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/fs/overlayfs/super.c ++++ b/fs/overlayfs/super.c +@@ -794,11 +794,11 @@ retry: + goto out_dput; + + err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_DEFAULT); +- if (err && err != -ENODATA) ++ if (err && err != -ENODATA && err != -EOPNOTSUPP) + goto out_dput; + + err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_ACCESS); +- if (err && err != -ENODATA) ++ if (err && err != -ENODATA && err != -EOPNOTSUPP) + goto out_dput; + + /* Clear any inherited mode bits */ -- 2.25.1