Skip to content

Commit

Permalink
ovl: make sure that real fid is 32bit aligned in memory
Browse files Browse the repository at this point in the history
Seprate on-disk encoding from in-memory and on-wire resresentation
of overlay file handle.

In-memory and on-wire we only ever pass around pointers to struct
ovl_fh, which encapsulates at offset 3 the on-disk format struct
ovl_fb. struct ovl_fb encapsulates at offset 21 the real file handle.
That makes sure that the real file handle is always 32bit aligned
in-memory when passed down to the underlying filesystem.

On-disk format remains the same and store/load are done into
correctly aligned buffer.

New nfs exported file handles are exported with aligned real fid.
Old nfs file handles are copied to an aligned buffer before being
decoded.

Reported-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
  • Loading branch information
Amir Goldstein authored and Miklos Szeredi committed Dec 10, 2019
1 parent 7e63c87 commit cbe7fba
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 73 deletions.
30 changes: 16 additions & 14 deletions fs/overlayfs/copy_up.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,29 +251,31 @@ struct ovl_fh *ovl_encode_real_fh(struct dentry *real, bool is_upper)
WARN_ON(fh_type == FILEID_INVALID))
goto out;

BUILD_BUG_ON(MAX_HANDLE_SZ + offsetof(struct ovl_fh, fid) > 255);
fh_len = offsetof(struct ovl_fh, fid) + buflen;
fh = kmalloc(fh_len, GFP_KERNEL);
/* Make sure the real fid stays 32bit aligned */
BUILD_BUG_ON(OVL_FH_FID_OFFSET % 4);
BUILD_BUG_ON(MAX_HANDLE_SZ + OVL_FH_FID_OFFSET > 255);
fh_len = OVL_FH_FID_OFFSET + buflen;
fh = kzalloc(fh_len, GFP_KERNEL);
if (!fh) {
fh = ERR_PTR(-ENOMEM);
goto out;
}

fh->version = OVL_FH_VERSION;
fh->magic = OVL_FH_MAGIC;
fh->type = fh_type;
fh->flags = OVL_FH_FLAG_CPU_ENDIAN;
fh->fb.version = OVL_FH_VERSION;
fh->fb.magic = OVL_FH_MAGIC;
fh->fb.type = fh_type;
fh->fb.flags = OVL_FH_FLAG_CPU_ENDIAN;
/*
* When we will want to decode an overlay dentry from this handle
* and all layers are on the same fs, if we get a disconncted real
* dentry when we decode fid, the only way to tell if we should assign
* it to upperdentry or to lowerstack is by checking this flag.
*/
if (is_upper)
fh->flags |= OVL_FH_FLAG_PATH_UPPER;
fh->len = fh_len;
fh->uuid = *uuid;
memcpy(fh->fid, buf, buflen);
fh->fb.flags |= OVL_FH_FLAG_PATH_UPPER;
fh->fb.len = fh_len - OVL_FH_WIRE_OFFSET;
fh->fb.uuid = *uuid;
memcpy(fh->fb.fid, buf, buflen);

out:
kfree(buf);
Expand All @@ -300,8 +302,8 @@ int ovl_set_origin(struct dentry *dentry, struct dentry *lower,
/*
* Do not fail when upper doesn't support xattrs.
*/
err = ovl_check_setxattr(dentry, upper, OVL_XATTR_ORIGIN, fh,
fh ? fh->len : 0, 0);
err = ovl_check_setxattr(dentry, upper, OVL_XATTR_ORIGIN, fh->buf,
fh ? fh->fb.len : 0, 0);
kfree(fh);

return err;
Expand All @@ -317,7 +319,7 @@ static int ovl_set_upper_fh(struct dentry *upper, struct dentry *index)
if (IS_ERR(fh))
return PTR_ERR(fh);

err = ovl_do_setxattr(index, OVL_XATTR_UPPER, fh, fh->len, 0);
err = ovl_do_setxattr(index, OVL_XATTR_UPPER, fh->buf, fh->fb.len, 0);

kfree(fh);
return err;
Expand Down
80 changes: 49 additions & 31 deletions fs/overlayfs/export.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,11 @@ static int ovl_check_encode_origin(struct dentry *dentry)
return 1;
}

