When a lower metacopy has an absolute redirect into a data-only layer, ovl_lookup_layers() defers the data lookup and skips ovl_check_follow_redirect(), but leaves the redirect in d->redirect. If there is no upper dentry and a sibling hardlink was already copied up to the index dir, the index branch calls ovl_check_follow_redirect() again and, with redirect_dir=nofollow (e.g. forced by userxattr), fails the lookup with -EPERM because of the lower data redirect. Take the lowerdata redirect out of d->redirect at the point where the data lookup is deferred, so that the index check only validates the redirect and metacopy state of the index entry. Fixes: 5ef7bcdeecc9 ("ovl: relax redirect/metacopy requirements for lower -> data redirect") Closes: https://sashiko.dev/#/bug/linux-29bea9ea-5376-4e54-88f6-04dabce4db70 Cc: stable@vger.kernel.org Signed-off-by: Amir Goldstein --- fs/overlayfs/namei.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index e25858c8dbcd7..b7fb5bb2e597f 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; @@ -1250,6 +1251,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; @@ -1366,15 +1369,14 @@ 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; + 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); - if (oip.lowerdata_redirect) - d->redirect = NULL; + d->lowerdata_redirect = NULL; ctx->inode = inode; if (ctx->upperdentry && !uppermetacopy) @@ -1427,6 +1429,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); -- 2.56.0