git: 5f51c9c328a6 - main - fusefs: add some more test cases for bad fuse servers

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Wed, 22 Feb 2023 17:04:30 UTC
The branch main has been updated by asomers:

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

commit 5f51c9c328a63d7290f634b1742f03545eaa2c1c
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2023-02-21 23:26:37 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2023-02-22 17:03:58 +0000

    fusefs: add some more test cases for bad fuse servers
    
    MFC after:      1 week
    Sponsored by:   Axcient
    Reviewed by:    emaste
    Differential Revision: https://reviews.freebsd.org/D38719
---
 tests/sys/fs/fusefs/Makefile      |   1 +
 tests/sys/fs/fusefs/bad_server.cc | 105 ++++++++++++++++++++++++++++++++++++++
 tests/sys/fs/fusefs/lookup.cc     |   2 +-
 tests/sys/fs/fusefs/mockfs.cc     |   6 +--
 tests/sys/fs/fusefs/mockfs.hh     |   7 ++-
 5 files changed, 112 insertions(+), 9 deletions(-)

diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
index 04b68681ec9a..df3753967603 100644
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -11,6 +11,7 @@ TESTSDIR=	${TESTSBASE}/sys/fs/fusefs
 # out, so we get more granular reporting.
 GTESTS+=	access
 GTESTS+=	allow_other
+GTESTS+=	bad_server
 GTESTS+=	bmap
 GTESTS+=	cache
 GTESTS+=	copy_file_range
diff --git a/tests/sys/fs/fusefs/bad_server.cc b/tests/sys/fs/fusefs/bad_server.cc
new file mode 100644
index 000000000000..34392da0090a
--- /dev/null
+++ b/tests/sys/fs/fusefs/bad_server.cc
@@ -0,0 +1,105 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2023 Axcient
+ *
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+extern "C" {
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+class BadServer: public FuseTest {};
+
+/*
+ * If the server sends a response for an unknown request, the kernel should
+ * gracefully return EINVAL.
+ */
+TEST_F(BadServer, UnknownUnique)
+{
+	mockfs_buf_out out;
+
+	out.header.len = sizeof(out.header);
+	out.header.error = 0;
+	out.header.unique = 99999;		// Invalid!
+	out.expected_errno = EINVAL;
+	m_mock->write_response(out);
+}
+
+/*
+ * If the server sends less than a header's worth of data, the kernel should
+ * gracefully return EINVAL.
+ */
+TEST_F(BadServer, ShortWrite)
+{
+	mockfs_buf_out out;
+
+	out.header.len = sizeof(out.header) - 1;
+	out.header.error = 0;
+	out.header.unique = 0;			// Asynchronous notification
+	out.expected_errno = EINVAL;
+	m_mock->write_response(out);
+}
+
+/*
+ * It is illegal to report an error, and also send back a payload.
+ */
+TEST_F(BadServer, ErrorWithPayload)
+{
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+
+	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
+	.WillOnce(Invoke([&](auto in, auto &out) {
+		// First send an invalid response
+		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+		out0->header.unique = in.header.unique;
+		out0->header.error = -ENOENT;
+		SET_OUT_HEADER_LEN(*out0, entry);	// Invalid!
+		out0->expected_errno = EINVAL;
+		out.push_back(std::move(out0));
+
+		// Then, respond to the lookup so we can complete the test
+		std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
+		out1->header.unique = in.header.unique;
+		out1->header.error = -ENOENT;
+		out1->header.len = sizeof(out1->header);
+		out.push_back(std::move(out1));
+
+		// The kernel may disconnect us for bad behavior, so don't try
+		// to read any more.
+		m_mock->m_quit = true;
+	}));
+
+	EXPECT_NE(0, access(FULLPATH, F_OK));
+
+	EXPECT_EQ(ENOENT, errno);
+}
diff --git a/tests/sys/fs/fusefs/lookup.cc b/tests/sys/fs/fusefs/lookup.cc
index c654dd46bae5..7a5be84b48be 100644
--- a/tests/sys/fs/fusefs/lookup.cc
+++ b/tests/sys/fs/fusefs/lookup.cc
@@ -289,7 +289,7 @@ TEST_F(Lookup, ejustreturn)
 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
 		out.header.len = sizeof(out.header);
 		out.header.error = 2;
-		m_mock->m_expected_write_errno = EINVAL;
+		out.expected_errno = EINVAL;
 	})));
 
 	EXPECT_NE(0, access(FULLPATH, F_OK));
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
index e012df8c3488..43e8dd3b0371 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -431,7 +431,6 @@ MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
 	const bool trueval = true;
 
 	m_daemon_id = NULL;
-	m_expected_write_errno = 0;
 	m_kernel_minor_version = kernel_minor_version;
 	m_maxreadahead = max_readahead;
 	m_maxwrite = MIN(max_write, max_max_write);
@@ -801,7 +800,6 @@ void MockFS::loop() {
 
 		bzero(in.get(), sizeof(*in));
 		read_request(*in, buflen);
-		m_expected_write_errno = 0;
 		if (m_quit)
 			break;
 		if (verbosity > 0)
@@ -1034,9 +1032,9 @@ void MockFS::write_response(const mockfs_buf_out &out) {
 		FAIL() << "not yet implemented";
 	}
 	r = write(m_fuse_fd, &out, out.header.len);
-	if (m_expected_write_errno) {
+	if (out.expected_errno) {
 		ASSERT_EQ(-1, r);
-		ASSERT_EQ(m_expected_write_errno, errno) << strerror(errno);
+		ASSERT_EQ(out.expected_errno, errno) << strerror(errno);
 	} else {
 		ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
 	}
diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh
index edbaf7ef770f..51ed5ad72ee6 100644
--- a/tests/sys/fs/fusefs/mockfs.hh
+++ b/tests/sys/fs/fusefs/mockfs.hh
@@ -233,6 +233,8 @@ union fuse_payloads_out {
 struct mockfs_buf_out {
 	fuse_out_header		header;
 	union fuse_payloads_out	body;
+	/* the expected errno of the write to /dev/fuse */
+	int			expected_errno;
 
 	/* Default constructor: zero everything */
 	mockfs_buf_out() {
@@ -333,16 +335,13 @@ class MockFS {
 	 */
 	void read_request(mockfs_buf_in& in, ssize_t& res);
 
+	public:
 	/* Write a single response back to the kernel */
 	void write_response(const mockfs_buf_out &out);
 
-	public:
 	/* pid of child process, for two-process test cases */
 	pid_t m_child_pid;
 
-	/* the expected errno of the next write to /dev/fuse */
-	int m_expected_write_errno;
-
 	/* Maximum size of a FUSE_WRITE write */
 	uint32_t m_maxwrite;