overlayfs fixes for 7.3

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE9zuTYTs0RXF+Ke33EVvVyTe/1WoFAmrkiJ8ACgkQEVvVyTe/
 1WqORg//XFzfWHcZWj9xutS0rNLnMHvvAa/aXO+7+V5/Dq6/12iQnKjzCRHIGDbj
 Pyb/PY+foJuXrNu3kqM4xxtX77zB628px3KUlYXSS0FmwO1sRu6Q+NERsHUsAoaM
 oFeWopsYODB+IsUR1saZylaAIhEV49fn4CWGLNZA+pqWQSIRgMXixJleacPWph5+
 k4r/hmVbXc3BkBZdtpUdfgSsctowv402Y/qMYzY8wIDYcDA5d5hhT57mEEt2Y7KY
 lQuArvn6wlx2Qds2vudYDajJjNJt3zpSNhc7omLlyP+0b+JA1F+xZQ3pWdUQZxus
 FaO9L4rM1lfqA3TTKIpsoWE84YpfWguwpxZInYfMHwRnrRy59lhGxuOeslBWOXqe
 skB/j+pkIpYc/Eq54GffC2NdNpeHa7LOqfJiWVZGeGMhGRLSyNpIYQWmbENBqDVz
 GN/4ozM1bbeTmgdzVYt3WAcy+ZbeNSuLBfvY77At4qJYkJbv5oPXetMTW6XXiQal
 EGnbLCY6TunIEGQIp+OLzZC5fpu+AwGFr+ZSXWt0bLnvpSm/pXjzYdM9fv2kFYCc
 1kPXkvx0LkjIO0g6u4ejCLWDsRCaPd0QdVLnyEnwqi5fBOGBHCflbVAAy0PMqe/h
 SZplOOwux/bknMtWNbuDER6Y8EjP3Fl36fdmLem49z4KJH0y1Uc=
 =HGfC
 -----END PGP SIGNATURE-----

Merge tag 'ovl-fixes-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs

Pull overlayfs fixes from Amir Goldstein:

 - Fixes for bugs found by Chris Mason's AI review, several of them
   reachable by unprivileged users: an info leak, use-after-free and
   NULL deref bugs, an RCU walk stall with remote layers and data loss
   with nfs_export

 - Fix copy up of files with an empty security.capability xattr, which
   any user could set to block copy up

 - Fix a regression from v6.16 with the override_creds mount option

 - Two casefold fixes

* tag 'ovl-fixes-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs:
  ovl: check the casefold flag of directories only
  ovl: reserve space for the casefolded name terminator
  ovl: fix mount setup failing when override_creds drops CAP_SYS_ADMIN
  selftests/ovl: fix detached layer mountinfo expectations
  ovl: set the revalidate flags after d_instantiate()
  ovl: always encode the origin fh when index is enabled
  ovl: read the nlink xattr of every indexed non-dir
  ovl: skip copy up of empty security.capability
  ovl: fix a torn path in ovl_get_redirect()
  ovl: do not copy up a dentry that was removed
  ovl: hold a reference on the parent in readdir
  ovl: use the pinned upper dir in ovl_fix_origin()
  ovl: do not take a name snapshot in RCU walk
  vfs: add try_take_dentry_name_snapshot()
  ovl: take a snapshot of the name for copy up
  ovl: check that the parents of volatile/dirty are dirs
  ovl: add assertions in dir cache code
  ovl: set OVL_WHITEOUTS before publishing the upper dir
  ovl: fortify ovl_copy_xattr() against underlying layer modifications
  ovl: do not copy up an xattr value that was never read
This commit is contained in:
Linus Torvalds 2026-10-31 11:35:53 -07:00
commit ca62fc8c6b
9 changed files with 224 additions and 77 deletions

View File

