The branch stable/15 has been updated by asomers:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=5206983e76c0bd42893eae98955bc8168a0a8d0a

commit 5206983e76c0bd42893eae98955bc8168a0a8d0a
Author:     Alan Somers <[email protected]>
AuthorDate: 2025-09-19 16:02:25 +0000
Commit:     Alan Somers <[email protected]>
CommitDate: 2025-09-26 18:11:37 +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:    [email protected]
    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 de60a4717dd4..be600b57f0a3 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -795,11 +795,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))
@@ -838,7 +842,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

Reply via email to