fusefs: fix page fault triggered by async notification when unmounted

A FUSE daemon can send asynchronous notification to the kernel in order
to, for example, invalidate an inode's cache.  Fix a page fault that can
happen if the file system isn't yet mounted, or is already unmounted,
when that notification arrives.

PR:		290519
MFC after:	1 week
Reviewed by:	emaste
Differential Revision: https://reviews.freebsd.org/D53356
This commit is contained in:
Alan Somers
2025-10-25 18:37:02 -06:00
parent 3559b8e983
commit 5d42c88139
4 changed files with 69 additions and 2 deletions
+7
View File
@@ -550,6 +550,13 @@ fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag)
} else if (ohead.unique == 0){
/* unique == 0 means asynchronous notification */
SDT_PROBE1(fusefs, , device, fuse_device_write_notify, &ohead);
if (data->mp == NULL) {
SDT_PROBE2(fusefs, , device, trace, 1,
"asynchronous notification before mount"
" or after unmount");
return (EXTERROR(ENODEV,
"This FUSE session is not mounted"));
}
mp = data->mp;
vfs_ref(mp);
err = vfs_busy(mp, 0);
+3 -1
View File
@@ -827,10 +827,12 @@ void MockFS::loop() {
}
}
int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen)
int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen,
int expected_errno)
{
std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
out->expected_errno = expected_errno;
out->header.unique = 0; /* 0 means asynchronous notification */
out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
out->body.inval_entry.parent = parent;
+3 -1
View File
@@ -390,8 +390,10 @@ class MockFS {
* @param parent Parent directory's inode number
* @param name name of dirent to invalidate
* @param namelen size of name, including the NUL
* @param expected_errno The error that write() should return
*/
int notify_inval_entry(ino_t parent, const char *name, size_t namelen);
int notify_inval_entry(ino_t parent, const char *name, size_t namelen,
int expected_errno = 0);
/*
* Send an asynchronous notification to invalidate an inode's cached
+56
View File
@@ -385,6 +385,27 @@ TEST_F(Notify, inval_inode_with_clean_cache)
leak(fd);
}
/*
* Attempting to invalidate an entry or inode after unmounting should fail, but
* nothing bad should happen.
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290519
*/
TEST_F(Notify, notify_after_unmount)
{
const static char *name = "foo";
struct inval_entry_args iea;
expect_destroy(0);
m_mock->unmount();
iea.mock = m_mock;
iea.parent = FUSE_ROOT_ID;
iea.name = name;
iea.namelen = strlen(name);
iea.mock->notify_inval_entry(iea.parent, iea.name, iea.namelen, ENODEV);
}
/* FUSE_NOTIFY_STORE with a file that's not in the entry cache */
/* disabled because FUSE_NOTIFY_STORE is not yet implemented */
TEST_F(Notify, DISABLED_store_nonexistent)
@@ -544,3 +565,38 @@ TEST_F(NotifyWriteback, inval_inode_attrs_only)
leak(fd);
}
/*
* Attempting asynchronous invalidation of an Entry before mounting the file
* system should fail, but nothing bad should happen.
*
* Note that invalidating an inode before mount goes through the same path, and
* is not separately tested.
*
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290519
*/
TEST(PreMount, inval_entry_before_mount)
{
const static char name[] = "foo";
size_t namelen = strlen(name);
struct mockfs_buf_out *out;
int r;
int fuse_fd;
fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
ASSERT_GE(fuse_fd, 0) << strerror(errno);
out = new mockfs_buf_out;
out->header.unique = 0; /* 0 means asynchronous notification */
out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
out->body.inval_entry.parent = FUSE_ROOT_ID;
out->body.inval_entry.namelen = namelen;
strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry),
name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry));
out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) +
namelen;
r = write(fuse_fd, out, out->header.len);
EXPECT_EQ(-1, r);
EXPECT_EQ(ENODEV, errno);
delete out;
}