git: f93a50d69df2 - main - fusefs: fix an uninitialized memory access in fuse_vnop_deallocate

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Sun, 08 Sep 2024 21:57:22 UTC
The branch main has been updated by asomers:

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

commit f93a50d69df2e996ff1d4f793d0dcb9de655ebdc
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2024-09-08 21:50:40 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2024-09-08 21:50:40 +0000

    fusefs: fix an uninitialized memory access in fuse_vnop_deallocate
    
    If the FUSE_GETATTR issued to query a file's size during
    fuse_vnop_deallocate failed for any reason, then fuse_vnop_deallocate
    would attempt to destroy an uninitialized fuse_dispatcher struct, with a
    crash the likely result.  This bug only affects FUSE file systems that
    implement FUSE_FALLOCATE, and is unlikely to be seen on those that don't
    disable attribute caching.
    
    Reported by:    Coverity Scan
    CID:            1505308
    MFC after:      2 weeks
---
 sys/fs/fuse/fuse_vnops.c         |  2 +-
 tests/sys/fs/fusefs/fallocate.cc | 51 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index bf272ab706da..30993441bd72 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -3075,8 +3075,8 @@ fuse_vnop_deallocate(struct vop_deallocate_args *ap)
 			    false);
 	}
 
-out:
 	fdisp_destroy(&fdi);
+out:
 	if (closefufh)
 		fuse_filehandle_close(vp, fufh, curthread, cred);
 
diff --git a/tests/sys/fs/fusefs/fallocate.cc b/tests/sys/fs/fusefs/fallocate.cc
index ff5e3eb4f4bb..a05760207648 100644
--- a/tests/sys/fs/fusefs/fallocate.cc
+++ b/tests/sys/fs/fusefs/fallocate.cc
@@ -310,6 +310,57 @@ TEST_F(Fspacectl, erofs)
 	leak(fd);
 }
 
+/*
+ * If FUSE_GETATTR fails when determining the size of the file, fspacectl
+ * should fail gracefully.  This failure mode is easiest to trigger when
+ * attribute caching is disabled.
+ */
+TEST_F(Fspacectl, getattr_fails)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	Sequence seq;
+	struct spacectl_range rqsr;
+	const uint64_t ino = 42;
+	const uint64_t fsize = 2000;
+	int fd;
+
+	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1, 0);
+	expect_open(ino, 0, 1);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in.header.opcode == FUSE_GETATTR &&
+				in.header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).Times(1)
+	.InSequence(seq)
+	.WillOnce(Invoke(ReturnImmediate([](auto i __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 = fsize;
+		out.body.attr.attr_valid = 0;
+	})));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([](auto in) {
+			return (in.header.opcode == FUSE_GETATTR &&
+				in.header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).InSequence(seq)
+	.WillOnce(ReturnErrno(EIO));
+
+	fd = open(FULLPATH, O_RDWR);
+	ASSERT_LE(0, fd) << strerror(errno);
+	rqsr.r_offset = 500;
+	rqsr.r_len = 1000;
+	EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
+	EXPECT_EQ(EIO, errno);
+
+	leak(fd);
+}
+
 TEST_F(Fspacectl, ok)
 {
 	const char FULLPATH[] = "mountpoint/some_file.txt";