static int ovl_d_to_fh(struct dentry *dentry, char *buf, int buflen)
static int ovl_dentry_to_fid(struct dentry *dentry, u32 *fid, int buflen)
{
struct ovl_fh *fh = NULL;
int err, enc_lower;
int len;

/*
* Check if we should encode a lower or upper file handle and maybe
Expand All @@ -231,43 +232,29 @@ static int ovl_d_to_fh(struct dentry *dentry, char *buf, int buflen)
return PTR_ERR(fh);

err = -EOVERFLOW;
if (fh->len > buflen)
len = OVL_FH_LEN(fh);
if (len > buflen)
goto fail;

memcpy(buf, (char *)fh, fh->len);
err = fh->len;
memcpy(fid, fh, len);
err = len;

out:
kfree(fh);
return err;

fail:
pr_warn_ratelimited("overlayfs: failed to encode file handle (%pd2, err=%i, buflen=%d, len=%d, type=%d)\n",
dentry, err, buflen, fh ? (int)fh->len : 0,
fh ? fh->type : 0);
dentry, err, buflen, fh ? (int)fh->fb.len : 0,
fh ? fh->fb.type : 0);
goto out;
}

static int ovl_dentry_to_fh(struct dentry *dentry, u32 *fid, int *max_len)
{
int res, len = *max_len << 2;

res = ovl_d_to_fh(dentry, (char *)fid, len);
if (res <= 0)
return FILEID_INVALID;

len = res;

/* Round up to dwords */
*max_len = (len + 3) >> 2;
return OVL_FILEID;
}

static int ovl_encode_fh(struct inode *inode, u32 *fid, int *max_len,
struct inode *parent)
{
struct dentry *dentry;
int type;
int bytes = *max_len << 2;

/* TODO: encode connectable file handles */
if (parent)
Expand All @@ -277,10 +264,14 @@ static int ovl_encode_fh(struct inode *inode, u32 *fid, int *max_len,
if (WARN_ON(!dentry))
return FILEID_INVALID;

type = ovl_dentry_to_fh(dentry, fid, max_len);

bytes = ovl_dentry_to_fid(dentry, fid, bytes);
dput(dentry);
return type;
if (bytes <= 0)
return FILEID_INVALID;

*max_len = bytes >> 2;

return OVL_FILEID_V1;
}

/*
Expand Down Expand Up @@ -777,37 +768,64 @@ static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
goto out;
}

static struct ovl_fh *ovl_fid_to_fh(struct fid *fid, int buflen, int fh_type)
{
struct ovl_fh *fh;

/* If on-wire inner fid is aligned - nothing to do */
if (fh_type == OVL_FILEID_V1)
return (struct ovl_fh *)fid;

if (fh_type != OVL_FILEID_V0)
return ERR_PTR(-EINVAL);

fh = kzalloc(buflen, GFP_KERNEL);
if (!fh)
return ERR_PTR(-ENOMEM);

/* Copy unaligned inner fh into aligned buffer */
memcpy(&fh->fb, fid, buflen - OVL_FH_WIRE_OFFSET);
return fh;
}

static struct dentry *ovl_fh_to_dentry(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
struct dentry *dentry = NULL;
struct ovl_fh *fh = (struct ovl_fh *) fid;
struct ovl_fh *fh = NULL;
int len = fh_len << 2;
unsigned int flags = 0;
int err;

err = -EINVAL;
if (fh_type != OVL_FILEID)
fh = ovl_fid_to_fh(fid, len, fh_type);
err = PTR_ERR(fh);
if (IS_ERR(fh))
goto out_err;

err = ovl_check_fh_len(fh, len);
if (err)
goto out_err;

flags = fh->flags;
flags = fh->fb.flags;
dentry = (flags & OVL_FH_FLAG_PATH_UPPER) ?
ovl_upper_fh_to_d(sb, fh) :
ovl_lower_fh_to_d(sb, fh);
err = PTR_ERR(dentry);
if (IS_ERR(dentry) && err != -ESTALE)
goto out_err;

out:
/* We may have needed to re-align OVL_FILEID_V0 */
if (!IS_ERR_OR_NULL(fh) && fh != (void *)fid)
kfree(fh);

return dentry;

out_err:
pr_warn_ratelimited("overlayfs: failed to decode file handle (len=%d, type=%d, flags=%x, err=%i)\n",
len, fh_type, flags, err);
return ERR_PTR(err);
fh_len, fh_type, flags, err);
dentry = ERR_PTR(err);
goto out;
}

