From b8ef5bfbee8ca7fa98b3acf8ef4a70cce616b336 Mon Sep 17 00:00:00 2001 From: Paul Lawrence Date: Wed, 7 Jun 2023 10:19:01 -0700 Subject: [PATCH] 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 --- fs/fuse/backing.c | 8 +++----- fs/fuse/inode.c | 23 ++++++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 269a4dfd76d8..6b9c29fe439e 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -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; } diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 1f593230bd4a..697e7b5b8319 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -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,