diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 23d64d51f331..35bb956af8e8 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -96,11 +96,15 @@ int ovl_getattr(const struct path *path, struct kstat *stat, WARN_ON_ONCE(stat->dev != lowerstat.dev); /* - * Lower hardlinks are broken on copy up to different + * Lower hardlinks may be broken on copy up to different * upper files, so we cannot use the lower origin st_ino * for those different files, even for the same fs case. + * With inodes index enabled, it is safe to use st_ino + * of an indexed hardlinked origin. The index validates + * that the upper hardlink is not broken. */ - if (is_dir || lowerstat.nlink == 1) + if (is_dir || lowerstat.nlink == 1 || + ovl_test_flag(OVL_INDEX, d_inode(dentry))) stat->ino = lowerstat.ino; } stat->dev = dentry->d_sb->s_dev; diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 0c816e9aa50c..3bec4cb39967 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -378,6 +378,94 @@ fail: goto out; } +/* + * Lookup in indexdir for the index entry of a lower real inode or a copy up + * origin inode. The index entry name is the hex representation of the lower + * inode file handle. + * + * If the index dentry in negative, then either no lower aliases have been + * copied up yet, or aliases have been copied up in older kernels and are + * not indexed. + * + * If the index dentry for a copy up origin inode is positive, but points + * to an inode different than the upper inode, then either the upper inode + * has been copied up and not indexed or it was indexed, but since then + * index dir was cleared. Either way, that index cannot be used to indentify + * the overlay inode. + */ +int ovl_get_index_name(struct dentry *origin, struct qstr *name) +{ + int err; + struct ovl_fh *fh; + char *n, *s; + + fh = ovl_encode_fh(origin, false); + if (IS_ERR(fh)) + return PTR_ERR(fh); + + err = -ENOMEM; + n = kzalloc(fh->len * 2, GFP_TEMPORARY); + if (n) { + s = bin2hex(n, fh, fh->len); + *name = (struct qstr) QSTR_INIT(n, s - n); + err = 0; + } + kfree(fh); + + return err; + +} + +static struct dentry *ovl_lookup_index(struct dentry *dentry, + struct dentry *upper, + struct dentry *origin) +{ + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + struct dentry *index; + struct inode *inode; + struct qstr name; + int err; + + err = ovl_get_index_name(origin, &name); + if (err) + return ERR_PTR(err); + + index = lookup_one_len_unlocked(name.name, ofs->indexdir, name.len); + if (IS_ERR(index)) { + pr_warn_ratelimited("overlayfs: failed inode index lookup (ino=%lu, key=%*s, err=%i);\n" + "overlayfs: mount with '-o index=off' to disable inodes index.\n", + d_inode(origin)->i_ino, name.len, name.name, + err); + goto out; + } + + if (d_is_negative(index)) { + if (upper && d_inode(origin)->i_nlink > 1) { + pr_warn_ratelimited("overlayfs: hard link with origin but no index (ino=%lu).\n", + d_inode(origin)->i_ino); + goto fail; + } + + dput(index); + index = NULL; + } else if (upper && d_inode(index) != d_inode(upper)) { + inode = d_inode(index); + pr_warn_ratelimited("overlayfs: wrong index found (index ino: %lu, upper ino: %lu).\n", + d_inode(index)->i_ino, + d_inode(upper)->i_ino); + goto fail; + } + +out: + kfree(name.name); + return index; + +fail: + dput(index); + index = ERR_PTR(-EIO); + goto out; +} + /* * Returns next layer in stack starting from top. * Returns -1 if this is the last layer. @@ -409,6 +497,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata; struct path *stack = NULL; struct dentry *upperdir, *upperdentry = NULL; + struct dentry *index = NULL; unsigned int ctr = 0; struct inode *inode = NULL; bool upperopaque = false; @@ -506,6 +595,18 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } + /* Lookup index by lower inode and verify it matches upper inode */ + if (ctr && !d.is_dir && ovl_indexdir(dentry->d_sb)) { + struct dentry *origin = stack[0].dentry; + + index = ovl_lookup_index(dentry, upperdentry, origin); + if (IS_ERR(index)) { + err = PTR_ERR(index); + index = NULL; + goto out_put; + } + } + oe = ovl_alloc_entry(ctr); err = -ENOMEM; if (!oe) @@ -515,6 +616,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); dentry->d_fsdata = oe; + if (index && !upperdentry) + upperdentry = dget(index); + if (upperdentry || ctr) { err = -ENOMEM; inode = ovl_get_inode(dentry, upperdentry); @@ -522,9 +626,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, goto out_free_oe; OVL_I(inode)->redirect = upperredirect; + if (index) + ovl_set_flag(OVL_INDEX, inode); } revert_creds(old_cred); + dput(index); kfree(stack); kfree(d.redirect); d_add(dentry, inode); @@ -535,6 +642,7 @@ out_free_oe: dentry->d_fsdata = NULL; kfree(oe); out_put: + dput(index); for (i = 0; i < ctr; i++) dput(stack[i].dentry); kfree(stack); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 58bbd135a7b3..437a0301e1b6 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -28,6 +28,7 @@ enum ovl_path_type { enum ovl_flag { OVL_IMPURE, + OVL_INDEX, }; /* @@ -236,6 +237,7 @@ static inline bool ovl_is_impuredir(struct dentry *dentry) /* namei.c */ int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt, struct dentry *origin, bool is_upper, bool set); +int ovl_get_index_name(struct dentry *origin, struct qstr *name); int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); bool ovl_lower_positive(struct dentry *dentry);