static struct dentry *ovl_fh_to_parent(struct super_block *sb, struct fid *fid,
Expand Down
44 changes: 22 additions & 22 deletions fs/overlayfs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,21 @@ static int ovl_acceptable(void *ctx, struct dentry *dentry)
* Return -ENODATA for "origin unknown".
* Return <0 for an invalid file handle.
*/
int ovl_check_fh_len(struct ovl_fh *fh, int fh_len)
int ovl_check_fb_len(struct ovl_fb *fb, int fb_len)
{
if (fh_len < sizeof(struct ovl_fh) || fh_len < fh->len)
if (fb_len < sizeof(struct ovl_fb) || fb_len < fb->len)
return -EINVAL;

if (fh->magic != OVL_FH_MAGIC)
if (fb->magic != OVL_FH_MAGIC)
return -EINVAL;

/* Treat larger version and unknown flags as "origin unknown" */
if (fh->version > OVL_FH_VERSION || fh->flags & ~OVL_FH_FLAG_ALL)
if (fb->version > OVL_FH_VERSION || fb->flags & ~OVL_FH_FLAG_ALL)
return -ENODATA;

/* 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)
if (!(fb->flags & OVL_FH_FLAG_ANY_ENDIAN) &&
(fb->flags & OVL_FH_FLAG_BIG_ENDIAN) != OVL_FH_FLAG_CPU_ENDIAN)
return -ENODATA;

return 0;
Expand All @@ -119,15 +119,15 @@ static struct ovl_fh *ovl_get_fh(struct dentry *dentry, const char *name)
if (res == 0)
return NULL;

fh = kzalloc(res, GFP_KERNEL);
fh = kzalloc(res + OVL_FH_WIRE_OFFSET, GFP_KERNEL);
if (!fh)
return ERR_PTR(-ENOMEM);

res = vfs_getxattr(dentry, name, fh, res);
res = vfs_getxattr(dentry, name, fh->buf, res);
if (res < 0)
goto fail;

err = ovl_check_fh_len(fh, res);
err = ovl_check_fb_len(&fh->fb, res);
if (err < 0) {
if (err == -ENODATA)
goto out;
Expand Down Expand Up @@ -158,12 +158,12 @@ struct dentry *ovl_decode_real_fh(struct ovl_fh *fh, struct vfsmount *mnt,
* 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))
if (!uuid_equal(&fh->fb.uuid, &mnt->mnt_sb->s_uuid))
return NULL;

bytes = (fh->len - offsetof(struct ovl_fh, fid));
real = exportfs_decode_fh(mnt, (struct fid *)fh->fid,
bytes >> 2, (int)fh->type,
bytes = (fh->fb.len - offsetof(struct ovl_fb, fid));
real = exportfs_decode_fh(mnt, (struct fid *)fh->fb.fid,
bytes >> 2, (int)fh->fb.type,
connected ? ovl_acceptable : NULL, mnt);
if (IS_ERR(real)) {
/*
Expand All @@ -173,7 +173,7 @@ struct dentry *ovl_decode_real_fh(struct ovl_fh *fh, struct vfsmount *mnt,
* index entries correctly.
*/
if (real == ERR_PTR(-ESTALE) &&
!(fh->flags & OVL_FH_FLAG_PATH_UPPER))
!(fh->fb.flags & OVL_FH_FLAG_PATH_UPPER))
real = NULL;
return real;
}
Expand Down Expand Up @@ -410,7 +410,7 @@ static int ovl_verify_fh(struct dentry *dentry, const char *name,
if (IS_ERR(ofh))
return PTR_ERR(ofh);

if (fh->len != ofh->len || memcmp(fh, ofh, fh->len))
if (fh->fb.len != ofh->fb.len || memcmp(&fh->fb, &ofh->fb, fh->fb.len))
err = -ESTALE;

kfree(ofh);
Expand Down Expand Up @@ -441,7 +441,7 @@ int ovl_verify_set_fh(struct dentry *dentry, const char *name,

err = ovl_verify_fh(dentry, name, fh);
if (set && err == -ENODATA)
err = ovl_do_setxattr(dentry, name, fh, fh->len, 0);
err = ovl_do_setxattr(dentry, name, fh->buf, fh->fb.len, 0);
if (err)
goto fail;

Expand Down Expand Up @@ -515,20 +515,20 @@ int ovl_verify_index(struct ovl_fs *ofs, struct dentry *index)
goto fail;

err = -EINVAL;
if (index->d_name.len < sizeof(struct ovl_fh)*2)
if (index->d_name.len < sizeof(struct ovl_fb)*2)
goto fail;

err = -ENOMEM;
len = index->d_name.len / 2;
fh = kzalloc(len, GFP_KERNEL);
fh = kzalloc(len + OVL_FH_WIRE_OFFSET, GFP_KERNEL);
if (!fh)
goto fail;

err = -EINVAL;
if (hex2bin((u8 *)fh, index->d_name.name, len))
if (hex2bin(fh->buf, index->d_name.name, len))
goto fail;

err = ovl_check_fh_len(fh, len);
err = ovl_check_fb_len(&fh->fb, len);
if (err)
goto fail;

Expand Down Expand Up @@ -607,11 +607,11 @@ static int ovl_get_index_name_fh(struct ovl_fh *fh, struct qstr *name)
{
char *n, *s;

n = kcalloc(fh->len, 2, GFP_KERNEL);
n = kcalloc(fh->fb.len, 2, GFP_KERNEL);
if (!n)
return -ENOMEM;

s = bin2hex(n, fh, fh->len);
s = bin2hex(n, fh->buf, fh->fb.len);
*name = (struct qstr) QSTR_INIT(n, s - n);

return 0;
Expand Down
Loading

0 comments on commit cbe7fba

Please sign in to comment.