/*
* Copyright (C) 2011 Novell Inc.
* Copyright (C) 2016 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#include <linux/fs.h>
#include <linux/cred.h>
#include <linux/namei.h>
#include <linux/xattr.h>
#include <linux/ratelimit.h>
#include <linux/mount.h>
#include <linux/exportfs.h>
#include "overlayfs.h"
#include "ovl_entry.h"
struct ovl_lookup_data {
struct qstr name;
bool is_dir;
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_KERNEL);
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 int ovl_acceptable(void *ctx, struct dentry *dentry)
{
return 1;
}
static struct ovl_fh *ovl_get_origin_fh(struct dentry *dentry)
{
int res;
struct ovl_fh *fh = NULL;
res = vfs_getxattr(dentry, OVL_XATTR_ORIGIN, NULL, 0);
if (res < 0) {
if (res == -ENODATA || res == -EOPNOTSUPP)
return NULL;
goto fail;
}
/* Zero size value means "copied up but origin unknown" */
if (res == 0)
return NULL;
fh = kzalloc(res, GFP_KERNEL);
if (!fh)
return ERR_PTR(-ENOMEM);
res = vfs_getxattr(dentry, OVL_XATTR_ORIGIN, fh, res);
if (res < 0)
goto fail;
if (res < sizeof(struct ovl_fh) || res < fh->len)
goto invalid;
if (fh->magic != OVL_FH_MAGIC)
goto invalid;
/* Treat larger version and unknown flags as "origin unknown" */
if (fh->version > OVL_FH_VERSION || fh->flags & ~OVL_FH_FLAG_ALL)
goto out;
/* Treat endianness mismatch as "origin unknown" */
if (!(fh->flags & OVL_FH_FLAG_ANY_ENDIAN) &&
(fh->flags & OVL_FH_FLAG_BIG_ENDIAN) != OVL_FH_FLAG_CPU_ENDIAN)
goto out;
return fh;
out:
kfree(fh);
return NULL;
fail:
pr_warn_ratelimited("overlayfs: failed to get origin (%i)\n", res);
goto out;
invalid:
pr_warn_ratelimited("overlayfs: invalid origin (%*phN)\n", res, fh);
goto out;
}
static struct dentry *ovl_get_origin(struct dentry *dentry,
struct vfsmount *mnt)
{
struct dentry *origin = NULL;
struct ovl_fh *fh = ovl_get_origin_fh(dentry);
int bytes;
if (IS_ERR_OR_NULL(fh))
return (struct dentry *)fh;
/*
* Make sure that the stored uuid matches the uuid of the lower
* layer where file handle will be decoded.
*/
if (!uuid_equal(&fh->uuid, &mnt->mnt_sb->s_uuid))
goto out;
bytes = (fh->len - offsetof(struct ovl_fh, fid));
origin = exportfs_decode_fh(mnt, (struct fid *)fh->fid,
bytes >> 2, (int)fh->type,
ovl_acceptable, NULL);
if (IS_ERR(origin)) {
/* Treat stale file handle as "origin unknown" */
if (origin == ERR_PTR(-ESTALE))
origin = NULL;
goto out;
}
if (ovl_dentry_weird(origin) ||