Skip to content

Commit

Permalink
ovl: lookup redirects
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Miklos Szeredi committed Dec 16, 2016
1 parent e28edc4 commit 02b69b2
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 2 deletions.
122 changes: 120 additions & 2 deletions fs/overlayfs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -91,7 +154,32 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
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;
}

/*
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -224,7 +340,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
kfree(stack);
out_put_upper:
dput(upperdentry);
kfree(upperredirect);
out:
kfree(d.redirect);
revert_creds(old_cred);
return ERR_PTR(err);
}
Expand Down
1 change: 1 addition & 0 deletions fs/overlayfs/overlayfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions fs/overlayfs/ovl_entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct ovl_entry {
union {
struct {
u64 version;
const char *redirect;
bool opaque;
};
struct rcu_head rcu;
Expand Down
1 change: 1 addition & 0 deletions fs/overlayfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 02b69b2

Please sign in to comment.