ANDROID: fuse-bpf: Ensure bpf field can never be nulled
By putting and nulling fuse_inode's bpf field in fuse_evict_inode, we left a race condition - this inode can still be active. Do not put the bpf program until we are doing the final free in fuse_free_inode. This was the root cause of the reported bug. The backing inode cannot be put in fuse_free_inode, since put_inode can sleep and this is called from an RCU handler. But the backing inode cannot be freed until an RCU interval, so move the put_inode to the same location as in overlayfs, which is destroy_inode. Remove a path in fuse_handle_bpf_prog whereby bpf can be nulled out. When we want to be able to null/change the bpf_prog in the future, we will have to use a mutex or maybe RCU to protect existing users. But until this time, ban this path. Bug: 284450048 Test: fuse_test passes, Pixel 6 passes basic tests Change-Id: Ie6844242f279a5b202eb021eac5a2dd3d08bf09d Signed-off-by: Paul Lawrence <paullawrence@google.com>
This commit is contained in:
parent
a97d54b54d
commit
b8ef5bfbee
2 changed files with 19 additions and 12 deletions
|
|
@ -1224,14 +1224,12 @@ int fuse_handle_bpf_prog(struct fuse_entry_bpf *feb, struct inode *parent,
|
|||
}
|
||||
|
||||
/* Cannot change existing program */
|
||||
if (*bpf && new_bpf) {
|
||||
bpf_prog_put(new_bpf);
|
||||
if (*bpf) {
|
||||
if (new_bpf)
|
||||
bpf_prog_put(new_bpf);
|
||||
return new_bpf == *bpf ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
if (*bpf)
|
||||
bpf_prog_put(*bpf);
|
||||
|
||||
*bpf = new_bpf;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ static void fuse_free_inode(struct inode *inode)
|
|||
kfree(fi->forget);
|
||||
#ifdef CONFIG_FUSE_DAX
|
||||
kfree(fi->dax);
|
||||
#endif
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
if (fi->bpf)
|
||||
bpf_prog_put(fi->bpf);
|
||||
#endif
|
||||
kmem_cache_free(fuse_inode_cachep, fi);
|
||||
}
|
||||
|
|
@ -123,13 +127,6 @@ static void fuse_evict_inode(struct inode *inode)
|
|||
|
||||
/* Will write inode on close/munmap and in all other dirtiers */
|
||||
WARN_ON(inode->i_state & I_DIRTY_INODE);
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
iput(fi->backing_inode);
|
||||
if (fi->bpf)
|
||||
bpf_prog_put(fi->bpf);
|
||||
fi->bpf = NULL;
|
||||
#endif
|
||||
truncate_inode_pages_final(&inode->i_data);
|
||||
clear_inode(inode);
|
||||
if (inode->i_sb->s_flags & SB_ACTIVE) {
|
||||
|
|
@ -149,6 +146,15 @@ static void fuse_evict_inode(struct inode *inode)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
static void fuse_destroy_inode(struct inode *inode)
|
||||
{
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
|
||||
iput(fi->backing_inode);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int fuse_reconfigure(struct fs_context *fsc)
|
||||
{
|
||||
struct super_block *sb = fsc->root->d_sb;
|
||||
|
|
@ -1209,6 +1215,9 @@ static const struct export_operations fuse_export_operations = {
|
|||
|
||||
static const struct super_operations fuse_super_operations = {
|
||||
.alloc_inode = fuse_alloc_inode,
|
||||
#ifdef CONFIG_FUSE_BPF
|
||||
.destroy_inode = fuse_destroy_inode,
|
||||
#endif
|
||||
.free_inode = fuse_free_inode,
|
||||
.evict_inode = fuse_evict_inode,
|
||||
.write_inode = fuse_write_inode,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue