git: 3152a2f5dd04 - stable/14 - fusefs: fix a kernel panic regarding SCM_RIGHTS
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sat, 27 Sep 2025 14:36:23 UTC
The branch stable/14 has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=3152a2f5dd0447daba80466f70c66ea794400015 commit 3152a2f5dd0447daba80466f70c66ea794400015 Author: Alan Somers <asomers@FreeBSD.org> AuthorDate: 2025-09-19 16:02:25 +0000 Commit: Alan Somers <asomers@FreeBSD.org> CommitDate: 2025-09-27 14:30:13 +0000 fusefs: fix a kernel panic regarding SCM_RIGHTS If the last copy of an open file resides within the socket buffer of a unix-domain socket, then VOP_CLOSE will be called with no thread information. Fix fusefs to handle that case, and add a regression test. Also add a test case for writes to a file that lies within a sockbuf. Along with close, a write from the writeback cache is the only other operation I can think of that might apply to a file residing in a sockbuf. PR: 289686 Reported by: iron.udjin@gmail.com Sponsored by: ConnectWise Reviewed by: glebius, markj Differential Revision: https://reviews.freebsd.org/D52625 (cherry picked from commit e043af9ca59608309cac2fd222c17f989ba0d35e) --- sys/fs/fuse/fuse_vnops.c | 10 ++++-- tests/sys/fs/fusefs/release.cc | 54 +++++++++++++++++++++++++++++++ tests/sys/fs/fusefs/write.cc | 73 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 265d1604e796..28dca332dfd3 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -785,11 +785,15 @@ fuse_vnop_close(struct vop_close_args *ap) struct mount *mp = vnode_mount(vp); struct ucred *cred = ap->a_cred; int fflag = ap->a_fflag; - struct thread *td = ap->a_td; - pid_t pid = td->td_proc->p_pid; + struct thread *td; struct fuse_vnode_data *fvdat = VTOFUD(vp); + pid_t pid; int err = 0; + /* NB: a_td will be NULL from some async kernel contexts */ + td = ap->a_td ? ap->a_td : curthread; + pid = td->td_proc->p_pid; + if (fuse_isdeadfs(vp)) return 0; if (vnode_isdir(vp)) @@ -828,7 +832,7 @@ fuse_vnop_close(struct vop_close_args *ap) } /* TODO: close the file handle, if we're sure it's no longer used */ if ((fvdat->flag & FN_SIZECHANGE) != 0) { - fuse_vnode_savesize(vp, cred, td->td_proc->p_pid); + fuse_vnode_savesize(vp, cred, pid); } return err; } diff --git a/tests/sys/fs/fusefs/release.cc b/tests/sys/fs/fusefs/release.cc index b664ba512b64..9df236bfbaf7 100644 --- a/tests/sys/fs/fusefs/release.cc +++ b/tests/sys/fs/fusefs/release.cc @@ -29,6 +29,9 @@ */ extern "C" { +#include <sys/socket.h> +#include <sys/un.h> + #include <fcntl.h> #include <unistd.h> } @@ -188,6 +191,57 @@ TEST_F(Release, ok) ASSERT_EQ(0, close(fd)) << strerror(errno); } +/* + * Nothing bad should happen when closing a Unix-domain named socket that + * contains a fusefs file descriptor within its receive buffer. + * Regression test for + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=289686 + */ +TEST_F(Release, scm_rights) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + struct msghdr msg; + struct iovec iov; + char message[CMSG_SPACE(sizeof(int))]; + uint64_t ino = 42; + int fd; + int s[2]; + union { + char buf[CMSG_SPACE(sizeof(fd))]; + struct cmsghdr align; + } u; + + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_flush(ino, 1, ReturnErrno(0)); + expect_release(ino, getpid(), O_RDONLY, 0); + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, s)) << strerror(errno); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + memset(&message, 0, sizeof(message)); + memset(&msg, 0, sizeof(msg)); + iov.iov_base = NULL; + iov.iov_len = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = u.buf, + msg.msg_controllen = sizeof(u.buf); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + ASSERT_GE(sendmsg(s[0], &msg, 0), 0) << strerror(errno); + + close(fd); // Close fd within our process + close(s[0]); + close(s[1]); // The last copy of fd is within this socket's rcvbuf +} + /* When closing a file with a POSIX file lock, release should release the lock*/ TEST_F(ReleaseWithLocks, unlock_on_close) { diff --git a/tests/sys/fs/fusefs/write.cc b/tests/sys/fs/fusefs/write.cc index 1fe2e3cc522d..f5573a865a04 100644 --- a/tests/sys/fs/fusefs/write.cc +++ b/tests/sys/fs/fusefs/write.cc @@ -32,9 +32,11 @@ extern "C" { #include <sys/param.h> #include <sys/mman.h> #include <sys/resource.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/uio.h> +#include <sys/un.h> #include <aio.h> #include <fcntl.h> @@ -1398,6 +1400,77 @@ TEST_F(WriteBackAsync, eof) leak(fd); } +/* + * Nothing bad should happen if a file with a dirty writeback cache is closed + * while the last copy lies in some socket's socket buffer. Inspired by bug + * 289686 . + */ +TEST_F(WriteBackAsync, scm_rights) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + int s[2]; + struct msghdr msg; + struct iovec iov; + char message[CMSG_SPACE(sizeof(int))]; + union { + char buf[CMSG_SPACE(sizeof(fd))]; + struct cmsghdr align; + } u; + + expect_lookup(RELPATH, ino, 0); + expect_open(ino, 0, 1); + /* VOP_SETATTR will try to set timestamps during flush */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_SETATTR && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.ino = ino; + out.body.attr.attr.mode = S_IFREG | 0644; + out.body.attr.attr.size = bufsize; + }))); + + expect_write(ino, 0, bufsize, bufsize, CONTENTS); + expect_flush(ino, 1, ReturnErrno(0)); + expect_release(ino, ReturnErrno(0)); + + /* Open a file on the fusefs file system */ + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + + /* Write to the file to dirty its writeback cache */ + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + + /* Send the file into a socket */ + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, s)) << strerror(errno); + memset(&message, 0, sizeof(message)); + memset(&msg, 0, sizeof(msg)); + iov.iov_base = NULL; + iov.iov_len = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = u.buf, + msg.msg_controllen = sizeof(u.buf); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + ASSERT_GE(sendmsg(s[0], &msg, 0), 0) << strerror(errno); + + close(fd); // Close fd within our process + close(s[0]); + close(s[1]); // The last copy of fd is within this socket's rcvbuf +} + /* * When a file has dirty writes that haven't been flushed, the server's notion * of its mtime and ctime will be wrong. The kernel should ignore those if it