kernel: backport upstream overlayfs fixes
authorRafał Miłecki <rafal@milecki.pl>
Tue, 6 Sep 2016 19:32:47 +0000 (21:32 +0200)
committerRafał Miłecki <rafal@milecki.pl>
Tue, 6 Sep 2016 19:37:49 +0000 (21:37 +0200)
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 <rafal@milecki.pl>
target/linux/generic/patches-4.4/051-0001-ovl-rename-is_merge-to-is_lowest.patch [new file with mode: 0644]
target/linux/generic/patches-4.4/051-0002-ovl-override-creds-with-the-ones-from-the-superblock.patch [new file with mode: 0644]
target/linux/generic/patches-4.4/051-0003-ovl-don-t-copy-up-opaqueness.patch [new file with mode: 0644]
target/linux/generic/patches-4.4/051-0004-ovl-remove-posix_acl_default-from-workdir.patch [new file with mode: 0644]
target/linux/generic/patches-4.4/051-0005-ovl-proper-cleanup-of-workdir.patch [new file with mode: 0644]
target/linux/generic/patches-4.4/051-0006-ovl-listxattr-use-strnlen.patch [new file with mode: 0644]
target/linux/generic/patches-4.4/051-0007-ovl-fix-workdir-creation.patch [new file with mode: 0644]

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 (file)
index 0000000..79140b9
--- /dev/null
@@ -0,0 +1,72 @@
+From 56656e960b555cb98bc414382566dcb59aae99a2 Mon Sep 17 00:00:00 2001
+From: Miklos Szeredi <mszeredi@redhat.com>
+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 <mszeredi@redhat.com>
+---
+ 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 (file)
index 0000000..8a7d00a
--- /dev/null
@@ -0,0 +1,336 @@
+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]);
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 (file)
index 0000000..da9e380
--- /dev/null
@@ -0,0 +1,73 @@
+From 0956254a2d5b9e2141385514553aeef694dfe3b5 Mon Sep 17 00:00:00 2001
+From: Miklos Szeredi <mszeredi@redhat.com>
+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 <dmcg@drizz.net>
+Link: https://bugzilla.kernel.org/show_bug.cgi?id=151291
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Cc: <stable@vger.kernel.org>
+---
+ 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 (file)
index 0000000..1785c9a
--- /dev/null
@@ -0,0 +1,49 @@
+From c11b9fdd6a612f376a5e886505f1c54c16d8c380 Mon Sep 17 00:00:00 2001
+From: Miklos Szeredi <mszeredi@redhat.com>
+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 <mszeredi@redhat.com>
+Cc: <stable@vger.kernel.org>
+---
+ 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 (file)
index 0000000..eb095b7
--- /dev/null
@@ -0,0 +1,131 @@
+From eea2fb4851e9dcbab6b991aaf47e2e024f1f55a0 Mon Sep 17 00:00:00 2001
+From: Miklos Szeredi <mszeredi@redhat.com>
+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 <mszeredi@redhat.com>
+Cc: <stable@vger.kernel.org>
+---
+ 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 (file)
index 0000000..82ad20d
--- /dev/null
@@ -0,0 +1,53 @@
+From 7cb35119d067191ce9ebc380a599db0b03cbd9d9 Mon Sep 17 00:00:00 2001
+From: Miklos Szeredi <mszeredi@redhat.com>
+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 <mszeredi@redhat.com>
+Cc: <stable@vger.kernel.org>
+---
+ 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 (file)
index 0000000..1f50170
--- /dev/null
@@ -0,0 +1,35 @@
+From e1ff3dd1ae52cef5b5373c8cc4ad949c2c25a71c Mon Sep 17 00:00:00 2001
+From: Miklos Szeredi <mszeredi@redhat.com>
+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 <ziegler@uni-freiburg.de>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+Fixes: c11b9fdd6a61 ("ovl: remove posix_acl_default from workdir")
+Cc: <stable@vger.kernel.org>
+---
+ 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 */