1616#include < linux/capability.h>
1717#include < linux/fuse.h>
1818#include < stdio.h>
19+ #include < sys/ioctl.h>
1920#include < sys/mount.h>
2021#include < sys/stat.h>
22+ #include < sys/uio.h>
2123#include < unistd.h>
2224
2325#include < cerrno>
3537#include " test/util/linux_capability_util.h"
3638#include " test/util/mount_util.h"
3739#include " test/util/posix_error.h"
40+ #include " test/util/save_util.h"
3841#include " test/util/temp_path.h"
3942#include " test/util/test_util.h"
43+ #include " test/util/thread_util.h"
4044
4145using ::testing::Ge;
4246
@@ -45,6 +49,34 @@ namespace testing {
4549
4650namespace {
4751
52+ void FuseInit (int fd) {
53+ alignas (fuse_in_header) char req_buf[FUSE_MIN_READ_BUFFER];
54+ ASSERT_THAT (read (fd, req_buf, sizeof (req_buf)),
55+ SyscallSucceedsWithValue (Ge (sizeof (fuse_in_header))));
56+
57+ fuse_in_header* in_hdr = reinterpret_cast <fuse_in_header*>(req_buf);
58+ ASSERT_EQ (in_hdr->opcode , FUSE_INIT);
59+
60+ fuse_out_header out_hdr;
61+ out_hdr.error = 0 ;
62+ out_hdr.unique = in_hdr->unique ;
63+ fuse_init_out out_payload = {};
64+ out_payload.major = FUSE_KERNEL_VERSION;
65+ out_payload.minor = FUSE_KERNEL_MINOR_VERSION;
66+ out_payload.max_readahead = 0 ;
67+ out_payload.flags = 0 ;
68+ out_payload.congestion_threshold = 0 ;
69+
70+ struct iovec iov[] = {
71+ {.iov_base = &out_hdr, .iov_len = sizeof (out_hdr)},
72+ {.iov_base = &out_payload, .iov_len = sizeof (out_payload)},
73+ };
74+ out_hdr.len = sizeof (out_hdr) + sizeof (out_payload);
75+
76+ ASSERT_THAT (writev (fd, iov, 2 ),
77+ SyscallSucceedsWithValue (sizeof (out_hdr) + sizeof (out_payload)));
78+ }
79+
4880TEST (FuseTest, RejectBadInit) {
4981 SKIP_IF (!ASSERT_NO_ERRNO_AND_VALUE (HaveCapability (CAP_SYS_ADMIN)));
5082 const FileDescriptor fd =
@@ -71,6 +103,106 @@ TEST(FuseTest, RejectBadInit) {
71103 SyscallFailsWithErrno (EINVAL));
72104}
73105
106+ TEST (FuseTest, CloneDevice) {
107+ SKIP_IF (!ASSERT_NO_ERRNO_AND_VALUE (HaveCapability (CAP_SYS_ADMIN)));
108+ SKIP_IF (IsRunningWithSaveRestore ());
109+
110+ const FileDescriptor fd1 =
111+ ASSERT_NO_ERRNO_AND_VALUE (Open (" /dev/fuse" , O_RDWR));
112+
113+ auto mount_point = ASSERT_NO_ERRNO_AND_VALUE (TempPath::CreateDir ());
114+ auto mount_opts =
115+ absl::StrFormat (" fd=%d,user_id=0,group_id=0,rootmode=40000" , fd1.get ());
116+ auto mount = ASSERT_NO_ERRNO_AND_VALUE (
117+ Mount (" fuse" , mount_point.path (), " fuse" , MS_NODEV | MS_NOSUID,
118+ mount_opts, 0 /* umountflags */ ));
119+ FuseInit (fd1.get ());
120+
121+ const FileDescriptor fd2 =
122+ ASSERT_NO_ERRNO_AND_VALUE (Open (" /dev/fuse" , O_RDWR));
123+ int fd1_num = fd1.get ();
124+ ASSERT_THAT (ioctl (fd2.get (), FUSE_DEV_IOC_CLONE, &fd1_num),
125+ SyscallSucceeds ());
126+
127+ ScopedThread fuse_server = ScopedThread ([&] {
128+ // Send stat reply from both FUSE servers.
129+ for (int fd : {fd1.get (), fd2.get ()}) {
130+ // Read the stat request.
131+ alignas (fuse_in_header) char req_buf[4096 * 4 ];
132+ ASSERT_THAT (read (fd, req_buf, sizeof (req_buf)),
133+ SyscallSucceedsWithValue (Ge (sizeof (fuse_in_header))));
134+
135+ fuse_in_header* in_hdr = reinterpret_cast <fuse_in_header*>(req_buf);
136+ ASSERT_EQ (in_hdr->opcode , FUSE_GETATTR);
137+
138+ // Send stat reply.
139+ fuse_out_header out_hdr;
140+ out_hdr.error = 0 ;
141+ out_hdr.unique = in_hdr->unique ;
142+ fuse_attr_out out_payload = {};
143+ out_payload.attr .mode = S_IFDIR | 0755 ;
144+ out_payload.attr .nlink = 1 ;
145+ out_payload.attr .uid = 0 ;
146+ out_payload.attr .gid = 0 ;
147+ out_payload.attr .size = fd;
148+ out_payload.attr .atime = 0 ;
149+ out_payload.attr .mtime = 0 ;
150+ out_payload.attr .ctime = 0 ;
151+
152+ struct iovec iov[] = {
153+ {.iov_base = &out_hdr, .iov_len = sizeof (out_hdr)},
154+ {.iov_base = &out_payload, .iov_len = sizeof (out_payload)},
155+ };
156+ out_hdr.len = sizeof (out_hdr) + sizeof (out_payload);
157+
158+ ASSERT_THAT (
159+ writev (fd, iov, 2 ),
160+ SyscallSucceedsWithValue (sizeof (out_hdr) + sizeof (out_payload)));
161+ }
162+ });
163+
164+ // Check if filesystem is responsive by stat'ing root. Both FUSE servers
165+ // should be able to respond.
166+ struct stat st;
167+ EXPECT_THAT (stat (mount_point.path ().c_str (), &st), SyscallSucceeds ());
168+ EXPECT_EQ (st.st_size , fd1.get ());
169+ EXPECT_THAT (stat (mount_point.path ().c_str (), &st), SyscallSucceeds ());
170+ EXPECT_EQ (st.st_size , fd2.get ());
171+
172+ fuse_server.Join ();
173+ }
174+
175+ TEST (FuseTest, CloneToConnectedDeviceFails) {
176+ SKIP_IF (!ASSERT_NO_ERRNO_AND_VALUE (HaveCapability (CAP_SYS_ADMIN)));
177+ const FileDescriptor fd1 =
178+ ASSERT_NO_ERRNO_AND_VALUE (Open (" /dev/fuse" , O_RDWR));
179+
180+ auto mount_point = ASSERT_NO_ERRNO_AND_VALUE (TempPath::CreateDir ());
181+ auto mount_opts =
182+ absl::StrFormat (" fd=%d,user_id=0,group_id=0,rootmode=40000" , fd1.get ());
183+ auto mount = ASSERT_NO_ERRNO_AND_VALUE (
184+ Mount (" fuse" , mount_point.path (), " fuse" , MS_NODEV | MS_NOSUID,
185+ mount_opts, 0 /* umountflags */ ));
186+ FuseInit (fd1.get ());
187+
188+ int fd1_num = fd1.get ();
189+ EXPECT_THAT (ioctl (fd1.get (), FUSE_DEV_IOC_CLONE, &fd1_num),
190+ SyscallFailsWithErrno (EINVAL));
191+ }
192+
193+ TEST (FuseTest, CloneFromUnconnectedDeviceFails) {
194+ SKIP_IF (!ASSERT_NO_ERRNO_AND_VALUE (HaveCapability (CAP_SYS_ADMIN)));
195+ const FileDescriptor fd1 =
196+ ASSERT_NO_ERRNO_AND_VALUE (Open (" /dev/fuse" , O_RDWR));
197+
198+ const FileDescriptor fd2 =
199+ ASSERT_NO_ERRNO_AND_VALUE (Open (" /dev/fuse" , O_RDWR));
200+
201+ int fd1_num = fd1.get ();
202+ EXPECT_THAT (ioctl (fd2.get (), FUSE_DEV_IOC_CLONE, &fd1_num),
203+ SyscallFailsWithErrno (EINVAL));
204+ }
205+
74206TEST (FuseTest, LookupUpdatesInode) {
75207 SKIP_IF (absl::NullSafeStringView (getenv (" GVISOR_FUSE_TEST" )) != " TRUE" );
76208 const std::string kFileData = " May thy knife chip and shatter.\n " ;
0 commit comments