diff mbox

[v3,08/16] ovl: validate lower layer uuid and root on redirect by fh

Message ID 1493242518-15266-9-git-send-email-amir73il@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Amir Goldstein April 26, 2017, 9:35 p.m. UTC
On copy up, we store xattr overlay.origin.uuid and
overlay.origin.root along with overlay.origin.fh.

Before decoding the file handle at overlay.origin.fh verify:
- All lower layers are on the same fs
- UUID of lower fs matches the stored uuid

If there are more than one lower layer, find the lower layer mount
in which origin.fh should be decoded by decoding origin.root and
matching the result to a lower layer root dentry.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/overlayfs/namei.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 75 insertions(+), 1 deletion(-)

Comments

Amir Goldstein April 27, 2017, 7:15 a.m. UTC | #1
On Thu, Apr 27, 2017 at 12:35 AM, Amir Goldstein <amir73il@gmail.com> wrote:
> On copy up, we store xattr overlay.origin.uuid and
> overlay.origin.root along with overlay.origin.fh.
>
> Before decoding the file handle at overlay.origin.fh verify:
> - All lower layers are on the same fs
> - UUID of lower fs matches the stored uuid
>
> If there are more than one lower layer, find the lower layer mount
> in which origin.fh should be decoded by decoding origin.root and
> matching the result to a lower layer root dentry.
>
> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> ---
>  fs/overlayfs/namei.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 75 insertions(+), 1 deletion(-)
>
> diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
> index 28d54e3..970d8158 100644
> --- a/fs/overlayfs/namei.c
> +++ b/fs/overlayfs/namei.c
> @@ -27,6 +27,8 @@ struct ovl_lookup_data {
>         char *redirect;         /* - path to follow */
>         bool by_fh;             /* redirect by file handle: */
>         struct ovl_fh *fh;      /* - file handle to follow */
> +       struct ovl_fh *rootfh;  /* - file handle of layer root */
> +       unsigned char uuid[16]; /* - uuid of layer filesystem */
>  };
>
>  static int ovl_check_redirect(struct dentry *dentry, struct ovl_lookup_data *d,
> @@ -121,8 +123,24 @@ static struct ovl_fh *ovl_get_fh(struct dentry *dentry, const char *name)
>  static int ovl_check_redirect_fh(struct dentry *dentry,
>                                  struct ovl_lookup_data *d)
>  {
> +       int res;
> +
>         kfree(d->fh);
>         d->fh = ovl_get_fh(dentry, OVL_XATTR_ORIGIN_FH);
> +       kfree(d->rootfh);
> +       d->rootfh = ovl_get_fh(dentry, OVL_XATTR_ORIGIN_ROOT);
> +
> +       res = vfs_getxattr(dentry, OVL_XATTR_ORIGIN_UUID, d->uuid,
> +                          sizeof(d->uuid));
> +       if (res == sizeof(d->uuid))
> +               return 0;
> +
> +       if (res != -ENODATA && res != -EOPNOTSUPP) {
> +               pr_warn_ratelimited("overlayfs: failed to get %s (%i)\n",
> +                                   OVL_XATTR_ORIGIN_UUID, res);
> +       }
> +
> +       memset(d->uuid, 0, sizeof(d->uuid));
>         return 0;
>  }
>
> @@ -130,6 +148,9 @@ static void ovl_reset_redirect_fh(struct ovl_lookup_data *d)
>  {
>         kfree(d->fh);
>         d->fh = NULL;
> +       kfree(d->rootfh);
> +       d->rootfh = NULL;
> +       memset(d->uuid, 0, sizeof(d->uuid));
>  }
>
>  static bool ovl_is_opaquedir(struct dentry *dentry)
> @@ -309,6 +330,53 @@ static int ovl_lookup_layer_fh(struct vfsmount *mnt, struct ovl_lookup_data *d,
>         return ovl_lookup_data(this, d, 0, "", ret);
>  }
>
> +static int ovl_is_dir(void *ctx, struct dentry *dentry)
> +{
> +       return d_is_dir(dentry);
> +}
> +
> +/* Find lower layer index by layer root file handle and uuid */
> +static int ovl_find_layer_by_fh(struct dentry *dentry, struct ovl_lookup_data *d)
> +{
> +       struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata;
> +       struct super_block *lower_sb = ovl_same_lower_sb(dentry->d_sb);
> +       struct dentry *this;
> +       int i;
> +
> +       /*
> +        * For now, we only support lookup by fh for all lower layers on the
> +        * same sb.  Not all filesystems set sb->s_uuid.  For those who don't
> +        * this code will compare zeros, which at least ensures us that the
> +        * file handles are not crossing from filesystem with sb->s_uuid to
> +        * a filesystem without sb->s_uuid and vice versa.

Nah. On second thought, it's better to disable redirect_fh at mount time
if lower s_uuid is NULL.

> +        */
> +       if (!lower_sb || memcmp(lower_sb->s_uuid, &d->uuid, sizeof(d->uuid)))
> +               return -1;
> +
> +       /* Don't bother verifying rootfh with a single lower layer */
> +       if (roe->numlower == 1)
> +               return 0;
> +
> +       /*
> +        * Layer root dentries are pinned, there are no aliases for dirs, and
> +        * all lower layers are on the same sb.  If rootfh is correct,
> +        * exportfs_decode_fh() will find it in dcache and return the only
> +        * instance, regardless of the mnt argument and we can compare the
> +        * returned pointer with the pointers in lowerstack.
> +        */
> +       this = ovl_decode_fh(roe->lowerstack[0].mnt, d->rootfh, ovl_is_dir);
> +       if (IS_ERR(this))
> +               return -1;
> +
> +       for (i = 0; i < roe->numlower; i++) {
> +               if (this == roe->lowerstack[i].dentry)
> +                       break;
> +       }
> +
> +       dput(this);
> +       return i < roe->numlower ? i : -1;
> +}
> +
>  /*
>   * Returns next layer in stack starting from top.
>   * Returns -1 if this is the last layer.
> @@ -357,6 +425,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
>                 .redirect = NULL,
>                 .by_fh = true,
>                 .fh = NULL,
> +               .rootfh = NULL,
>         };
>
>         if (dentry->d_name.len > ofs->namelen)
> @@ -405,8 +474,13 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
>         /* Try to lookup lower layers by file handle */
>         d.by_path = false;
>         if (!d.stop && d.fh) {
> -               struct vfsmount *lowermnt = roe->lowerstack[0].mnt;
> +               struct vfsmount *lowermnt;
> +               int layer = ovl_find_layer_by_fh(dentry, &d);
> +
> +               if (layer < 0 || layer >= roe->numlower)
> +                       goto lookup_by_path;
>
> +               lowermnt = roe->lowerstack[layer].mnt;
>                 d.last = true;
>                 err = ovl_lookup_layer_fh(lowermnt, &d, &this);
>                 if (err)
> --
> 2.7.4
>
diff mbox

Patch

diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index 28d54e3..970d8158 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -27,6 +27,8 @@  struct ovl_lookup_data {
 	char *redirect;		/* - path to follow */
 	bool by_fh;		/* redirect by file handle: */
 	struct ovl_fh *fh;	/* - file handle to follow */
+	struct ovl_fh *rootfh;	/* - file handle of layer root */
+	unsigned char uuid[16];	/* - uuid of layer filesystem */
 };
 
 static int ovl_check_redirect(struct dentry *dentry, struct ovl_lookup_data *d,
@@ -121,8 +123,24 @@  static struct ovl_fh *ovl_get_fh(struct dentry *dentry, const char *name)
 static int ovl_check_redirect_fh(struct dentry *dentry,
 				 struct ovl_lookup_data *d)
 {
+	int res;
+
 	kfree(d->fh);
 	d->fh = ovl_get_fh(dentry, OVL_XATTR_ORIGIN_FH);
+	kfree(d->rootfh);
+	d->rootfh = ovl_get_fh(dentry, OVL_XATTR_ORIGIN_ROOT);
+
+	res = vfs_getxattr(dentry, OVL_XATTR_ORIGIN_UUID, d->uuid,
+			   sizeof(d->uuid));
+	if (res == sizeof(d->uuid))
+		return 0;
+
+	if (res != -ENODATA && res != -EOPNOTSUPP) {
+		pr_warn_ratelimited("overlayfs: failed to get %s (%i)\n",
+				    OVL_XATTR_ORIGIN_UUID, res);
+	}
+
+	memset(d->uuid, 0, sizeof(d->uuid));
 	return 0;
 }
 
@@ -130,6 +148,9 @@  static void ovl_reset_redirect_fh(struct ovl_lookup_data *d)
 {
 	kfree(d->fh);
 	d->fh = NULL;
+	kfree(d->rootfh);
+	d->rootfh = NULL;
+	memset(d->uuid, 0, sizeof(d->uuid));
 }
 
 static bool ovl_is_opaquedir(struct dentry *dentry)
@@ -309,6 +330,53 @@  static int ovl_lookup_layer_fh(struct vfsmount *mnt, struct ovl_lookup_data *d,
 	return ovl_lookup_data(this, d, 0, "", ret);
 }
 
+static int ovl_is_dir(void *ctx, struct dentry *dentry)
+{
+	return d_is_dir(dentry);
+}
+
+/* Find lower layer index by layer root file handle and uuid */
+static int ovl_find_layer_by_fh(struct dentry *dentry, struct ovl_lookup_data *d)
+{
+	struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata;
+	struct super_block *lower_sb = ovl_same_lower_sb(dentry->d_sb);
+	struct dentry *this;
+	int i;
+
+	/*
+	 * For now, we only support lookup by fh for all lower layers on the
+	 * same sb.  Not all filesystems set sb->s_uuid.  For those who don't
+	 * this code will compare zeros, which at least ensures us that the
+	 * file handles are not crossing from filesystem with sb->s_uuid to
+	 * a filesystem without sb->s_uuid and vice versa.
+	 */
+	if (!lower_sb || memcmp(lower_sb->s_uuid, &d->uuid, sizeof(d->uuid)))
+		return -1;
+
+	/* Don't bother verifying rootfh with a single lower layer */
+	if (roe->numlower == 1)
+		return 0;
+
+	/*
+	 * Layer root dentries are pinned, there are no aliases for dirs, and
+	 * all lower layers are on the same sb.  If rootfh is correct,
+	 * exportfs_decode_fh() will find it in dcache and return the only
+	 * instance, regardless of the mnt argument and we can compare the
+	 * returned pointer with the pointers in lowerstack.
+	 */
+	this = ovl_decode_fh(roe->lowerstack[0].mnt, d->rootfh, ovl_is_dir);
+	if (IS_ERR(this))
+		return -1;
+
+	for (i = 0; i < roe->numlower; i++) {
+		if (this == roe->lowerstack[i].dentry)
+			break;
+	}
+
+	dput(this);
+	return i < roe->numlower ? i : -1;
+}
+
 /*
  * Returns next layer in stack starting from top.
  * Returns -1 if this is the last layer.
@@ -357,6 +425,7 @@  struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
 		.redirect = NULL,
 		.by_fh = true,
 		.fh = NULL,
+		.rootfh = NULL,
 	};
 
 	if (dentry->d_name.len > ofs->namelen)
@@ -405,8 +474,13 @@  struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
 	/* Try to lookup lower layers by file handle */
 	d.by_path = false;
 	if (!d.stop && d.fh) {
-		struct vfsmount *lowermnt = roe->lowerstack[0].mnt;
+		struct vfsmount *lowermnt;
+		int layer = ovl_find_layer_by_fh(dentry, &d);
+
+		if (layer < 0 || layer >= roe->numlower)
+			goto lookup_by_path;
 
+		lowermnt = roe->lowerstack[layer].mnt;
 		d.last = true;
 		err = ovl_lookup_layer_fh(lowermnt, &d, &this);
 		if (err)