@ -359,7 +359,8 @@ static inline int dname_external(const struct dentry *dentry)
return dentry->d_name.name != dentry->d_shortname.string;
}
void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry)
static bool __take_dentry_name_snapshot(struct name_snapshot *name,
struct dentry *dentry, bool retry)
{
unsigned seq;
const unsigned char *s;
@ -376,8 +377,12 @@ void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry
struct external_name *p;
p = container_of(s, struct external_name, name[0]);
// get a valid reference
if (unlikely(!atomic_inc_not_zero(&p->count)))
goto retry;
if (unlikely(!atomic_inc_not_zero(&p->count))) {
if (retry)
goto retry;
rcu_read_unlock();
return false;
}
name->name.name = s;
}
if (read_seqcount_retry(&dentry->d_seq, seq)) {
@ -385,9 +390,27 @@ void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry
goto retry;
}
rcu_read_unlock();
return true;
}
void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry)
{
__take_dentry_name_snapshot(name, dentry, true);
}
EXPORT_SYMBOL(take_dentry_name_snapshot);
/*
* For a caller that holds no reference on @dentry, e.g. in RCU walk.
* The external name of a dead dentry has a zero refcount forever, so
* take_dentry_name_snapshot() would retry without end. Fail instead.
*/
bool try_take_dentry_name_snapshot(struct name_snapshot *name,
struct dentry *dentry)
{
return __take_dentry_name_snapshot(name, dentry, false);
}
EXPORT_SYMBOL(try_take_dentry_name_snapshot);
void release_dentry_name_snapshot(struct name_snapshot *name)
{
if (unlikely(name->name.name != name->inline_name.string)) {

View File

@ -72,6 +72,18 @@ static int ovl_copy_acl(struct ovl_fs *ofs, const struct path *path,
return err;
}
static ssize_t ovl_getxattr_value(const struct path *path, char *name,
char **value, ssize_t *value_size);
/*
* Any user can store an empty security.capability, which fails to read with
* -EINVAL. It grants no capability, so treat it as none, like no xattr.
*/
static bool ovl_no_fscap(ssize_t err)
{
return err == -ENODATA || err == -EOPNOTSUPP || err == -EINVAL;
}
int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct dentry *new)
{
struct dentry *old = oldpath->dentry;
@ -129,30 +141,16 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de
break;
}
retry:
size = ovl_do_getxattr(oldpath, name, value, value_size);
if (size == -ERANGE)
size = ovl_do_getxattr(oldpath, name, NULL, 0);
size = ovl_getxattr_value(oldpath, name, &value, &value_size);
if (size < 0) {
if (!strcmp(name, XATTR_NAME_CAPS) && ovl_no_fscap(size)) {
error = 0;
continue;
}
error = size;
break;
}
if (size > value_size) {
void *new;
new = kvmalloc(size, GFP_KERNEL);
if (!new) {
error = -ENOMEM;
break;
}
kvfree(value);
value = new;
value_size = size;
goto retry;
}
error = ovl_do_setxattr(OVL_FS(sb), new, name, value, size, 0);
if (error) {
if (error != -EOPNOTSUPP || ovl_must_copy_xattr(name))
@ -476,8 +474,12 @@ struct ovl_fh *ovl_get_origin_fh(struct ovl_fs *ofs, struct dentry *origin)
* When lower layer doesn't support export operations store a 'null' fh,
* so we can use the overlay.origin xattr to distignuish between a copy
* up and a pure upper inode.
*
* With index enabled, mount already found that all layers can decode
* file handles. Do not ask again, because an LSM may change its answer
* about CAP_DAC_READ_SEARCH and index users cannot cope with a 'null' fh.
*/
if (!ovl_can_decode_fh(origin->d_sb))
if (!ofs->config.index && !ovl_can_decode_fh(origin->d_sb))
return NULL;
return ovl_encode_real_fh(ofs, d_inode(origin), false);
@ -808,9 +810,10 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
rd.old_parent = c->workdir;
rd.new_parent = c->destdir;
rd.flags = 0;
err = start_renaming_dentry(&rd, 0, temp,
/* The name may have been taken since lookup, never replace it */
err = start_renaming_dentry(&rd, LOOKUP_EXCL, temp,
&QSTR_LEN(c->destname.name, c->destname.len));
if (err) {
if (err && err != -EEXIST) {
/* temp or workdir moved underneath us? map to -EIO */
err = -EIO;
}
@ -834,9 +837,9 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
if (!c->metacopy)
ovl_set_upperdata(inode);
ovl_inode_update(inode, temp);
if (S_ISDIR(inode->i_mode))
ovl_set_flag(OVL_WHITEOUTS, inode);
ovl_inode_update(inode, temp);
out:
ovl_end_write(c->dentry);
@ -935,6 +938,7 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
struct dentry *origin = c->lowerpath.dentry;
struct ovl_fh *fh = NULL;
struct name_snapshot name;
bool to_index = false;
/*
@ -972,12 +976,15 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
goto out_free_fh;
} else {
/*
* c->dentry->d_name is stabilzed by ovl_copy_up_start(),
* because if we got here, it means that c->dentry has no upper
* alias and changing ->d_name means going through ovl_rename()
* that will call ovl_copy_up() on source and target dentry.
* ovl_copy_up_start() keeps ovl_rename() away from c->dentry,
* because ovl_rename() calls ovl_copy_up() on source and target
* dentry. It does not stop d_splice_alias() from moving a
* directory that a lookup found under another name after a
* change to the lower layer. That move frees an external name,
* so take a snapshot.
*/
c->destname = c->dentry->d_name;
take_dentry_name_snapshot(&name, c->dentry);
c->destname = name.name;
/*
* Mark parent "impure" because it may now contain non-pure
* upper
@ -986,7 +993,7 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
err = ovl_set_impure(c->parent, c->destdir);
ovl_end_write(c->dentry);
if (err)
goto out_free_fh;
goto out;
}
/* Should we copyup with O_TMPFILE or with workdir? */
@ -1020,6 +1027,8 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
out:
if (to_index)
kfree(c->destname.name);
else
release_dentry_name_snapshot(&name);
out_free_fh:
kfree(fh);
return err;
@ -1055,26 +1064,39 @@ static bool ovl_need_meta_copy_up(struct dentry *dentry, umode_t mode,
return true;
}
static ssize_t ovl_getxattr_value(const struct path *path, char *name, char **value)
static ssize_t ovl_getxattr_value(const struct path *path, char *name,
char **value, ssize_t *value_size)
{
ssize_t res;
char *buf;
res = ovl_do_getxattr(path, name, NULL, 0);
if (res == -ENODATA || res == -EOPNOTSUPP)
res = 0;
res = ovl_do_getxattr(path, name, *value, *value_size);
if (res == -ERANGE) {
res = ovl_do_getxattr(path, name, NULL, 0);
/* Xattr changed underneath us? */
if (res >= 0 && res <= *value_size)
return -EIO;
}
if (res > 0) {
buf = kzalloc(res, GFP_KERNEL);
if (res > *value_size) {
buf = kvmalloc(res, GFP_KERNEL);
if (!buf)
return -ENOMEM;
res = ovl_do_getxattr(path, name, buf, res);
if (res < 0)
kfree(buf);
else
*value = buf;
if (res < 0) {
kvfree(buf);
/* Xattr changed underneath us? */
if (res == -ERANGE)
return -EIO;
return res;
}
kvfree(*value);
*value = buf;
*value_size = res;
}
return res;
}
@ -1085,7 +1107,7 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
struct path upperpath;
int err;
char *capability = NULL;
ssize_t cap_size;
ssize_t cap_size = 0;
ovl_path_upper(c->dentry, &upperpath);
if (WARN_ON(upperpath.dentry == NULL))
@ -1093,8 +1115,10 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
if (c->stat.size) {
err = cap_size = ovl_getxattr_value(&upperpath, XATTR_NAME_CAPS,
&capability);
if (cap_size < 0)
&capability, &cap_size);
if (ovl_no_fscap(cap_size))
cap_size = 0;
else if (cap_size < 0)
goto out;
}
@ -1123,7 +1147,7 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
ovl_clear_flag(OVL_VERIFIED_DIGEST, d_inode(c->dentry));
ovl_set_upperdata(d_inode(c->dentry));
out_free:
kfree(capability);
kvfree(capability);
out:
return err;
}

View File

@ -304,7 +304,6 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
};
ovl_dentry_set_upper_alias(dentry);
ovl_dentry_init_reval(dentry, newdentry, NULL);
if (!hardlink) {
/*
@ -335,6 +334,13 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
d_mark_tmpfile(tmpfile, inode);
d_instantiate(dentry, inode);
/*
* Unless it is a tmpfile, the dentry is hashed and a path walk can find
* it at any time. Set the revalidate flags only now, because
* ovl_dentry_revalidate_common() does not expect them on a negative
* dentry.
*/
ovl_dentry_init_reval(dentry, ovl_dentry_upper(dentry), NULL);
if (inode != oip.newinode) {
pr_warn_ratelimited("newly created inode found in cache (%pd2)\n",
dentry);
@ -1027,7 +1033,19 @@ static char *ovl_get_redirect(struct dentry *dentry, bool abs_redirect)
const char *name;
int thislen;
/*
* A rename can move d to another directory at any time. Hold
* d_lock to copy the name that d has in this very parent, or
* else the path mixes the old name with the new parent.
*/
tmp = dget_parent(d);
spin_lock(&d->d_lock);
if (unlikely(tmp != d->d_parent)) {
spin_unlock(&d->d_lock);
dput(tmp);
continue;
}
name = ovl_dentry_get_redirect(d);
if (name) {
thislen = strlen(name);
@ -1040,13 +1058,13 @@ static char *ovl_get_redirect(struct dentry *dentry, bool abs_redirect)
if (thislen + (name[0] != '/') > buflen) {
ret = ERR_PTR(-EXDEV);
spin_unlock(&d->d_lock);
dput(tmp);
goto out_put;
}
buflen -= thislen;
memcpy(&buf[buflen], name, thislen);
spin_unlock(&d->d_lock);
tmp = dget_parent(d);
dput(d);
d = tmp;

View File

@ -998,7 +998,7 @@ int ovl_set_nlink_lower(struct dentry *dentry)
}
unsigned int ovl_get_nlink(struct ovl_fs *ofs, struct dentry *lowerdentry,
struct dentry *upperdentry,
struct dentry *upperdentry, bool indexed,
unsigned int fallback)
{
int nlink_diff;
@ -1006,7 +1006,15 @@ unsigned int ovl_get_nlink(struct ovl_fs *ofs, struct dentry *lowerdentry,
char buf[13];
int err;
if (!lowerdentry || !upperdentry || d_inode(lowerdentry)->i_nlink == 1)
if (!lowerdentry || !upperdentry)
return fallback;
/*
* A lower inode with one link is not indexed on copy up, unless all
* files are indexed for NFS export. If the caller found an index entry,
* then the xattr holds the union nlink, whatever the lower nlink is.
*/
if (!indexed && d_inode(lowerdentry)->i_nlink == 1)
return fallback;
err = ovl_getxattr_upper(ofs, upperdentry, OVL_XATTR_NLINK,
@ -1263,7 +1271,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
/* Recalculate nlink for non-dir due to indexing */
if (!is_dir)
nlink = ovl_get_nlink(ofs, lowerdentry, upperdentry,
nlink);
oip->index, nlink);
set_nlink(inode, nlink);
ino = key->i_ino;
} else {
@ -1278,7 +1286,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
}
ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev);
ovl_inode_init(inode, oip, ino, fsid);
WARN_ON_ONCE(!!IS_CASEFOLDED(inode) != ofs->casefold);
WARN_ON_ONCE(is_dir && !!IS_CASEFOLDED(inode) != ofs->casefold);
if (upperdentry && ovl_is_impuredir(sb, upperdentry))
ovl_set_flag(OVL_IMPURE, inode);

View File

@ -266,7 +266,8 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
goto out_err;
}
if (ofs->casefold != ovl_dentry_casefolded(this)) {
/* Filesystems set the casefold flag only on directories */
if (d_is_dir(this) && ofs->casefold != ovl_dentry_casefolded(this)) {
warn = "child wrong casefold";
err = -EREMOTE;
goto out_warn;
@ -712,7 +713,7 @@ int ovl_verify_index(struct ovl_fs *ofs, struct dentry *index)
if (err)
goto fail;
if (ovl_get_nlink(ofs, origin.dentry, index, 0) == 0)
if (ovl_get_nlink(ofs, origin.dentry, index, true, 0) == 0)
goto orphan;
}
@ -942,8 +943,10 @@ static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry,
goto out;
err = ovl_set_origin_fh(ofs, fh, upper);
if (!err)
err = ovl_set_impure(dentry->d_parent, upper->d_parent);
if (!err) {
/* upper->d_parent is not stable, use the overlay parent */
err = ovl_set_impure_dir(dentry->d_parent);
}
ovl_drop_write(dentry);
out:

View File

@ -574,6 +574,12 @@ int ovl_check_setxattr(struct ovl_fs *ofs, struct dentry *upperdentry,
enum ovl_xattr ox, const void *value, size_t size,
int xerr);
int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry);
static inline int ovl_set_impure_dir(struct dentry *dentry)
{
return ovl_set_impure(dentry, ovl_dentry_upper(dentry));
}
bool ovl_inuse_trylock(struct dentry *dentry);
void ovl_inuse_unlock(struct dentry *dentry);
bool ovl_is_inuse(struct dentry *dentry);
@ -804,7 +810,7 @@ static inline bool ovl_dir_is_real(struct inode *dir)
int ovl_set_nlink_upper(struct dentry *dentry);
int ovl_set_nlink_lower(struct dentry *dentry);
unsigned int ovl_get_nlink(struct ovl_fs *ofs, struct dentry *lowerdentry,
struct dentry *upperdentry,
struct dentry *upperdentry, bool indexed,
unsigned int fallback);
int ovl_permission(const struct mnt_idmap *idmap, struct inode *inode,
int mask);

View File

@ -36,6 +36,7 @@ struct ovl_cache_entry {
struct ovl_dir_cache {
long refcount;
u64 version;
bool is_refcounted;
struct list_head entries;
struct rb_root root;
};
@ -81,13 +82,13 @@ static int ovl_casefold(struct ovl_readdir_data *rdd, const char *str, int len,
name_is_dot_dotdot(str, len))
return 0;
cf_name = kmalloc(NAME_MAX, GFP_KERNEL);
cf_name = kmalloc(NAME_MAX + 1, GFP_KERNEL);
if (!cf_name) {
rdd->err = -ENOMEM;
return -ENOMEM;
}
cf_len = utf8_casefold(rdd->map, &qstr, cf_name, NAME_MAX);
cf_len = utf8_casefold(rdd->map, &qstr, cf_name, NAME_MAX + 1);
if (cf_len > 0)
*dst = cf_name;
else
@ -479,13 +480,31 @@ static void ovl_seek_cursor(struct ovl_dir_file *od, loff_t pos)
od->cursor = p;
}
/*
* Merged (refcounted) and impure (not refcounted) caches are never
* expected to be mixed. On mismatch, detach the cache from the inode
* and leak it rather than misuse it. A merged cache is still freed by
* its last ovl_cache_put().
*/
static struct ovl_dir_cache *ovl_dir_cache_check(struct inode *inode,
bool is_refcounted)
{
struct ovl_dir_cache *cache = ovl_dir_cache(inode);
if (cache && WARN_ON(cache->is_refcounted != is_refcounted)) {
ovl_set_dir_cache(inode, NULL);
return NULL;
}
return cache;
}
static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
{
int res;
struct ovl_dir_cache *cache;
struct inode *inode = d_inode(dentry);
cache = ovl_dir_cache(inode);
cache = ovl_dir_cache_check(inode, true);
if (cache && ovl_inode_version_get(inode) == cache->version) {
WARN_ON(!cache->refcount);
cache->refcount++;
@ -498,6 +517,7 @@ static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
return ERR_PTR(-ENOMEM);
cache->refcount = 1;
cache->is_refcounted = true;
INIT_LIST_HEAD(&cache->entries);
cache->root = RB_ROOT;
@ -568,8 +588,8 @@ static int ovl_cache_update(const struct path *path, struct ovl_cache_entry *p,
goto get;
}
if (p->len == 2) {
/* we shall not be moved */
this = dget(dir->d_parent);
/* d_splice_alias() can move even a locked dir */
this = dget_parent(dir);
goto get;
}
}
@ -698,7 +718,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(const struct path *path)
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct ovl_dir_cache *cache;
cache = ovl_dir_cache(inode);
cache = ovl_dir_cache_check(inode, false);
if (cache && ovl_inode_version_get(inode) == cache->version)
return cache;
@ -710,6 +730,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(const struct path *path)
if (!cache)
return ERR_PTR(-ENOMEM);
cache->is_refcounted = false;
res = ovl_dir_read_impure(path, &cache->entries, &cache->root);
if (res) {
ovl_cache_free(&cache->entries);
@ -797,6 +818,7 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
struct dentry *dir = file->f_path.dentry;
struct ovl_fs *ofs = OVL_FS(dir->d_sb);
const struct ovl_layer *lower_layer = ovl_layer_lower(dir);
struct dentry *parent;
struct ovl_readdir_translate rdt = {
.ctx.actor = ovl_fill_real,
.ctx.count = ctx->count,
@ -808,18 +830,26 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
if (rdt.xinobits && lower_layer)
rdt.fsid = lower_layer->fsid;
if (OVL_TYPE_MERGE(ovl_path_type(dir->d_parent))) {
/*
* The lock on dir does not stop d_splice_alias() from moving it to
* another parent, which drops the reference dir holds on this one.
*/
parent = dget_parent(dir);
if (OVL_TYPE_MERGE(ovl_path_type(parent))) {
struct kstat stat;
struct path statpath = file->f_path;
statpath.dentry = dir->d_parent;
statpath.dentry = parent;
err = vfs_getattr(&statpath, &stat, STATX_INO, 0);
if (err)
if (err) {
dput(parent);
return err;
}
WARN_ON_ONCE(dir->d_sb->s_dev != stat.dev);
rdt.parent_ino = stat.ino;
}
dput(parent);
if (ovl_is_impure_dir(file)) {
rdt.cache = ovl_cache_get_impure(&file->f_path);
@ -875,8 +905,10 @@ static bool ovl_need_adjust_d_ino(struct file *file)
{
struct dentry *dentry = file->f_path.dentry;
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct dentry *parent;
enum ovl_path_type type;
/* If parent is merge, then need to adjust d_ino for '..' */
/* With xino, need to map real d_ino to the unified overlay ino space */
if (ovl_xino_bits(ofs))
return true;
@ -885,8 +917,14 @@ static bool ovl_need_adjust_d_ino(struct file *file)
return false;
/* If dir is impure then need to adjust d_ino for copied up entries */
if (ovl_is_impure_dir(file) ||
OVL_TYPE_MERGE(ovl_path_type(dentry->d_parent)))
if (ovl_is_impure_dir(file))
return true;
/* If parent is merge, then need to adjust d_ino for '..' */
parent = dget_parent(dentry);
type = ovl_path_type(parent);
dput(parent);
if (OVL_TYPE_MERGE(type))
return true;
/* Pure: no need to adjust d_ino */
@ -1055,7 +1093,8 @@ static int ovl_dir_open(struct inode *inode, struct file *file)
return PTR_ERR(realfile);
}
od->realfile = realfile;
od->is_real = ovl_dir_is_real(inode);
od->is_real = !OVL_TYPE_MERGE(type) &&
!ovl_test_flag(OVL_WHITEOUTS, inode);
od->is_upper = OVL_TYPE_UPPER(type);
file->private_data = od;

View File

@ -100,11 +100,17 @@ static int ovl_revalidate_real(struct dentry *d, unsigned int flags, bool weak)
dir = d_inode_rcu(parent);
if (!dir)
return -ECHILD;
/*
* Nothing pins d in RCU walk: eviction of the overlay
* inode may drop the last reference at any time.
*/
if (!try_take_dentry_name_snapshot(&n, d))
return -ECHILD;
} else {
parent = dget_parent(d);
dir = d_inode(parent);
take_dentry_name_snapshot(&n, d);
}
take_dentry_name_snapshot(&n, d);
ret = d->d_op->d_revalidate(dir, &n.name, d, flags);
release_dentry_name_snapshot(&n);
if (!(flags & LOOKUP_RCU))
@ -516,6 +522,20 @@ static int ovl_report_in_use(struct ovl_fs *ofs, const char *name)
}
}
/*
* clone_private_mount() checks CAP_SYS_ADMIN in current->cred.
* ovl_fill_super_creds() runs under the stashed creator cred, which
* override_creds may have dropped.
* The task that issued FSCONFIG_CMD_CREATE is still current->real_cred, so
* use that only for the clone. creator_cred stays in effect for every other
* backing-fs operation.
*/
static struct vfsmount *ovl_clone_private_mount(const struct path *path)
{
scoped_with_creds(current_real_cred())
return clone_private_mount(path);
}
static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
struct ovl_layer *upper_layer,
const struct path *upperpath)
@ -539,7 +559,7 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
if (err)
goto out;
upper_mnt = clone_private_mount(upperpath);
upper_mnt = ovl_clone_private_mount(upperpath);
err = PTR_ERR(upper_mnt);
if (IS_ERR(upper_mnt)) {
pr_err("failed to clone upperpath\n");
@ -640,16 +660,21 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
static struct dentry *ovl_lookup_or_create(struct ovl_fs *ofs,
struct dentry *parent,
const char *name, umode_t mode)
const char *name, bool is_dir)
{
struct dentry *child;
child = ovl_start_creating_upper(ofs, parent, &QSTR(name));
if (!IS_ERR(child)) {
if (!child->d_inode)
if (!child->d_inode) {
child = ovl_create_real(ofs, parent, child,
&QSTR(name),
OVL_CATTR(mode));
OVL_CATTR(is_dir ? S_IFDIR :
S_IFREG));
} else if (is_dir && !d_can_lookup(child)) {
end_creating(child);
child = ERR_PTR(-ENOTDIR);
}
end_creating_keep(child);
}
dput(parent);
@ -671,7 +696,7 @@ static int ovl_create_volatile_dirty(struct ovl_fs *ofs)
const char *const *name = volatile_path;
for (ctr = ARRAY_SIZE(volatile_path); ctr; ctr--, name++) {
d = ovl_lookup_or_create(ofs, d, *name, ctr > 1 ? S_IFDIR : S_IFREG);
d = ovl_lookup_or_create(ofs, d, *name, ctr > 1);
if (IS_ERR(d))
return PTR_ERR(d);
}
@ -1112,7 +1137,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
}
}
mnt = clone_private_mount(&l->path);
mnt = ovl_clone_private_mount(&l->path);
err = PTR_ERR(mnt);
if (IS_ERR(mnt)) {
pr_err("failed to clone lowerpath\n");

View File

@ -665,6 +665,7 @@ struct name_snapshot {
union shortname_store inline_name;
};
void take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
bool try_take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
void release_dentry_name_snapshot(struct name_snapshot *);
static inline struct dentry *d_first_child(const struct dentry *dentry)