From nobody Wed May 20 19:40:36 2026 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 4gLMMm6BYGz6fJy9 for ; Wed, 20 May 2026 19:40:36 +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 "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gLMMm0fp6z3bBN for ; Wed, 20 May 2026 19:40:36 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1779306036; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=GcW+xt74xUt3ZSTVtGwXxd5XTova+19blrh8ktjaqPM=; b=rH6w1rOSX1m/3cjmqM/ntwoGaeeet3XbPQBe4MXDO6frMGDxg7/F7LJmLGLXq8SGiyXFle VODKJY0+VbhBVUKNbWQtDggHP6fL6T85K1ik7HwbS6JZm867sLOJtNuXGybRhaBRsITzyg bUx/2XIm+vyubw5YWpWBIAV4Pq9LbnWjUwgRapLTB7f3TclswKyXwOeHJs/foqXhYMaHwK XJkMR3IYAWGfnqowHehPv4Gdnzrw2PcJ2cZ/12ooDdHE2dsfmqrfNFBANOULJBkB0EiF2P /D/INccABagWr3eIqOjyVvzDyrst7s60O2gvOWGnxnGns2GoODtcyWBuqjsWKg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1779306036; a=rsa-sha256; cv=none; b=DFLuS1zjCVDsqrTd5lUJ1wDAiH/fWTdZFj2SbRcyxWEF901RwkMtE1Pm+1N2ki3JApc2X5 C5VY4ThPDtVnVOlVSF3fC/fqR1y+6L3NZTrZ5+9ZLqYDqqTIaiATWJ4aqUEHeTlwpd2fdr PiBo6O69UKqCJswmGuDqrq3/xYBcXohVUrEDcv44KFU8XNyDHDOa8FUHGvm5+1Yb0Vfd2Q 9F6Cyc1G3rNS8vXPJOkmsX7sBF1dWYmpgAh7J4NzVP8ZGtjh0SbRYPRWeBCik62RR8bT8Y WQNsivaOtMxgyBb/2cQvJkYxP2HCVBsMAUi2surqaK5XRUJ1ZbVdX7AoSMfyMQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1779306036; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=GcW+xt74xUt3ZSTVtGwXxd5XTova+19blrh8ktjaqPM=; b=bx381jYY07AQR3mrsrRM5XniLtiVxIQ6biO/O4xdFmjfvlPBrPW123N3mJYdnYtPwdfhCN 0u48D0wZkO+OD5IlS9iPhhUhljoL9bxPOiojIH5CIM7SshuTskW7y375hSAUJCgPKKw/cN 7uuJnvdI2Bz13MY1j/KURJRcKya8t4K9HRTs7nGQme7gsqYeVlrztQiIHAhE+2E6PxY0rp umlFy8XghpIEYxLk2BIT6j5aIeRujg+8iLgxjBD9pQ0IE3aHFQ8TNya8JrcwuV4lcl90dK n240fesdOUDx5kB+uA3pFGwirUcmctTsPCx0OwLtmB1fcs0AGMPK8nxctaCUaA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gLMMm072Vz1DfW for ; Wed, 20 May 2026 19:40:36 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 39ccd by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Wed, 20 May 2026 19:40:36 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org Cc: Alan Somers From: Mark Johnston Subject: git: 53f3bf4ee1ce - releng/14.3 - fusefs: Handle buggy servers' LISTXATTR response 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 List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/releng/14.3 X-Git-Reftype: branch X-Git-Commit: 53f3bf4ee1ce1d985c1070d4e5b1e4978f2bb92b Auto-Submitted: auto-generated Date: Wed, 20 May 2026 19:40:36 +0000 Message-Id: <6a0e0e34.39ccd.31d701b8@gitrepo.freebsd.org> The branch releng/14.3 has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=53f3bf4ee1ce1d985c1070d4e5b1e4978f2bb92b commit 53f3bf4ee1ce1d985c1070d4e5b1e4978f2bb92b Author: Alan Somers AuthorDate: 2026-05-04 19:35:11 +0000 Commit: Mark Johnston CommitDate: 2026-05-20 02:12:20 +0000 fusefs: Handle buggy servers' LISTXATTR response The fuse protocol requires server to respond to LISTXATTR with a NUL-terminated string. If they don't, report an error rather than attempt to scan through uninitialized memory for a NUL. Approved by: so Security: FreeBSD-SA-26:20.fusefs Security: CVE-2026-45252 admbugs: 1039 Reported by: Joshua Rogers Sponsored by: ConnectWise --- sys/fs/fuse/fuse_ipc.h | 1 + sys/fs/fuse/fuse_vnops.c | 17 +++++++---- tests/sys/fs/fusefs/xattr.cc | 73 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h index 5648624f4c63..7627eadb3a95 100644 --- a/sys/fs/fuse/fuse_ipc.h +++ b/sys/fs/fuse/fuse_ipc.h @@ -238,6 +238,7 @@ struct fuse_data { #define FSESS_WARN_WB_CACHE_INCOHERENT 0x400000 /* WB cache incoherent */ #define FSESS_WARN_ILLEGAL_INODE 0x800000 /* Illegal inode for new file */ #define FSESS_WARN_READLINK_EMBEDDED_NUL 0x1000000 /* corrupt READLINK output */ +#define FSESS_WARN_LSEXTATTR_NUL 0x20000000 /* Non nul-terminated xattr list */ #define FSESS_MNTOPTS_MASK ( \ FSESS_DAEMON_CAN_SPY | FSESS_PUSH_SYMLINKS_IN | \ FSESS_DEFAULT_PERMISSIONS | FSESS_INTR) diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 87e44051d9a9..5987a2a3061e 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -2806,8 +2806,8 @@ out: * bsd_list, bsd_list_len - output list compatible with bsd vfs */ static int -fuse_xattrlist_convert(char *prefix, const char *list, int list_len, - char *bsd_list, int *bsd_list_len) +fuse_xattrlist_convert(struct fuse_data *data, char *prefix, const char *list, + int list_len, char *bsd_list, int *bsd_list_len) { int len, pos, dist_to_next, prefix_len; @@ -2816,7 +2816,13 @@ fuse_xattrlist_convert(char *prefix, const char *list, int list_len, prefix_len = strlen(prefix); while (pos < list_len && list[pos] != '\0') { - dist_to_next = strlen(&list[pos]) + 1; + dist_to_next = strnlen(&list[pos], list_len - pos - 1) + 1; + if (list[pos + dist_to_next - 1] != '\0') { + fuse_warn(data, FSESS_WARN_LSEXTATTR_NUL, + "The FUSE server returned a non nul-terminated " + "LISTXATTR response."); + return (EIO); + } if (bcmp(&list[pos], prefix, prefix_len) == 0 && list[pos + prefix_len] == extattr_namespace_separator) { len = dist_to_next - @@ -2872,6 +2878,7 @@ fuse_vnop_listextattr(struct vop_listextattr_args *ap) struct fuse_listxattr_in *list_xattr_in; struct fuse_listxattr_out *list_xattr_out; struct mount *mp = vnode_mount(vp); + struct fuse_data *data = fuse_get_mpdata(mp); struct thread *td = ap->a_td; struct ucred *cred = ap->a_cred; char *prefix; @@ -2949,8 +2956,6 @@ fuse_vnop_listextattr(struct vop_listextattr_args *ap) linux_list = fdi.answ; /* FUSE doesn't allow the server to return more data than requested */ if (fdi.iosize > linux_list_len) { - struct fuse_data *data = fuse_get_mpdata(mp); - fuse_warn(data, FSESS_WARN_LSEXTATTR_LONG, "server returned " "more extended attribute data than requested; " @@ -2967,7 +2972,7 @@ fuse_vnop_listextattr(struct vop_listextattr_args *ap) * FreeBSD's format before giving it to the user. */ bsd_list = malloc(linux_list_len, M_TEMP, M_WAITOK); - err = fuse_xattrlist_convert(prefix, linux_list, linux_list_len, + err = fuse_xattrlist_convert(data, prefix, linux_list, linux_list_len, bsd_list, &bsd_list_len); if (err != 0) goto out; diff --git a/tests/sys/fs/fusefs/xattr.cc b/tests/sys/fs/fusefs/xattr.cc index 7fa2a741e074..a348315265c2 100644 --- a/tests/sys/fs/fusefs/xattr.cc +++ b/tests/sys/fs/fusefs/xattr.cc @@ -448,6 +448,79 @@ TEST_F(ListxattrSig, erange_forever) ASSERT_TRUE(WIFSIGNALED(status)); } +/* + * A buggy or malicious server returns a list that isn't nul-terminated. The + * kernel should handle it gracefully. + */ +TEST_F(Listxattr, not_nul_terminated) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + char *data; + const char expected[4] = {3, 'f', 'o', 'o'}; + const char first[255] = "user.foo\0system.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + const uint8_t badlist[9] = {'u', 's', 'e', 'r', '.', 'f', 'o', 'o', 'd'}; + Sequence seq; + + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) + .WillRepeatedly(Invoke( + ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, entry); + out.body.entry.attr.mode = S_IFREG | 0644; + out.body.entry.nodeid = ino; + out.body.entry.attr.nlink = 1; + out.body.entry.attr_valid = UINT64_MAX; + out.body.entry.entry_valid = UINT64_MAX; + }))); + + /* + * On the first LISTXATTRS call, return a big attribute just to fill + * the heap with non-NUL data. + */ + expect_listxattr(ino, 0, + ReturnImmediate([&](auto in __unused, auto& out) { + out.body.listxattr.size = sizeof(first); + SET_OUT_HEADER_LEN(out, listxattr); + }), &seq + ); + expect_listxattr(ino, sizeof(first), + ReturnImmediate([&](auto in __unused, auto& out) { + memcpy((void*)out.body.bytes, first, sizeof(first)); + out.header.len = sizeof(fuse_out_header) + sizeof(first); + }), &seq + ); + /* + * On the second LISTXATTRS call, return a malformed list with no NUL + * termination. The heap might still be full of the data from the + * first call. + */ + expect_listxattr(ino, 0, + ReturnImmediate([&](auto in __unused, auto& out) { + out.body.listxattr.size = sizeof(badlist); + SET_OUT_HEADER_LEN(out, listxattr); + }), &seq + ); + expect_listxattr(ino, sizeof(badlist), + ReturnImmediate([&](auto in __unused, auto& out) { + memset((void*)out.body.bytes, 'x', sizeof(first)); + memcpy((void*)out.body.bytes, badlist, sizeof(badlist)); + out.header.len = sizeof(fuse_out_header) + sizeof(badlist); + }), &seq + ); + + data = new char[1024]; + + ASSERT_EQ(static_cast(sizeof(expected)), + extattr_list_file(FULLPATH, ns, data, sizeof(data))) + << strerror(errno); + /* + * Receiving this malformed list, the kernel should log it to dmesg and + * report an IO error to the caller. + */ + ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, data, sizeof(data))); + EXPECT_EQ(EIO, errno); +} + /* * Get the size of the list that it would take to list no extended attributes */