Skip to content

Commit 525aa49

Browse files
manninglucasgvisor-bot
authored andcommitted
Add support for FUSE_DEV_IOC_CLONE.
PiperOrigin-RevId: 811542458
1 parent 41a5b6c commit 525aa49

File tree

5 files changed

+189
-0
lines changed

5 files changed

+189
-0
lines changed

pkg/abi/linux/ioctl.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,8 @@ const (
184184
KCOV_MODE_TRACE_PC = 2
185185
KCOV_MODE_TRACE_CMP = 3
186186
)
187+
188+
// FUSE_DEV_IOC_CLONE from include/uapi/linux/fuse.h.
189+
var (
190+
FUSE_DEV_IOC_CLONE = IOR(229, 0, 4)
191+
)

pkg/sentry/fsimpl/fuse/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ go_library(
8686
"//pkg/marshal/primitive",
8787
"//pkg/refs",
8888
"//pkg/safemem",
89+
"//pkg/sentry/arch",
8990
"//pkg/sentry/fsimpl/kernfs",
9091
"//pkg/sentry/fsutil",
9192
"//pkg/sentry/kernel",

pkg/sentry/fsimpl/fuse/dev.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
package fuse
1616

1717
import (
18+
"gvisor.dev/gvisor/pkg/abi/linux"
1819
"gvisor.dev/gvisor/pkg/context"
1920
"gvisor.dev/gvisor/pkg/errors/linuxerr"
21+
"gvisor.dev/gvisor/pkg/marshal/primitive"
22+
"gvisor.dev/gvisor/pkg/sentry/arch"
23+
"gvisor.dev/gvisor/pkg/sentry/kernel"
2024
"gvisor.dev/gvisor/pkg/sentry/vfs"
2125
"gvisor.dev/gvisor/pkg/sync"
2226
"gvisor.dev/gvisor/pkg/usermem"
@@ -184,3 +188,49 @@ func (fd *DeviceFD) Seek(ctx context.Context, offset int64, whence int32) (int64
184188

185189
return 0, linuxerr.ENOSYS
186190
}
191+
192+
// Ioctl implements vfs.FileDescriptionImpl.Ioctl.
193+
func (fd *DeviceFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
194+
cmd := args[1].Uint()
195+
switch cmd {
196+
case linux.FUSE_DEV_IOC_CLONE:
197+
t := kernel.TaskFromContext(ctx)
198+
if t == nil {
199+
return 0, linuxerr.ESRCH
200+
}
201+
var userFuseFD int32
202+
if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &userFuseFD); err != nil {
203+
return 0, err
204+
}
205+
userFuseFile, _ := t.FDTable().Get(userFuseFD)
206+
if userFuseFile == nil {
207+
return 0, linuxerr.EBADF
208+
}
209+
defer userFuseFile.DecRef(ctx)
210+
fuseFD, ok := userFuseFile.Impl().(*DeviceFD)
211+
if !ok {
212+
return 0, linuxerr.EINVAL
213+
}
214+
215+
fuseFD.mu.Lock()
216+
if fuseFD.conn == nil {
217+
fuseFD.mu.Unlock()
218+
return 0, linuxerr.EINVAL
219+
}
220+
conn := fuseFD.conn
221+
conn.IncRef()
222+
fuseFD.mu.Unlock()
223+
224+
fd.mu.Lock()
225+
defer fd.mu.Unlock()
226+
if fd.conn != nil {
227+
conn.DecRef(ctx)
228+
return 0, linuxerr.EINVAL
229+
}
230+
fd.conn = conn
231+
232+
return 0, nil
233+
}
234+
235+
return 0, linuxerr.ENOSYS
236+
}

test/syscalls/linux/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4780,6 +4780,7 @@ cc_binary(
47804780
"//test/util:fs_util",
47814781
"//test/util:mount_util",
47824782
"//test/util:posix_error",
4783+
"//test/util:save_util",
47834784
"//test/util:temp_path",
47844785
"//test/util:test_main",
47854786
"//test/util:test_util",

test/syscalls/linux/fuse.cc

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
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>
@@ -35,8 +37,10 @@
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

4145
using ::testing::Ge;
4246

@@ -45,6 +49,34 @@ namespace testing {
4549

4650
namespace {
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+
4880
TEST(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+
74206
TEST(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

Comments
 (0)