xref: /freebsd/tests/sys/fs/fusefs/ioctl.cc (revision 17ba6f428683b661178b50a9d59f8b9e0dd2138a)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2025 CismonX <admin@cismon.net>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 extern "C" {
29 #include <sys/types.h>
30 #include <sys/ioctl.h>
31 #include <fcntl.h>
32 #include <string.h>
33 }
34 
35 #include "mockfs.hh"
36 #include "utils.hh"
37 
38 using namespace testing;
39 
40 using IoctlTestProcT = std::function<void (int)>;
41 
42 static const char INPUT_DATA[] = "input_data";
43 static const char OUTPUT_DATA[] = "output_data";
44 
45 class Ioctl: public FuseTest {
46 public:
expect_ioctl(uint64_t ino,ProcessMockerT r)47 void expect_ioctl(uint64_t ino, ProcessMockerT r)
48 {
49 	EXPECT_CALL(*m_mock, process(
50 		ResultOf([=](auto in) {
51 			return (in.header.opcode == FUSE_IOCTL &&
52 				in.header.nodeid == ino);
53 		}, Eq(true)), _)
54 	).WillOnce(Invoke(r)).RetiresOnSaturation();
55 }
56 
expect_ioctl_rw(uint64_t ino)57 void expect_ioctl_rw(uint64_t ino)
58 {
59 	/*
60 	 * _IOR(): Compare the input data with INPUT_DATA.
61 	 * _IOW(): Copy out OUTPUT_DATA.
62 	 * _IOWR(): Combination of above.
63 	 * _IOWINT(): Return the integer argument value.
64 	 */
65 	expect_ioctl(ino, ReturnImmediate([](auto in, auto& out) {
66 		uint8_t *in_buf = in.body.bytes + sizeof(in.body.ioctl);
67 		uint8_t *out_buf = out.body.bytes + sizeof(out.body.ioctl);
68 		uint32_t cmd = in.body.ioctl.cmd;
69 		uint32_t arg_len = IOCPARM_LEN(cmd);
70 		int result = 0;
71 
72 		out.header.error = 0;
73 		SET_OUT_HEADER_LEN(out, ioctl);
74 		if ((cmd & IOC_VOID) != 0 && arg_len > 0) {
75 			memcpy(&result, in_buf, sizeof(int));
76 			goto out;
77 		}
78 		if ((cmd & IOC_IN) != 0) {
79 			if (0 != strncmp(INPUT_DATA, (char *)in_buf, arg_len)) {
80 				result = -EINVAL;
81 				goto out;
82 			}
83 		}
84 		if ((cmd & IOC_OUT) != 0) {
85 			memcpy(out_buf, OUTPUT_DATA, sizeof(OUTPUT_DATA));
86 			out.header.len += sizeof(OUTPUT_DATA);
87 		}
88 
89 out:
90 		out.body.ioctl.result = result;
91 	}));
92 }
93 };
94 
95 /**
96  * If the server does not implement FUSE_IOCTL handler (returns ENOSYS),
97  * the kernel should return ENOTTY to the user instead.
98  */
TEST_F(Ioctl,enosys)99 TEST_F(Ioctl, enosys)
100 {
101 	unsigned long req = _IO(0xff, 0);
102 	int fd;
103 
104 	expect_opendir(FUSE_ROOT_ID);
105 	expect_ioctl(FUSE_ROOT_ID, ReturnErrno(ENOSYS));
106 
107 	fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
108 	ASSERT_LE(0, fd) << strerror(errno);
109 
110 	EXPECT_EQ(-1, ioctl(fd, req));
111 	EXPECT_EQ(ENOTTY, errno);
112 
113 	leak(fd);
114 }
115 
116 /*
117  * For _IOR() and _IOWR(), The server is allowed to write fewer bytes
118  * than IOCPARM_LEN(req).
119  */
TEST_F(Ioctl,ior)120 TEST_F(Ioctl, ior)
121 {
122 	char buf[sizeof(OUTPUT_DATA) + 1] = { 0 };
123 	unsigned long req = _IOR(0xff, 1, buf);
124 	int fd;
125 
126 	expect_opendir(FUSE_ROOT_ID);
127 	expect_ioctl_rw(FUSE_ROOT_ID);
128 
129 	fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
130 	ASSERT_LE(0, fd) << strerror(errno);
131 
132 	EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
133 	EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
134 
135 	leak(fd);
136 }
137 
138 /*
139  * For _IOR() and _IOWR(), if the server attempts to write more bytes
140  * than IOCPARM_LEN(req), the kernel should fail the syscall with EIO.
141  */
TEST_F(Ioctl,ior_overflow)142 TEST_F(Ioctl, ior_overflow)
143 {
144 	char buf[sizeof(OUTPUT_DATA) - 1] = { 0 };
145 	unsigned long req = _IOR(0xff, 2, buf);
146 	int fd;
147 
148 	expect_opendir(FUSE_ROOT_ID);
149 	expect_ioctl_rw(FUSE_ROOT_ID);
150 
151 	fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
152 	ASSERT_LE(0, fd) << strerror(errno);
153 
154 	EXPECT_EQ(-1, ioctl(fd, req, buf));
155 	EXPECT_EQ(EIO, errno);
156 
157 	leak(fd);
158 }
159 
TEST_F(Ioctl,iow)160 TEST_F(Ioctl, iow)
161 {
162 	unsigned long req = _IOW(0xff, 3, INPUT_DATA);
163 	int fd;
164 
165 	expect_opendir(FUSE_ROOT_ID);
166 	expect_ioctl_rw(FUSE_ROOT_ID);
167 
168 	fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
169 	ASSERT_LE(0, fd) << strerror(errno);
170 
171 	EXPECT_EQ(0, ioctl(fd, req, INPUT_DATA)) << strerror(errno);
172 
173 	leak(fd);
174 }
175 
TEST_F(Ioctl,iowr)176 TEST_F(Ioctl, iowr)
177 {
178 	char buf[std::max(sizeof(INPUT_DATA), sizeof(OUTPUT_DATA))] = { 0 };
179 	unsigned long req = _IOWR(0xff, 4, buf);
180 	int fd;
181 
182 	expect_opendir(FUSE_ROOT_ID);
183 	expect_ioctl_rw(FUSE_ROOT_ID);
184 
185 	fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
186 	ASSERT_LE(0, fd) << strerror(errno);
187 
188 	memcpy(buf, INPUT_DATA, sizeof(INPUT_DATA));
189 	EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
190 	EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
191 
192 	leak(fd);
193 }
194 
TEST_F(Ioctl,iowint)195 TEST_F(Ioctl, iowint)
196 {
197 	unsigned long req = _IOWINT(0xff, 5);
198 	int arg = 1337;
199 	int fd, r;
200 
201 	expect_opendir(FUSE_ROOT_ID);
202 	expect_ioctl_rw(FUSE_ROOT_ID);
203 
204 	fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
205 	ASSERT_LE(0, fd) << strerror(errno);
206 
207 	/* The server is allowed to return a positive value on success */
208 	r = ioctl(fd, req, arg);
209 	EXPECT_LE(0, r) << strerror(errno);
210 	EXPECT_EQ(arg, r);
211 
212 	leak(fd);
213 }
214