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