diff options
Diffstat (limited to 'fs/overlayfs/namei.c')
-rw-r--r-- | fs/overlayfs/namei.c | 201 |
1 files changed, 157 insertions, 44 deletions
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index cfb3420b7df0..57adf911735f 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -14,6 +14,8 @@ #include <linux/exportfs.h> #include "overlayfs.h" +#include "../internal.h" /* for vfs_path_lookup */ + struct ovl_lookup_data { struct super_block *sb; struct vfsmount *mnt; @@ -24,6 +26,8 @@ struct ovl_lookup_data { bool last; char *redirect; bool metacopy; + /* Referring to last redirect xattr */ + bool absolute_redirect; }; static int ovl_check_redirect(const struct path *path, struct ovl_lookup_data *d, @@ -33,11 +37,13 @@ static int ovl_check_redirect(const struct path *path, struct ovl_lookup_data *d char *buf; struct ovl_fs *ofs = OVL_FS(d->sb); + d->absolute_redirect = false; buf = ovl_get_redirect_xattr(ofs, path, prelen + strlen(post)); if (IS_ERR_OR_NULL(buf)) return PTR_ERR(buf); if (buf[0] == '/') { + d->absolute_redirect = true; /* * One of the ancestor path elements in an absolute path * lookup in ovl_lookup_layer() could have been opaque and @@ -349,6 +355,61 @@ static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d, return 0; } +static int ovl_lookup_data_layer(struct dentry *dentry, const char *redirect, + const struct ovl_layer *layer, + struct path *datapath) +{ + int err; + + err = vfs_path_lookup(layer->mnt->mnt_root, layer->mnt, redirect, + LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS | LOOKUP_NO_XDEV, + datapath); + pr_debug("lookup lowerdata (%pd2, redirect=\"%s\", layer=%d, err=%i)\n", + dentry, redirect, layer->idx, err); + + if (err) + return err; + + err = -EREMOTE; + if (ovl_dentry_weird(datapath->dentry)) + goto out_path_put; + + err = -ENOENT; + /* Only regular file is acceptable as lower data */ + if (!d_is_reg(datapath->dentry)) + goto out_path_put; + + return 0; + +out_path_put: + path_put(datapath); + + return err; +} + +/* Lookup in data-only layers by absolute redirect to layer root */ +static int ovl_lookup_data_layers(struct dentry *dentry, const char *redirect, + struct ovl_path *lowerdata) +{ + struct ovl_fs *ofs = OVL_FS(dentry->d_sb); + const struct ovl_layer *layer; + struct path datapath; + int err = -ENOENT; + int i; + + layer = &ofs->layers[ofs->numlayer - ofs->numdatalayer]; + for (i = 0; i < ofs->numdatalayer; i++, layer++) { + err = ovl_lookup_data_layer(dentry, redirect, layer, &datapath); + if (!err) { + mntput(datapath.mnt); + lowerdata->dentry = datapath.dentry; + lowerdata->layer = layer; + return 0; + } + } + + return err; +} int ovl_check_origin_fh(struct ovl_fs *ofs, struct ovl_fh *fh, bool connected, struct dentry *upperdentry, struct ovl_path **stackp) @@ -356,7 +417,7 @@ int ovl_check_origin_fh(struct ovl_fs *ofs, struct ovl_fh *fh, bool connected, struct dentry *origin = NULL; int i; - for (i = 1; i < ofs->numlayer; i++) { + for (i = 1; i <= ovl_numlowerlayer(ofs); i++) { /* * If lower fs uuid is not unique among lower fs we cannot match * fh->uuid to layer. @@ -790,20 +851,21 @@ fail: */ int ovl_path_next(int idx, struct dentry *dentry, struct path *path) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerstack = ovl_lowerstack(oe); BUG_ON(idx < 0); if (idx == 0) { ovl_path_upper(dentry, path); if (path->dentry) - return oe->numlower ? 1 : -1; + return ovl_numlower(oe) ? 1 : -1; idx++; } - BUG_ON(idx > oe->numlower); - path->dentry = oe->lowerstack[idx - 1].dentry; - path->mnt = oe->lowerstack[idx - 1].layer->mnt; + BUG_ON(idx > ovl_numlower(oe)); + path->dentry = lowerstack[idx - 1].dentry; + path->mnt = lowerstack[idx - 1].layer->mnt; - return (idx < oe->numlower) ? idx + 1 : -1; + return (idx < ovl_numlower(oe)) ? idx + 1 : -1; } /* Fix missing 'origin' xattr */ @@ -827,14 +889,60 @@ static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry, return err; } +/* Lazy lookup of lowerdata */ +int ovl_maybe_lookup_lowerdata(struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + const char *redirect = ovl_lowerdata_redirect(inode); + struct ovl_path datapath = {}; + const struct cred *old_cred; + int err; + + if (!redirect || ovl_dentry_lowerdata(dentry)) + return 0; + + if (redirect[0] != '/') + return -EIO; + + err = ovl_inode_lock_interruptible(inode); + if (err) + return err; + + err = 0; + /* Someone got here before us? */ + if (ovl_dentry_lowerdata(dentry)) + goto out; + + old_cred = ovl_override_creds(dentry->d_sb); + err = ovl_lookup_data_layers(dentry, redirect, &datapath); + revert_creds(old_cred); + if (err) + goto out_err; + + err = ovl_dentry_set_lowerdata(dentry, &datapath); + if (err) + goto out_err; + +out: + ovl_inode_unlock(inode); + dput(datapath.dentry); + + return err; + +out_err: + pr_warn_ratelimited("lazy lowerdata lookup failed (%pd2, err=%i)\n", + dentry, err); + goto out; +} + struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { - struct ovl_entry *oe; + struct ovl_entry *oe = NULL; const struct cred *old_cred; struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - struct ovl_entry *poe = dentry->d_parent->d_fsdata; - struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata; + struct ovl_entry *poe = OVL_E(dentry->d_parent); + struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root); struct ovl_path *stack = NULL, *origin_path = NULL; struct dentry *upperdir, *upperdentry = NULL; struct dentry *origin = NULL; @@ -853,7 +961,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, .is_dir = false, .opaque = false, .stop = false, - .last = ofs->config.redirect_follow ? false : !poe->numlower, + .last = ovl_redirect_follow(ofs) ? false : !ovl_numlower(poe), .redirect = NULL, .metacopy = false, }; @@ -904,21 +1012,20 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, upperopaque = d.opaque; } - if (!d.stop && poe->numlower) { + if (!d.stop && ovl_numlower(poe)) { err = -ENOMEM; - stack = kcalloc(ofs->numlayer - 1, sizeof(struct ovl_path), - GFP_KERNEL); + stack = ovl_stack_alloc(ofs->numlayer - 1); if (!stack) goto out_put_upper; } - for (i = 0; !d.stop && i < poe->numlower; i++) { - struct ovl_path lower = poe->lowerstack[i]; + for (i = 0; !d.stop && i < ovl_numlower(poe); i++) { + struct ovl_path lower = ovl_lowerstack(poe)[i]; - if (!ofs->config.redirect_follow) - d.last = i == poe->numlower - 1; - else - d.last = lower.layer->idx == roe->numlower; + if (!ovl_redirect_follow(ofs)) + d.last = i == ovl_numlower(poe) - 1; + else if (d.is_dir || !ofs->numdatalayer) + d.last = lower.layer->idx == ovl_numlower(roe); d.mnt = lower.layer->mnt; err = ovl_lookup_layer(lower.dentry, &d, &this, false); @@ -995,7 +1102,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, * this attack vector when not necessary. */ err = -EPERM; - if (d.redirect && !ofs->config.redirect_follow) { + if (d.redirect && !ovl_redirect_follow(ofs)) { pr_warn_ratelimited("refusing to follow redirect for (%pd2)\n", dentry); goto out_put; @@ -1011,6 +1118,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } + /* Defer lookup of lowerdata in data-only layers to first access */ + if (d.metacopy && ctr && ofs->numdatalayer && d.absolute_redirect) { + d.metacopy = false; + ctr++; + } + /* * For regular non-metacopy upper dentries, there is no lower * path based lookup, hence ctr will be zero. If a dentry is found @@ -1067,13 +1180,14 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } - oe = ovl_alloc_entry(ctr); - err = -ENOMEM; - if (!oe) - goto out_put; + if (ctr) { + oe = ovl_alloc_entry(ctr); + err = -ENOMEM; + if (!oe) + goto out_put; - memcpy(oe->lowerstack, stack, sizeof(struct ovl_path) * ctr); - dentry->d_fsdata = oe; + ovl_stack_cpy(ovl_lowerstack(oe), stack, ctr); + } if (upperopaque) ovl_dentry_set_opaque(dentry); @@ -1106,14 +1220,16 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (upperdentry || ctr) { struct ovl_inode_params oip = { .upperdentry = upperdentry, - .lowerpath = stack, + .oe = oe, .index = index, - .numlower = ctr, .redirect = upperredirect, - .lowerdata = (ctr > 1 && !d.is_dir) ? - stack[ctr - 1].dentry : NULL, }; + /* Store lowerdata redirect for lazy lookup */ + if (ctr > 1 && !d.is_dir && !stack[ctr - 1].dentry) { + oip.lowerdata_redirect = d.redirect; + d.redirect = NULL; + } inode = ovl_get_inode(dentry->d_sb, &oip); err = PTR_ERR(inode); if (IS_ERR(inode)) @@ -1122,8 +1238,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ovl_set_flag(OVL_UPPERDATA, inode); } - ovl_dentry_update_reval(dentry, upperdentry, - DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE); + ovl_dentry_init_reval(dentry, upperdentry, OVL_I_E(inode)); revert_creds(old_cred); if (origin_path) { @@ -1131,18 +1246,15 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, kfree(origin_path); } dput(index); - kfree(stack); + ovl_stack_free(stack, ctr); kfree(d.redirect); return d_splice_alias(inode, dentry); out_free_oe: - dentry->d_fsdata = NULL; - kfree(oe); + ovl_free_entry(oe); out_put: dput(index); - for (i = 0; i < ctr; i++) - dput(stack[i].dentry); - kfree(stack); + ovl_stack_free(stack, ctr); out_put_upper: if (origin_path) { dput(origin_path->dentry); @@ -1158,7 +1270,7 @@ out: bool ovl_lower_positive(struct dentry *dentry) { - struct ovl_entry *poe = dentry->d_parent->d_fsdata; + struct ovl_entry *poe = OVL_E(dentry->d_parent); const struct qstr *name = &dentry->d_name; const struct cred *old_cred; unsigned int i; @@ -1178,12 +1290,13 @@ bool ovl_lower_positive(struct dentry *dentry) old_cred = ovl_override_creds(dentry->d_sb); /* Positive upper -> have to look up lower to see whether it exists */ - for (i = 0; !done && !positive && i < poe->numlower; i++) { + for (i = 0; !done && !positive && i < ovl_numlower(poe); i++) { struct dentry *this; - struct dentry *lowerdir = poe->lowerstack[i].dentry; + struct ovl_path *parentpath = &ovl_lowerstack(poe)[i]; - this = lookup_one_positive_unlocked(mnt_idmap(poe->lowerstack[i].layer->mnt), - name->name, lowerdir, name->len); + this = lookup_one_positive_unlocked( + mnt_idmap(parentpath->layer->mnt), + name->name, parentpath->dentry, name->len); if (IS_ERR(this)) { switch (PTR_ERR(this)) { case -ENOENT: |