From nobody Thu Jun 12 23:06:12 2025 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4bJJ6r4t5Gz5yZ0R; Thu, 12 Jun 2025 23:06:12 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R11" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4bJJ6r49r9z3dwl; Thu, 12 Jun 2025 23:06:12 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1749769572; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=Ev1QWCpBz4vQ2jE1wJqBoz68JBOz0/pMGEEPaWPLeyQ=; b=Zw232RO47ssiNRDVHDB88up6g9p3fW6chl7k4+rrOQJkV8/prXzDzV6JBY9QsBL3uwq7zX hVinaIjp6ANTzSSsHuw91G3DYf3Y9kr3Rj/69Sa6DMF/CE/pyE1mENs9ckJGWy7+FyWXGt 7L47iGd38d3Foo54CIgBzD8KWZGDFW0s3Nlf6lbB2222bnJgDh7jsRQ6QeOur9H+IIawA6 ywHDqxFbsAk3fCq0DmLLI648deYWjCxjD/KSNLUtIVHwHBSPsqd5QLIzuI29geOXFN4Pe/ j0Fp1mQUkJYeNIRSvHkZHUhuC+SS8jMbb5yM/LpM2rgd0WtM9D9eYVRv8jPL/w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1749769572; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=Ev1QWCpBz4vQ2jE1wJqBoz68JBOz0/pMGEEPaWPLeyQ=; b=avKOX8fTbcBQzgwiyLY6FyV9FrSi+Dsf76WF1Q7mrXQMr3hoxiP94P9+BkKAlqP9vg2Z2j RQfjKXUpqu4wO81z4nZpUDGMAHNcv54vMoRWwoMcc4Ynr/0bQccQcWHcEJbm8EbEzK4v7a 6n60ENLxw5z9FH1bb03F+Prh+/7uhMtZ0/fPopCrU3FBAlpub3ToJKXE9fi+LBW55UCknZ 6yjx6yQTPLyem2gPVag4qVLg9MlzeusL+pg+a18srvko/SVGVvyHzA5wZgI65b6RMumtNx IulSxqhk/XIYEKGNgIDvjI4R/wJg9A9dJmQM/JDsyIJFQD6HbUd4LYr9lz7GyQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1749769572; a=rsa-sha256; cv=none; b=EGlFFWks1YnQRZZ3aqN63Mc6rRIZ8XQtrbF+/0Bk2FURl0nrfP+avG/lpeMaoNQLF0ro6a jDfKc22xv261c5I7H/LgXhEHx1Q123jAY+UGSJuX70gS06KTAfLnJLW0C5KNhhx/TlVzpz cCXXI4+1uoxjK+i0jsSWZUgpisa0R1A6ZG/QhUc2BBFgFUrN0YJ4VOXsBwEbfN+G39bY3U tw0CRYj6WwgP8vLDgfnr7tJZdKGfhLFKJ8JQfILTru/JYL6sHcd5w2AQnKC2wEc2YkEWUr uByLtLA5bcXe/4fXBfz/U9FsptacMScKNS1pHIu9DFmX7NcCkeeMZMBmaKvVTA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4bJJ6r3lvGzYvP; Thu, 12 Jun 2025 23:06:12 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.18.1/8.18.1) with ESMTP id 55CN6CdQ031426; Thu, 12 Jun 2025 23:06:12 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.18.1/8.18.1/Submit) id 55CN6CuX031423; Thu, 12 Jun 2025 23:06:12 GMT (envelope-from git) Date: Thu, 12 Jun 2025 23:06:12 GMT Message-Id: <202506122306.55CN6CuX031423@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Alan Somers Subject: git: cc9c21308679 - main - fusefs: Fix a panic when unmounting before init List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: asomers X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: cc9c213086795fd821e6c17ee99a1db81a2141b2 Auto-Submitted: auto-generated The branch main has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=cc9c213086795fd821e6c17ee99a1db81a2141b2 commit cc9c213086795fd821e6c17ee99a1db81a2141b2 Author: Alan Somers AuthorDate: 2025-06-05 20:00:48 +0000 Commit: Alan Somers CommitDate: 2025-06-12 23:04:34 +0000 fusefs: Fix a panic when unmounting before init fusefs would page fault due to the following sequence of events: * The server did not respond to FUSE_INIT in timely fashion. * Some other process tried to do unmount. * That other process got killed by a signal. * The server finally responded to FUSE_INIT. PR: 287438 MFC after: 2 weeks Sponsored by: ConnectWise Reviewed by: arrowd Differential Revision: https://reviews.freebsd.org/D50799 --- sys/fs/fuse/fuse_internal.c | 3 + tests/sys/fs/fusefs/Makefile | 1 + tests/sys/fs/fusefs/mockfs.cc | 18 +++-- tests/sys/fs/fusefs/mockfs.hh | 6 +- tests/sys/fs/fusefs/pre-init.cc | 154 ++++++++++++++++++++++++++++++++++++++++ tests/sys/fs/fusefs/utils.cc | 3 +- tests/sys/fs/fusefs/utils.hh | 2 + 7 files changed, 179 insertions(+), 8 deletions(-) diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index c6354ae7150f..61fe2ed032f6 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -979,6 +979,9 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) struct fuse_data *data = tick->tk_data; struct fuse_init_out *fiio = NULL; + if (fdata_get_dead(data)) + goto out; + if ((err = tick->tk_aw_ohead.error)) { goto out; } diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile index 4265f5b71dfa..b1ac704ab4f6 100644 --- a/tests/sys/fs/fusefs/Makefile +++ b/tests/sys/fs/fusefs/Makefile @@ -39,6 +39,7 @@ GTESTS+= nfs GTESTS+= notify GTESTS+= open GTESTS+= opendir +GTESTS+= pre-init GTESTS+= read GTESTS+= readdir GTESTS+= readlink diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc index b1621d05703c..65cdc3919652 100644 --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -420,7 +420,7 @@ MockFS::MockFS(int max_read, int max_readahead, bool allow_other, bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags, uint32_t kernel_minor_version, uint32_t max_write, bool async, bool noclusterr, unsigned time_gran, bool nointr, bool noatime, - const char *fsname, const char *subtype) + const char *fsname, const char *subtype, bool no_auto_init) : m_daemon_id(NULL), m_kernel_minor_version(kernel_minor_version), m_kq(pm == KQ ? kqueue() : -1), @@ -529,7 +529,9 @@ MockFS::MockFS(int max_read, int max_readahead, bool allow_other, ON_CALL(*this, process(_, _)) .WillByDefault(Invoke(this, &MockFS::process_default)); - init(flags); + if (!no_auto_init) + init(flags); + bzero(&sa, sizeof(sa)); sa.sa_handler = sigint_handler; sa.sa_flags = 0; /* Don't set SA_RESTART! */ @@ -543,10 +545,7 @@ MockFS::MockFS(int max_read, int max_readahead, bool allow_other, MockFS::~MockFS() { kill_daemon(); - if (m_daemon_id != NULL) { - pthread_join(m_daemon_id, NULL); - m_daemon_id = NULL; - } + join_daemon(); ::unmount("mountpoint", MNT_FORCE); rmdir("mountpoint"); if (m_kq >= 0) @@ -787,6 +786,13 @@ void MockFS::kill_daemon() { m_fuse_fd = -1; } +void MockFS::join_daemon() { + if (m_daemon_id != NULL) { + pthread_join(m_daemon_id, NULL); + m_daemon_id = NULL; + } +} + void MockFS::loop() { std::vector> out; diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh index 1de77767d0c9..ba6f7fded9d0 100644 --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -366,13 +366,17 @@ class MockFS { enum poll_method pm, uint32_t flags, uint32_t kernel_minor_version, uint32_t max_write, bool async, bool no_clusterr, unsigned time_gran, bool nointr, - bool noatime, const char *fsname, const char *subtype); + bool noatime, const char *fsname, const char *subtype, + bool no_auto_init); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ void kill_daemon(); + /* Wait until the daemon thread terminates */ + void join_daemon(); + /* Process FUSE requests endlessly */ void loop(); diff --git a/tests/sys/fs/fusefs/pre-init.cc b/tests/sys/fs/fusefs/pre-init.cc new file mode 100644 index 000000000000..e990d3cafffa --- /dev/null +++ b/tests/sys/fs/fusefs/pre-init.cc @@ -0,0 +1,154 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 ConnectWise + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +#include + +#include +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +/* Tests for behavior that happens before the server responds to FUSE_INIT */ +class PreInit: public FuseTest { +void SetUp() { + m_no_auto_init = true; + FuseTest::SetUp(); +} +}; + +static void* unmount1(void* arg __unused) { + ssize_t r; + + r = unmount("mountpoint", 0); + if (r >= 0) + return 0; + else + return (void*)(intptr_t)errno; +} + +/* + * Attempting to unmount the file system before it fully initializes should + * work fine. The unmount will complete after initialization does. + */ +TEST_F(PreInit, unmount_before_init) +{ + sem_t sem0; + pthread_t th1; + + ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in.header.opcode == FUSE_INIT); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { + SET_OUT_HEADER_LEN(out, init); + out.body.init.major = FUSE_KERNEL_VERSION; + out.body.init.minor = FUSE_KERNEL_MINOR_VERSION; + out.body.init.flags = in.body.init.flags & m_init_flags; + out.body.init.max_write = m_maxwrite; + out.body.init.max_readahead = m_maxreadahead; + out.body.init.time_gran = m_time_gran; + sem_wait(&sem0); + }))); + expect_destroy(0); + + ASSERT_EQ(0, pthread_create(&th1, NULL, unmount1, NULL)); + nap(); /* Wait for th1 to block in unmount() */ + sem_post(&sem0); + /* The daemon will quit after receiving FUSE_DESTROY */ + m_mock->join_daemon(); +} + +/* + * Don't panic in this very specific scenario: + * + * The server does not respond to FUSE_INIT in timely fashion. + * Some other process tries to do unmount. + * That other process gets killed by a signal. + * The server finally responds to FUSE_INIT. + * + * Regression test for bug 287438 + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=287438 + */ +TEST_F(PreInit, signal_during_unmount_before_init) +{ + sem_t sem0; + pid_t child; + + ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in.header.opcode == FUSE_INIT); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { + SET_OUT_HEADER_LEN(out, init); + out.body.init.major = FUSE_KERNEL_VERSION; + /* + * Use protocol 7.19, like libfuse2 does. The server must use + * protocol 7.27 or older to trigger the bug. + */ + out.body.init.minor = 19; + out.body.init.flags = in.body.init.flags & m_init_flags; + out.body.init.max_write = m_maxwrite; + out.body.init.max_readahead = m_maxreadahead; + out.body.init.time_gran = m_time_gran; + sem_wait(&sem0); + }))); + + if ((child = ::fork()) == 0) { + /* + * In child. This will block waiting for FUSE_INIT to complete + * or the receipt of an asynchronous signal. + */ + (void) unmount("mountpoint", 0); + _exit(0); /* Unreachable, unless parent dies after fork */ + } else if (child > 0) { + /* In parent. Wait for child process to start, then kill it */ + nap(); + kill(child, SIGINT); + waitpid(child, NULL, WEXITED); + } else { + FAIL() << strerror(errno); + } + m_mock->m_quit = true; /* Since we are by now unmounted. */ + sem_post(&sem0); + m_mock->join_daemon(); +} diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc index d702dec50247..125b7e2d6fc7 100644 --- a/tests/sys/fs/fusefs/utils.cc +++ b/tests/sys/fs/fusefs/utils.cc @@ -151,7 +151,8 @@ void FuseTest::SetUp() { m_default_permissions, m_push_symlinks_in, m_ro, m_pm, m_init_flags, m_kernel_minor_version, m_maxwrite, m_async, m_noclusterr, m_time_gran, - m_nointr, m_noatime, m_fsname, m_subtype); + m_nointr, m_noatime, m_fsname, m_subtype, + m_no_auto_init); /* * FUSE_ACCESS is called almost universally. Expecting it in * each test case would be super-annoying. Instead, set a diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh index 9dd8dad6b5cc..91bbba909672 100644 --- a/tests/sys/fs/fusefs/utils.hh +++ b/tests/sys/fs/fusefs/utils.hh @@ -69,6 +69,7 @@ class FuseTest : public ::testing::Test { bool m_async; bool m_noclusterr; bool m_nointr; + bool m_no_auto_init; unsigned m_time_gran; MockFS *m_mock = NULL; const static uint64_t FH = 0xdeadbeef1a7ebabe; @@ -95,6 +96,7 @@ class FuseTest : public ::testing::Test { m_async(false), m_noclusterr(false), m_nointr(false), + m_no_auto_init(false), m_time_gran(1), m_fsname(""), m_subtype(""),