diff options
Diffstat (limited to 'fs/overlayfs/namei.c')
| -rw-r--r-- | fs/overlayfs/namei.c | 61 |
1 files changed, 49 insertions, 12 deletions
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 64d63b260709..ddf60254888a 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -27,6 +27,7 @@ struct ovl_lookup_data { bool last; char *redirect; char *upperredirect; + char *lowerdata_redirect; int metacopy; /* Referring to last redirect xattr */ bool absolute_redirect; @@ -39,13 +40,12 @@ 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; + d->absolute_redirect = buf[0] == '/'; + if (d->absolute_redirect) { /* * One of the ancestor path elements in an absolute path * lookup in ovl_lookup_layer() could have been opaque and @@ -1047,6 +1047,14 @@ out: out_err: pr_warn_ratelimited("lazy lowerdata lookup failed (%pd2, err=%i)\n", dentry, err); + /* + * The data of a metacopy file is missing or is not a regular file in + * the data-only layers: the layers are corrupted. Do not return the + * error of the lookup (e.g. ENOENT) to the caller, it would make the + * file look like it does not exist. + */ + if (err != -ENOMEM) + err = -EIO; goto out; } @@ -1079,7 +1087,8 @@ static bool ovl_check_follow_redirect(struct ovl_lookup_data *d) pr_warn_ratelimited("refusing to follow metacopy origin for (%pd2)\n", d->dentry); return false; } - if ((d->redirect || d->upperredirect) && !ovl_redirect_follow(ofs)) { + /* Check follow to lower layer, redirects to lowerdata are not checked here */ + if ((d->redirect || d->upperredirect) && !ovl_redirect_follow_lower(ofs)) { pr_warn_ratelimited("refusing to follow redirect for (%pd2)\n", d->dentry); return false; } @@ -1097,13 +1106,26 @@ struct ovl_lookup_ctx { unsigned int ctr; }; +/* + * Without metacopy=on, the absolute redirect of a lower metacopy file is + * followed only into the data-only layers. + */ +static bool ovl_is_lowerdata_redirect(struct ovl_lookup_ctx *ctx, + struct ovl_lookup_data *d) +{ + struct ovl_fs *ofs = OVL_FS(d->sb); + + return ofs->numdatalayer && !ofs->config.metacopy && ctx->ctr && + d->metacopy && d->absolute_redirect; +} + static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data *d) { struct dentry *dentry = ctx->dentry; struct ovl_fs *ofs = OVL_FS(dentry->d_sb); struct ovl_entry *poe = OVL_E(dentry->d_parent); struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root); - bool check_redirect = (ovl_redirect_follow(ofs) || ofs->numdatalayer); + bool check_redirect = ovl_redirect_follow(ofs); struct dentry *upperdir; struct dentry *this; struct dentry *origin = NULL; @@ -1234,6 +1256,10 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data ctx->ctr++; } + /* Lookup of lowerdata in the data-only layers only */ + if (ovl_is_lowerdata_redirect(ctx, d)) + d->stop = true; + if (d->stop) break; @@ -1251,6 +1277,8 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data if (d->metacopy && ctx->ctr && ofs->numdatalayer && d->absolute_redirect) { d->metacopy = 0; ctx->ctr++; + d->lowerdata_redirect = d->redirect; + d->redirect = NULL; } else if (!ovl_check_follow_redirect(d)) { err = -EPERM; return err; @@ -1367,24 +1395,32 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data }; /* Store lowerdata redirect for lazy lookup */ - if (ctx->ctr > 1 && !d->is_dir && !ctx->stack[ctx->ctr - 1].dentry) { - oip.lowerdata_redirect = d->redirect; - d->redirect = NULL; - } + if (d->lowerdata_redirect) + oip.lowerdata_redirect = d->lowerdata_redirect; inode = ovl_get_inode(dentry->d_sb, &oip); if (IS_ERR(inode)) return PTR_ERR(inode); + d->lowerdata_redirect = NULL; + ctx->inode = inode; if (ctx->upperdentry && !uppermetacopy) ovl_set_flag(OVL_UPPERDATA, ctx->inode); if (metacopy_size > OVL_METACOPY_MIN_SIZE) ovl_set_flag(OVL_HAS_DIGEST, ctx->inode); - } - ovl_dentry_init_reval(dentry, ctx->upperdentry, OVL_I_E(ctx->inode)); + /* + * ovl_get_inode() puts ctx->upperdentry if it finds a cached + * inode, e.g. for another hard link, so use the upper dentry + * of the inode. + */ + ovl_dentry_init_reval(dentry, ovl_i_dentry_upper(inode), + OVL_I_E(inode)); + } else { + ovl_dentry_init_reval(dentry, NULL, NULL); + } return 0; } @@ -1394,7 +1430,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, { struct ovl_fs *ofs = OVL_FS(dentry->d_sb); struct ovl_entry *poe = OVL_E(dentry->d_parent); - bool check_redirect = (ovl_redirect_follow(ofs) || ofs->numdatalayer); + bool check_redirect = ovl_redirect_follow(ofs); int err; struct ovl_lookup_ctx ctx = { .dentry = dentry, @@ -1419,6 +1455,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, dput(ctx.index); ovl_stack_free(ctx.stack, ctx.ctr); kfree(d.redirect); + kfree(d.lowerdata_redirect); if (err) { ovl_free_entry(ctx.oe); |
