diff options
| author | Kumar Kartikeya Dwivedi <memxor@gmail.com> | 2026-09-04 10:43:19 +0200 |
|---|---|---|
| committer | Alexei Starovoitov <ast@kernel.org> | 2026-09-04 07:58:36 -0700 |
| commit | 6668ed271eaefaa63e686bdfbedaeb7b8e492722 (patch) | |
| tree | 58e8288fead484f6755ad0815fc74c3742663761 /fs/erofs/ishare.c | |
| download | linux-stable-6668ed271eaefaa63e686bdfbedaeb7b8e492722.tar.gz linux-stable-6668ed271eaefaa63e686bdfbedaeb7b8e492722.zip | |
selftests/bpf: Reject graph kptr use after RCU unlockgrafted
Add a sleepable verifier test that loads a graph-node local kptr in an
explicit RCU read-side critical section, then passes its node to
bpf_rbtree_remove() after the section ends.
Before the verifier fix, the stale NON_OWN_REF flag makes the node look like
a live borrowed reference and the program is accepted. After the fix, the
pointer is demoted without NON_OWN_REF and the graph kfunc argument is
rejected.
Also exercise a graph kptr loaded while a spin lock provides implicit RCU
protection. The pointer must be invalidated when the lock is released, which
guards the required ordering between non-owning-reference invalidation and
RCU demotion.
Update the existing fault-protected load test state description. The
post-unlock pointer no longer carries NON_OWN_REF, but remains readable
because the load is rewritten to use BPF_PROBE_MEM.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20260904084325.52250-7-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'fs/erofs/ishare.c')
| -rw-r--r-- | fs/erofs/ishare.c | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c new file mode 100644 index 000000000..fa7d4112d --- /dev/null +++ b/fs/erofs/ishare.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024, Alibaba Cloud + */ +#include <linux/backing-file.h> +#include <linux/xxhash.h> +#include <linux/mount.h> +#include <linux/security.h> +#include "internal.h" +#include "xattr.h" + +static struct vfsmount *erofs_ishare_mnt; + +static int erofs_ishare_iget5_eq(struct inode *inode, void *data) +{ + struct erofs_inode_fingerprint *fp1 = &EROFS_I(inode)->fingerprint; + struct erofs_inode_fingerprint *fp2 = data; + + return fp1->size == fp2->size && + !memcmp(fp1->opaque, fp2->opaque, fp2->size); +} + +static int erofs_ishare_iget5_set(struct inode *inode, void *data) +{ + struct erofs_inode *vi = EROFS_I(inode); + + vi->fingerprint = *(struct erofs_inode_fingerprint *)data; + INIT_LIST_HEAD(&vi->ishare_list); + spin_lock_init(&vi->ishare_lock); + return 0; +} + +bool erofs_ishare_fill_inode(struct inode *inode) +{ + static const struct file_operations empty_fops = {}; + struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb); + const struct address_space_operations *aops; + struct erofs_inode *vi = EROFS_I(inode); + struct erofs_inode_fingerprint fp; + struct dentry *sd; + struct inode *si; + + aops = erofs_get_aops(inode); + if (IS_ERR(aops)) + return false; + if (erofs_xattr_fill_inode_fingerprint(&fp, inode, sbi->domain_id)) + return false; + + si = iget5_locked(erofs_ishare_mnt->mnt_sb, + xxh32(fp.opaque, fp.size, 0), + erofs_ishare_iget5_eq, erofs_ishare_iget5_set, &fp); + if (si && (inode_state_read_once(si) & I_NEW)) { + si->i_fop = &empty_fops; + si->i_mapping->a_ops = aops; + si->i_mode = 0444 | S_IFREG; + si->i_size = inode->i_size; + unlock_new_inode(si); + } else { + kfree(fp.opaque); + if (!si || aops != si->i_mapping->a_ops) { + iput(si); + return false; + } + if (si->i_size != inode->i_size) { + erofs_warn(inode->i_sb, "i_size mismatch (%lld != %lld) for the same fingerprint", + inode->i_size, si->i_size); + iput(si); + return false; + } + } + sd = d_obtain_alias(si); /* disconnected denties for sharedinodes */ + if (IS_ERR(sd)) + return false; + vi->sharedentry = sd; + INIT_LIST_HEAD(&vi->ishare_list); + spin_lock(&EROFS_I(si)->ishare_lock); + list_add(&vi->ishare_list, &EROFS_I(si)->ishare_list); + spin_unlock(&EROFS_I(si)->ishare_lock); + return true; +} + +void erofs_ishare_free_inode(struct inode *inode) +{ + struct erofs_inode *vi = EROFS_I(inode), *svi; + + if (!vi->sharedentry) + return; + svi = EROFS_I(d_inode(vi->sharedentry)); + spin_lock(&svi->ishare_lock); + list_del(&vi->ishare_list); + spin_unlock(&svi->ishare_lock); + dput(vi->sharedentry); + vi->sharedentry = NULL; +} + +static int erofs_ishare_file_open(struct inode *inode, struct file *file) +{ + struct path sharedpath = { + .mnt = erofs_ishare_mnt, + .dentry = EROFS_I(inode)->sharedentry, + }; + struct file *rf; + + if (file->f_flags & O_DIRECT) + return -EINVAL; + + rf = backing_file_open(file, file->f_flags | O_NOATIME, + &sharedpath, current_cred()); + if (IS_ERR(rf)) + return PTR_ERR(rf); + file->private_data = rf; + return 0; +} + +static int erofs_ishare_file_release(struct inode *inode, struct file *file) +{ + fput(file->private_data); + file->private_data = NULL; + return 0; +} + +static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb, + struct iov_iter *to) +{ + struct file *realfile = iocb->ki_filp->private_data; + struct kiocb dedup_iocb; + ssize_t nread; + + if (!iov_iter_count(to)) + return 0; + kiocb_clone(&dedup_iocb, iocb, realfile); + nread = filemap_read(&dedup_iocb, to, 0); + iocb->ki_pos = dedup_iocb.ki_pos; + return nread; +} + +static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct file *realfile = file->private_data; + int err; + + vma_set_file(vma, realfile); + + err = security_mmap_backing_file(vma, realfile, file); + if (err) + return err; + + return generic_file_readonly_mmap(file, vma); +} + +static int erofs_ishare_fadvise(struct file *file, loff_t offset, + loff_t len, int advice) +{ + return vfs_fadvise(file->private_data, offset, len, advice); +} + +const struct file_operations erofs_ishare_fops = { + .open = erofs_ishare_file_open, + .llseek = generic_file_llseek, + .read_iter = erofs_ishare_file_read_iter, + .mmap = erofs_ishare_mmap, + .release = erofs_ishare_file_release, + .get_unmapped_area = thp_get_unmapped_area, + .splice_read = filemap_splice_read, + .fadvise = erofs_ishare_fadvise, +}; + +struct inode *erofs_real_inode(struct inode *inode, bool *need_iput) +{ + struct erofs_inode *vi, *vi_share; + struct inode *realinode; + + *need_iput = false; + if (inode->i_sb != erofs_ishare_mnt->mnt_sb) + return inode; + + vi_share = EROFS_I(inode); + spin_lock(&vi_share->ishare_lock); + /* fetch any one as real inode */ + DBG_BUGON(list_empty(&vi_share->ishare_list)); + list_for_each_entry(vi, &vi_share->ishare_list, ishare_list) { + realinode = igrab(&vi->vfs_inode); + if (realinode) { + *need_iput = true; + break; + } + } + spin_unlock(&vi_share->ishare_lock); + + DBG_BUGON(!realinode); + return realinode; +} + +int __init erofs_init_ishare(void) +{ + struct vfsmount *mnt; + int ret; + + mnt = kern_mount(&erofs_anon_fs_type); + if (IS_ERR(mnt)) + return PTR_ERR(mnt); + /* generic_fadvise() doesn't work if s_bdi == &noop_backing_dev_info */ + ret = super_setup_bdi(mnt->mnt_sb); + if (ret) + kern_unmount(mnt); + else + erofs_ishare_mnt = mnt; + return ret; +} + +void erofs_exit_ishare(void) +{ + kern_unmount(erofs_ishare_mnt); +} |
