mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
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:
commit
ca62fc8c6b
29
fs/dcache.c
29
fs/dcache.c
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user