aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@redhat.com>2016-12-16 11:02:56 +0100
committerMiklos Szeredi <mszeredi@redhat.com>2016-12-16 11:02:56 +0100
commit02b69b284cd7815239fabfe895bfef9a9eb5a3ce (patch)
tree1e17c647edb7d60e175a154b3fb9127454c03e3a
parente28edc46b8e29d2a4c10263cd7769e657582fff4 (diff)
downloadlinux-02b69b284cd7815239fabfe895bfef9a9eb5a3ce.tar.gz
ovl: lookup redirects
If a directory has the "trusted.overlay.redirect" xattr, it means that the value of the xattr should be used to find the underlying directory on the next lower layer. The redirect may be relative or absolute. Absolute redirects begin with a slash. A relative redirect means: instead of the current dentry's name use the value of the redirect to find the directory in the next lower layer. Relative redirects must not contain a slash. An absolute redirect means: look up the directory relative to the root of the overlay using the value of the redirect in the next lower layer. Redirects work on lower layers as well. Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
-rw-r--r--fs/overlayfs/namei.c122
-rw-r--r--fs/overlayfs/overlayfs.h1
-rw-r--r--fs/overlayfs/ovl_entry.h1
-rw-r--r--fs/overlayfs/super.c1
4 files changed, 123 insertions, 2 deletions
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index f213297d187e8..9ad48d9202a99 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -10,6 +10,7 @@
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/xattr.h>
+#include <linux/ratelimit.h>
#include "overlayfs.h"
#include "ovl_entry.h"
@@ -19,8 +20,66 @@ struct ovl_lookup_data {
bool opaque;
bool stop;
bool last;
+ char *redirect;
};
+static int ovl_check_redirect(struct dentry *dentry, struct ovl_lookup_data *d,
+ size_t prelen, const char *post)
+{
+ int res;
+ char *s, *next, *buf = NULL;
+
+ res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, NULL, 0);
+ if (res < 0) {
+ if (res == -ENODATA || res == -EOPNOTSUPP)
+ return 0;
+ goto fail;
+ }
+ buf = kzalloc(prelen + res + strlen(post) + 1, GFP_TEMPORARY);
+ if (!buf)
+ return -ENOMEM;
+
+ if (res == 0)
+ goto invalid;
+
+ res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, buf, res);
+ if (res < 0)
+ goto fail;
+ if (res == 0)
+ goto invalid;
+ if (buf[0] == '/') {
+ for (s = buf; *s++ == '/'; s = next) {
+ next = strchrnul(s, '/');
+ if (s == next)
+ goto invalid;
+ }
+ } else {
+ if (strchr(buf, '/') != NULL)
+ goto invalid;
+
+ memmove(buf + prelen, buf, res);
+ memcpy(buf, d->name.name, prelen);
+ }
+
+ strcat(buf, post);
+ kfree(d->redirect);
+ d->redirect = buf;
+ d->name.name = d->redirect;
+ d->name.len = strlen(d->redirect);
+
+ return 0;
+
+err_free:
+ kfree(buf);
+ return 0;
+fail:
+ pr_warn_ratelimited("overlayfs: failed to get redirect (%i)\n", res);
+ goto err_free;
+invalid:
+ pr_warn_ratelimited("overlayfs: invalid redirect (%s)\n", buf);
+ goto err_free;
+}
+
static bool ovl_is_opaquedir(struct dentry *dentry)
{
int res;
@@ -38,6 +97,7 @@ static bool ovl_is_opaquedir(struct dentry *dentry)
static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
const char *name, unsigned int namelen,
+ size_t prelen, const char *post,
struct dentry **ret)
{
struct dentry *this;
@@ -74,6 +134,9 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
d->stop = d->opaque = true;
goto out;
}
+ err = ovl_check_redirect(this, d, prelen, post);
+ if (err)
+ goto out_err;
out:
*ret = this;
return 0;
@@ -91,7 +154,32 @@ out_err:
static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d,
struct dentry **ret)
{
- return ovl_lookup_single(base, d, d->name.name, d->name.len, ret);
+ const char *s = d->name.name;
+ struct dentry *dentry = NULL;
+ int err;
+
+ if (*s != '/')
+ return ovl_lookup_single(base, d, d->name.name, d->name.len,
+ 0, "", ret);
+
+ while (*s++ == '/' && !IS_ERR_OR_NULL(base) && d_can_lookup(base)) {
+ const char *next = strchrnul(s, '/');
+ size_t slen = strlen(s);
+
+ if (WARN_ON(slen > d->name.len) ||
+ WARN_ON(strcmp(d->name.name + d->name.len - slen, s)))
+ return -EIO;
+
+ err = ovl_lookup_single(base, d, s, next - s,
+ d->name.len - slen, next, &base);
+ dput(dentry);
+ if (err)
+ return err;
+ dentry = base;
+ s = next;
+ }
+ *ret = dentry;
+ return 0;
}
/*
@@ -127,6 +215,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int ctr = 0;
struct inode *inode = NULL;
bool upperopaque = false;
+ char *upperredirect = NULL;
struct dentry *this;
unsigned int i;
int err;
@@ -136,6 +225,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
.opaque = false,
.stop = false,
.last = !poe->numlower,
+ .redirect = NULL,
};
if (dentry->d_name.len > ofs->namelen)
@@ -153,12 +243,20 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
err = -EREMOTE;
goto out;
}
+
+ if (d.redirect) {
+ upperredirect = kstrdup(d.redirect, GFP_KERNEL);
+ if (!upperredirect)
+ goto out_put_upper;
+ if (d.redirect[0] == '/')
+ poe = dentry->d_sb->s_root->d_fsdata;
+ }
upperopaque = d.opaque;
}
if (!d.stop && poe->numlower) {
err = -ENOMEM;
- stack = kcalloc(poe->numlower, sizeof(struct path),
+ stack = kcalloc(ofs->numlower, sizeof(struct path),
GFP_TEMPORARY);
if (!stack)
goto out_put_upper;
@@ -178,6 +276,22 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
stack[ctr].dentry = this;
stack[ctr].mnt = lowerpath.mnt;
ctr++;
+
+ if (d.stop)
+ break;
+
+ if (d.redirect &&
+ d.redirect[0] == '/' &&
+ poe != dentry->d_sb->s_root->d_fsdata) {
+ poe = dentry->d_sb->s_root->d_fsdata;
+
+ /* Find the current layer on the root dentry */
+ for (i = 0; i < poe->numlower; i++)
+ if (poe->lowerstack[i].mnt == lowerpath.mnt)
+ break;
+ if (WARN_ON(i == poe->numlower))
+ break;
+ }
}
oe = ovl_alloc_entry(ctr);
@@ -208,9 +322,11 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
revert_creds(old_cred);
oe->opaque = upperopaque;
+ oe->redirect = upperredirect;
oe->__upperdentry = upperdentry;
memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr);
kfree(stack);
+ kfree(d.redirect);
dentry->d_fsdata = oe;
d_add(dentry, inode);
@@ -224,7 +340,9 @@ out_put:
kfree(stack);
out_put_upper:
dput(upperdentry);
+ kfree(upperredirect);
out:
+ kfree(d.redirect);
revert_creds(old_cred);
return ERR_PTR(err);
}
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index f6e4d3539a251..e76d9d529e64c 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -19,6 +19,7 @@ enum ovl_path_type {
#define OVL_XATTR_PREFIX XATTR_TRUSTED_PREFIX "overlay."
#define OVL_XATTR_OPAQUE OVL_XATTR_PREFIX "opaque"
+#define OVL_XATTR_REDIRECT OVL_XATTR_PREFIX "redirect"
#define OVL_ISUPPER_MASK 1UL
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index b10745edfc936..eb29882b6a54d 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -35,6 +35,7 @@ struct ovl_entry {
union {
struct {
u64 version;
+ const char *redirect;
bool opaque;
};
struct rcu_head rcu;
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index aadb25413e6e6..4e44e865b716c 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -37,6 +37,7 @@ static void ovl_dentry_release(struct dentry *dentry)
unsigned int i;
dput(oe->__upperdentry);
+ kfree(oe->redirect);
for (i = 0; i < oe->numlower; i++)
dput(oe->lowerstack[i].dentry);
kfree_rcu(oe, rcu);