diff --git a/pkg/sentry/syscalls/BUILD b/pkg/sentry/syscalls/BUILD index 7a7c80ac6..971e075a6 100644 --- a/pkg/sentry/syscalls/BUILD +++ b/pkg/sentry/syscalls/BUILD @@ -5,7 +5,6 @@ package(licenses = ["notice"]) go_library( name = "syscalls", srcs = [ - "epoll.go", "syscalls.go", ], visibility = ["//:sandbox"], @@ -14,8 +13,5 @@ go_library( "//pkg/errors/linuxerr", "//pkg/sentry/arch", "//pkg/sentry/kernel", - "//pkg/sentry/kernel/epoll", - "//pkg/sentry/kernel/time", - "//pkg/waiter", ], ) diff --git a/pkg/sentry/syscalls/epoll.go b/pkg/sentry/syscalls/epoll.go deleted file mode 100644 index 01e5f991f..000000000 --- a/pkg/sentry/syscalls/epoll.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package syscalls - -import ( - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/epoll" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/waiter" -) - -// CreateEpoll implements the epoll_create(2) linux syscall. -func CreateEpoll(t *kernel.Task, closeOnExec bool) (int32, error) { - file := epoll.NewEventPoll(t) - defer file.DecRef(t) - - fd, err := t.NewFDFrom(0, file, kernel.FDFlags{ - CloseOnExec: closeOnExec, - }) - if err != nil { - return 0, err - } - - return fd, nil -} - -// AddEpoll implements the epoll_ctl(2) linux syscall when op is EPOLL_CTL_ADD. -func AddEpoll(t *kernel.Task, epfd int32, fd int32, flags epoll.EntryFlags, mask waiter.EventMask, userData [2]int32) error { - // Get epoll from the file descriptor. - epollfile := t.GetFile(epfd) - if epollfile == nil { - return linuxerr.EBADF - } - defer epollfile.DecRef(t) - - // Get the target file id. - file := t.GetFile(fd) - if file == nil { - return linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the epollPoll operations. - e, ok := epollfile.FileOperations.(*epoll.EventPoll) - if !ok { - return linuxerr.EBADF - } - - // Try to add the entry. - return e.AddEntry(epoll.FileIdentifier{file, fd}, flags, mask, userData) -} - -// UpdateEpoll implements the epoll_ctl(2) linux syscall when op is EPOLL_CTL_MOD. -func UpdateEpoll(t *kernel.Task, epfd int32, fd int32, flags epoll.EntryFlags, mask waiter.EventMask, userData [2]int32) error { - // Get epoll from the file descriptor. - epollfile := t.GetFile(epfd) - if epollfile == nil { - return linuxerr.EBADF - } - defer epollfile.DecRef(t) - - // Get the target file id. - file := t.GetFile(fd) - if file == nil { - return linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the epollPoll operations. - e, ok := epollfile.FileOperations.(*epoll.EventPoll) - if !ok { - return linuxerr.EBADF - } - - // Try to update the entry. - return e.UpdateEntry(epoll.FileIdentifier{file, fd}, flags, mask, userData) -} - -// RemoveEpoll implements the epoll_ctl(2) linux syscall when op is EPOLL_CTL_DEL. -func RemoveEpoll(t *kernel.Task, epfd int32, fd int32) error { - // Get epoll from the file descriptor. - epollfile := t.GetFile(epfd) - if epollfile == nil { - return linuxerr.EBADF - } - defer epollfile.DecRef(t) - - // Get the target file id. - file := t.GetFile(fd) - if file == nil { - return linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the epollPoll operations. - e, ok := epollfile.FileOperations.(*epoll.EventPoll) - if !ok { - return linuxerr.EBADF - } - - // Try to remove the entry. - return e.RemoveEntry(t, epoll.FileIdentifier{file, fd}) -} - -// WaitEpoll implements the epoll_wait(2) linux syscall. -func WaitEpoll(t *kernel.Task, fd int32, max int, timeoutInNanos int64) ([]linux.EpollEvent, error) { - // Get epoll from the file descriptor. - epollfile := t.GetFile(fd) - if epollfile == nil { - return nil, linuxerr.EBADF - } - defer epollfile.DecRef(t) - - // Extract the epollPoll operations. - e, ok := epollfile.FileOperations.(*epoll.EventPoll) - if !ok { - return nil, linuxerr.EBADF - } - - // Try to read events and return right away if we got them or if the - // caller requested a non-blocking "wait". - r := e.ReadEvents(max) - if len(r) != 0 || timeoutInNanos == 0 { - return r, nil - } - - // We'll have to wait. Set up the timer if a timeout was specified and - // and register with the epoll object for readability events. - var haveDeadline bool - var deadline ktime.Time - if timeoutInNanos > 0 { - timeoutDur := time.Duration(timeoutInNanos) * time.Nanosecond - deadline = t.Kernel().MonotonicClock().Now().Add(timeoutDur) - haveDeadline = true - } - - w, ch := waiter.NewChannelEntry(waiter.ReadableEvents) - e.EventRegister(&w) - defer e.EventUnregister(&w) - - // Try to read the events again until we succeed, timeout or get - // interrupted. - for { - r = e.ReadEvents(max) - if len(r) != 0 { - return r, nil - } - - if err := t.BlockWithDeadline(ch, haveDeadline, deadline); err != nil { - if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { - return nil, nil - } - - return nil, err - } - } -} diff --git a/pkg/sentry/syscalls/linux/BUILD b/pkg/sentry/syscalls/linux/BUILD index 29f7b4cc6..4d490ce26 100644 --- a/pkg/sentry/syscalls/linux/BUILD +++ b/pkg/sentry/syscalls/linux/BUILD @@ -6,8 +6,8 @@ go_library( name = "linux", srcs = [ "error.go", - "flags.go", "linux64.go", + "path.go", "points.go", "sigset.go", "sys_aio.go", @@ -21,17 +21,19 @@ go_library( "sys_getdents.go", "sys_identity.go", "sys_inotify.go", - "sys_lseek.go", + "sys_iouring.go", "sys_membarrier.go", "sys_mempolicy.go", "sys_mmap.go", "sys_mount.go", + "sys_mq.go", "sys_msgqueue.go", "sys_pipe.go", "sys_poll.go", "sys_prctl.go", + "sys_process_vm.go", "sys_random.go", - "sys_read.go", + "sys_read_write.go", "sys_rlimit.go", "sys_rseq.go", "sys_rusage.go", @@ -55,7 +57,6 @@ go_library( "sys_tls_amd64.go", "sys_tls_arm64.go", "sys_utsname.go", - "sys_write.go", "sys_xattr.go", "timespec.go", ], @@ -64,9 +65,12 @@ go_library( deps = [ "//pkg/abi", "//pkg/abi/linux", + "//pkg/bits", "//pkg/bpf", "//pkg/context", "//pkg/errors/linuxerr", + "//pkg/fspath", + "//pkg/gohacks", "//pkg/hostarch", "//pkg/log", "//pkg/marshal", @@ -75,23 +79,24 @@ go_library( "//pkg/rand", "//pkg/safemem", "//pkg/sentry/arch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/anon", "//pkg/sentry/fs/lock", - "//pkg/sentry/fs/timerfd", - "//pkg/sentry/fs/tmpfs", "//pkg/sentry/fsbridge", + "//pkg/sentry/fsimpl/eventfd", + "//pkg/sentry/fsimpl/host", + "//pkg/sentry/fsimpl/iouringfs", + "//pkg/sentry/fsimpl/pipefs", + "//pkg/sentry/fsimpl/signalfd", + "//pkg/sentry/fsimpl/timerfd", + "//pkg/sentry/fsimpl/tmpfs", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/epoll", - "//pkg/sentry/kernel/eventfd", "//pkg/sentry/kernel/fasync", "//pkg/sentry/kernel/ipc", + "//pkg/sentry/kernel/mq", "//pkg/sentry/kernel/msgqueue", "//pkg/sentry/kernel/pipe", "//pkg/sentry/kernel/sched", "//pkg/sentry/kernel/shm", - "//pkg/sentry/kernel/signalfd", "//pkg/sentry/kernel/time", "//pkg/sentry/limits", "//pkg/sentry/loader", diff --git a/pkg/sentry/syscalls/linux/error.go b/pkg/sentry/syscalls/linux/error.go index e73e94f32..ed0fd7a72 100644 --- a/pkg/sentry/syscalls/linux/error.go +++ b/pkg/sentry/syscalls/linux/error.go @@ -22,7 +22,6 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/metric" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" @@ -40,11 +39,11 @@ func incrementPartialResultMetric() { metric.WeirdnessMetric.Increment("partial_result") } -// HandleIOErrorVFS2 handles special error cases for partial results. For some +// HandleIOError handles special error cases for partial results. For some // errors, we may consume the error and return only the partial read/write. // // op and f are used only for panics. -func HandleIOErrorVFS2(ctx context.Context, partialResult bool, ioerr, intr error, op string, f *vfs.FileDescription) error { +func HandleIOError(ctx context.Context, partialResult bool, ioerr, intr error, op string, f *vfs.FileDescription) error { known, err := handleIOErrorImpl(ctx, partialResult, ioerr, intr, op) if err != nil { return err @@ -60,24 +59,6 @@ func HandleIOErrorVFS2(ctx context.Context, partialResult bool, ioerr, intr erro return nil } -// handleIOError handles special error cases for partial results. For some -// errors, we may consume the error and return only the partial read/write. -// -// op and f are used only for panics. -func handleIOError(ctx context.Context, partialResult bool, ioerr, intr error, op string, f *fs.File) error { - known, err := handleIOErrorImpl(ctx, partialResult, ioerr, intr, op) - if err != nil { - return err - } - if !known { - // An unknown error is encountered with a partial read/write. - name, _ := f.Dirent.FullName(nil /* ignore chroot */) - log.Traceback("Invalid request partialResult %v and err (type %T) %v for %s operation on %q, %T", partialResult, ioerr, ioerr, op, name, f.FileOperations) - partialResultOnce.Do(incrementPartialResultMetric) - } - return nil -} - // handleIOError handles special error cases for partial results. For some // errors, we may consume the error and return only the partial read/write. // diff --git a/pkg/sentry/syscalls/linux/flags.go b/pkg/sentry/syscalls/linux/flags.go deleted file mode 100644 index 07961dad9..000000000 --- a/pkg/sentry/syscalls/linux/flags.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package linux - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// flagsToPermissions returns a Permissions object from Linux flags. -// This includes truncate permission if O_TRUNC is set in the mask. -func flagsToPermissions(mask uint) (p fs.PermMask) { - if mask&linux.O_TRUNC != 0 { - p.Write = true - } - switch mask & linux.O_ACCMODE { - case linux.O_WRONLY: - p.Write = true - case linux.O_RDWR: - p.Write = true - p.Read = true - case linux.O_RDONLY: - p.Read = true - } - return -} - -// linuxToFlags converts Linux file flags to a FileFlags object. -func linuxToFlags(mask uint) fs.FileFlags { - return fs.FileFlags{ - Direct: mask&linux.O_DIRECT != 0, - DSync: mask&(linux.O_DSYNC|linux.O_SYNC) != 0, - Sync: mask&linux.O_SYNC != 0, - NonBlocking: mask&linux.O_NONBLOCK != 0, - Read: (mask & linux.O_ACCMODE) != linux.O_WRONLY, - Write: (mask & linux.O_ACCMODE) != linux.O_RDONLY, - Append: mask&linux.O_APPEND != 0, - Directory: mask&linux.O_DIRECTORY != 0, - Async: mask&linux.O_ASYNC != 0, - LargeFile: mask&linux.O_LARGEFILE != 0, - Truncate: mask&linux.O_TRUNC != 0, - } -} diff --git a/pkg/sentry/syscalls/linux/linux64.go b/pkg/sentry/syscalls/linux/linux64.go index 3399cba2d..a3afeee69 100644 --- a/pkg/sentry/syscalls/linux/linux64.go +++ b/pkg/sentry/syscalls/linux/linux64.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package linux provides syscall tables for amd64 Linux. +// Package linux provides syscall tables for amd64 and arm64 Linux. package linux import ( @@ -54,21 +54,21 @@ var AMD64 = &kernel.SyscallTable{ Table: map[uintptr]kernel.Syscall{ 0: syscalls.SupportedPoint("read", Read, PointRead), 1: syscalls.Supported("write", Write), - 2: syscalls.PartiallySupportedPoint("open", Open, PointOpen, "Options O_DIRECT, O_NOATIME, O_PATH, O_TMPFILE, O_SYNC are not supported.", nil), - 3: syscalls.Supported("close", Close), + 2: syscalls.SupportedPoint("open", Open, PointOpen), + 3: syscalls.SupportedPoint("close", Close, PointClose), 4: syscalls.Supported("stat", Stat), 5: syscalls.Supported("fstat", Fstat), 6: syscalls.Supported("lstat", Lstat), 7: syscalls.Supported("poll", Poll), 8: syscalls.Supported("lseek", Lseek), - 9: syscalls.PartiallySupported("mmap", Mmap, "Generally supported with exceptions. Options MAP_FIXED_NOREPLACE, MAP_SHARED_VALIDATE, MAP_SYNC MAP_GROWSDOWN, MAP_HUGETLB are not supported.", nil), + 9: syscalls.Supported("mmap", Mmap), 10: syscalls.Supported("mprotect", Mprotect), 11: syscalls.Supported("munmap", Munmap), 12: syscalls.Supported("brk", Brk), 13: syscalls.Supported("rt_sigaction", RtSigaction), 14: syscalls.Supported("rt_sigprocmask", RtSigprocmask), 15: syscalls.Supported("rt_sigreturn", RtSigreturn), - 16: syscalls.PartiallySupported("ioctl", Ioctl, "Only a few ioctls are implemented for backing devices and file systems.", nil), + 16: syscalls.Supported("ioctl", Ioctl), 17: syscalls.Supported("pread64", Pread64), 18: syscalls.Supported("pwrite64", Pwrite64), 19: syscalls.Supported("readv", Readv), @@ -93,21 +93,21 @@ var AMD64 = &kernel.SyscallTable{ 38: syscalls.Supported("setitimer", Setitimer), 39: syscalls.Supported("getpid", Getpid), 40: syscalls.Supported("sendfile", Sendfile), - 41: syscalls.PartiallySupported("socket", Socket, "Limited support for AF_NETLINK, NETLINK_ROUTE sockets. Limited support for SOCK_RAW.", nil), + 41: syscalls.SupportedPoint("socket", Socket, PointSocket), 42: syscalls.SupportedPoint("connect", Connect, PointConnect), 43: syscalls.SupportedPoint("accept", Accept, PointAccept), 44: syscalls.Supported("sendto", SendTo), 45: syscalls.Supported("recvfrom", RecvFrom), 46: syscalls.Supported("sendmsg", SendMsg), - 47: syscalls.PartiallySupported("recvmsg", RecvMsg, "Not all flags and control messages are supported.", nil), - 48: syscalls.PartiallySupported("shutdown", Shutdown, "Not all flags and control messages are supported.", nil), - 49: syscalls.PartiallySupportedPoint("bind", Bind, PointBind, "Autobind for abstract Unix sockets is not supported.", nil), + 47: syscalls.Supported("recvmsg", RecvMsg), + 48: syscalls.Supported("shutdown", Shutdown), + 49: syscalls.SupportedPoint("bind", Bind, PointBind), 50: syscalls.Supported("listen", Listen), 51: syscalls.Supported("getsockname", GetSockName), 52: syscalls.Supported("getpeername", GetPeerName), 53: syscalls.SupportedPoint("socketpair", SocketPair, PointSocketpair), - 54: syscalls.PartiallySupported("setsockopt", SetSockOpt, "Not all socket options are supported.", nil), - 55: syscalls.PartiallySupported("getsockopt", GetSockOpt, "Not all socket options are supported.", nil), + 54: syscalls.Supported("setsockopt", SetSockOpt), + 55: syscalls.Supported("getsockopt", GetSockOpt), 56: syscalls.PartiallySupportedPoint("clone", Clone, PointClone, "Mount namespace (CLONE_NEWNS) not supported. Options CLONE_PARENT, CLONE_SYSVSEM not supported.", nil), 57: syscalls.SupportedPoint("fork", Fork, PointFork), 58: syscalls.SupportedPoint("vfork", Vfork, PointVfork), @@ -124,10 +124,10 @@ var AMD64 = &kernel.SyscallTable{ 69: syscalls.Supported("msgsnd", Msgsnd), 70: syscalls.Supported("msgrcv", Msgrcv), 71: syscalls.Supported("msgctl", Msgctl), - 72: syscalls.PartiallySupportedPoint("fcntl", Fcntl, PointFcntl, "Not all options are supported.", nil), - 73: syscalls.PartiallySupported("flock", Flock, "Locks are held within the sandbox only.", nil), - 74: syscalls.PartiallySupported("fsync", Fsync, "Full data flush is not guaranteed at this time.", nil), - 75: syscalls.PartiallySupported("fdatasync", Fdatasync, "Full data flush is not guaranteed at this time.", nil), + 72: syscalls.SupportedPoint("fcntl", Fcntl, PointFcntl), + 73: syscalls.Supported("flock", Flock), + 74: syscalls.Supported("fsync", Fsync), + 75: syscalls.Supported("fdatasync", Fdatasync), 76: syscalls.Supported("truncate", Truncate), 77: syscalls.Supported("ftruncate", Ftruncate), 78: syscalls.Supported("getdents", Getdents), @@ -137,13 +137,13 @@ var AMD64 = &kernel.SyscallTable{ 82: syscalls.Supported("rename", Rename), 83: syscalls.Supported("mkdir", Mkdir), 84: syscalls.Supported("rmdir", Rmdir), - 85: syscalls.Supported("creat", Creat), - 86: syscalls.PartiallySupported("link", Link, "Limited support with Gofer. Link count and linked files may get out of sync because gVisor is not aware of external hardlinks.", nil), + 85: syscalls.SupportedPoint("creat", Creat, PointCreat), + 86: syscalls.Supported("link", Link), 87: syscalls.Supported("unlink", Unlink), 88: syscalls.Supported("symlink", Symlink), 89: syscalls.Supported("readlink", Readlink), 90: syscalls.Supported("chmod", Chmod), - 91: syscalls.PartiallySupported("fchmod", Fchmod, "Options S_ISUID and S_ISGID not supported.", nil), + 91: syscalls.Supported("fchmod", Fchmod), 92: syscalls.Supported("chown", Chown), 93: syscalls.Supported("fchown", Fchown), 94: syscalls.Supported("lchown", Lchown), @@ -185,12 +185,12 @@ var AMD64 = &kernel.SyscallTable{ 130: syscalls.Supported("rt_sigsuspend", RtSigsuspend), 131: syscalls.Supported("sigaltstack", Sigaltstack), 132: syscalls.Supported("utime", Utime), - 133: syscalls.PartiallySupported("mknod", Mknod, "Device creation is not generally supported. Only regular file and FIFO creation are supported.", nil), + 133: syscalls.Supported("mknod", Mknod), 134: syscalls.Error("uselib", linuxerr.ENOSYS, "Obsolete", nil), 135: syscalls.ErrorWithEvent("personality", linuxerr.EINVAL, "Unable to change personality.", nil), 136: syscalls.ErrorWithEvent("ustat", linuxerr.ENOSYS, "Needs filesystem support.", nil), - 137: syscalls.PartiallySupported("statfs", Statfs, "Depends on the backing file system implementation.", nil), - 138: syscalls.PartiallySupported("fstatfs", Fstatfs, "Depends on the backing file system implementation.", nil), + 137: syscalls.Supported("statfs", Statfs), + 138: syscalls.Supported("fstatfs", Fstatfs), 139: syscalls.ErrorWithEvent("sysfs", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/165"}), 140: syscalls.PartiallySupported("getpriority", Getpriority, "Stub implementation.", nil), 141: syscalls.PartiallySupported("setpriority", Setpriority, "Stub implementation.", nil), @@ -207,18 +207,18 @@ var AMD64 = &kernel.SyscallTable{ 152: syscalls.PartiallySupported("munlockall", Munlockall, "Stub implementation. The sandbox lacks appropriate permissions.", nil), 153: syscalls.CapError("vhangup", linux.CAP_SYS_TTY_CONFIG, "", nil), 154: syscalls.Error("modify_ldt", linuxerr.EPERM, "", nil), - 155: syscalls.Error("pivot_root", linuxerr.EPERM, "", nil), + 155: syscalls.Supported("pivot_root", PivotRoot), 156: syscalls.Error("sysctl", linuxerr.EPERM, "Deprecated. Use /proc/sys instead.", nil), 157: syscalls.PartiallySupported("prctl", Prctl, "Not all options are supported.", nil), 158: syscalls.PartiallySupported("arch_prctl", ArchPrctl, "Options ARCH_GET_GS, ARCH_SET_GS not supported.", nil), 159: syscalls.CapError("adjtimex", linux.CAP_SYS_TIME, "", nil), 160: syscalls.PartiallySupported("setrlimit", Setrlimit, "Not all rlimits are enforced.", nil), 161: syscalls.SupportedPoint("chroot", Chroot, PointChroot), - 162: syscalls.PartiallySupported("sync", Sync, "Full data flush is not guaranteed at this time.", nil), + 162: syscalls.Supported("sync", Sync), 163: syscalls.CapError("acct", linux.CAP_SYS_PACCT, "", nil), 164: syscalls.CapError("settimeofday", linux.CAP_SYS_TIME, "", nil), - 165: syscalls.PartiallySupported("mount", Mount, "Not all options or file systems are supported.", nil), - 166: syscalls.PartiallySupported("umount2", Umount2, "Not all options or file systems are supported.", nil), + 165: syscalls.Supported("mount", Mount), + 166: syscalls.Supported("umount2", Umount2), 167: syscalls.CapError("swapon", linux.CAP_SYS_ADMIN, "", nil), 168: syscalls.CapError("swapoff", linux.CAP_SYS_ADMIN, "", nil), 169: syscalls.CapError("reboot", linux.CAP_SYS_BOOT, "", nil), @@ -240,18 +240,18 @@ var AMD64 = &kernel.SyscallTable{ 185: syscalls.Error("security", linuxerr.ENOSYS, "Not implemented in Linux.", nil), 186: syscalls.Supported("gettid", Gettid), 187: syscalls.Supported("readahead", Readahead), - 188: syscalls.PartiallySupported("setxattr", SetXattr, "Only supported for tmpfs.", nil), - 189: syscalls.PartiallySupported("lsetxattr", LSetXattr, "Only supported for tmpfs.", nil), - 190: syscalls.PartiallySupported("fsetxattr", FSetXattr, "Only supported for tmpfs.", nil), - 191: syscalls.PartiallySupported("getxattr", GetXattr, "Only supported for tmpfs.", nil), - 192: syscalls.PartiallySupported("lgetxattr", LGetXattr, "Only supported for tmpfs.", nil), - 193: syscalls.PartiallySupported("fgetxattr", FGetXattr, "Only supported for tmpfs.", nil), - 194: syscalls.PartiallySupported("listxattr", ListXattr, "Only supported for tmpfs", nil), - 195: syscalls.PartiallySupported("llistxattr", LListXattr, "Only supported for tmpfs", nil), - 196: syscalls.PartiallySupported("flistxattr", FListXattr, "Only supported for tmpfs", nil), - 197: syscalls.PartiallySupported("removexattr", RemoveXattr, "Only supported for tmpfs", nil), - 198: syscalls.PartiallySupported("lremovexattr", LRemoveXattr, "Only supported for tmpfs", nil), - 199: syscalls.PartiallySupported("fremovexattr", FRemoveXattr, "Only supported for tmpfs", nil), + 188: syscalls.Supported("setxattr", SetXattr), + 189: syscalls.Supported("lsetxattr", Lsetxattr), + 190: syscalls.Supported("fsetxattr", Fsetxattr), + 191: syscalls.Supported("getxattr", GetXattr), + 192: syscalls.Supported("lgetxattr", Lgetxattr), + 193: syscalls.Supported("fgetxattr", Fgetxattr), + 194: syscalls.Supported("listxattr", ListXattr), + 195: syscalls.Supported("llistxattr", Llistxattr), + 196: syscalls.Supported("flistxattr", Flistxattr), + 197: syscalls.Supported("removexattr", RemoveXattr), + 198: syscalls.Supported("lremovexattr", Lremovexattr), + 199: syscalls.Supported("fremovexattr", Fremovexattr), 200: syscalls.Supported("tkill", Tkill), 201: syscalls.Supported("time", Time), 202: syscalls.PartiallySupported("futex", Futex, "Robust futexes not supported.", nil), @@ -273,7 +273,7 @@ var AMD64 = &kernel.SyscallTable{ 218: syscalls.Supported("set_tid_address", SetTidAddress), 219: syscalls.Supported("restart_syscall", RestartSyscall), 220: syscalls.Supported("semtimedop", Semtimedop), - 221: syscalls.PartiallySupported("fadvise64", Fadvise64, "Not all options are supported.", nil), + 221: syscalls.PartiallySupported("fadvise64", Fadvise64, "The syscall is 'supported', but ignores all provided advice.", nil), 222: syscalls.Supported("timer_create", TimerCreate), 223: syscalls.Supported("timer_settime", TimerSettime), 224: syscalls.Supported("timer_gettime", TimerGettime), @@ -292,8 +292,8 @@ var AMD64 = &kernel.SyscallTable{ 237: syscalls.PartiallySupported("mbind", Mbind, "Stub implementation. Only a single NUMA node is advertised, and mempolicy is ignored accordingly, but mbind() will succeed and has effects reflected by get_mempolicy.", []string{"gvisor.dev/issue/262"}), 238: syscalls.PartiallySupported("set_mempolicy", SetMempolicy, "Stub implementation.", nil), 239: syscalls.PartiallySupported("get_mempolicy", GetMempolicy, "Stub implementation.", nil), - 240: syscalls.ErrorWithEvent("mq_open", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) - 241: syscalls.ErrorWithEvent("mq_unlink", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) + 240: syscalls.Supported("mq_open", MqOpen), + 241: syscalls.Supported("mq_unlink", MqUnlink), 242: syscalls.ErrorWithEvent("mq_timedsend", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) 243: syscalls.ErrorWithEvent("mq_timedreceive", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) 244: syscalls.ErrorWithEvent("mq_notify", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) @@ -305,19 +305,19 @@ var AMD64 = &kernel.SyscallTable{ 250: syscalls.Error("keyctl", linuxerr.EACCES, "Not available to user.", nil), 251: syscalls.CapError("ioprio_set", linux.CAP_SYS_ADMIN, "", nil), // requires cap_sys_nice or cap_sys_admin (depending) 252: syscalls.CapError("ioprio_get", linux.CAP_SYS_ADMIN, "", nil), // requires cap_sys_nice or cap_sys_admin (depending) - 253: syscalls.PartiallySupportedPoint("inotify_init", InotifyInit, PointInotifyInit, "Inotify events are only available inside the sandbox. Hard links are treated as different watch targets in gofer fs.", nil), - 254: syscalls.PartiallySupportedPoint("inotify_add_watch", InotifyAddWatch, PointInotifyAddWatch, "Inotify events are only available inside the sandbox. Hard links are treated as different watch targets in gofer fs.", nil), - 255: syscalls.PartiallySupportedPoint("inotify_rm_watch", InotifyRmWatch, PointInotifyRmWatch, "Inotify events are only available inside the sandbox. Hard links are treated as different watch targets in gofer fs.", nil), + 253: syscalls.PartiallySupportedPoint("inotify_init", InotifyInit, PointInotifyInit, "inotify events are only available inside the sandbox.", nil), + 254: syscalls.PartiallySupportedPoint("inotify_add_watch", InotifyAddWatch, PointInotifyAddWatch, "inotify events are only available inside the sandbox.", nil), + 255: syscalls.PartiallySupportedPoint("inotify_rm_watch", InotifyRmWatch, PointInotifyRmWatch, "inotify events are only available inside the sandbox.", nil), 256: syscalls.CapError("migrate_pages", linux.CAP_SYS_NICE, "", nil), 257: syscalls.SupportedPoint("openat", Openat, PointOpenat), 258: syscalls.Supported("mkdirat", Mkdirat), 259: syscalls.Supported("mknodat", Mknodat), 260: syscalls.Supported("fchownat", Fchownat), 261: syscalls.Supported("futimesat", Futimesat), - 262: syscalls.Supported("fstatat", Fstatat), + 262: syscalls.Supported("newfstatat", Newfstatat), 263: syscalls.Supported("unlinkat", Unlinkat), 264: syscalls.Supported("renameat", Renameat), - 265: syscalls.PartiallySupported("linkat", Linkat, "See link(2).", nil), + 265: syscalls.Supported("linkat", Linkat), 266: syscalls.Supported("symlinkat", Symlinkat), 267: syscalls.Supported("readlinkat", Readlinkat), 268: syscalls.Supported("fchmodat", Fchmodat), @@ -329,37 +329,37 @@ var AMD64 = &kernel.SyscallTable{ 274: syscalls.Supported("get_robust_list", GetRobustList), 275: syscalls.Supported("splice", Splice), 276: syscalls.Supported("tee", Tee), - 277: syscalls.PartiallySupported("sync_file_range", SyncFileRange, "Full data flush is not guaranteed at this time.", nil), + 277: syscalls.Supported("sync_file_range", SyncFileRange), 278: syscalls.ErrorWithEvent("vmsplice", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/138"}), // TODO(b/29354098) 279: syscalls.CapError("move_pages", linux.CAP_SYS_NICE, "", nil), // requires cap_sys_nice (mostly) 280: syscalls.Supported("utimensat", Utimensat), 281: syscalls.Supported("epoll_pwait", EpollPwait), - 282: syscalls.PartiallySupportedPoint("signalfd", Signalfd, PointSignalfd, "Semantics are slightly different.", []string{"gvisor.dev/issue/139"}), + 282: syscalls.SupportedPoint("signalfd", Signalfd, PointSignalfd), 283: syscalls.SupportedPoint("timerfd_create", TimerfdCreate, PointTimerfdCreate), 284: syscalls.SupportedPoint("eventfd", Eventfd, PointEventfd), 285: syscalls.PartiallySupported("fallocate", Fallocate, "Not all options are supported.", nil), 286: syscalls.SupportedPoint("timerfd_settime", TimerfdSettime, PointTimerfdSettime), 287: syscalls.SupportedPoint("timerfd_gettime", TimerfdGettime, PointTimerfdGettime), 288: syscalls.SupportedPoint("accept4", Accept4, PointAccept4), - 289: syscalls.PartiallySupportedPoint("signalfd4", Signalfd4, PointSignalfd4, "Semantics are slightly different.", []string{"gvisor.dev/issue/139"}), + 289: syscalls.SupportedPoint("signalfd4", Signalfd4, PointSignalfd4), 290: syscalls.SupportedPoint("eventfd2", Eventfd2, PointEventfd2), 291: syscalls.Supported("epoll_create1", EpollCreate1), 292: syscalls.SupportedPoint("dup3", Dup3, PointDup3), 293: syscalls.SupportedPoint("pipe2", Pipe2, PointPipe2), - 294: syscalls.PartiallySupportedPoint("inotify_init1", InotifyInit1, PointInotifyInit1, "Inotify events are only available inside the sandbox. Hard links are treated as different watch targets in gofer fs.", nil), + 294: syscalls.PartiallySupportedPoint("inotify_init1", InotifyInit1, PointInotifyInit1, "inotify events are only available inside the sandbox.", nil), 295: syscalls.Supported("preadv", Preadv), 296: syscalls.Supported("pwritev", Pwritev), 297: syscalls.Supported("rt_tgsigqueueinfo", RtTgsigqueueinfo), 298: syscalls.ErrorWithEvent("perf_event_open", linuxerr.ENODEV, "No support for perf counters", nil), - 299: syscalls.PartiallySupported("recvmmsg", RecvMMsg, "Not all flags and control messages are supported.", nil), + 299: syscalls.Supported("recvmmsg", RecvMMsg), 300: syscalls.ErrorWithEvent("fanotify_init", linuxerr.ENOSYS, "Needs CONFIG_FANOTIFY", nil), 301: syscalls.ErrorWithEvent("fanotify_mark", linuxerr.ENOSYS, "Needs CONFIG_FANOTIFY", nil), 302: syscalls.SupportedPoint("prlimit64", Prlimit64, PointPrlimit64), 303: syscalls.Error("name_to_handle_at", linuxerr.EOPNOTSUPP, "Not supported by gVisor filesystems", nil), 304: syscalls.Error("open_by_handle_at", linuxerr.EOPNOTSUPP, "Not supported by gVisor filesystems", nil), 305: syscalls.CapError("clock_adjtime", linux.CAP_SYS_TIME, "", nil), - 306: syscalls.PartiallySupported("syncfs", Syncfs, "Depends on backing file system.", nil), - 307: syscalls.PartiallySupported("sendmmsg", SendMMsg, "Not all flags and control messages are supported.", nil), + 306: syscalls.Supported("syncfs", Syncfs), + 307: syscalls.Supported("sendmmsg", SendMMsg), 308: syscalls.ErrorWithEvent("setns", linuxerr.EOPNOTSUPP, "Needs filesystem support", []string{"gvisor.dev/issue/140"}), // TODO(b/29354995) 309: syscalls.Supported("getcpu", Getcpu), 310: syscalls.ErrorWithEvent("process_vm_readv", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/158"}), @@ -368,7 +368,7 @@ var AMD64 = &kernel.SyscallTable{ 313: syscalls.CapError("finit_module", linux.CAP_SYS_MODULE, "", nil), 314: syscalls.ErrorWithEvent("sched_setattr", linuxerr.ENOSYS, "gVisor does not implement a scheduler.", []string{"gvisor.dev/issue/264"}), // TODO(b/118902272) 315: syscalls.ErrorWithEvent("sched_getattr", linuxerr.ENOSYS, "gVisor does not implement a scheduler.", []string{"gvisor.dev/issue/264"}), // TODO(b/118902272) - 316: syscalls.ErrorWithEvent("renameat2", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/263"}), // TODO(b/118902772) + 316: syscalls.Supported("renameat2", Renameat2), 317: syscalls.Supported("seccomp", Seccomp), 318: syscalls.Supported("getrandom", GetRandom), 319: syscalls.Supported("memfd_create", MemfdCreate), @@ -383,7 +383,7 @@ var AMD64 = &kernel.SyscallTable{ // of Linux after 4.4. 326: syscalls.ErrorWithEvent("copy_file_range", linuxerr.ENOSYS, "", nil), 327: syscalls.Supported("preadv2", Preadv2), - 328: syscalls.PartiallySupported("pwritev2", Pwritev2, "Flag RWF_HIPRI is not supported.", nil), + 328: syscalls.Supported("pwritev2", Pwritev2), 329: syscalls.ErrorWithEvent("pkey_mprotect", linuxerr.ENOSYS, "", nil), 330: syscalls.ErrorWithEvent("pkey_alloc", linuxerr.ENOSYS, "", nil), 331: syscalls.ErrorWithEvent("pkey_free", linuxerr.ENOSYS, "", nil), @@ -393,8 +393,8 @@ var AMD64 = &kernel.SyscallTable{ // Linux skips ahead to syscall 424 to sync numbers between arches. 424: syscalls.ErrorWithEvent("pidfd_send_signal", linuxerr.ENOSYS, "", nil), - 425: syscalls.ErrorWithEvent("io_uring_setup", linuxerr.ENOSYS, "", nil), - 426: syscalls.ErrorWithEvent("io_uring_enter", linuxerr.ENOSYS, "", nil), + 425: syscalls.PartiallySupported("io_uring_setup", IOUringSetup, "Not all flags and functionality supported.", nil), + 426: syscalls.PartiallySupported("io_uring_enter", IOUringEnter, "Not all flags and functionality supported.", nil), 427: syscalls.ErrorWithEvent("io_uring_register", linuxerr.ENOSYS, "", nil), 428: syscalls.ErrorWithEvent("open_tree", linuxerr.ENOSYS, "", nil), 429: syscalls.ErrorWithEvent("move_mount", linuxerr.ENOSYS, "", nil), @@ -405,6 +405,7 @@ var AMD64 = &kernel.SyscallTable{ 434: syscalls.ErrorWithEvent("pidfd_open", linuxerr.ENOSYS, "", nil), 435: syscalls.ErrorWithEvent("clone3", linuxerr.ENOSYS, "", nil), 436: syscalls.Supported("close_range", CloseRange), + 439: syscalls.Supported("faccessat2", Faccessat2), 441: syscalls.Supported("epoll_pwait2", EpollPwait2), }, Emulate: map[hostarch.Addr]uintptr{ @@ -435,18 +436,18 @@ var ARM64 = &kernel.SyscallTable{ 2: syscalls.PartiallySupported("io_submit", IoSubmit, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}), 3: syscalls.PartiallySupported("io_cancel", IoCancel, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}), 4: syscalls.PartiallySupported("io_getevents", IoGetevents, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}), - 5: syscalls.PartiallySupported("setxattr", SetXattr, "Only supported for tmpfs.", nil), - 6: syscalls.PartiallySupported("lsetxattr", LSetXattr, "Only supported for tmpfs.", nil), - 7: syscalls.PartiallySupported("fsetxattr", FSetXattr, "Only supported for tmpfs.", nil), - 8: syscalls.PartiallySupported("getxattr", GetXattr, "Only supported for tmpfs.", nil), - 9: syscalls.PartiallySupported("lgetxattr", LGetXattr, "Only supported for tmpfs.", nil), - 10: syscalls.PartiallySupported("fgetxattr", FGetXattr, "Only supported for tmpfs.", nil), - 11: syscalls.PartiallySupported("listxattr", ListXattr, "Only supported for tmpfs", nil), - 12: syscalls.PartiallySupported("llistxattr", LListXattr, "Only supported for tmpfs", nil), - 13: syscalls.PartiallySupported("flistxattr", FListXattr, "Only supported for tmpfs", nil), - 14: syscalls.PartiallySupported("removexattr", RemoveXattr, "Only supported for tmpfs", nil), - 15: syscalls.PartiallySupported("lremovexattr", LRemoveXattr, "Only supported for tmpfs", nil), - 16: syscalls.PartiallySupported("fremovexattr", FRemoveXattr, "Only supported for tmpfs", nil), + 5: syscalls.Supported("setxattr", SetXattr), + 6: syscalls.Supported("lsetxattr", Lsetxattr), + 7: syscalls.Supported("fsetxattr", Fsetxattr), + 8: syscalls.Supported("getxattr", GetXattr), + 9: syscalls.Supported("lgetxattr", Lgetxattr), + 10: syscalls.Supported("fgetxattr", Fgetxattr), + 11: syscalls.Supported("listxattr", ListXattr), + 12: syscalls.Supported("llistxattr", Llistxattr), + 13: syscalls.Supported("flistxattr", Flistxattr), + 14: syscalls.Supported("removexattr", RemoveXattr), + 15: syscalls.Supported("lremovexattr", Lremovexattr), + 16: syscalls.Supported("fremovexattr", Fremovexattr), 17: syscalls.Supported("getcwd", Getcwd), 18: syscalls.CapError("lookup_dcookie", linux.CAP_SYS_ADMIN, "", nil), 19: syscalls.SupportedPoint("eventfd2", Eventfd2, PointEventfd2), @@ -455,26 +456,26 @@ var ARM64 = &kernel.SyscallTable{ 22: syscalls.Supported("epoll_pwait", EpollPwait), 23: syscalls.SupportedPoint("dup", Dup, PointDup), 24: syscalls.SupportedPoint("dup3", Dup3, PointDup3), - 25: syscalls.PartiallySupportedPoint("fcntl", Fcntl, PointFcntl, "Not all options are supported.", nil), - 26: syscalls.PartiallySupportedPoint("inotify_init1", InotifyInit1, PointInotifyInit1, "Inotify events are only available inside the sandbox. Hard links are treated as different watch targets in gofer fs.", nil), - 27: syscalls.PartiallySupportedPoint("inotify_add_watch", InotifyAddWatch, PointInotifyAddWatch, "Inotify events are only available inside the sandbox. Hard links are treated as different watch targets in gofer fs.", nil), - 28: syscalls.PartiallySupportedPoint("inotify_rm_watch", InotifyRmWatch, PointInotifyRmWatch, "Inotify events are only available inside the sandbox. Hard links are treated as different watch targets in gofer fs.", nil), - 29: syscalls.PartiallySupported("ioctl", Ioctl, "Only a few ioctls are implemented for backing devices and file systems.", nil), + 25: syscalls.SupportedPoint("fcntl", Fcntl, PointFcntl), + 26: syscalls.PartiallySupportedPoint("inotify_init1", InotifyInit1, PointInotifyInit1, "inotify events are only available inside the sandbox.", nil), + 27: syscalls.PartiallySupportedPoint("inotify_add_watch", InotifyAddWatch, PointInotifyAddWatch, "inotify events are only available inside the sandbox.", nil), + 28: syscalls.PartiallySupportedPoint("inotify_rm_watch", InotifyRmWatch, PointInotifyRmWatch, "inotify events are only available inside the sandbox.", nil), + 29: syscalls.Supported("ioctl", Ioctl), 30: syscalls.CapError("ioprio_set", linux.CAP_SYS_ADMIN, "", nil), // requires cap_sys_nice or cap_sys_admin (depending) 31: syscalls.CapError("ioprio_get", linux.CAP_SYS_ADMIN, "", nil), // requires cap_sys_nice or cap_sys_admin (depending) - 32: syscalls.PartiallySupported("flock", Flock, "Locks are held within the sandbox only.", nil), + 32: syscalls.Supported("flock", Flock), 33: syscalls.Supported("mknodat", Mknodat), 34: syscalls.Supported("mkdirat", Mkdirat), 35: syscalls.Supported("unlinkat", Unlinkat), 36: syscalls.Supported("symlinkat", Symlinkat), 37: syscalls.Supported("linkat", Linkat), 38: syscalls.Supported("renameat", Renameat), - 39: syscalls.PartiallySupported("umount2", Umount2, "Not all options or file systems are supported.", nil), - 40: syscalls.PartiallySupported("mount", Mount, "Not all options or file systems are supported.", nil), - 41: syscalls.Error("pivot_root", linuxerr.EPERM, "", nil), + 39: syscalls.Supported("umount2", Umount2), + 40: syscalls.Supported("mount", Mount), + 41: syscalls.Supported("pivot_root", PivotRoot), 42: syscalls.Error("nfsservctl", linuxerr.ENOSYS, "Removed after Linux 3.1.", nil), - 43: syscalls.PartiallySupported("statfs", Statfs, "Depends on the backing file system implementation.", nil), - 44: syscalls.PartiallySupported("fstatfs", Fstatfs, "Depends on the backing file system implementation.", nil), + 43: syscalls.Supported("statfs", Statfs), + 44: syscalls.Supported("fstatfs", Fstatfs), 45: syscalls.Supported("truncate", Truncate), 46: syscalls.Supported("ftruncate", Ftruncate), 47: syscalls.PartiallySupported("fallocate", Fallocate, "Not all options are supported.", nil), @@ -482,12 +483,12 @@ var ARM64 = &kernel.SyscallTable{ 49: syscalls.SupportedPoint("chdir", Chdir, PointChdir), 50: syscalls.SupportedPoint("fchdir", Fchdir, PointFchdir), 51: syscalls.SupportedPoint("chroot", Chroot, PointChroot), - 52: syscalls.PartiallySupported("fchmod", Fchmod, "Options S_ISUID and S_ISGID not supported.", nil), + 52: syscalls.Supported("fchmod", Fchmod), 53: syscalls.Supported("fchmodat", Fchmodat), 54: syscalls.Supported("fchownat", Fchownat), 55: syscalls.Supported("fchown", Fchown), 56: syscalls.SupportedPoint("openat", Openat, PointOpenat), - 57: syscalls.Supported("close", Close), + 57: syscalls.SupportedPoint("close", Close, PointClose), 58: syscalls.CapError("vhangup", linux.CAP_SYS_TTY_CONFIG, "", nil), 59: syscalls.SupportedPoint("pipe2", Pipe2, PointPipe2), 60: syscalls.CapError("quotactl", linux.CAP_SYS_ADMIN, "", nil), // requires cap_sys_admin for most operations @@ -504,17 +505,17 @@ var ARM64 = &kernel.SyscallTable{ 71: syscalls.Supported("sendfile", Sendfile), 72: syscalls.Supported("pselect", Pselect), 73: syscalls.Supported("ppoll", Ppoll), - 74: syscalls.PartiallySupportedPoint("signalfd4", Signalfd4, PointSignalfd4, "Semantics are slightly different.", []string{"gvisor.dev/issue/139"}), + 74: syscalls.SupportedPoint("signalfd4", Signalfd4, PointSignalfd4), 75: syscalls.ErrorWithEvent("vmsplice", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/138"}), // TODO(b/29354098) 76: syscalls.Supported("splice", Splice), 77: syscalls.Supported("tee", Tee), 78: syscalls.Supported("readlinkat", Readlinkat), - 79: syscalls.Supported("fstatat", Fstatat), + 79: syscalls.Supported("newfstatat", Newfstatat), 80: syscalls.Supported("fstat", Fstat), - 81: syscalls.PartiallySupported("sync", Sync, "Full data flush is not guaranteed at this time.", nil), - 82: syscalls.PartiallySupported("fsync", Fsync, "Full data flush is not guaranteed at this time.", nil), - 83: syscalls.PartiallySupported("fdatasync", Fdatasync, "Full data flush is not guaranteed at this time.", nil), - 84: syscalls.PartiallySupported("sync_file_range", SyncFileRange, "Full data flush is not guaranteed at this time.", nil), + 81: syscalls.Supported("sync", Sync), + 82: syscalls.Supported("fsync", Fsync), + 83: syscalls.Supported("fdatasync", Fdatasync), + 84: syscalls.Supported("sync_file_range", SyncFileRange), 85: syscalls.SupportedPoint("timerfd_create", TimerfdCreate, PointTimerfdCreate), 86: syscalls.SupportedPoint("timerfd_settime", TimerfdSettime, PointTimerfdSettime), 87: syscalls.SupportedPoint("timerfd_gettime", TimerfdGettime, PointTimerfdGettime), @@ -610,8 +611,8 @@ var ARM64 = &kernel.SyscallTable{ 177: syscalls.Supported("getegid", Getegid), 178: syscalls.Supported("gettid", Gettid), 179: syscalls.PartiallySupported("sysinfo", Sysinfo, "Fields loads, sharedram, bufferram, totalswap, freeswap, totalhigh, freehigh not supported.", nil), - 180: syscalls.ErrorWithEvent("mq_open", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) - 181: syscalls.ErrorWithEvent("mq_unlink", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) + 180: syscalls.Supported("mq_open", MqOpen), + 181: syscalls.Supported("mq_unlink", MqUnlink), 182: syscalls.ErrorWithEvent("mq_timedsend", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) 183: syscalls.ErrorWithEvent("mq_timedreceive", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) 184: syscalls.ErrorWithEvent("mq_notify", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921) @@ -628,9 +629,9 @@ var ARM64 = &kernel.SyscallTable{ 195: syscalls.PartiallySupported("shmctl", Shmctl, "Options SHM_LOCK, SHM_UNLOCK are not supported.", nil), 196: syscalls.PartiallySupported("shmat", Shmat, "Option SHM_RND is not supported.", nil), 197: syscalls.Supported("shmdt", Shmdt), - 198: syscalls.PartiallySupported("socket", Socket, "Limited support for AF_NETLINK, NETLINK_ROUTE sockets. Limited support for SOCK_RAW.", nil), + 198: syscalls.SupportedPoint("socket", Socket, PointSocket), 199: syscalls.SupportedPoint("socketpair", SocketPair, PointSocketpair), - 200: syscalls.PartiallySupportedPoint("bind", Bind, PointBind, "Autobind for abstract Unix sockets is not supported.", nil), + 200: syscalls.SupportedPoint("bind", Bind, PointBind), 201: syscalls.Supported("listen", Listen), 202: syscalls.SupportedPoint("accept", Accept, PointAccept), 203: syscalls.SupportedPoint("connect", Connect, PointConnect), @@ -638,11 +639,11 @@ var ARM64 = &kernel.SyscallTable{ 205: syscalls.Supported("getpeername", GetPeerName), 206: syscalls.Supported("sendto", SendTo), 207: syscalls.Supported("recvfrom", RecvFrom), - 208: syscalls.PartiallySupported("setsockopt", SetSockOpt, "Not all socket options are supported.", nil), - 209: syscalls.PartiallySupported("getsockopt", GetSockOpt, "Not all socket options are supported.", nil), - 210: syscalls.PartiallySupported("shutdown", Shutdown, "Not all flags and control messages are supported.", nil), + 208: syscalls.Supported("setsockopt", SetSockOpt), + 209: syscalls.Supported("getsockopt", GetSockOpt), + 210: syscalls.Supported("shutdown", Shutdown), 211: syscalls.Supported("sendmsg", SendMsg), - 212: syscalls.PartiallySupported("recvmsg", RecvMsg, "Not all flags and control messages are supported.", nil), + 212: syscalls.Supported("recvmsg", RecvMsg), 213: syscalls.Supported("readahead", Readahead), 214: syscalls.Supported("brk", Brk), 215: syscalls.Supported("munmap", Munmap), @@ -652,7 +653,7 @@ var ARM64 = &kernel.SyscallTable{ 219: syscalls.Error("keyctl", linuxerr.EACCES, "Not available to user.", nil), 220: syscalls.PartiallySupportedPoint("clone", Clone, PointClone, "Mount namespace (CLONE_NEWNS) not supported. Options CLONE_PARENT, CLONE_SYSVSEM not supported.", nil), 221: syscalls.SupportedPoint("execve", Execve, PointExecve), - 222: syscalls.PartiallySupported("mmap", Mmap, "Generally supported with exceptions. Options MAP_FIXED_NOREPLACE, MAP_SHARED_VALIDATE, MAP_SYNC MAP_GROWSDOWN, MAP_HUGETLB are not supported.", nil), + 222: syscalls.Supported("mmap", Mmap), 223: syscalls.PartiallySupported("fadvise64", Fadvise64, "Not all options are supported.", nil), 224: syscalls.CapError("swapon", linux.CAP_SYS_ADMIN, "", nil), 225: syscalls.CapError("swapoff", linux.CAP_SYS_ADMIN, "", nil), @@ -673,7 +674,7 @@ var ARM64 = &kernel.SyscallTable{ 240: syscalls.Supported("rt_tgsigqueueinfo", RtTgsigqueueinfo), 241: syscalls.ErrorWithEvent("perf_event_open", linuxerr.ENODEV, "No support for perf counters", nil), 242: syscalls.SupportedPoint("accept4", Accept4, PointAccept4), - 243: syscalls.PartiallySupported("recvmmsg", RecvMMsg, "Not all flags and control messages are supported.", nil), + 243: syscalls.Supported("recvmmsg", RecvMMsg), 260: syscalls.Supported("wait4", Wait4), 261: syscalls.SupportedPoint("prlimit64", Prlimit64, PointPrlimit64), 262: syscalls.ErrorWithEvent("fanotify_init", linuxerr.ENOSYS, "Needs CONFIG_FANOTIFY", nil), @@ -681,16 +682,16 @@ var ARM64 = &kernel.SyscallTable{ 264: syscalls.Error("name_to_handle_at", linuxerr.EOPNOTSUPP, "Not supported by gVisor filesystems", nil), 265: syscalls.Error("open_by_handle_at", linuxerr.EOPNOTSUPP, "Not supported by gVisor filesystems", nil), 266: syscalls.CapError("clock_adjtime", linux.CAP_SYS_TIME, "", nil), - 267: syscalls.PartiallySupported("syncfs", Syncfs, "Depends on backing file system.", nil), + 267: syscalls.Supported("syncfs", Syncfs), 268: syscalls.ErrorWithEvent("setns", linuxerr.EOPNOTSUPP, "Needs filesystem support", []string{"gvisor.dev/issue/140"}), // TODO(b/29354995) - 269: syscalls.PartiallySupported("sendmmsg", SendMMsg, "Not all flags and control messages are supported.", nil), + 269: syscalls.Supported("sendmmsg", SendMMsg), 270: syscalls.ErrorWithEvent("process_vm_readv", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/158"}), 271: syscalls.ErrorWithEvent("process_vm_writev", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/158"}), 272: syscalls.CapError("kcmp", linux.CAP_SYS_PTRACE, "", nil), 273: syscalls.CapError("finit_module", linux.CAP_SYS_MODULE, "", nil), 274: syscalls.ErrorWithEvent("sched_setattr", linuxerr.ENOSYS, "gVisor does not implement a scheduler.", []string{"gvisor.dev/issue/264"}), // TODO(b/118902272) 275: syscalls.ErrorWithEvent("sched_getattr", linuxerr.ENOSYS, "gVisor does not implement a scheduler.", []string{"gvisor.dev/issue/264"}), // TODO(b/118902272) - 276: syscalls.ErrorWithEvent("renameat2", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/263"}), // TODO(b/118902772) + 276: syscalls.Supported("renameat2", Renameat2), 277: syscalls.Supported("seccomp", Seccomp), 278: syscalls.Supported("getrandom", GetRandom), 279: syscalls.Supported("memfd_create", MemfdCreate), @@ -703,7 +704,7 @@ var ARM64 = &kernel.SyscallTable{ // Syscalls after 284 are "backports" from versions of Linux after 4.4. 285: syscalls.ErrorWithEvent("copy_file_range", linuxerr.ENOSYS, "", nil), 286: syscalls.Supported("preadv2", Preadv2), - 287: syscalls.PartiallySupported("pwritev2", Pwritev2, "Flag RWF_HIPRI is not supported.", nil), + 287: syscalls.Supported("pwritev2", Pwritev2), 288: syscalls.ErrorWithEvent("pkey_mprotect", linuxerr.ENOSYS, "", nil), 289: syscalls.ErrorWithEvent("pkey_alloc", linuxerr.ENOSYS, "", nil), 290: syscalls.ErrorWithEvent("pkey_free", linuxerr.ENOSYS, "", nil), @@ -713,8 +714,8 @@ var ARM64 = &kernel.SyscallTable{ // Linux skips ahead to syscall 424 to sync numbers between arches. 424: syscalls.ErrorWithEvent("pidfd_send_signal", linuxerr.ENOSYS, "", nil), - 425: syscalls.ErrorWithEvent("io_uring_setup", linuxerr.ENOSYS, "", nil), - 426: syscalls.ErrorWithEvent("io_uring_enter", linuxerr.ENOSYS, "", nil), + 425: syscalls.PartiallySupported("io_uring_setup", IOUringSetup, "Not all flags and functionality supported.", nil), + 426: syscalls.PartiallySupported("io_uring_enter", IOUringEnter, "Not all flags and functionality supported.", nil), 427: syscalls.ErrorWithEvent("io_uring_register", linuxerr.ENOSYS, "", nil), 428: syscalls.ErrorWithEvent("open_tree", linuxerr.ENOSYS, "", nil), 429: syscalls.ErrorWithEvent("move_mount", linuxerr.ENOSYS, "", nil), @@ -725,6 +726,7 @@ var ARM64 = &kernel.SyscallTable{ 434: syscalls.ErrorWithEvent("pidfd_open", linuxerr.ENOSYS, "", nil), 435: syscalls.ErrorWithEvent("clone3", linuxerr.ENOSYS, "", nil), 436: syscalls.Supported("close_range", CloseRange), + 439: syscalls.Supported("faccessat2", Faccessat2), 441: syscalls.Supported("epoll_pwait2", EpollPwait2), }, Emulate: map[hostarch.Addr]uintptr{}, diff --git a/pkg/sentry/syscalls/linux/vfs2/path.go b/pkg/sentry/syscalls/linux/path.go similarity index 99% rename from pkg/sentry/syscalls/linux/vfs2/path.go rename to pkg/sentry/syscalls/linux/path.go index 38796d4db..275400316 100644 --- a/pkg/sentry/syscalls/linux/vfs2/path.go +++ b/pkg/sentry/syscalls/linux/path.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vfs2 +package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" diff --git a/pkg/sentry/syscalls/linux/sys_aio.go b/pkg/sentry/syscalls/linux/sys_aio.go index 3a7a77295..d807a70e8 100644 --- a/pkg/sentry/syscalls/linux/sys_aio.go +++ b/pkg/sentry/syscalls/linux/sys_aio.go @@ -21,11 +21,11 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/eventfd" "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/eventfd" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/mm" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/usermem" ) @@ -218,116 +218,6 @@ func IoCancel(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return 0, nil, linuxerr.ENOSYS } -// LINT.IfChange - -func getAIOCallback(t *kernel.Task, file *fs.File, cbAddr hostarch.Addr, cb *linux.IOCallback, ioseq usermem.IOSequence, actx *mm.AIOContext, eventFile *fs.File) kernel.AIOCallback { - return func(ctx context.Context) { - if actx.Dead() { - actx.CancelPendingRequest() - return - } - ev := &linux.IOEvent{ - Data: cb.Data, - Obj: uint64(cbAddr), - } - - var err error - switch cb.OpCode { - case linux.IOCB_CMD_PREAD, linux.IOCB_CMD_PREADV: - ev.Result, err = file.Preadv(ctx, ioseq, cb.Offset) - case linux.IOCB_CMD_PWRITE, linux.IOCB_CMD_PWRITEV: - ev.Result, err = file.Pwritev(ctx, ioseq, cb.Offset) - case linux.IOCB_CMD_FSYNC: - err = file.Fsync(ctx, 0, fs.FileMaxOffset, fs.SyncAll) - case linux.IOCB_CMD_FDSYNC: - err = file.Fsync(ctx, 0, fs.FileMaxOffset, fs.SyncData) - } - - // Update the result. - if err != nil { - err = handleIOError(t, ev.Result != 0 /* partial */, err, nil /* never interrupted */, "aio", file) - ev.Result = -int64(kernel.ExtractErrno(err, 0)) - } - - file.DecRef(ctx) - - // Queue the result for delivery. - actx.FinishRequest(ev) - - // Notify the event file if one was specified. This needs to happen - // *after* queueing the result to avoid racing with the thread we may - // wake up. - if eventFile != nil { - eventFile.FileOperations.(*eventfd.EventOperations).Signal(1) - eventFile.DecRef(ctx) - } - } -} - -// submitCallback processes a single callback. -func submitCallback(t *kernel.Task, id uint64, cb *linux.IOCallback, cbAddr hostarch.Addr) error { - file := t.GetFile(cb.FD) - if file == nil { - // File not found. - return linuxerr.EBADF - } - defer file.DecRef(t) - - // Was there an eventFD? Extract it. - var eventFile *fs.File - if cb.Flags&linux.IOCB_FLAG_RESFD != 0 { - eventFile = t.GetFile(cb.ResFD) - if eventFile == nil { - // Bad FD. - return linuxerr.EBADF - } - defer eventFile.DecRef(t) - - // Check that it is an eventfd. - if _, ok := eventFile.FileOperations.(*eventfd.EventOperations); !ok { - // Not an event FD. - return linuxerr.EINVAL - } - } - - ioseq, err := memoryFor(t, cb) - if err != nil { - return err - } - - // Check offset for reads/writes. - switch cb.OpCode { - case linux.IOCB_CMD_PREAD, linux.IOCB_CMD_PREADV, linux.IOCB_CMD_PWRITE, linux.IOCB_CMD_PWRITEV: - if cb.Offset < 0 { - return linuxerr.EINVAL - } - } - - // Prepare the request. - ctx, ok := t.MemoryManager().LookupAIOContext(t, id) - if !ok { - return linuxerr.EINVAL - } - if err := ctx.Prepare(); err != nil { - return err - } - - if eventFile != nil { - // The request is set. Make sure there's a ref on the file. - // - // This is necessary when the callback executes on completion, - // which is also what will release this reference. - eventFile.IncRef() - } - - // Perform the request asynchronously. - file.IncRef() - t.QueueAIO(getAIOCallback(t, file, cbAddr, cb, ioseq, ctx, eventFile)) - - // All set. - return nil -} - // IoSubmit implements linux syscall io_submit(2). func IoSubmit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { id := args[0].Uint64() @@ -360,7 +250,6 @@ func IoSubmit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc // Copy in this callback. var cb linux.IOCallback if _, err := cb.CopyIn(t, cbAddr); err != nil { - if i > 0 { // Some have been successful. return uintptr(i), nil, nil @@ -386,4 +275,110 @@ func IoSubmit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return uintptr(nrEvents), nil, nil } -// LINT.ThenChange(vfs2/aio.go) +// submitCallback processes a single callback. +func submitCallback(t *kernel.Task, id uint64, cb *linux.IOCallback, cbAddr hostarch.Addr) error { + if cb.Reserved2 != 0 { + return linuxerr.EINVAL + } + + fd := t.GetFileVFS2(cb.FD) + if fd == nil { + return linuxerr.EBADF + } + defer fd.DecRef(t) + + // Was there an eventFD? Extract it. + var eventFD *vfs.FileDescription + if cb.Flags&linux.IOCB_FLAG_RESFD != 0 { + eventFD = t.GetFileVFS2(cb.ResFD) + if eventFD == nil { + return linuxerr.EBADF + } + defer eventFD.DecRef(t) + + // Check that it is an eventfd. + if _, ok := eventFD.Impl().(*eventfd.EventFileDescription); !ok { + return linuxerr.EINVAL + } + } + + ioseq, err := memoryFor(t, cb) + if err != nil { + return err + } + + // Check offset for reads/writes. + switch cb.OpCode { + case linux.IOCB_CMD_PREAD, linux.IOCB_CMD_PREADV, linux.IOCB_CMD_PWRITE, linux.IOCB_CMD_PWRITEV: + if cb.Offset < 0 { + return linuxerr.EINVAL + } + } + + // Prepare the request. + aioCtx, ok := t.MemoryManager().LookupAIOContext(t, id) + if !ok { + return linuxerr.EINVAL + } + if err := aioCtx.Prepare(); err != nil { + return err + } + + if eventFD != nil { + // The request is set. Make sure there's a ref on the file. + // + // This is necessary when the callback executes on completion, + // which is also what will release this reference. + eventFD.IncRef() + } + + // Perform the request asynchronously. + fd.IncRef() + t.QueueAIO(getAIOCallback(t, fd, eventFD, cbAddr, cb, ioseq, aioCtx)) + return nil +} + +func getAIOCallback(t *kernel.Task, fd, eventFD *vfs.FileDescription, cbAddr hostarch.Addr, cb *linux.IOCallback, ioseq usermem.IOSequence, aioCtx *mm.AIOContext) kernel.AIOCallback { + return func(ctx context.Context) { + // Release references after completing the callback. + defer fd.DecRef(ctx) + if eventFD != nil { + defer eventFD.DecRef(ctx) + } + + if aioCtx.Dead() { + aioCtx.CancelPendingRequest() + return + } + ev := &linux.IOEvent{ + Data: cb.Data, + Obj: uint64(cbAddr), + } + + var err error + switch cb.OpCode { + case linux.IOCB_CMD_PREAD, linux.IOCB_CMD_PREADV: + ev.Result, err = fd.PRead(ctx, ioseq, cb.Offset, vfs.ReadOptions{}) + case linux.IOCB_CMD_PWRITE, linux.IOCB_CMD_PWRITEV: + ev.Result, err = fd.PWrite(ctx, ioseq, cb.Offset, vfs.WriteOptions{}) + case linux.IOCB_CMD_FSYNC, linux.IOCB_CMD_FDSYNC: + err = fd.Sync(ctx) + } + + // Update the result. + if err != nil { + err = HandleIOError(ctx, ev.Result != 0 /* partial */, err, nil /* never interrupted */, "aio", fd) + ev.Result = -int64(kernel.ExtractErrno(err, 0)) + } + + // Queue the result for delivery. + aioCtx.FinishRequest(ev) + + // Notify the event file if one was specified. This needs to happen + // *after* queueing the result to avoid racing with the thread we may + // wake up. + if eventFD != nil { + eventFD.Impl().(*eventfd.EventFileDescription).Signal(1) + } + } +} diff --git a/pkg/sentry/syscalls/linux/sys_epoll.go b/pkg/sentry/syscalls/linux/sys_epoll.go index 4d5d76fc5..d148198ff 100644 --- a/pkg/sentry/syscalls/linux/sys_epoll.go +++ b/pkg/sentry/syscalls/linux/sys_epoll.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,142 +15,200 @@ package linux import ( + "math" + "time" + "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/epoll" - "gvisor.dev/gvisor/pkg/sentry/syscalls" + ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/waiter" ) -// LINT.IfChange +var sizeofEpollEvent = (*linux.EpollEvent)(nil).SizeBytes() -// EpollCreate1 implements the epoll_create1(2) linux syscall. +// EpollCreate1 implements Linux syscall epoll_create1(2). func EpollCreate1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { flags := args[0].Int() - if flags & ^linux.EPOLL_CLOEXEC != 0 { + if flags&^linux.EPOLL_CLOEXEC != 0 { return 0, nil, linuxerr.EINVAL } - closeOnExec := flags&linux.EPOLL_CLOEXEC != 0 - fd, err := syscalls.CreateEpoll(t, closeOnExec) + file, err := t.Kernel().VFS().NewEpollInstanceFD(t) if err != nil { return 0, nil, err } + defer file.DecRef(t) + fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ + CloseOnExec: flags&linux.EPOLL_CLOEXEC != 0, + }) + if err != nil { + return 0, nil, err + } return uintptr(fd), nil, nil } -// EpollCreate implements the epoll_create(2) linux syscall. +// EpollCreate implements Linux syscall epoll_create(2). func EpollCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { size := args[0].Int() + // "Since Linux 2.6.8, the size argument is ignored, but must be greater + // than zero" - epoll_create(2) if size <= 0 { return 0, nil, linuxerr.EINVAL } - fd, err := syscalls.CreateEpoll(t, false) + file, err := t.Kernel().VFS().NewEpollInstanceFD(t) if err != nil { return 0, nil, err } + defer file.DecRef(t) + fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{}) + if err != nil { + return 0, nil, err + } return uintptr(fd), nil, nil } -// EpollCtl implements the epoll_ctl(2) linux syscall. +// EpollCtl implements Linux syscall epoll_ctl(2). func EpollCtl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { epfd := args[0].Int() op := args[1].Int() fd := args[2].Int() eventAddr := args[3].Pointer() - // Capture the event state if needed. - flags := epoll.EntryFlags(0) - mask := waiter.EventMask(0) - var data [2]int32 - if op != linux.EPOLL_CTL_DEL { - var e linux.EpollEvent - if _, err := e.CopyIn(t, eventAddr); err != nil { - return 0, nil, err - } - - if e.Events&linux.EPOLLONESHOT != 0 { - flags |= epoll.OneShot - } - - if e.Events&linux.EPOLLET != 0 { - flags |= epoll.EdgeTriggered - } - - mask = waiter.EventMaskFromLinux(e.Events) - data = e.Data + epfile := t.GetFileVFS2(epfd) + if epfile == nil { + return 0, nil, linuxerr.EBADF + } + defer epfile.DecRef(t) + ep, ok := epfile.Impl().(*vfs.EpollInstance) + if !ok { + return 0, nil, linuxerr.EINVAL + } + file := t.GetFileVFS2(fd) + if file == nil { + return 0, nil, linuxerr.EBADF + } + defer file.DecRef(t) + if epfile == file { + return 0, nil, linuxerr.EINVAL } - // Perform the requested operations. + var event linux.EpollEvent switch op { case linux.EPOLL_CTL_ADD: - // See fs/eventpoll.c. - mask |= waiter.EventHUp | waiter.EventErr - return 0, nil, syscalls.AddEpoll(t, epfd, fd, flags, mask, data) + if _, err := event.CopyIn(t, eventAddr); err != nil { + return 0, nil, err + } + return 0, nil, ep.AddInterest(file, fd, event) case linux.EPOLL_CTL_DEL: - return 0, nil, syscalls.RemoveEpoll(t, epfd, fd) + return 0, nil, ep.DeleteInterest(file, fd) case linux.EPOLL_CTL_MOD: - // Same as EPOLL_CTL_ADD. - mask |= waiter.EventHUp | waiter.EventErr - return 0, nil, syscalls.UpdateEpoll(t, epfd, fd, flags, mask, data) + if _, err := event.CopyIn(t, eventAddr); err != nil { + return 0, nil, err + } + return 0, nil, ep.ModifyInterest(file, fd, event) default: return 0, nil, linuxerr.EINVAL } } -func waitEpoll(t *kernel.Task, fd int32, eventsAddr hostarch.Addr, max int, timeoutInNanos int64) (uintptr, *kernel.SyscallControl, error) { - r, err := syscalls.WaitEpoll(t, fd, max, timeoutInNanos) - if err != nil { - return 0, nil, linuxerr.ConvertIntr(err, linuxerr.EINTR) +func waitEpoll(t *kernel.Task, epfd int32, eventsAddr hostarch.Addr, maxEvents int, timeoutInNanos int64) (uintptr, *kernel.SyscallControl, error) { + var _EP_MAX_EVENTS = math.MaxInt32 / sizeofEpollEvent // Linux: fs/eventpoll.c:EP_MAX_EVENTS + if maxEvents <= 0 || maxEvents > _EP_MAX_EVENTS { + return 0, nil, linuxerr.EINVAL } - if len(r) != 0 { - if _, err := linux.CopyEpollEventSliceOut(t, eventsAddr, r); err != nil { + epfile := t.GetFileVFS2(epfd) + if epfile == nil { + return 0, nil, linuxerr.EBADF + } + defer epfile.DecRef(t) + ep, ok := epfile.Impl().(*vfs.EpollInstance) + if !ok { + return 0, nil, linuxerr.EINVAL + } + + // Allocate space for a few events on the stack for the common case in + // which we don't have too many events. + var ( + eventsArr [16]linux.EpollEvent + ch chan struct{} + haveDeadline bool + deadline ktime.Time + ) + for { + events := ep.ReadEvents(eventsArr[:0], maxEvents) + if len(events) != 0 { + copiedBytes, err := linux.CopyEpollEventSliceOut(t, eventsAddr, events) + copiedEvents := copiedBytes / sizeofEpollEvent // rounded down + if copiedEvents != 0 { + return uintptr(copiedEvents), nil, nil + } return 0, nil, err } + if timeoutInNanos == 0 { + return 0, nil, nil + } + // In the first iteration of this loop, register with the epoll + // instance for readability events, but then immediately continue the + // loop since we need to retry ReadEvents() before blocking. In all + // subsequent iterations, block until events are available, the timeout + // expires, or an interrupt arrives. + if ch == nil { + var w waiter.Entry + w, ch = waiter.NewChannelEntry(waiter.ReadableEvents) + if err := epfile.EventRegister(&w); err != nil { + return 0, nil, err + } + defer epfile.EventUnregister(&w) + } else { + // Set up the timer if a timeout was specified. + if timeoutInNanos > 0 && !haveDeadline { + timeoutDur := time.Duration(timeoutInNanos) * time.Nanosecond + deadline = t.Kernel().MonotonicClock().Now().Add(timeoutDur) + haveDeadline = true + } + if err := t.BlockWithDeadline(ch, haveDeadline, deadline); err != nil { + if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { + err = nil + } + return 0, nil, err + } + } } - return uintptr(len(r)), nil, nil - } -// EpollWait implements the epoll_wait(2) linux syscall. +// EpollWait implements Linux syscall epoll_wait(2). func EpollWait(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { epfd := args[0].Int() eventsAddr := args[1].Pointer() maxEvents := int(args[2].Int()) - // Convert milliseconds to nanoseconds. timeoutInNanos := int64(args[3].Int()) * 1000000 + return waitEpoll(t, epfd, eventsAddr, maxEvents, timeoutInNanos) } -// EpollPwait implements the epoll_pwait(2) linux syscall. +// EpollPwait implements Linux syscall epoll_pwait(2). func EpollPwait(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { maskAddr := args[4].Pointer() maskSize := uint(args[5].Uint()) - if maskAddr != 0 { - mask, err := CopyInSigSet(t, maskAddr, maskSize) - if err != nil { - return 0, nil, err - } - - oldmask := t.SignalMask() - t.SetSignalMask(mask) - t.SetSavedSignalMask(oldmask) + if err := setTempSignalSet(t, maskAddr, maskSize); err != nil { + return 0, nil, err } return EpollWait(t, args) } -// EpollPwait2 implements the epoll_pwait(2) linux syscall. +// EpollPwait2 implements Linux syscall epoll_pwait(2). func EpollPwait2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { epfd := args[0].Int() eventsAddr := args[1].Pointer() @@ -162,26 +220,16 @@ func EpollPwait2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S var timeoutInNanos int64 = -1 if haveTimeout { - timeout, err := copyTimespecIn(t, timeoutPtr) - if err != nil { + var timeout linux.Timespec + if _, err := timeout.CopyIn(t, timeoutPtr); err != nil { return 0, nil, err } timeoutInNanos = timeout.ToNsec() - } - if maskAddr != 0 { - mask, err := CopyInSigSet(t, maskAddr, maskSize) - if err != nil { - return 0, nil, err - } - - oldmask := t.SignalMask() - t.SetSignalMask(mask) - t.SetSavedSignalMask(oldmask) + if err := setTempSignalSet(t, maskAddr, maskSize); err != nil { + return 0, nil, err } return waitEpoll(t, epfd, eventsAddr, maxEvents, timeoutInNanos) } - -// LINT.ThenChange(vfs2/epoll.go) diff --git a/pkg/sentry/syscalls/linux/sys_eventfd.go b/pkg/sentry/syscalls/linux/sys_eventfd.go index 7ba9a755e..2a7716f7c 100644 --- a/pkg/sentry/syscalls/linux/sys_eventfd.go +++ b/pkg/sentry/syscalls/linux/sys_eventfd.go @@ -18,14 +18,13 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/eventfd" "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/eventfd" ) // Eventfd2 implements linux syscall eventfd2(2). func Eventfd2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - initVal := args[0].Int() + initVal := uint64(args[0].Uint()) flags := uint(args[1].Uint()) allOps := uint(linux.EFD_SEMAPHORE | linux.EFD_NONBLOCK | linux.EFD_CLOEXEC) @@ -33,13 +32,19 @@ func Eventfd2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return 0, nil, linuxerr.EINVAL } - event := eventfd.New(t, uint64(initVal), flags&linux.EFD_SEMAPHORE != 0) - event.SetFlags(fs.SettableFileFlags{ - NonBlocking: flags&linux.EFD_NONBLOCK != 0, - }) - defer event.DecRef(t) + vfsObj := t.Kernel().VFS() + fileFlags := uint32(linux.O_RDWR) + if flags&linux.EFD_NONBLOCK != 0 { + fileFlags |= linux.O_NONBLOCK + } + semMode := flags&linux.EFD_SEMAPHORE != 0 + eventfd, err := eventfd.New(t, vfsObj, initVal, semMode, fileFlags) + if err != nil { + return 0, nil, err + } + defer eventfd.DecRef(t) - fd, err := t.NewFDFrom(0, event, kernel.FDFlags{ + fd, err := t.NewFDFromVFS2(0, eventfd, kernel.FDFlags{ CloseOnExec: flags&linux.EFD_CLOEXEC != 0, }) if err != nil { diff --git a/pkg/sentry/syscalls/linux/sys_file.go b/pkg/sentry/syscalls/linux/sys_file.go index 42c0ec50d..0f4022640 100644 --- a/pkg/sentry/syscalls/linux/sys_file.go +++ b/pkg/sentry/syscalls/linux/sys_file.go @@ -17,522 +17,167 @@ package linux import ( "math" - "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/fspath" + "gvisor.dev/gvisor/pkg/gohacks" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fs/lock" - "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/fasync" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" + "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" "gvisor.dev/gvisor/pkg/sentry/limits" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) -// fileOpAt performs an operation on the second last component in the path. -func fileOpAt(t *kernel.Task, dirFD int32, path string, fn func(root *fs.Dirent, d *fs.Dirent, name string, remainingTraversals uint) error) error { - // Extract the last component. - dir, name := fs.SplitLast(path) - if dir == "/" { - // Common case: we are accessing a file in the root. - root := t.FSContext().RootDirectory() - err := fn(root, root, name, linux.MaxSymlinkTraversals) - root.DecRef(t) - return err - } else if dir == "." && dirFD == linux.AT_FDCWD { - // Common case: we are accessing a file relative to the current - // working directory; skip the look-up. - wd := t.FSContext().WorkingDirectory() - root := t.FSContext().RootDirectory() - err := fn(root, wd, name, linux.MaxSymlinkTraversals) - wd.DecRef(t) - root.DecRef(t) - return err - } - - return fileOpOn(t, dirFD, dir, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, remainingTraversals uint) error { - return fn(root, d, name, remainingTraversals) - }) -} - -// fileOpOn performs an operation on the last entry of the path. -func fileOpOn(t *kernel.Task, dirFD int32, path string, resolve bool, fn func(root *fs.Dirent, d *fs.Dirent, remainingTraversals uint) error) error { - var ( - d *fs.Dirent // The file. - wd *fs.Dirent // The working directory (if required.) - rel *fs.Dirent // The relative directory for search (if required.) - f *fs.File // The file corresponding to dirFD (if required.) - err error - ) - - // Extract the working directory (maybe). - if len(path) > 0 && path[0] == '/' { - // Absolute path; rel can be nil. - } else if dirFD == linux.AT_FDCWD { - // Need to reference the working directory. - wd = t.FSContext().WorkingDirectory() - rel = wd - } else { - // Need to extract the given FD. - f = t.GetFile(dirFD) - if f == nil { - return linuxerr.EBADF - } - rel = f.Dirent - if !fs.IsDir(rel.Inode.StableAttr) { - f.DecRef(t) - return linuxerr.ENOTDIR - } - } - - // Grab the root (always required.) - root := t.FSContext().RootDirectory() - - // Lookup the node. - remainingTraversals := uint(linux.MaxSymlinkTraversals) - if resolve { - d, err = t.MountNamespace().FindInode(t, root, rel, path, &remainingTraversals) - } else { - d, err = t.MountNamespace().FindLink(t, root, rel, path, &remainingTraversals) - } - root.DecRef(t) - if wd != nil { - wd.DecRef(t) - } - if f != nil { - f.DecRef(t) - } - if err != nil { - return err - } - - err = fn(root, d, remainingTraversals) - d.DecRef(t) - return err -} - -// copyInPath copies a path in. -func copyInPath(t *kernel.Task, addr hostarch.Addr, allowEmpty bool) (path string, dirPath bool, err error) { - path, err = t.CopyInString(addr, linux.PATH_MAX) - if err != nil { - return "", false, err - } - if path == "" && !allowEmpty { - return "", false, linuxerr.ENOENT - } - - // If the path ends with a /, then checks must be enforced in various - // ways in the different callers. We pass this back to the caller. - path, dirPath = fs.TrimTrailingSlashes(path) - - return path, dirPath, nil -} - -// LINT.IfChange - -func openAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, flags uint) (fd uintptr, err error) { - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) - if err != nil { - return 0, err - } - - resolve := flags&linux.O_NOFOLLOW == 0 - err = fileOpOn(t, dirFD, path, resolve, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - // First check a few things about the filesystem before trying to get the file - // reference. - // - // It's required that Check does not try to open files not that aren't backed by - // this dirent (e.g. pipes and sockets) because this would result in opening these - // files an extra time just to check permissions. - if err := d.Inode.CheckPermission(t, flagsToPermissions(flags)); err != nil { - return err - } - - if fs.IsSymlink(d.Inode.StableAttr) && !resolve { - return linuxerr.ELOOP - } - - fileFlags := linuxToFlags(flags) - // Linux always adds the O_LARGEFILE flag when running in 64-bit mode. - fileFlags.LargeFile = true - if fs.IsDir(d.Inode.StableAttr) { - // Don't allow directories to be opened writable. - if fileFlags.Write { - return linuxerr.EISDIR - } - } else { - // If O_DIRECTORY is set, but the file is not a directory, then fail. - if fileFlags.Directory { - return linuxerr.ENOTDIR - } - // If it's a directory, then make sure. - if dirPath { - return linuxerr.ENOTDIR - } - } - - file, err := d.Inode.GetFile(t, d, fileFlags) - if err != nil { - return linuxerr.ConvertIntr(err, linuxerr.ERESTARTSYS) - } - defer file.DecRef(t) - - // Truncate is called when O_TRUNC is specified for any kind of - // existing Dirent. Behavior is delegated to the entry's Truncate - // implementation. - if flags&linux.O_TRUNC != 0 { - if err := d.Inode.Truncate(t, d, 0); err != nil { - return err - } - } - - // Success. - newFD, err := t.NewFDFrom(0, file, kernel.FDFlags{ - CloseOnExec: flags&linux.O_CLOEXEC != 0, - }) - if err != nil { - return err - } - - // Set return result in frame. - fd = uintptr(newFD) - - // Generate notification for opened file. - d.InotifyEvent(linux.IN_OPEN, 0) - - return nil - }) - return fd, err // Use result in frame. -} - -func mknodAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, mode linux.FileMode) error { - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) - if err != nil { - return err - } - if dirPath { - return linuxerr.ENOENT - } - - return fileOpAt(t, dirFD, path, func(root *fs.Dirent, d *fs.Dirent, name string, _ uint) error { - if !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Do we have the appropriate permissions on the parent? - if err := d.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil { - return err - } - - // Attempt a creation. - perms := fs.FilePermsFromMode(mode &^ linux.FileMode(t.FSContext().Umask())) - - switch mode.FileType() { - case 0: - // "Zero file type is equivalent to type S_IFREG." - mknod(2) - fallthrough - case linux.ModeRegular: - // We are not going to return the file, so the actual - // flags used don't matter, but they cannot be empty or - // Create will complain. - flags := fs.FileFlags{Read: true, Write: true} - file, err := d.Create(t, root, name, flags, perms) - if err != nil { - return err - } - file.DecRef(t) - return nil - - case linux.ModeNamedPipe: - return d.CreateFifo(t, root, name, perms) - - case linux.ModeSocket: - // While it is possible create a unix domain socket file on linux - // using mknod(2), in practice this is pretty useless from an - // application. Linux internally uses mknod() to create the socket - // node during bind(2), but we implement bind(2) independently. If - // an application explicitly creates a socket node using mknod(), - // you can't seem to bind() or connect() to the resulting socket. - // - // Instead of emulating this seemingly useless behaviour, we'll - // indicate that the filesystem doesn't support the creation of - // sockets. - return linuxerr.EOPNOTSUPP - - case linux.ModeCharacterDevice: - fallthrough - case linux.ModeBlockDevice: - // TODO(b/72101894): We don't support creating block or character - // devices at the moment. - // - // When we start supporting block and character devices, we'll - // need to check for CAP_MKNOD here. - return linuxerr.EPERM - - default: - // "EINVAL - mode requested creation of something other than a - // regular file, device special file, FIFO or socket." - mknod(2) - return linuxerr.EINVAL - } - }) -} - -// Mknod implements the linux syscall mknod(2). +// Mknod implements Linux syscall mknod(2). func Mknod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - path := args[0].Pointer() - mode := linux.FileMode(args[1].ModeT()) - // We don't need this argument until we support creation of device nodes. - _ = args[2].Uint() // dev - - return 0, nil, mknodAt(t, linux.AT_FDCWD, path, mode) + addr := args[0].Pointer() + mode := args[1].ModeT() + dev := args[2].Uint() + return 0, nil, mknodat(t, linux.AT_FDCWD, addr, linux.FileMode(mode), dev) } -// Mknodat implements the linux syscall mknodat(2). +// Mknodat implements Linux syscall mknodat(2). func Mknodat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() - path := args[1].Pointer() - mode := linux.FileMode(args[2].ModeT()) - // We don't need this argument until we support creation of device nodes. - _ = args[3].Uint() // dev - - return 0, nil, mknodAt(t, dirFD, path, mode) + dirfd := args[0].Int() + addr := args[1].Pointer() + mode := args[2].ModeT() + dev := args[3].Uint() + return 0, nil, mknodat(t, dirfd, addr, linux.FileMode(mode), dev) } -func createAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, flags uint, mode linux.FileMode) (fd uintptr, err error) { - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) +func mknodat(t *kernel.Task, dirfd int32, addr hostarch.Addr, mode linux.FileMode, dev uint32) error { + path, err := copyInPath(t, addr) if err != nil { - return 0, err + return err } - if dirPath { - return 0, linuxerr.ENOENT + tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return err } + defer tpop.Release(t) - fileFlags := linuxToFlags(flags) - // Linux always adds the O_LARGEFILE flag when running in 64-bit mode. - fileFlags.LargeFile = true - - err = fileOpAt(t, dirFD, path, func(root *fs.Dirent, parent *fs.Dirent, name string, remainingTraversals uint) error { - // Resolve the name to see if it exists, and follow any - // symlinks along the way. We must do the symlink resolution - // manually because if the symlink target does not exist, we - // must create the target (and not the symlink itself). - var ( - found *fs.Dirent - err error - ) - for { - if !fs.IsDir(parent.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Start by looking up the dirent at 'name'. - found, err = t.MountNamespace().FindLink(t, root, parent, name, &remainingTraversals) - if err != nil { - break - } - defer found.DecRef(t) - - // We found something (possibly a symlink). If the - // O_EXCL flag was passed, then we can immediately - // return EEXIST. - if flags&linux.O_EXCL != 0 { - return linuxerr.EEXIST - } - - // If we have a non-symlink, then we can proceed. - if !fs.IsSymlink(found.Inode.StableAttr) { - break - } - - // If O_NOFOLLOW was passed, then don't try to resolve - // anything. - if flags&linux.O_NOFOLLOW != 0 { - return linuxerr.ELOOP - } - - // Try to resolve the symlink directly to a Dirent. - var resolved *fs.Dirent - resolved, err = found.Inode.Getlink(t) - if err == nil { - // No more resolution necessary. - defer resolved.DecRef(t) - break - } - if err != fs.ErrResolveViaReadlink { - return err - } - - // Are we able to resolve further? - if remainingTraversals == 0 { - return unix.ELOOP - } - - // Resolve the symlink to a path via Readlink. - var path string - path, err = found.Inode.Readlink(t) - if err != nil { - break - } - remainingTraversals-- - - // Get the new parent from the target path. - var newParent *fs.Dirent - newParentPath, newName := fs.SplitLast(path) - newParent, err = t.MountNamespace().FindInode(t, root, parent, newParentPath, &remainingTraversals) - if err != nil { - break - } - defer newParent.DecRef(t) - - // Repeat the process with the parent and name of the - // symlink target. - parent = newParent - name = newName - } - - var newFile *fs.File - switch { - case err == nil: - // Like sys_open, check for a few things about the - // filesystem before trying to get a reference to the - // fs.File. The same constraints on Check apply. - if err := found.Inode.CheckPermission(t, flagsToPermissions(flags)); err != nil { - return err - } - - // Truncate is called when O_TRUNC is specified for any kind of - // existing Dirent. Behavior is delegated to the entry's Truncate - // implementation. - if flags&linux.O_TRUNC != 0 { - if err := found.Inode.Truncate(t, found, 0); err != nil { - return err - } - } - - // Create a new fs.File. - newFile, err = found.Inode.GetFile(t, found, fileFlags) - if err != nil { - return linuxerr.ConvertIntr(err, linuxerr.ERESTARTSYS) - } - defer newFile.DecRef(t) - case linuxerr.Equals(linuxerr.ENOENT, err): - // File does not exist. Proceed with creation. - - // Do we have write permissions on the parent? - if err := parent.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil { - return err - } - - // Attempt a creation. - perms := fs.FilePermsFromMode(mode &^ linux.FileMode(t.FSContext().Umask())) - newFile, err = parent.Create(t, root, name, fileFlags, perms) - if err != nil { - // No luck, bail. - return err - } - defer newFile.DecRef(t) - found = newFile.Dirent - default: - return err - } - - // Success. - newFD, err := t.NewFDFrom(0, newFile, kernel.FDFlags{ - CloseOnExec: flags&linux.O_CLOEXEC != 0, - }) - if err != nil { - return err - } - - // Set result in frame. - fd = uintptr(newFD) - - // Queue the open inotify event. The creation event is - // automatically queued when the dirent is found. The open - // events are implemented at the syscall layer so we need to - // manually queue one here. - found.InotifyEvent(linux.IN_OPEN, 0) - - return nil + // "Zero file type is equivalent to type S_IFREG." - mknod(2) + if mode.FileType() == 0 { + mode |= linux.ModeRegular + } + major, minor := linux.DecodeDeviceID(dev) + return t.Kernel().VFS().MknodAt(t, t.Credentials(), &tpop.pop, &vfs.MknodOptions{ + Mode: mode &^ linux.FileMode(t.FSContext().Umask()), + DevMajor: uint32(major), + DevMinor: minor, }) - return fd, err // Use result in frame. } -// Open implements linux syscall open(2). +// Open implements Linux syscall open(2). func Open(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { addr := args[0].Pointer() - flags := uint(args[1].Uint()) - if flags&linux.O_CREAT != 0 { - mode := linux.FileMode(args[2].ModeT()) - n, err := createAt(t, linux.AT_FDCWD, addr, flags, mode) - return n, nil, err - } - n, err := openAt(t, linux.AT_FDCWD, addr, flags) - return n, nil, err + flags := args[1].Uint() + mode := args[2].ModeT() + return openat(t, linux.AT_FDCWD, addr, flags, mode) } -// Openat implements linux syscall openat(2). +// Openat implements Linux syscall openat(2). func Openat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() + dirfd := args[0].Int() addr := args[1].Pointer() - flags := uint(args[2].Uint()) - if flags&linux.O_CREAT != 0 { - mode := linux.FileMode(args[3].ModeT()) - n, err := createAt(t, dirFD, addr, flags, mode) - return n, nil, err - } - n, err := openAt(t, dirFD, addr, flags) - return n, nil, err + flags := args[2].Uint() + mode := args[3].ModeT() + return openat(t, dirfd, addr, flags, mode) } -// Creat implements linux syscall creat(2). +// Creat implements Linux syscall creat(2). func Creat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { addr := args[0].Pointer() - mode := linux.FileMode(args[1].ModeT()) - n, err := createAt(t, linux.AT_FDCWD, addr, linux.O_WRONLY|linux.O_TRUNC, mode) - return n, nil, err + mode := args[1].ModeT() + return openat(t, linux.AT_FDCWD, addr, linux.O_WRONLY|linux.O_CREAT|linux.O_TRUNC, mode) } -// accessContext is a context that overrides the credentials used, but -// otherwise carries the same values as the embedded context. -// -// accessContext should only be used for access(2). -type accessContext struct { - context.Context - creds *auth.Credentials -} - -// Value implements context.Context. -func (ac accessContext) Value(key any) any { - switch key { - case auth.CtxCredentials: - return ac.creds - default: - return ac.Context.Value(key) +func openat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, flags uint32, mode uint) (uintptr, *kernel.SyscallControl, error) { + path, err := copyInPath(t, pathAddr) + if err != nil { + return 0, nil, err } + tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, shouldFollowFinalSymlink(flags&linux.O_NOFOLLOW == 0)) + if err != nil { + return 0, nil, err + } + defer tpop.Release(t) + + file, err := t.Kernel().VFS().OpenAt(t, t.Credentials(), &tpop.pop, &vfs.OpenOptions{ + Flags: flags | linux.O_LARGEFILE, + Mode: linux.FileMode(mode & (0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX) &^ t.FSContext().Umask()), + }) + if err != nil { + return 0, nil, err + } + defer file.DecRef(t) + + fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ + CloseOnExec: flags&linux.O_CLOEXEC != 0, + }) + return uintptr(fd), nil, err } -func accessAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, mode uint) error { +// Access implements Linux syscall access(2). +func Access(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + addr := args[0].Pointer() + mode := args[1].ModeT() + + return 0, nil, accessAt(t, linux.AT_FDCWD, addr, mode, 0 /* flags */) +} + +// Faccessat implements Linux syscall faccessat(2). +func Faccessat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + dirfd := args[0].Int() + addr := args[1].Pointer() + mode := args[2].ModeT() + + return 0, nil, accessAt(t, dirfd, addr, mode, 0 /* flags */) +} + +// Faccessat2 implements Linux syscall faccessat2(2). +func Faccessat2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + dirfd := args[0].Int() + addr := args[1].Pointer() + mode := args[2].ModeT() + flags := args[3].Int() + + return 0, nil, accessAt(t, dirfd, addr, mode, flags) +} + +func accessAt(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint, flags int32) error { const rOK = 4 const wOK = 2 const xOK = 1 - path, _, err := copyInPath(t, addr, false /* allowEmpty */) - if err != nil { - return err - } - // Sanity check the mode. if mode&^(rOK|wOK|xOK) != 0 { return linuxerr.EINVAL } - return fileOpOn(t, dirFD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { + // faccessat2(2) isn't documented as supporting AT_EMPTY_PATH, but it does. + if flags&^(linux.AT_EACCESS|linux.AT_SYMLINK_NOFOLLOW|linux.AT_EMPTY_PATH) != 0 { + return linuxerr.EINVAL + } + + path, err := copyInPath(t, pathAddr) + if err != nil { + return err + } + tpop, err := getTaskPathOperation(t, dirfd, path, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_NOFOLLOW == 0)) + if err != nil { + return err + } + defer tpop.Release(t) + + creds := t.Credentials() + if flags&linux.AT_EACCESS == 0 { // access(2) and faccessat(2) check permissions using real // UID/GID, not effective UID/GID. // @@ -540,7 +185,7 @@ func accessAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, mode uint) error // uid/gid. We do this by temporarily clearing all FS-related // capabilities and switching the fsuid/fsgid around to the // real ones." -fs/open.c:faccessat - creds := t.Credentials().Fork() + creds = creds.Fork() creds.EffectiveKUID = creds.RealKUID creds.EffectiveKGID = creds.RealKGID if creds.EffectiveKUID.In(creds.UserNamespace) == auth.RootUID { @@ -548,68 +193,35 @@ func accessAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, mode uint) error } else { creds.EffectiveCaps = 0 } + } - ctx := &accessContext{ - Context: t, - creds: creds, - } - - return d.Inode.CheckPermission(ctx, fs.PermMask{ - Read: mode&rOK != 0, - Write: mode&wOK != 0, - Execute: mode&xOK != 0, - }) - }) + return t.Kernel().VFS().AccessAt(t, creds, vfs.AccessTypes(mode), &tpop.pop) } -// Access implements linux syscall access(2). -func Access(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - mode := args[1].ModeT() - - return 0, nil, accessAt(t, linux.AT_FDCWD, addr, mode) -} - -// Faccessat implements linux syscall faccessat(2). -// -// Note that the faccessat() system call does not take a flags argument: -// "The raw faccessat() system call takes only the first three arguments. The -// AT_EACCESS and AT_SYMLINK_NOFOLLOW flags are actually implemented within -// the glibc wrapper function for faccessat(). If either of these flags is -// specified, then the wrapper function employs fstatat(2) to determine access -// permissions." - faccessat(2) -func Faccessat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() - addr := args[1].Pointer() - mode := args[2].ModeT() - - return 0, nil, accessAt(t, dirFD, addr, mode) -} - -// LINT.ThenChange(vfs2/filesystem.go) - -// LINT.IfChange - -// Ioctl implements linux syscall ioctl(2). +// Ioctl implements Linux syscall ioctl(2). func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - request := int(args[1].Int()) - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - // Shared flags between file and socket. - switch request { + if file.StatusFlags()&linux.O_PATH != 0 { + return 0, nil, linuxerr.EBADF + } + + // Handle ioctls that apply to all FDs. + switch args[1].Int() { case linux.FIONCLEX: - t.FDTable().SetFlags(t, fd, kernel.FDFlags{ + t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ CloseOnExec: false, }) return 0, nil, nil + case linux.FIOCLEX: - t.FDTable().SetFlags(t, fd, kernel.FDFlags{ + t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ CloseOnExec: true, }) return 0, nil, nil @@ -619,73 +231,74 @@ func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &set); err != nil { return 0, nil, err } - flags := file.Flags() + flags := file.StatusFlags() if set != 0 { - flags.NonBlocking = true + flags |= linux.O_NONBLOCK } else { - flags.NonBlocking = false + flags &^= linux.O_NONBLOCK } - file.SetFlags(flags.Settable()) - return 0, nil, nil + return 0, nil, file.SetStatusFlags(t, t.Credentials(), flags) case linux.FIOASYNC: var set int32 if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &set); err != nil { return 0, nil, err } - flags := file.Flags() + flags := file.StatusFlags() if set != 0 { - flags.Async = true + flags |= linux.O_ASYNC } else { - flags.Async = false + flags &^= linux.O_ASYNC } - file.SetFlags(flags.Settable()) - return 0, nil, nil - - case linux.FIOSETOWN, linux.SIOCSPGRP: - var set int32 - if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &set); err != nil { - return 0, nil, err - } - fSetOwn(t, int(fd), file, set) + file.SetStatusFlags(t, t.Credentials(), flags) return 0, nil, nil case linux.FIOGETOWN, linux.SIOCGPGRP: - owner, err := fGetOwn(t, file) - if err != nil { - return 0, nil, err + var who int32 + owner, hasOwner := getAsyncOwner(t, file) + if hasOwner { + if owner.Type == linux.F_OWNER_PGRP { + who = -owner.PID + } else { + who = owner.PID + } } - _, err = primitive.CopyInt32Out(t, args[2].Pointer(), owner) + _, err := primitive.CopyInt32Out(t, args[2].Pointer(), who) return 0, nil, err - default: - ret, err := file.FileOperations.Ioctl(t, file, t.MemoryManager(), args) - if err != nil { + case linux.FIOSETOWN, linux.SIOCSPGRP: + var who int32 + if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &who); err != nil { return 0, nil, err } - - return ret, nil, nil + ownerType := int32(linux.F_OWNER_PID) + if who < 0 { + // Check for overflow before flipping the sign. + if who-1 > who { + return 0, nil, linuxerr.EINVAL + } + ownerType = linux.F_OWNER_PGRP + who = -who + } + return 0, nil, setAsyncOwner(t, int(fd), file, ownerType, who) } + + ret, err := file.Ioctl(t, t.MemoryManager(), args) + return ret, nil, err } -// LINT.ThenChange(vfs2/ioctl.go) - -// LINT.IfChange - -// Getcwd implements the linux syscall getcwd(2). +// Getcwd implements Linux syscall getcwd(2). func Getcwd(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { addr := args[0].Pointer() size := args[1].SizeT() - cwd := t.FSContext().WorkingDirectory() - defer cwd.DecRef(t) - root := t.FSContext().RootDirectory() - defer root.DecRef(t) - // Get our fullname from the root and preprend unreachable if the root was - // unreachable from our current dirent this is the same behavior as on linux. - s, reachable := cwd.FullName(root) - if !reachable { - s = "(unreachable)" + s + root := t.FSContext().RootDirectoryVFS2() + wd := t.FSContext().WorkingDirectoryVFS2() + s, err := t.Kernel().VFS().PathnameForGetcwd(t, root, wd) + root.DecRef(t) + wd.DecRef(t) + if err != nil { + return 0, nil, err } // Note this is >= because we need a terminator. @@ -693,18 +306,66 @@ func Getcwd(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal return 0, nil, linuxerr.ERANGE } - // Copy out the path name for the node. - bytes, err := t.CopyOutBytes(addr, []byte(s)) + // Construct a byte slice containing a NUL terminator. + buf := t.CopyScratchBuffer(len(s) + 1) + copy(buf, s) + buf[len(buf)-1] = 0 + + // Write the pathname slice. + n, err := t.CopyOutBytes(addr, buf) if err != nil { return 0, nil, err } - - // Top it off with a terminator. - _, err = t.CopyOutBytes(addr+hostarch.Addr(bytes), []byte("\x00")) - return uintptr(bytes + 1), nil, err + return uintptr(n), nil, nil } -// Chroot implements the linux syscall chroot(2). +// Chdir implements Linux syscall chdir(2). +func Chdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + addr := args[0].Pointer() + + path, err := copyInPath(t, addr) + if err != nil { + return 0, nil, err + } + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink) + if err != nil { + return 0, nil, err + } + defer tpop.Release(t) + + vd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{ + CheckSearchable: true, + }) + if err != nil { + return 0, nil, err + } + t.FSContext().SetWorkingDirectoryVFS2(t, vd) + vd.DecRef(t) + return 0, nil, nil +} + +// Fchdir implements Linux syscall fchdir(2). +func Fchdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + fd := args[0].Int() + + tpop, err := getTaskPathOperation(t, fd, fspath.Path{}, allowEmptyPath, nofollowFinalSymlink) + if err != nil { + return 0, nil, err + } + defer tpop.Release(t) + + vd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{ + CheckSearchable: true, + }) + if err != nil { + return 0, nil, err + } + t.FSContext().SetWorkingDirectoryVFS2(t, vd) + vd.DecRef(t) + return 0, nil, nil +} + +// Chroot implements Linux syscall chroot(2). func Chroot(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { addr := args[0].Pointer() @@ -712,95 +373,87 @@ func Chroot(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal return 0, nil, linuxerr.EPERM } - path, _, err := copyInPath(t, addr, false /* allowEmpty */) + path, err := copyInPath(t, addr) if err != nil { return 0, nil, err } - - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - // Is it a directory? - if !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Does it have execute permissions? - if err := d.Inode.CheckPermission(t, fs.PermMask{Execute: true}); err != nil { - return err - } - - t.FSContext().SetRootDirectory(t, d) - return nil - }) -} - -// Chdir implements the linux syscall chdir(2). -func Chdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - - path, _, err := copyInPath(t, addr, false /* allowEmpty */) + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink) if err != nil { return 0, nil, err } + defer tpop.Release(t) - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - // Is it a directory? - if !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Does it have execute permissions? - if err := d.Inode.CheckPermission(t, fs.PermMask{Execute: true}); err != nil { - return err - } - - t.FSContext().SetWorkingDirectory(t, d) - return nil + vd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{ + CheckSearchable: true, }) -} - -// Fchdir implements the linux syscall fchdir(2). -func Fchdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Is it a directory? - if !fs.IsDir(file.Dirent.Inode.StableAttr) { - return 0, nil, linuxerr.ENOTDIR - } - - // Does it have execute permissions? - if err := file.Dirent.Inode.CheckPermission(t, fs.PermMask{Execute: true}); err != nil { + if err != nil { return 0, nil, err } - - t.FSContext().SetWorkingDirectory(t, file.Dirent) + t.FSContext().SetRootDirectoryVFS2(t, vd) + vd.DecRef(t) return 0, nil, nil } -// LINT.ThenChange(vfs2/fscontext.go) +// PivotRoot implements Linux syscall pivot_root(2). +func PivotRoot(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + addr1 := args[0].Pointer() + addr2 := args[1].Pointer() -// LINT.IfChange + if !t.HasCapability(linux.CAP_SYS_ADMIN) { + return 0, nil, linuxerr.EPERM + } -// Close implements linux syscall close(2). + newRootPath, err := copyInPath(t, addr1) + if err != nil { + return 0, nil, err + } + newRootTpop, err := getTaskPathOperation(t, linux.AT_FDCWD, newRootPath, disallowEmptyPath, followFinalSymlink) + if err != nil { + return 0, nil, err + } + defer newRootTpop.Release(t) + putOldPath, err := copyInPath(t, addr2) + if err != nil { + return 0, nil, err + } + putOldTpop, err := getTaskPathOperation(t, linux.AT_FDCWD, putOldPath, disallowEmptyPath, followFinalSymlink) + if err != nil { + return 0, nil, err + } + defer putOldTpop.Release(t) + + oldRootVd := t.FSContext().RootDirectoryVFS2() + defer oldRootVd.DecRef(t) + newRootVd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &newRootTpop.pop, &vfs.GetDentryOptions{ + CheckSearchable: true, + }) + if err != nil { + return 0, nil, err + } + defer newRootVd.DecRef(t) + + if err := t.Kernel().VFS().PivotRoot(t, t.Credentials(), &newRootTpop.pop, &putOldTpop.pop); err != nil { + return 0, nil, err + } + t.Kernel().ReplaceFSContextRoots(t, oldRootVd, newRootVd) + return 0, nil, nil +} + +// Close implements Linux syscall close(2). func Close(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() // Note that Remove provides a reference on the file that we may use to // flush. It is still active until we drop the final reference below // (and other reference-holding operations complete). - file, _ := t.FDTable().Remove(t, fd) + _, file := t.FDTable().Remove(t, fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - err := file.Flush(t) - return 0, nil, handleIOError(t, false /* partial */, err, linuxerr.EINTR, "close", file) + err := file.OnClose(t) + return 0, nil, HandleIOError(t, false /* partial */, err, linuxerr.EINTR, "close", file) } // CloseRange implements linux syscall close_range(2). @@ -836,67 +489,63 @@ func CloseRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy flagToApply := kernel.FDFlags{ CloseOnExec: true, } - t.FDTable().SetFlagsForRange(t.AsyncContext(), int32(first), int32(last), flagToApply) + t.FDTable().SetFlagsForRangeVFS2(t.AsyncContext(), int32(first), int32(last), flagToApply) return 0, nil, nil } fdTable := t.FDTable() fd := int32(first) for { - fd, file, _ := fdTable.RemoveNextInRange(t, fd, int32(last)) + fd, _, file := fdTable.RemoveNextInRange(t, fd, int32(last)) if file == nil { break } fd++ // Per the close_range(2) documentation, errors upon closing file descriptors are ignored. - _ = file.Flush(t) + _ = file.OnClose(t) file.DecRef(t) } return 0, nil, nil } -// Dup implements linux syscall dup(2). +// Dup implements Linux syscall dup(2). func Dup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - newFD, err := t.NewFDFrom(0, file, kernel.FDFlags{}) + newFD, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{}) if err != nil { return 0, nil, linuxerr.EMFILE } return uintptr(newFD), nil, nil } -// Dup2 implements linux syscall dup2(2). +// Dup2 implements Linux syscall dup2(2). func Dup2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { oldfd := args[0].Int() newfd := args[1].Int() - // If oldfd is a valid file descriptor, and newfd has the same value as oldfd, - // then dup2() does nothing, and returns newfd. if oldfd == newfd { - oldFile := t.GetFile(oldfd) - if oldFile == nil { + // As long as oldfd is valid, dup2() does nothing and returns newfd. + file := t.GetFileVFS2(oldfd) + if file == nil { return 0, nil, linuxerr.EBADF } - defer oldFile.DecRef(t) - + file.DecRef(t) return uintptr(newfd), nil, nil } - // Zero out flags arg to be used by Dup3. - args[2].Value = 0 - return Dup3(t, args) + return dup3(t, oldfd, newfd, 0) } -// Dup3 implements linux syscall dup3(2). +// Dup3 implements Linux syscall dup3(2). func Dup3(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { oldfd := args[0].Int() newfd := args[1].Int() @@ -906,101 +555,53 @@ func Dup3(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC return 0, nil, linuxerr.EINVAL } - oldFile := t.GetFile(oldfd) - if oldFile == nil { + return dup3(t, oldfd, newfd, flags) +} + +func dup3(t *kernel.Task, oldfd, newfd int32, flags uint32) (uintptr, *kernel.SyscallControl, error) { + if flags&^linux.O_CLOEXEC != 0 { + return 0, nil, linuxerr.EINVAL + } + + file := t.GetFileVFS2(oldfd) + if file == nil { return 0, nil, linuxerr.EBADF } - defer oldFile.DecRef(t) + defer file.DecRef(t) - err := t.NewFDAt(newfd, oldFile, kernel.FDFlags{CloseOnExec: flags&linux.O_CLOEXEC != 0}) + err := t.NewFDAtVFS2(newfd, file, kernel.FDFlags{ + CloseOnExec: flags&linux.O_CLOEXEC != 0, + }) if err != nil { return 0, nil, err } - return uintptr(newfd), nil, nil } -func fGetOwnEx(t *kernel.Task, file *fs.File) (linux.FOwnerEx, error) { - ma, err := file.Async(nil) - if err != nil { - return linux.FOwnerEx{}, err - } - if ma == nil { - return linux.FOwnerEx{}, nil - } - a := ma.(*fasync.FileAsync) - ot, otg, opg := a.Owner() - switch { - case ot != nil: - return linux.FOwnerEx{ - Type: linux.F_OWNER_TID, - PID: int32(t.PIDNamespace().IDOfTask(ot)), - }, nil - case otg != nil: - return linux.FOwnerEx{ - Type: linux.F_OWNER_PID, - PID: int32(t.PIDNamespace().IDOfThreadGroup(otg)), - }, nil - case opg != nil: - return linux.FOwnerEx{ - Type: linux.F_OWNER_PGRP, - PID: int32(t.PIDNamespace().IDOfProcessGroup(opg)), - }, nil - default: - return linux.FOwnerEx{}, nil - } -} - -func fGetOwn(t *kernel.Task, file *fs.File) (int32, error) { - owner, err := fGetOwnEx(t, file) - if err != nil { - return 0, err - } - if owner.Type == linux.F_OWNER_PGRP { - return -owner.PID, nil - } - return owner.PID, nil -} - -// fSetOwn sets the file's owner with the semantics of F_SETOWN in Linux. -// -// If who is positive, it represents a PID. If negative, it represents a PGID. -// If the PID or PGID is invalid, the owner is silently unset. -func fSetOwn(t *kernel.Task, fd int, file *fs.File, who int32) error { - a, err := file.Async(fasync.New(fd)) - if err != nil { - return err - } - async := a.(*fasync.FileAsync) - if who < 0 { - // Check for overflow before flipping the sign. - if who-1 > who { - return linuxerr.EINVAL - } - pg := t.PIDNamespace().ProcessGroupWithID(kernel.ProcessGroupID(-who)) - async.SetOwnerProcessGroup(t, pg) - } else { - tg := t.PIDNamespace().ThreadGroupWithID(kernel.ThreadID(who)) - async.SetOwnerThreadGroup(t, tg) - } - return nil -} - // Fcntl implements linux syscall fcntl(2). func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() cmd := args[1].Int() - file, flags := t.FDTable().Get(fd) + file, flags := t.FDTable().GetVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) + if file.StatusFlags()&linux.O_PATH != 0 { + switch cmd { + case linux.F_DUPFD, linux.F_DUPFD_CLOEXEC, linux.F_GETFD, linux.F_SETFD, linux.F_GETFL: + // allowed + default: + return 0, nil, linuxerr.EBADF + } + } + switch cmd { case linux.F_DUPFD, linux.F_DUPFD_CLOEXEC: - from := args[2].Int() - fd, err := t.NewFDFrom(from, file, kernel.FDFlags{ + minfd := args[2].Int() + fd, err := t.NewFDFromVFS2(minfd, file, kernel.FDFlags{ CloseOnExec: cmd == linux.F_DUPFD_CLOEXEC, }) if err != nil { @@ -1011,181 +612,89 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return uintptr(flags.ToLinuxFDFlags()), nil, nil case linux.F_SETFD: flags := args[2].Uint() - err := t.FDTable().SetFlags(t, fd, kernel.FDFlags{ + err := t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ CloseOnExec: flags&linux.FD_CLOEXEC != 0, }) return 0, nil, err case linux.F_GETFL: - return uintptr(file.Flags().ToLinux()), nil, nil + return uintptr(file.StatusFlags()), nil, nil case linux.F_SETFL: - flags := uint(args[2].Uint()) - file.SetFlags(linuxToFlags(flags).Settable()) - return 0, nil, nil - case linux.F_SETLK, linux.F_SETLKW: - // In Linux the file system can choose to provide lock operations for an inode. - // Normally pipe and socket types lack lock operations. We diverge and use a heavy - // hammer by only allowing locks on files and directories. - if !fs.IsFile(file.Dirent.Inode.StableAttr) && !fs.IsDir(file.Dirent.Inode.StableAttr) { - return 0, nil, linuxerr.EBADF - } - - // Copy in the lock request. - flockAddr := args[2].Pointer() - var flock linux.Flock - if _, err := flock.CopyIn(t, flockAddr); err != nil { - return 0, nil, err - } - - // Compute the lock whence. - var sw fs.SeekWhence - switch flock.Whence { - case 0: - sw = fs.SeekSet - case 1: - sw = fs.SeekCurrent - case 2: - sw = fs.SeekEnd - default: - return 0, nil, linuxerr.EINVAL - } - - // Compute the lock offset. - var off int64 - switch sw { - case fs.SeekSet: - off = 0 - case fs.SeekCurrent: - // Note that Linux does not hold any mutexes while retrieving the file offset, - // see fs/locks.c:flock_to_posix_lock and fs/locks.c:fcntl_setlk. - off = file.Offset() - case fs.SeekEnd: - uattr, err := file.Dirent.Inode.UnstableAttr(t) - if err != nil { - return 0, nil, err - } - off = uattr.Size - default: - return 0, nil, linuxerr.EINVAL - } - - // Compute the lock range. - rng, err := lock.ComputeRange(flock.Start, flock.Len, off) - if err != nil { - return 0, nil, err - } - - // These locks don't block; execute the non-blocking operation using the inode's lock - // context directly. - switch flock.Type { - case linux.F_RDLCK: - if !file.Flags().Read { - return 0, nil, linuxerr.EBADF - } - // Lock the given region. - if err := file.Dirent.Inode.LockCtx.Posix.LockRegionVFS1(t, t.FDTable(), lock.ReadLock, rng, cmd != linux.F_SETLK /* block */); err != nil { - return 0, nil, err - } - return 0, nil, nil - case linux.F_WRLCK: - if !file.Flags().Write { - return 0, nil, linuxerr.EBADF - } - // Lock the given region. - if err := file.Dirent.Inode.LockCtx.Posix.LockRegionVFS1(t, t.FDTable(), lock.WriteLock, rng, cmd != linux.F_SETLK /* block */); err != nil { - return 0, nil, err - } - return 0, nil, nil - case linux.F_UNLCK: - file.Dirent.Inode.LockCtx.Posix.UnlockRegion(t.FDTable(), rng) - return 0, nil, nil - default: - return 0, nil, linuxerr.EINVAL - } + return 0, nil, file.SetStatusFlags(t, t.Credentials(), args[2].Uint()) case linux.F_GETOWN: - owner, err := fGetOwn(t, file) - if err != nil { - return 0, nil, err + owner, hasOwner := getAsyncOwner(t, file) + if !hasOwner { + return 0, nil, nil } - return uintptr(owner), nil, nil + if owner.Type == linux.F_OWNER_PGRP { + return uintptr(-owner.PID), nil, nil + } + return uintptr(owner.PID), nil, nil case linux.F_SETOWN: - return 0, nil, fSetOwn(t, int(fd), file, args[2].Int()) - case linux.F_GETOWN_EX: - addr := args[2].Pointer() - owner, err := fGetOwnEx(t, file) - if err != nil { - return 0, nil, err + who := args[2].Int() + ownerType := int32(linux.F_OWNER_PID) + if who < 0 { + // Check for overflow before flipping the sign. + if who-1 > who { + return 0, nil, linuxerr.EINVAL + } + ownerType = linux.F_OWNER_PGRP + who = -who } - _, err = owner.CopyOut(t, addr) + return 0, nil, setAsyncOwner(t, int(fd), file, ownerType, who) + case linux.F_GETOWN_EX: + owner, hasOwner := getAsyncOwner(t, file) + if !hasOwner { + return 0, nil, nil + } + _, err := owner.CopyOut(t, args[2].Pointer()) return 0, nil, err case linux.F_SETOWN_EX: - addr := args[2].Pointer() var owner linux.FOwnerEx - _, err := owner.CopyIn(t, addr) + _, err := owner.CopyIn(t, args[2].Pointer()) if err != nil { return 0, nil, err } - a, err := file.Async(fasync.New(int(fd))) + return 0, nil, setAsyncOwner(t, int(fd), file, owner.Type, owner.PID) + case linux.F_SETPIPE_SZ: + pipefile, ok := file.Impl().(*pipe.VFSPipeFD) + if !ok { + return 0, nil, linuxerr.EBADF + } + n, err := pipefile.SetPipeSize(int64(args[2].Int())) if err != nil { return 0, nil, err } - async := a.(*fasync.FileAsync) - switch owner.Type { - case linux.F_OWNER_TID: - task := t.PIDNamespace().TaskWithID(kernel.ThreadID(owner.PID)) - if task == nil { - return 0, nil, linuxerr.ESRCH - } - async.SetOwnerTask(t, task) - return 0, nil, nil - case linux.F_OWNER_PID: - tg := t.PIDNamespace().ThreadGroupWithID(kernel.ThreadID(owner.PID)) - if tg == nil { - return 0, nil, linuxerr.ESRCH - } - async.SetOwnerThreadGroup(t, tg) - return 0, nil, nil - case linux.F_OWNER_PGRP: - pg := t.PIDNamespace().ProcessGroupWithID(kernel.ProcessGroupID(owner.PID)) - if pg == nil { - return 0, nil, linuxerr.ESRCH - } - async.SetOwnerProcessGroup(t, pg) - return 0, nil, nil - default: - return 0, nil, linuxerr.EINVAL + return uintptr(n), nil, nil + case linux.F_GETPIPE_SZ: + pipefile, ok := file.Impl().(*pipe.VFSPipeFD) + if !ok { + return 0, nil, linuxerr.EBADF } + return uintptr(pipefile.PipeSize()), nil, nil case linux.F_GET_SEALS: - val, err := tmpfs.GetSeals(file.Dirent.Inode) + val, err := tmpfs.GetSeals(file) return uintptr(val), nil, err case linux.F_ADD_SEALS: - if !file.Flags().Write { + if !file.IsWritable() { return 0, nil, linuxerr.EPERM } - err := tmpfs.AddSeals(file.Dirent.Inode, args[2].Uint()) + err := tmpfs.AddSeals(file, args[2].Uint()) return 0, nil, err - case linux.F_GETPIPE_SZ: - sz, ok := file.FileOperations.(fs.FifoSizer) - if !ok { - return 0, nil, linuxerr.EINVAL - } - size, err := sz.FifoSize(t, file) - return uintptr(size), nil, err - case linux.F_SETPIPE_SZ: - sz, ok := file.FileOperations.(fs.FifoSizer) - if !ok { - return 0, nil, linuxerr.EINVAL - } - n, err := sz.SetFifoSize(int64(args[2].Int())) - return uintptr(n), nil, err + case linux.F_SETLK: + return 0, nil, posixLock(t, args, file, false /* block */) + case linux.F_SETLKW: + return 0, nil, posixLock(t, args, file, true /* block */) + case linux.F_GETLK: + return 0, nil, posixTestLock(t, args, file) case linux.F_GETSIG: - a, err := file.Async(fasync.New(int(fd))) - if err != nil { - return 0, nil, err + a := file.AsyncHandler() + if a == nil { + // Default behavior aka SIGIO. + return 0, nil, nil } - async := a.(*fasync.FileAsync) - return uintptr(async.Signal()), nil, nil + return uintptr(a.(*fasync.FileAsync).Signal()), nil, nil case linux.F_SETSIG: - a, err := file.Async(fasync.New(int(fd))) + a, err := file.SetAsyncHandler(fasync.NewVFS2(int(fd))) if err != nil { return 0, nil, err } @@ -1197,7 +706,156 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall } } -// Fadvise64 implements linux syscall fadvise64(2). +func getAsyncOwner(t *kernel.Task, fd *vfs.FileDescription) (ownerEx linux.FOwnerEx, hasOwner bool) { + a := fd.AsyncHandler() + if a == nil { + return linux.FOwnerEx{}, false + } + + ot, otg, opg := a.(*fasync.FileAsync).Owner() + switch { + case ot != nil: + return linux.FOwnerEx{ + Type: linux.F_OWNER_TID, + PID: int32(t.PIDNamespace().IDOfTask(ot)), + }, true + case otg != nil: + return linux.FOwnerEx{ + Type: linux.F_OWNER_PID, + PID: int32(t.PIDNamespace().IDOfThreadGroup(otg)), + }, true + case opg != nil: + return linux.FOwnerEx{ + Type: linux.F_OWNER_PGRP, + PID: int32(t.PIDNamespace().IDOfProcessGroup(opg)), + }, true + default: + return linux.FOwnerEx{}, true + } +} + +func setAsyncOwner(t *kernel.Task, fd int, file *vfs.FileDescription, ownerType, pid int32) error { + switch ownerType { + case linux.F_OWNER_TID, linux.F_OWNER_PID, linux.F_OWNER_PGRP: + // Acceptable type. + default: + return linuxerr.EINVAL + } + + a, err := file.SetAsyncHandler(fasync.NewVFS2(fd)) + if err != nil { + return err + } + async := a.(*fasync.FileAsync) + if pid == 0 { + async.ClearOwner() + return nil + } + + switch ownerType { + case linux.F_OWNER_TID: + task := t.PIDNamespace().TaskWithID(kernel.ThreadID(pid)) + if task == nil { + return linuxerr.ESRCH + } + async.SetOwnerTask(t, task) + return nil + case linux.F_OWNER_PID: + tg := t.PIDNamespace().ThreadGroupWithID(kernel.ThreadID(pid)) + if tg == nil { + return linuxerr.ESRCH + } + async.SetOwnerThreadGroup(t, tg) + return nil + case linux.F_OWNER_PGRP: + pg := t.PIDNamespace().ProcessGroupWithID(kernel.ProcessGroupID(pid)) + if pg == nil { + return linuxerr.ESRCH + } + async.SetOwnerProcessGroup(t, pg) + return nil + default: + return linuxerr.EINVAL + } +} + +func posixTestLock(t *kernel.Task, args arch.SyscallArguments, file *vfs.FileDescription) error { + // Copy in the lock request. + flockAddr := args[2].Pointer() + var flock linux.Flock + if _, err := flock.CopyIn(t, flockAddr); err != nil { + return err + } + var typ lock.LockType + switch flock.Type { + case linux.F_RDLCK: + typ = lock.ReadLock + case linux.F_WRLCK: + typ = lock.WriteLock + default: + return linuxerr.EINVAL + } + r, err := file.ComputeLockRange(t, uint64(flock.Start), uint64(flock.Len), flock.Whence) + if err != nil { + return err + } + + newFlock, err := file.TestPOSIX(t, t.FDTable(), typ, r) + if err != nil { + return err + } + newFlock.PID = translatePID(t.PIDNamespace().Root(), t.PIDNamespace(), newFlock.PID) + if _, err = newFlock.CopyOut(t, flockAddr); err != nil { + return err + } + return nil +} + +// translatePID translates a pid from one namespace to another. Note that this +// may race with task termination/creation, in which case the original task +// corresponding to pid may no longer exist. This is used to implement the +// F_GETLK fcntl, which has the same potential race in Linux as well (i.e., +// there is no synchronization between retrieving the lock PID and translating +// it). See fs/locks.c:posix_lock_to_flock. +func translatePID(old, new *kernel.PIDNamespace, pid int32) int32 { + return int32(new.IDOfTask(old.TaskWithID(kernel.ThreadID(pid)))) +} + +func posixLock(t *kernel.Task, args arch.SyscallArguments, file *vfs.FileDescription, block bool) error { + // Copy in the lock request. + flockAddr := args[2].Pointer() + var flock linux.Flock + if _, err := flock.CopyIn(t, flockAddr); err != nil { + return err + } + + r, err := file.ComputeLockRange(t, uint64(flock.Start), uint64(flock.Len), flock.Whence) + if err != nil { + return err + } + + switch flock.Type { + case linux.F_RDLCK: + if !file.IsReadable() { + return linuxerr.EBADF + } + return file.LockPOSIX(t, t.FDTable(), int32(t.TGIDInRoot()), lock.ReadLock, r, block) + + case linux.F_WRLCK: + if !file.IsWritable() { + return linuxerr.EBADF + } + return file.LockPOSIX(t, t.FDTable(), int32(t.TGIDInRoot()), lock.WriteLock, r, block) + + case linux.F_UNLCK: + return file.UnlockPOSIX(t, t.FDTable(), r) + + default: + return linuxerr.EINVAL + } +} + +// Fadvise64 implements fadvise64(2). // This implementation currently ignores the provided advice. func Fadvise64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() @@ -1209,14 +867,18 @@ func Fadvise64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys return 0, nil, linuxerr.EINVAL } - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) + if file.StatusFlags()&linux.O_PATH != 0 { + return 0, nil, linuxerr.EBADF + } + // If the FD refers to a pipe or FIFO, return error. - if fs.IsPipe(file.Dirent.Inode.StableAttr) { + if _, isPipe := file.Impl().(*pipe.VFSPipeFD); isPipe { return 0, nil, linuxerr.ESPIPE } @@ -1235,398 +897,271 @@ func Fadvise64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys return 0, nil, nil } -// LINT.ThenChange(vfs2/fd.go) - -// LINT.IfChange - -func mkdirAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, mode linux.FileMode) error { - path, _, err := copyInPath(t, addr, false /* allowEmpty */) - if err != nil { - return err - } - - return fileOpAt(t, dirFD, path, func(root *fs.Dirent, d *fs.Dirent, name string, _ uint) error { - if !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Does this directory exist already? - remainingTraversals := uint(linux.MaxSymlinkTraversals) - f, err := t.MountNamespace().FindInode(t, root, d, name, &remainingTraversals) - switch { - case err == nil: - // The directory existed. - defer f.DecRef(t) - return linuxerr.EEXIST - case linuxerr.Equals(linuxerr.EACCES, err): - // Permission denied while walking to the directory. - return err - default: - // Do we have write permissions on the parent? - if err := d.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil { - return err - } - - // Create the directory. - perms := fs.FilePermsFromMode(mode &^ linux.FileMode(t.FSContext().Umask())) - return d.CreateDirectory(t, root, name, perms) - } - }) -} - -// Mkdir implements linux syscall mkdir(2). +// Mkdir implements Linux syscall mkdir(2). func Mkdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { addr := args[0].Pointer() - mode := linux.FileMode(args[1].ModeT()) - - return 0, nil, mkdirAt(t, linux.AT_FDCWD, addr, mode) + mode := args[1].ModeT() + return 0, nil, mkdirat(t, linux.AT_FDCWD, addr, mode) } -// Mkdirat implements linux syscall mkdirat(2). +// Mkdirat implements Linux syscall mkdirat(2). func Mkdirat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() + dirfd := args[0].Int() addr := args[1].Pointer() - mode := linux.FileMode(args[2].ModeT()) - - return 0, nil, mkdirAt(t, dirFD, addr, mode) + mode := args[2].ModeT() + return 0, nil, mkdirat(t, dirfd, addr, mode) } -func rmdirAt(t *kernel.Task, dirFD int32, addr hostarch.Addr) error { - path, _, err := copyInPath(t, addr, false /* allowEmpty */) +func mkdirat(t *kernel.Task, dirfd int32, addr hostarch.Addr, mode uint) error { + path, err := copyInPath(t, addr) if err != nil { return err } - - // Special case: removing the root always returns EBUSY. - if path == "/" { - return linuxerr.EBUSY + tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return err } - - return fileOpAt(t, dirFD, path, func(root *fs.Dirent, d *fs.Dirent, name string, _ uint) error { - if !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Linux returns different ernos when the path ends in single - // dot vs. double dots. - switch name { - case ".": - return linuxerr.EINVAL - case "..": - return linuxerr.ENOTEMPTY - } - - if err := d.MayDelete(t, root, name); err != nil { - return err - } - - return d.RemoveDirectory(t, root, name) + defer tpop.Release(t) + return t.Kernel().VFS().MkdirAt(t, t.Credentials(), &tpop.pop, &vfs.MkdirOptions{ + Mode: linux.FileMode(mode & (0777 | linux.S_ISVTX) &^ t.FSContext().Umask()), }) } -// Rmdir implements linux syscall rmdir(2). +// Rmdir implements Linux syscall rmdir(2). func Rmdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - - return 0, nil, rmdirAt(t, linux.AT_FDCWD, addr) + pathAddr := args[0].Pointer() + return 0, nil, rmdirat(t, linux.AT_FDCWD, pathAddr) } -func symlinkAt(t *kernel.Task, dirFD int32, newAddr hostarch.Addr, oldAddr hostarch.Addr) error { - newPath, dirPath, err := copyInPath(t, newAddr, false /* allowEmpty */) +func rmdirat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr) error { + path, err := copyInPath(t, pathAddr) if err != nil { return err } - if dirPath { - return linuxerr.ENOENT - } - - // The oldPath is copied in verbatim. This is because the symlink - // will include all details, including trailing slashes. - oldPath, err := t.CopyInString(oldAddr, linux.PATH_MAX) + tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) if err != nil { return err } - if oldPath == "" { - return linuxerr.ENOENT - } - - return fileOpAt(t, dirFD, newPath, func(root *fs.Dirent, d *fs.Dirent, name string, _ uint) error { - if !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Make sure we have write permissions on the parent directory. - if err := d.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil { - return err - } - return d.CreateLink(t, root, oldPath, name) - }) + defer tpop.Release(t) + return t.Kernel().VFS().RmdirAt(t, t.Credentials(), &tpop.pop) } -// Symlink implements linux syscall symlink(2). +// Symlink implements Linux syscall symlink(2). func Symlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldAddr := args[0].Pointer() - newAddr := args[1].Pointer() - - return 0, nil, symlinkAt(t, linux.AT_FDCWD, newAddr, oldAddr) + targetAddr := args[0].Pointer() + linkpathAddr := args[1].Pointer() + return 0, nil, symlinkat(t, targetAddr, linux.AT_FDCWD, linkpathAddr) } -// Symlinkat implements linux syscall symlinkat(2). +// Symlinkat implements Linux syscall symlinkat(2). func Symlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldAddr := args[0].Pointer() - dirFD := args[1].Int() - newAddr := args[2].Pointer() - - return 0, nil, symlinkAt(t, dirFD, newAddr, oldAddr) + targetAddr := args[0].Pointer() + newdirfd := args[1].Int() + linkpathAddr := args[2].Pointer() + return 0, nil, symlinkat(t, targetAddr, newdirfd, linkpathAddr) } -// mayLinkAt determines whether t can create a hard link to target. -// -// This corresponds to Linux's fs/namei.c:may_linkat. -func mayLinkAt(t *kernel.Task, target *fs.Inode) error { - // Linux will impose the following restrictions on hard links only if - // sysctl_protected_hardlinks is enabled. The kernel disables this - // setting by default for backward compatibility (see commit - // 561ec64ae67e), but also recommends that distributions enable it (and - // Debian does: - // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=889098). - // - // gVisor currently behaves as though sysctl_protected_hardlinks is - // always enabled, and thus imposes the following restrictions on hard - // links. - - if target.CheckOwnership(t) { - // fs/namei.c:may_linkat: "Source inode owner (or CAP_FOWNER) - // can hardlink all they like." - return nil - } - - // If we are not the owner, then the file must be regular and have - // Read+Write permissions. - if !fs.IsRegular(target.StableAttr) { - return linuxerr.EPERM - } - if target.CheckPermission(t, fs.PermMask{Read: true, Write: true}) != nil { - return linuxerr.EPERM - } - - return nil -} - -// linkAt creates a hard link to the target specified by oldDirFD and oldAddr, -// specified by newDirFD and newAddr. If resolve is true, then the symlinks -// will be followed when evaluating the target. -func linkAt(t *kernel.Task, oldDirFD int32, oldAddr hostarch.Addr, newDirFD int32, newAddr hostarch.Addr, resolve, allowEmpty bool) error { - oldPath, _, err := copyInPath(t, oldAddr, allowEmpty) +func symlinkat(t *kernel.Task, targetAddr hostarch.Addr, newdirfd int32, linkpathAddr hostarch.Addr) error { + target, err := t.CopyInString(targetAddr, linux.PATH_MAX) if err != nil { return err } - newPath, dirPath, err := copyInPath(t, newAddr, false /* allowEmpty */) + if len(target) == 0 { + return linuxerr.ENOENT + } + linkpath, err := copyInPath(t, linkpathAddr) if err != nil { return err } - if dirPath { + tpop, err := getTaskPathOperation(t, newdirfd, linkpath, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return err + } + defer tpop.Release(t) + return t.Kernel().VFS().SymlinkAt(t, t.Credentials(), &tpop.pop, target) +} + +// Link implements Linux syscall link(2). +func Link(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + oldpathAddr := args[0].Pointer() + newpathAddr := args[1].Pointer() + return 0, nil, linkat(t, linux.AT_FDCWD, oldpathAddr, linux.AT_FDCWD, newpathAddr, 0 /* flags */) +} + +// Linkat implements Linux syscall linkat(2). +func Linkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + olddirfd := args[0].Int() + oldpathAddr := args[1].Pointer() + newdirfd := args[2].Int() + newpathAddr := args[3].Pointer() + flags := args[4].Int() + return 0, nil, linkat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, flags) +} + +func linkat(t *kernel.Task, olddirfd int32, oldpathAddr hostarch.Addr, newdirfd int32, newpathAddr hostarch.Addr, flags int32) error { + if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_FOLLOW) != 0 { + return linuxerr.EINVAL + } + if flags&linux.AT_EMPTY_PATH != 0 && !t.HasCapability(linux.CAP_DAC_READ_SEARCH) { return linuxerr.ENOENT } - if allowEmpty && oldPath == "" { - target := t.GetFile(oldDirFD) - if target == nil { - return linuxerr.EBADF - } - defer target.DecRef(t) - if err := mayLinkAt(t, target.Dirent.Inode); err != nil { - return err - } - - // Resolve the target directory. - return fileOpAt(t, newDirFD, newPath, func(root *fs.Dirent, newParent *fs.Dirent, newName string, _ uint) error { - if !fs.IsDir(newParent.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Make sure we have write permissions on the parent directory. - if err := newParent.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil { - return err - } - return newParent.CreateHardLink(t, root, target.Dirent, newName) - }) + oldpath, err := copyInPath(t, oldpathAddr) + if err != nil { + return err } + oldtpop, err := getTaskPathOperation(t, olddirfd, oldpath, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_FOLLOW != 0)) + if err != nil { + return err + } + defer oldtpop.Release(t) - // Resolve oldDirFD and oldAddr to a dirent. The "resolve" argument - // only applies to this name. - return fileOpOn(t, oldDirFD, oldPath, resolve, func(root *fs.Dirent, target *fs.Dirent, _ uint) error { - if err := mayLinkAt(t, target.Inode); err != nil { - return err - } + newpath, err := copyInPath(t, newpathAddr) + if err != nil { + return err + } + newtpop, err := getTaskPathOperation(t, newdirfd, newpath, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return err + } + defer newtpop.Release(t) - // Next resolve newDirFD and newAddr to the parent dirent and name. - return fileOpAt(t, newDirFD, newPath, func(root *fs.Dirent, newParent *fs.Dirent, newName string, _ uint) error { - if !fs.IsDir(newParent.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Make sure we have write permissions on the parent directory. - if err := newParent.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil { - return err - } - return newParent.CreateHardLink(t, root, target, newName) - }) - }) + return t.Kernel().VFS().LinkAt(t, t.Credentials(), &oldtpop.pop, &newtpop.pop) } -// Link implements linux syscall link(2). -func Link(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldAddr := args[0].Pointer() - newAddr := args[1].Pointer() - - // man link(2): - // POSIX.1-2001 says that link() should dereference oldpath if it is a - // symbolic link. However, since kernel 2.0, Linux does not do so: if - // oldpath is a symbolic link, then newpath is created as a (hard) link - // to the same symbolic link file (i.e., newpath becomes a symbolic - // link to the same file that oldpath refers to). - resolve := false - return 0, nil, linkAt(t, linux.AT_FDCWD, oldAddr, linux.AT_FDCWD, newAddr, resolve, false /* allowEmpty */) +// Readlinkat implements Linux syscall readlinkat(2). +func Readlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + dirfd := args[0].Int() + pathAddr := args[1].Pointer() + bufAddr := args[2].Pointer() + size := args[3].SizeT() + return readlinkat(t, dirfd, pathAddr, bufAddr, size) } -// Linkat implements linux syscall linkat(2). -func Linkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldDirFD := args[0].Int() - oldAddr := args[1].Pointer() - newDirFD := args[2].Int() - newAddr := args[3].Pointer() +// Readlink implements Linux syscall readlink(2). +func Readlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + pathAddr := args[0].Pointer() + bufAddr := args[1].Pointer() + size := args[2].SizeT() + return readlinkat(t, linux.AT_FDCWD, pathAddr, bufAddr, size) +} - // man linkat(2): - // By default, linkat(), does not dereference oldpath if it is a - // symbolic link (like link(2)). Since Linux 2.6.18, the flag - // AT_SYMLINK_FOLLOW can be specified in flags to cause oldpath to be - // dereferenced if it is a symbolic link. - flags := args[4].Int() - - // Sanity check flags. - if flags&^(linux.AT_SYMLINK_FOLLOW|linux.AT_EMPTY_PATH) != 0 { +func readlinkat(t *kernel.Task, dirfd int32, pathAddr, bufAddr hostarch.Addr, size uint) (uintptr, *kernel.SyscallControl, error) { + if int(size) <= 0 { return 0, nil, linuxerr.EINVAL } - resolve := flags&linux.AT_SYMLINK_FOLLOW == linux.AT_SYMLINK_FOLLOW - allowEmpty := flags&linux.AT_EMPTY_PATH == linux.AT_EMPTY_PATH - - if allowEmpty && !t.HasCapabilityIn(linux.CAP_DAC_READ_SEARCH, t.UserNamespace().Root()) { - return 0, nil, linuxerr.ENOENT - } - - return 0, nil, linkAt(t, oldDirFD, oldAddr, newDirFD, newAddr, resolve, allowEmpty) -} - -// LINT.ThenChange(vfs2/filesystem.go) - -// LINT.IfChange - -func readlinkAt(t *kernel.Task, dirFD int32, addr hostarch.Addr, bufAddr hostarch.Addr, size uint) (copied uintptr, err error) { - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) + path, err := copyInPath(t, pathAddr) if err != nil { - return 0, err + return 0, nil, err } - if dirPath { - return 0, linuxerr.ENOENT - } - - err = fileOpOn(t, dirFD, path, false /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - // Check for Read permission. - if err := d.Inode.CheckPermission(t, fs.PermMask{Read: true}); err != nil { - return err - } - - s, err := d.Inode.Readlink(t) - if linuxerr.Equals(linuxerr.ENOLINK, err) { - return linuxerr.EINVAL - } - if err != nil { - return err - } - - buffer := []byte(s) - if uint(len(buffer)) > size { - buffer = buffer[:size] - } - - n, err := t.CopyOutBytes(bufAddr, buffer) - - // Update frame return value. - copied = uintptr(n) - - return err - }) - return copied, err // Return frame value. -} - -// Readlink implements linux syscall readlink(2). -func Readlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - bufAddr := args[1].Pointer() - size := args[2].SizeT() - - n, err := readlinkAt(t, linux.AT_FDCWD, addr, bufAddr, size) - return n, nil, err -} - -// Readlinkat implements linux syscall readlinkat(2). -func Readlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() - addr := args[1].Pointer() - bufAddr := args[2].Pointer() - size := args[3].SizeT() - - n, err := readlinkAt(t, dirFD, addr, bufAddr, size) - return n, nil, err -} - -// LINT.ThenChange(vfs2/stat.go) - -// LINT.IfChange - -func unlinkAt(t *kernel.Task, dirFD int32, addr hostarch.Addr) error { - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) + // "Since Linux 2.6.39, pathname can be an empty string, in which case the + // call operates on the symbolic link referred to by dirfd ..." - + // readlinkat(2) + tpop, err := getTaskPathOperation(t, dirfd, path, allowEmptyPath, nofollowFinalSymlink) if err != nil { - return err + return 0, nil, err + } + defer tpop.Release(t) + + target, err := t.Kernel().VFS().ReadlinkAt(t, t.Credentials(), &tpop.pop) + if err != nil { + return 0, nil, err } - return fileOpAt(t, dirFD, path, func(root *fs.Dirent, d *fs.Dirent, name string, _ uint) error { - if !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - if err := d.MayDelete(t, root, name); err != nil { - return err - } - - return d.Remove(t, root, name, dirPath) - }) + if len(target) > int(size) { + target = target[:size] + } + n, err := t.CopyOutBytes(bufAddr, gohacks.ImmutableBytesFromString(target)) + if n == 0 { + return 0, nil, err + } + return uintptr(n), nil, nil } -// Unlink implements linux syscall unlink(2). +// Unlink implements Linux syscall unlink(2). func Unlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - return 0, nil, unlinkAt(t, linux.AT_FDCWD, addr) + pathAddr := args[0].Pointer() + return 0, nil, unlinkat(t, linux.AT_FDCWD, pathAddr) } -// Unlinkat implements linux syscall unlinkat(2). -func Unlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() - addr := args[1].Pointer() - flags := args[2].Uint() - if flags&linux.AT_REMOVEDIR != 0 { - return 0, nil, rmdirAt(t, dirFD, addr) +func unlinkat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr) error { + path, err := copyInPath(t, pathAddr) + if err != nil { + return err } - return 0, nil, unlinkAt(t, dirFD, addr) + tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return err + } + defer tpop.Release(t) + return t.Kernel().VFS().UnlinkAt(t, t.Credentials(), &tpop.pop) } -// LINT.ThenChange(vfs2/filesystem.go) +// Unlinkat implements Linux syscall unlinkat(2). +func Unlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + dirfd := args[0].Int() + pathAddr := args[1].Pointer() + flags := args[2].Int() -// LINT.IfChange + if flags&^linux.AT_REMOVEDIR != 0 { + return 0, nil, linuxerr.EINVAL + } -// Truncate implements linux syscall truncate(2). + if flags&linux.AT_REMOVEDIR != 0 { + return 0, nil, rmdirat(t, dirfd, pathAddr) + } + return 0, nil, unlinkat(t, dirfd, pathAddr) +} + +func setstatat(t *kernel.Task, dirfd int32, path fspath.Path, shouldAllowEmptyPath shouldAllowEmptyPath, shouldFollowFinalSymlink shouldFollowFinalSymlink, opts *vfs.SetStatOptions) error { + root := t.FSContext().RootDirectoryVFS2() + defer root.DecRef(t) + start := root + if !path.Absolute { + if !path.HasComponents() && !bool(shouldAllowEmptyPath) { + return linuxerr.ENOENT + } + if dirfd == linux.AT_FDCWD { + start = t.FSContext().WorkingDirectoryVFS2() + defer start.DecRef(t) + } else { + dirfile := t.GetFileVFS2(dirfd) + if dirfile == nil { + return linuxerr.EBADF + } + if !path.HasComponents() { + // Use FileDescription.SetStat() instead of + // VirtualFilesystem.SetStatAt(), since the former may be able + // to use opened file state to expedite the SetStat. + err := dirfile.SetStat(t, *opts) + dirfile.DecRef(t) + return err + } + start = dirfile.VirtualDentry() + start.IncRef() + defer start.DecRef(t) + dirfile.DecRef(t) + } + } + return t.Kernel().VFS().SetStatAt(t, t.Credentials(), &vfs.PathOperation{ + Root: root, + Start: start, + Path: path, + FollowFinalSymlink: bool(shouldFollowFinalSymlink), + }, opts) +} + +func handleSetSizeError(t *kernel.Task, err error) error { + if err == linuxerr.ErrExceedsFileSizeLimit { + // Convert error to EFBIG and send a SIGXFSZ per setrlimit(2). + t.SendSignal(kernel.SignalInfoNoInfo(linux.SIGXFSZ, t, t)) + return linuxerr.EFBIG + } + return err +} + +// Truncate implements Linux syscall truncate(2). func Truncate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { addr := args[0].Pointer() length := args[1].Int64() @@ -1635,97 +1170,49 @@ func Truncate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return 0, nil, linuxerr.EINVAL } - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) + path, err := copyInPath(t, addr) if err != nil { return 0, nil, err } - if dirPath { - return 0, nil, linuxerr.EINVAL - } - if uint64(length) >= t.ThreadGroup().Limits().Get(limits.FileSize).Cur { - t.SendSignal(&linux.SignalInfo{ - Signo: int32(linux.SIGXFSZ), - Code: linux.SI_USER, - }) - return 0, nil, linuxerr.EFBIG - } - - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - if fs.IsDir(d.Inode.StableAttr) { - return linuxerr.EISDIR - } - // In contrast to open(O_TRUNC), truncate(2) is only valid for file - // types. - if !fs.IsFile(d.Inode.StableAttr) { - return linuxerr.EINVAL - } - - // Reject truncation if the access permissions do not allow truncation. - // This is different from the behavior of sys_ftruncate, see below. - if err := d.Inode.CheckPermission(t, fs.PermMask{Write: true}); err != nil { - return err - } - - if err := d.Inode.Truncate(t, d, length); err != nil { - return err - } - - // File length modified, generate notification. - d.InotifyEvent(linux.IN_MODIFY, 0) - - return nil + err = setstatat(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink, &vfs.SetStatOptions{ + Stat: linux.Statx{ + Mask: linux.STATX_SIZE, + Size: uint64(length), + }, + NeedWritePerm: true, }) + return 0, nil, handleSetSizeError(t, err) } -// Ftruncate implements linux syscall ftruncate(2). +// Ftruncate implements Linux syscall ftruncate(2). func Ftruncate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() length := args[1].Int64() - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Reject truncation if the file flags do not permit this operation. - // This is different from truncate(2) above. - if !file.Flags().Write { - return 0, nil, linuxerr.EINVAL - } - - // In contrast to open(O_TRUNC), truncate(2) is only valid for file - // types. Note that this is different from truncate(2) above, where a - // directory returns EISDIR. - if !fs.IsFile(file.Dirent.Inode.StableAttr) { - return 0, nil, linuxerr.EINVAL - } - if length < 0 { return 0, nil, linuxerr.EINVAL } - if uint64(length) >= t.ThreadGroup().Limits().Get(limits.FileSize).Cur { - t.SendSignal(&linux.SignalInfo{ - Signo: int32(linux.SIGXFSZ), - Code: linux.SI_USER, - }) - return 0, nil, linuxerr.EFBIG + file := t.GetFileVFS2(fd) + if file == nil { + return 0, nil, linuxerr.EBADF + } + defer file.DecRef(t) + + if !file.IsWritable() { + return 0, nil, linuxerr.EINVAL } - if err := file.Dirent.Inode.Truncate(t, file.Dirent, length); err != nil { - return 0, nil, err - } - - // File length modified, generate notification. - file.Dirent.InotifyEvent(linux.IN_MODIFY, 0) - - return 0, nil, nil + err := file.SetStat(t, vfs.SetStatOptions{ + Stat: linux.Statx{ + Mask: linux.STATX_SIZE, + Size: uint64(length), + }, + }) + return 0, nil, handleSetSizeError(t, err) } -// LINT.ThenChange(vfs2/setstat.go) - // Umask implements linux syscall umask(2). func Umask(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { mask := args[0].ModeT() @@ -1733,488 +1220,396 @@ func Umask(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return uintptr(mask), nil, nil } -// LINT.IfChange - -// Change ownership of a file. -// -// uid and gid may be -1, in which case they will not be changed. -func chown(t *kernel.Task, d *fs.Dirent, uid auth.UID, gid auth.GID) error { - owner := fs.FileOwner{ - UID: auth.NoID, - GID: auth.NoID, - } - - uattr, err := d.Inode.UnstableAttr(t) - if err != nil { - return err - } - - c := t.Credentials() - hasCap := d.Inode.CheckCapability(t, linux.CAP_CHOWN) - isOwner := uattr.Owner.UID == c.EffectiveKUID - var clearPrivilege bool - if uid.Ok() { - kuid := c.UserNamespace.MapToKUID(uid) - // Valid UID must be supplied if UID is to be changed. - if !kuid.Ok() { - return linuxerr.EINVAL - } - - // "Only a privileged process (CAP_CHOWN) may change the owner - // of a file." -chown(2) - // - // Linux also allows chown if you own the file and are - // explicitly not changing its UID. - isNoop := uattr.Owner.UID == kuid - if !(hasCap || (isOwner && isNoop)) { - return linuxerr.EPERM - } - - // The setuid and setgid bits are cleared during a chown. - if uattr.Owner.UID != kuid { - clearPrivilege = true - } - - owner.UID = kuid - } - if gid.Ok() { - kgid := c.UserNamespace.MapToKGID(gid) - // Valid GID must be supplied if GID is to be changed. - if !kgid.Ok() { - return linuxerr.EINVAL - } - - // "The owner of a file may change the group of the file to any - // group of which that owner is a member. A privileged process - // (CAP_CHOWN) may change the group arbitrarily." -chown(2) - isNoop := uattr.Owner.GID == kgid - isMemberGroup := c.InGroup(kgid) - if !(hasCap || (isOwner && (isNoop || isMemberGroup))) { - return linuxerr.EPERM - } - - // The setuid and setgid bits are cleared during a chown. - if uattr.Owner.GID != kgid { - clearPrivilege = true - } - - owner.GID = kgid - } - - // FIXME(b/62949101): This is racy; the inode's owner may have changed in - // the meantime. (Linux holds i_mutex while calling - // fs/attr.c:notify_change() => inode_operations::setattr => - // inode_change_ok().) - if err := d.Inode.SetOwner(t, d, owner); err != nil { - return err - } - // Clear privilege bits if needed and they are set. - if clearPrivilege && uattr.Perms.HasSetUIDOrGID() && !fs.IsDir(d.Inode.StableAttr) { - uattr.Perms.DropSetUIDAndMaybeGID() - if !d.Inode.SetPermissions(t, d, uattr.Perms) { - return linuxerr.EPERM - } - } - - return nil -} - -func chownAt(t *kernel.Task, fd int32, addr hostarch.Addr, resolve, allowEmpty bool, uid auth.UID, gid auth.GID) error { - path, _, err := copyInPath(t, addr, allowEmpty) - if err != nil { - return err - } - - if path == "" { - // Annoying. What's wrong with fchown? - file := t.GetFile(fd) - if file == nil { - return linuxerr.EBADF - } - defer file.DecRef(t) - - return chown(t, file.Dirent, uid, gid) - } - - return fileOpOn(t, fd, path, resolve, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - return chown(t, d, uid, gid) - }) -} - -// Chown implements linux syscall chown(2). +// Chown implements Linux syscall chown(2). func Chown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - uid := auth.UID(args[1].Uint()) - gid := auth.GID(args[2].Uint()) - - return 0, nil, chownAt(t, linux.AT_FDCWD, addr, true /* resolve */, false /* allowEmpty */, uid, gid) + pathAddr := args[0].Pointer() + owner := args[1].Int() + group := args[2].Int() + return 0, nil, fchownat(t, linux.AT_FDCWD, pathAddr, owner, group, 0 /* flags */) } -// Lchown implements linux syscall lchown(2). +// Lchown implements Linux syscall lchown(2). func Lchown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - uid := auth.UID(args[1].Uint()) - gid := auth.GID(args[2].Uint()) - - return 0, nil, chownAt(t, linux.AT_FDCWD, addr, false /* resolve */, false /* allowEmpty */, uid, gid) -} - -// Fchown implements linux syscall fchown(2). -func Fchown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - uid := auth.UID(args[1].Uint()) - gid := auth.GID(args[2].Uint()) - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - return 0, nil, chown(t, file.Dirent, uid, gid) + pathAddr := args[0].Pointer() + owner := args[1].Int() + group := args[2].Int() + return 0, nil, fchownat(t, linux.AT_FDCWD, pathAddr, owner, group, linux.AT_SYMLINK_NOFOLLOW) } // Fchownat implements Linux syscall fchownat(2). func Fchownat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() - addr := args[1].Pointer() - uid := auth.UID(args[2].Uint()) - gid := auth.GID(args[3].Uint()) + dirfd := args[0].Int() + pathAddr := args[1].Pointer() + owner := args[2].Int() + group := args[3].Int() flags := args[4].Int() + return 0, nil, fchownat(t, dirfd, pathAddr, owner, group, flags) +} +func fchownat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, owner, group, flags int32) error { if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 { - return 0, nil, linuxerr.EINVAL + return linuxerr.EINVAL } - return 0, nil, chownAt(t, dirFD, addr, flags&linux.AT_SYMLINK_NOFOLLOW == 0, flags&linux.AT_EMPTY_PATH != 0, uid, gid) -} - -func chmod(t *kernel.Task, d *fs.Dirent, mode linux.FileMode) error { - // Must own file to change mode. - if !d.Inode.CheckOwnership(t) { - return linuxerr.EPERM - } - - p := fs.FilePermsFromMode(mode) - if !d.Inode.SetPermissions(t, d, p) { - return linuxerr.EPERM - } - - // File attribute changed, generate notification. - d.InotifyEvent(linux.IN_ATTRIB, 0) - - return nil -} - -func chmodAt(t *kernel.Task, fd int32, addr hostarch.Addr, mode linux.FileMode) error { - path, _, err := copyInPath(t, addr, false /* allowEmpty */) + path, err := copyInPath(t, pathAddr) if err != nil { return err } - return fileOpOn(t, fd, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - return chmod(t, d, mode) - }) + var opts vfs.SetStatOptions + if err := populateSetStatOptionsForChown(t, owner, group, &opts); err != nil { + return err + } + + return setstatat(t, dirfd, path, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_NOFOLLOW == 0), &opts) } -// Chmod implements linux syscall chmod(2). -func Chmod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - mode := linux.FileMode(args[1].ModeT()) - - return 0, nil, chmodAt(t, linux.AT_FDCWD, addr, mode) +func populateSetStatOptionsForChown(t *kernel.Task, owner, group int32, opts *vfs.SetStatOptions) error { + userns := t.UserNamespace() + if owner != -1 { + kuid := userns.MapToKUID(auth.UID(owner)) + if !kuid.Ok() { + return linuxerr.EINVAL + } + opts.Stat.Mask |= linux.STATX_UID + opts.Stat.UID = uint32(kuid) + } + if group != -1 { + kgid := userns.MapToKGID(auth.GID(group)) + if !kgid.Ok() { + return linuxerr.EINVAL + } + opts.Stat.Mask |= linux.STATX_GID + opts.Stat.GID = uint32(kgid) + } + return nil } -// Fchmod implements linux syscall fchmod(2). -func Fchmod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { +// Fchown implements Linux syscall fchown(2). +func Fchown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - mode := linux.FileMode(args[1].ModeT()) + owner := args[1].Int() + group := args[2].Int() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - return 0, nil, chmod(t, file.Dirent, mode) + var opts vfs.SetStatOptions + if err := populateSetStatOptionsForChown(t, owner, group, &opts); err != nil { + return 0, nil, err + } + return 0, nil, file.SetStat(t, opts) } -// Fchmodat implements linux syscall fchmodat(2). +const chmodMask = 0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX + +// Chmod implements Linux syscall chmod(2). +func Chmod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + pathAddr := args[0].Pointer() + mode := args[1].ModeT() + return 0, nil, fchmodat(t, linux.AT_FDCWD, pathAddr, mode) +} + +// Fchmodat implements Linux syscall fchmodat(2). func Fchmodat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - mode := linux.FileMode(args[2].ModeT()) - - return 0, nil, chmodAt(t, fd, addr, mode) + dirfd := args[0].Int() + pathAddr := args[1].Pointer() + mode := args[2].ModeT() + return 0, nil, fchmodat(t, dirfd, pathAddr, mode) } -// defaultSetToSystemTimeSpec returns a TimeSpec that will set ATime and MTime -// to the system time. -func defaultSetToSystemTimeSpec() fs.TimeSpec { - return fs.TimeSpec{ - ATimeSetSystemTime: true, - MTimeSetSystemTime: true, - } -} - -func utimes(t *kernel.Task, dirFD int32, addr hostarch.Addr, ts fs.TimeSpec, resolve bool) error { - setTimestamp := func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - // Does the task own the file? - if !d.Inode.CheckOwnership(t) { - // Trying to set a specific time? Must be owner. - if (ts.ATimeOmit || !ts.ATimeSetSystemTime) && (ts.MTimeOmit || !ts.MTimeSetSystemTime) { - return linuxerr.EPERM - } - - // Trying to set to current system time? Must have write access. - if err := d.Inode.CheckPermission(t, fs.PermMask{Write: true}); err != nil { - return err - } - } - - if err := d.Inode.SetTimestamps(t, d, ts); err != nil { - return err - } - - // File attribute changed, generate notification. - d.InotifyEvent(linux.IN_ATTRIB, 0) - return nil - } - - // From utimes.c: - // "If filename is NULL and dfd refers to an open file, then operate on - // the file. Otherwise look up filename, possibly using dfd as a - // starting point." - if addr == 0 && dirFD != linux.AT_FDCWD { - if !resolve { - // Linux returns EINVAL in this case. See utimes.c. - return linuxerr.EINVAL - } - f := t.GetFile(dirFD) - if f == nil { - return linuxerr.EBADF - } - defer f.DecRef(t) - - root := t.FSContext().RootDirectory() - defer root.DecRef(t) - - return setTimestamp(root, f.Dirent, linux.MaxSymlinkTraversals) - } - - path, _, err := copyInPath(t, addr, false /* allowEmpty */) +func fchmodat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint) error { + path, err := copyInPath(t, pathAddr) if err != nil { return err } - return fileOpOn(t, dirFD, path, resolve, setTimestamp) + return setstatat(t, dirfd, path, disallowEmptyPath, followFinalSymlink, &vfs.SetStatOptions{ + Stat: linux.Statx{ + Mask: linux.STATX_MODE, + Mode: uint16(mode & chmodMask), + }, + }) } -// Utime implements linux syscall utime(2). +// Fchmod implements Linux syscall fchmod(2). +func Fchmod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + fd := args[0].Int() + mode := args[1].ModeT() + + file := t.GetFileVFS2(fd) + if file == nil { + return 0, nil, linuxerr.EBADF + } + defer file.DecRef(t) + + return 0, nil, file.SetStat(t, vfs.SetStatOptions{ + Stat: linux.Statx{ + Mask: linux.STATX_MODE, + Mode: uint16(mode & chmodMask), + }, + }) +} + +// Utime implements Linux syscall utime(2). func Utime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - filenameAddr := args[0].Pointer() + pathAddr := args[0].Pointer() timesAddr := args[1].Pointer() - // No timesAddr argument will be interpreted as current system time. - ts := defaultSetToSystemTimeSpec() - if timesAddr != 0 { + path, err := copyInPath(t, pathAddr) + if err != nil { + return 0, nil, err + } + + opts := vfs.SetStatOptions{ + Stat: linux.Statx{ + Mask: linux.STATX_ATIME | linux.STATX_MTIME, + }, + } + if timesAddr == 0 { + opts.Stat.Atime.Nsec = linux.UTIME_NOW + opts.Stat.Mtime.Nsec = linux.UTIME_NOW + } else { var times linux.Utime if _, err := times.CopyIn(t, timesAddr); err != nil { return 0, nil, err } - ts = fs.TimeSpec{ - ATime: ktime.FromSeconds(times.Actime), - MTime: ktime.FromSeconds(times.Modtime), - } + opts.Stat.Atime.Sec = times.Actime + opts.Stat.Mtime.Sec = times.Modtime } - return 0, nil, utimes(t, linux.AT_FDCWD, filenameAddr, ts, true) + + return 0, nil, setstatat(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink, &opts) } -// Utimes implements linux syscall utimes(2). +// Utimes implements Linux syscall utimes(2). func Utimes(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - filenameAddr := args[0].Pointer() + pathAddr := args[0].Pointer() timesAddr := args[1].Pointer() - // No timesAddr argument will be interpreted as current system time. - ts := defaultSetToSystemTimeSpec() - if timesAddr != 0 { - var times [2]linux.Timeval - if _, err := linux.CopyTimevalSliceIn(t, timesAddr, times[:]); err != nil { + path, err := copyInPath(t, pathAddr) + if err != nil { + return 0, nil, err + } + + var opts vfs.SetStatOptions + if err := populateSetStatOptionsForUtimes(t, timesAddr, &opts); err != nil { + return 0, nil, err + } + + return 0, nil, setstatat(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink, &opts) +} + +// Futimesat implements Linux syscall futimesat(2). +func Futimesat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + dirfd := args[0].Int() + pathAddr := args[1].Pointer() + timesAddr := args[2].Pointer() + + // "If filename is NULL and dfd refers to an open file, then operate on the + // file. Otherwise look up filename, possibly using dfd as a starting + // point." - fs/utimes.c + var path fspath.Path + shouldAllowEmptyPath := allowEmptyPath + if dirfd == linux.AT_FDCWD || pathAddr != 0 { + var err error + path, err = copyInPath(t, pathAddr) + if err != nil { return 0, nil, err } - ts = fs.TimeSpec{ - ATime: ktime.FromTimeval(times[0]), - MTime: ktime.FromTimeval(times[1]), - } + shouldAllowEmptyPath = disallowEmptyPath } - return 0, nil, utimes(t, linux.AT_FDCWD, filenameAddr, ts, true) + + var opts vfs.SetStatOptions + if err := populateSetStatOptionsForUtimes(t, timesAddr, &opts); err != nil { + return 0, nil, err + } + + return 0, nil, setstatat(t, dirfd, path, shouldAllowEmptyPath, followFinalSymlink, &opts) } -// timespecIsValid checks that the timespec is valid for use in utimensat. -func timespecIsValid(ts linux.Timespec) bool { - // Nsec must be UTIME_OMIT, UTIME_NOW, or less than 10^9. - return ts.Nsec == linux.UTIME_OMIT || ts.Nsec == linux.UTIME_NOW || ts.Nsec < 1e9 +func populateSetStatOptionsForUtimes(t *kernel.Task, timesAddr hostarch.Addr, opts *vfs.SetStatOptions) error { + if timesAddr == 0 { + opts.Stat.Mask = linux.STATX_ATIME | linux.STATX_MTIME + opts.Stat.Atime.Nsec = linux.UTIME_NOW + opts.Stat.Mtime.Nsec = linux.UTIME_NOW + return nil + } + var times [2]linux.Timeval + if _, err := linux.CopyTimevalSliceIn(t, timesAddr, times[:]); err != nil { + return err + } + if times[0].Usec < 0 || times[0].Usec > 999999 || times[1].Usec < 0 || times[1].Usec > 999999 { + return linuxerr.EINVAL + } + opts.Stat.Mask = linux.STATX_ATIME | linux.STATX_MTIME + opts.Stat.Atime = linux.StatxTimestamp{ + Sec: times[0].Sec, + Nsec: uint32(times[0].Usec * 1000), + } + opts.Stat.Mtime = linux.StatxTimestamp{ + Sec: times[1].Sec, + Nsec: uint32(times[1].Usec * 1000), + } + return nil } -// Utimensat implements linux syscall utimensat(2). +// Utimensat implements Linux syscall utimensat(2). func Utimensat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() - pathnameAddr := args[1].Pointer() + dirfd := args[0].Int() + pathAddr := args[1].Pointer() timesAddr := args[2].Pointer() flags := args[3].Int() - // No timesAddr argument will be interpreted as current system time. - ts := defaultSetToSystemTimeSpec() - if timesAddr != 0 { - var times [2]linux.Timespec - if _, err := linux.CopyTimespecSliceIn(t, timesAddr, times[:]); err != nil { + // Linux requires that the UTIME_OMIT check occur before checking path or + // flags. + var opts vfs.SetStatOptions + if err := populateSetStatOptionsForUtimens(t, timesAddr, &opts); err != nil { + return 0, nil, err + } + if opts.Stat.Mask == 0 { + return 0, nil, nil + } + + if flags&^linux.AT_SYMLINK_NOFOLLOW != 0 { + return 0, nil, linuxerr.EINVAL + } + + // "If filename is NULL and dfd refers to an open file, then operate on the + // file. Otherwise look up filename, possibly using dfd as a starting + // point." - fs/utimes.c + var path fspath.Path + shouldAllowEmptyPath := allowEmptyPath + if dirfd == linux.AT_FDCWD || pathAddr != 0 { + var err error + path, err = copyInPath(t, pathAddr) + if err != nil { return 0, nil, err } - if !timespecIsValid(times[0]) || !timespecIsValid(times[1]) { - return 0, nil, linuxerr.EINVAL - } - - // If both are UTIME_OMIT, this is a noop. - if times[0].Nsec == linux.UTIME_OMIT && times[1].Nsec == linux.UTIME_OMIT { - return 0, nil, nil - } - - ts = fs.TimeSpec{ - ATime: ktime.FromTimespec(times[0]), - ATimeOmit: times[0].Nsec == linux.UTIME_OMIT, - ATimeSetSystemTime: times[0].Nsec == linux.UTIME_NOW, - MTime: ktime.FromTimespec(times[1]), - MTimeOmit: times[1].Nsec == linux.UTIME_OMIT, - MTimeSetSystemTime: times[0].Nsec == linux.UTIME_NOW, - } + shouldAllowEmptyPath = disallowEmptyPath } - return 0, nil, utimes(t, dirFD, pathnameAddr, ts, flags&linux.AT_SYMLINK_NOFOLLOW == 0) + + return 0, nil, setstatat(t, dirfd, path, shouldAllowEmptyPath, shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_NOFOLLOW == 0), &opts) } -// Futimesat implements linux syscall futimesat(2). -func Futimesat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() - pathnameAddr := args[1].Pointer() - timesAddr := args[2].Pointer() - - // No timesAddr argument will be interpreted as current system time. - ts := defaultSetToSystemTimeSpec() - if timesAddr != 0 { - var times [2]linux.Timeval - if _, err := linux.CopyTimevalSliceIn(t, timesAddr, times[:]); err != nil { - return 0, nil, err +func populateSetStatOptionsForUtimens(t *kernel.Task, timesAddr hostarch.Addr, opts *vfs.SetStatOptions) error { + if timesAddr == 0 { + opts.Stat.Mask = linux.STATX_ATIME | linux.STATX_MTIME + opts.Stat.Atime.Nsec = linux.UTIME_NOW + opts.Stat.Mtime.Nsec = linux.UTIME_NOW + return nil + } + var times [2]linux.Timespec + if _, err := linux.CopyTimespecSliceIn(t, timesAddr, times[:]); err != nil { + return err + } + if times[0].Nsec != linux.UTIME_OMIT { + if times[0].Nsec != linux.UTIME_NOW && (times[0].Nsec < 0 || times[0].Nsec > 999999999) { + return linuxerr.EINVAL } - if times[0].Usec >= 1e6 || times[0].Usec < 0 || - times[1].Usec >= 1e6 || times[1].Usec < 0 { - return 0, nil, linuxerr.EINVAL - } - - ts = fs.TimeSpec{ - ATime: ktime.FromTimeval(times[0]), - MTime: ktime.FromTimeval(times[1]), + opts.Stat.Mask |= linux.STATX_ATIME + opts.Stat.Atime = linux.StatxTimestamp{ + Sec: times[0].Sec, + Nsec: uint32(times[0].Nsec), } } - return 0, nil, utimes(t, dirFD, pathnameAddr, ts, true) + if times[1].Nsec != linux.UTIME_OMIT { + if times[1].Nsec != linux.UTIME_NOW && (times[1].Nsec < 0 || times[1].Nsec > 999999999) { + return linuxerr.EINVAL + } + opts.Stat.Mask |= linux.STATX_MTIME + opts.Stat.Mtime = linux.StatxTimestamp{ + Sec: times[1].Sec, + Nsec: uint32(times[1].Nsec), + } + } + return nil } -// LINT.ThenChange(vfs2/setstat.go) +// Rename implements Linux syscall rename(2). +func Rename(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + oldpathAddr := args[0].Pointer() + newpathAddr := args[1].Pointer() + return 0, nil, renameat(t, linux.AT_FDCWD, oldpathAddr, linux.AT_FDCWD, newpathAddr, 0 /* flags */) +} -// LINT.IfChange +// Renameat implements Linux syscall renameat(2). +func Renameat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + olddirfd := args[0].Int() + oldpathAddr := args[1].Pointer() + newdirfd := args[2].Int() + newpathAddr := args[3].Pointer() + return 0, nil, renameat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, 0 /* flags */) +} -func renameAt(t *kernel.Task, oldDirFD int32, oldAddr hostarch.Addr, newDirFD int32, newAddr hostarch.Addr) error { - newPath, _, err := copyInPath(t, newAddr, false /* allowEmpty */) +// Renameat2 implements Linux syscall renameat2(2). +func Renameat2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + olddirfd := args[0].Int() + oldpathAddr := args[1].Pointer() + newdirfd := args[2].Int() + newpathAddr := args[3].Pointer() + flags := args[4].Uint() + return 0, nil, renameat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, flags) +} + +func renameat(t *kernel.Task, olddirfd int32, oldpathAddr hostarch.Addr, newdirfd int32, newpathAddr hostarch.Addr, flags uint32) error { + oldpath, err := copyInPath(t, oldpathAddr) if err != nil { return err } - oldPath, _, err := copyInPath(t, oldAddr, false /* allowEmpty */) + // "If oldpath refers to a symbolic link, the link is renamed" - rename(2) + oldtpop, err := getTaskPathOperation(t, olddirfd, oldpath, disallowEmptyPath, nofollowFinalSymlink) if err != nil { return err } + defer oldtpop.Release(t) - return fileOpAt(t, oldDirFD, oldPath, func(root *fs.Dirent, oldParent *fs.Dirent, oldName string, _ uint) error { - if !fs.IsDir(oldParent.Inode.StableAttr) { - return linuxerr.ENOTDIR - } + newpath, err := copyInPath(t, newpathAddr) + if err != nil { + return err + } + newtpop, err := getTaskPathOperation(t, newdirfd, newpath, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return err + } + defer newtpop.Release(t) - // Rename rejects paths that end in ".", "..", or empty (i.e. - // the root) with EBUSY. - switch oldName { - case "", ".", "..": - return linuxerr.EBUSY - } - - return fileOpAt(t, newDirFD, newPath, func(root *fs.Dirent, newParent *fs.Dirent, newName string, _ uint) error { - if !fs.IsDir(newParent.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Rename rejects paths that end in ".", "..", or empty - // (i.e. the root) with EBUSY. - switch newName { - case "", ".", "..": - return linuxerr.EBUSY - } - - return fs.Rename(t, root, oldParent, oldName, newParent, newName) - }) + return t.Kernel().VFS().RenameAt(t, t.Credentials(), &oldtpop.pop, &newtpop.pop, &vfs.RenameOptions{ + Flags: flags, }) } -// Rename implements linux syscall rename(2). -func Rename(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldPathAddr := args[0].Pointer() - newPathAddr := args[1].Pointer() - return 0, nil, renameAt(t, linux.AT_FDCWD, oldPathAddr, linux.AT_FDCWD, newPathAddr) -} - -// Renameat implements linux syscall renameat(2). -func Renameat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldDirFD := args[0].Int() - oldPathAddr := args[1].Pointer() - newDirFD := args[2].Int() - newPathAddr := args[3].Pointer() - return 0, nil, renameAt(t, oldDirFD, oldPathAddr, newDirFD, newPathAddr) -} - -// LINT.ThenChange(vfs2/filesystem.go) - // Fallocate implements linux system call fallocate(2). func Fallocate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - mode := args[1].Int64() + mode := args[1].Uint64() offset := args[2].Int64() length := args[3].Int64() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) + if !file.IsWritable() { + return 0, nil, linuxerr.EBADF + } + if mode != 0 { + return 0, nil, linuxerr.ENOTSUP + } if offset < 0 || length <= 0 { return 0, nil, linuxerr.EINVAL } - if mode != 0 { - t.Kernel().EmitUnimplementedEvent(t) - return 0, nil, linuxerr.ENOTSUP - } - if !file.Flags().Write { - return 0, nil, linuxerr.EBADF - } - if fs.IsPipe(file.Dirent.Inode.StableAttr) { - return 0, nil, linuxerr.ESPIPE - } - if fs.IsDir(file.Dirent.Inode.StableAttr) { - return 0, nil, linuxerr.EISDIR - } - if !fs.IsRegular(file.Dirent.Inode.StableAttr) { - return 0, nil, linuxerr.ENODEV - } + size := offset + length if size < 0 { return 0, nil, linuxerr.EFBIG } - if uint64(size) >= t.ThreadGroup().Limits().Get(limits.FileSize).Cur { + limit := limits.FromContext(t).Get(limits.FileSize).Cur + if uint64(size) >= limit { t.SendSignal(&linux.SignalInfo{ Signo: int32(linux.SIGXFSZ), Code: linux.SI_USER, @@ -2222,14 +1617,7 @@ func Fallocate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys return 0, nil, linuxerr.EFBIG } - if err := file.Dirent.Inode.Allocate(t, file.Dirent, offset, length); err != nil { - return 0, nil, err - } - - // File length modified, generate notification. - file.Dirent.InotifyEvent(linux.IN_MODIFY, 0) - - return 0, nil, nil + return 0, nil, file.Allocate(t, mode, uint64(offset), uint64(length)) } // Flock implements linux syscall flock(2). @@ -2237,7 +1625,7 @@ func Flock(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall fd := args[0].Int() operation := args[1].Int() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { // flock(2): EBADF fd is not an open file descriptor. return 0, nil, linuxerr.EBADF @@ -2247,25 +1635,19 @@ func Flock(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall nonblocking := operation&linux.LOCK_NB != 0 operation &^= linux.LOCK_NB - // A BSD style lock spans the entire file. - rng := lock.LockRange{ - Start: 0, - End: lock.LockEOF, - } - switch operation { case linux.LOCK_EX: - // Lock the given region. - if err := file.Dirent.Inode.LockCtx.BSD.LockRegionVFS1(t, file, lock.WriteLock, rng, !nonblocking /* block */); err != nil { + if err := file.LockBSD(t, int32(t.TGIDInRoot()), lock.WriteLock, !nonblocking /* block */); err != nil { return 0, nil, err } case linux.LOCK_SH: - // Lock the given region. - if err := file.Dirent.Inode.LockCtx.BSD.LockRegionVFS1(t, file, lock.ReadLock, rng, !nonblocking /* block */); err != nil { + if err := file.LockBSD(t, int32(t.TGIDInRoot()), lock.ReadLock, !nonblocking /* block */); err != nil { return 0, nil, err } case linux.LOCK_UN: - file.Dirent.Inode.LockCtx.BSD.UnlockRegion(file, rng) + if err := file.UnlockBSD(t); err != nil { + return 0, nil, err + } default: // flock(2): EINVAL operation is invalid. return 0, nil, linuxerr.EINVAL @@ -2275,9 +1657,9 @@ func Flock(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall } const ( - memfdPrefix = "/memfd:" + memfdPrefix = "memfd:" + memfdMaxNameLen = linux.NAME_MAX - len(memfdPrefix) memfdAllFlags = uint32(linux.MFD_CLOEXEC | linux.MFD_ALLOW_SEALING) - memfdMaxNameLen = linux.NAME_MAX - len(memfdPrefix) + 1 ) // MemfdCreate implements the linux syscall memfd_create(2). @@ -2293,33 +1675,24 @@ func MemfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S allowSeals := flags&linux.MFD_ALLOW_SEALING != 0 cloExec := flags&linux.MFD_CLOEXEC != 0 - name, err := t.CopyInString(addr, unix.PathMax-len(memfdPrefix)) - if err != nil { - return 0, nil, err - } - if len(name) > memfdMaxNameLen { - return 0, nil, linuxerr.EINVAL - } - name = memfdPrefix + name - - inode := tmpfs.NewMemfdInode(t, allowSeals) - dirent := fs.NewDirent(t, inode, name) - // Per Linux, mm/shmem.c:__shmem_file_setup(), memfd files are set up with - // FMODE_READ | FMODE_WRITE. - file, err := inode.GetFile(t, dirent, fs.FileFlags{Read: true, Write: true}) + name, err := t.CopyInString(addr, memfdMaxNameLen) if err != nil { return 0, nil, err } - defer dirent.DecRef(t) + shmMount := t.Kernel().ShmMount() + file, err := tmpfs.NewMemfd(t, t.Credentials(), shmMount, allowSeals, memfdPrefix+name) + if err != nil { + return 0, nil, err + } defer file.DecRef(t) - newFD, err := t.NewFDFrom(0, file, kernel.FDFlags{ + fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ CloseOnExec: cloExec, }) if err != nil { return 0, nil, err } - return uintptr(newFD), nil, nil + return uintptr(fd), nil, nil } diff --git a/pkg/sentry/syscalls/linux/sys_getdents.go b/pkg/sentry/syscalls/linux/sys_getdents.go index c74ca0f49..e2e3d4342 100644 --- a/pkg/sentry/syscalls/linux/sys_getdents.go +++ b/pkg/sentry/syscalls/linux/sys_getdents.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,240 +15,185 @@ package linux import ( - "bytes" - "io" + "fmt" - "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/vfs" + "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/usermem" ) -// LINT.IfChange - -// Getdents implements linux syscall getdents(2) for 64bit systems. +// Getdents implements Linux syscall getdents(2). func Getdents(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - size := int(args[2].Uint()) - - minSize := int(smallestDirent(t.Arch())) - if size < minSize { - // size is smaller than smallest possible dirent. - return 0, nil, linuxerr.EINVAL - } - - n, err := getdents(t, fd, addr, size, (*dirent).Serialize) - return n, nil, err + return getdents(t, args, false /* isGetdents64 */) } -// Getdents64 implements linux syscall getdents64(2). +// Getdents64 implements Linux syscall getdents64(2). func Getdents64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return getdents(t, args, true /* isGetdents64 */) +} + +// DirentStructBytesWithoutName is enough to fit (struct linux_dirent) and +// (struct linux_dirent64) without accounting for the name parameter. +const DirentStructBytesWithoutName = 8 + 8 + 2 + 1 + 1 + +func getdents(t *kernel.Task, args arch.SyscallArguments, isGetdents64 bool) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() addr := args[1].Pointer() size := int(args[2].Uint()) - - minSize := int(smallestDirent64(t.Arch())) - if size < minSize { - // size is smaller than smallest possible dirent. + if size < DirentStructBytesWithoutName { return 0, nil, linuxerr.EINVAL } - n, err := getdents(t, fd, addr, size, (*dirent).Serialize64) - return n, nil, err -} - -// getdents implements the core of getdents(2)/getdents64(2). -// f is the syscall implementation dirent serialization function. -func getdents(t *kernel.Task, fd int32, addr hostarch.Addr, size int, f func(*dirent, io.Writer) (int, error)) (uintptr, error) { - dir := t.GetFile(fd) - if dir == nil { - return 0, linuxerr.EBADF + file := t.GetFileVFS2(fd) + if file == nil { + return 0, nil, linuxerr.EBADF } - defer dir.DecRef(t) + defer file.DecRef(t) - w := &usermem.IOReadWriter{ - Ctx: t, - IO: t.MemoryManager(), - Addr: addr, - Opts: usermem.IOOpts{ - AddressSpaceActive: true, - }, + // We want to be sure of the allowed buffer size before calling IterDirents, + // because this function depends on IterDirents saving state of which dirent + // was the last one that was successfully operated on. + allowedSize, err := t.MemoryManager().EnsurePMAsExist(t, addr, int64(size), usermem.IOOpts{ + AddressSpaceActive: true, + }) + if allowedSize == 0 { + return 0, nil, err } - ds := newDirentSerializer(f, w, t.Arch(), size) - rerr := dir.Readdir(t, ds) + cb := getGetdentsCallback(t, int(allowedSize), size, isGetdents64) + err = file.IterDirents(t, cb) + n, _ := t.CopyOutBytes(addr, cb.buf[:cb.copied]) - switch err := handleIOError(t, ds.Written() > 0, rerr, linuxerr.ERESTARTSYS, "getdents", dir); err { - case nil: - dir.Dirent.InotifyEvent(linux.IN_ACCESS, 0) - return uintptr(ds.Written()), nil - case io.EOF: - return 0, nil - default: - return 0, err - } -} + putGetdentsCallback(cb) -// oldDirentHdr is a fixed sized header matching the fixed size fields found in -// the old linux dirent struct. -// -// +marshal -type oldDirentHdr struct { - Ino uint64 - Off uint64 - Reclen uint16 `marshal:"unaligned"` // Struct ends mid-word. -} - -// direntHdr is a fixed sized header matching the fixed size fields found in the -// new linux dirent struct. -// -// +marshal -type direntHdr struct { - OldHdr oldDirentHdr - Typ uint8 `marshal:"unaligned"` // Struct ends mid-word. -} - -// dirent contains the data pointed to by a new linux dirent struct. -type dirent struct { - Hdr direntHdr - Name []byte -} - -// newDirent returns a dirent from an fs.InodeOperationsInfo. -func newDirent(width uint, name string, attr fs.DentAttr, offset uint64) *dirent { - d := &dirent{ - Hdr: direntHdr{ - OldHdr: oldDirentHdr{ - Ino: attr.InodeID, - Off: offset, - }, - Typ: fs.ToDirentType(attr.Type), - }, - Name: []byte(name), - } - d.Hdr.OldHdr.Reclen = d.padRec(int(width)) - return d -} - -// smallestDirent returns the size of the smallest possible dirent using -// the old linux dirent format. -func smallestDirent(a *arch.Context64) uint { - d := dirent{} - return uint(d.Hdr.OldHdr.SizeBytes()) + a.Width() + 1 -} - -// smallestDirent64 returns the size of the smallest possible dirent using -// the new linux dirent format. -func smallestDirent64(a *arch.Context64) uint { - d := dirent{} - return uint(d.Hdr.SizeBytes()) + a.Width() -} - -// padRec pads the name field until the rec length is a multiple of the width, -// which must be a power of 2. It returns the padded rec length. -func (d *dirent) padRec(width int) uint16 { - a := d.Hdr.SizeBytes() + len(d.Name) - r := (a + width) &^ (width - 1) - padding := r - a - d.Name = append(d.Name, make([]byte, padding)...) - return uint16(r) -} - -// Serialize64 serializes a Dirent struct to a byte slice, keeping the new -// linux dirent format. Returns the number of bytes serialized or an error. -func (d *dirent) Serialize64(w io.Writer) (int, error) { - n1, err := d.Hdr.WriteTo(w) - if err != nil { - return 0, err - } - n2, err := w.Write(d.Name) - if err != nil { - return 0, err - } - return int(n1) + n2, nil -} - -// Serialize serializes a Dirent struct to a byte slice, using the old linux -// dirent format. -// Returns the number of bytes serialized or an error. -func (d *dirent) Serialize(w io.Writer) (int, error) { - n1, err := d.Hdr.OldHdr.WriteTo(w) - if err != nil { - return 0, err - } - n2, err := w.Write(d.Name) - if err != nil { - return 0, err - } - n3, err := w.Write([]byte{d.Hdr.Typ}) - if err != nil { - return 0, err - } - return int(n1) + n2 + n3, nil -} - -// direntSerializer implements fs.InodeOperationsInfoSerializer, serializing dirents to an -// io.Writer. -type direntSerializer struct { - serialize func(*dirent, io.Writer) (int, error) - w io.Writer - // width is the arch native value width. - width uint - // offset is the current dirent offset. - offset uint64 - // written is the total bytes serialized. - written int - // size is the size of the buffer to serialize into. - size int -} - -func newDirentSerializer(f func(d *dirent, w io.Writer) (int, error), w io.Writer, ac *arch.Context64, size int) *direntSerializer { - return &direntSerializer{ - serialize: f, - w: w, - width: ac.Width(), - size: size, - } -} - -// CopyOut implements fs.InodeOperationsInfoSerializer.CopyOut. -// It serializes and writes the fs.DentAttr to the direntSerializer io.Writer. -func (ds *direntSerializer) CopyOut(name string, attr fs.DentAttr) error { - ds.offset++ - - d := newDirent(ds.width, name, attr, ds.offset) - - // Serialize dirent into a temp buffer. - var b bytes.Buffer - n, err := ds.serialize(d, &b) - if err != nil { - ds.offset-- - return err + // Only report an error in case we didn't copy anything. + // If we did manage to give _something_ to the caller then the correct + // behaviour is to return success. + if n == 0 { + return 0, nil, err } - // Check that we have enough room remaining to write the dirent. - if n > (ds.size - ds.written) { - ds.offset-- - return io.EOF + return uintptr(n), nil, nil +} + +type getdentsCallback struct { + t *kernel.Task + buf []byte + copied int + userReportedSize int + isGetdents64 bool +} + +var getdentsCallbackPool = sync.Pool{ + New: func() any { + return &getdentsCallback{} + }, +} + +func getGetdentsCallback(t *kernel.Task, size int, userReportedSize int, isGetdents64 bool) *getdentsCallback { + cb := getdentsCallbackPool.Get().(*getdentsCallback) + buf := cb.buf + if cap(buf) < size { + buf = make([]byte, size) + } else { + buf = buf[:size] } - // Write out the temp buffer. - if _, err := b.WriteTo(ds.w); err != nil { - ds.offset-- - return err + *cb = getdentsCallback{ + t: t, + buf: buf, + copied: 0, + userReportedSize: userReportedSize, + isGetdents64: isGetdents64, + } + return cb +} + +func putGetdentsCallback(cb *getdentsCallback) { + cb.t = nil + cb.buf = cb.buf[:0] + getdentsCallbackPool.Put(cb) +} + +// Handle implements vfs.IterDirentsCallback.Handle. +func (cb *getdentsCallback) Handle(dirent vfs.Dirent) error { + remaining := len(cb.buf) - cb.copied + if cb.isGetdents64 { + // struct linux_dirent64 { + // ino64_t d_ino; /* 64-bit inode number */ + // off64_t d_off; /* 64-bit offset to next structure */ + // unsigned short d_reclen; /* Size of this dirent */ + // unsigned char d_type; /* File type */ + // char d_name[]; /* Filename (null-terminated) */ + // }; + size := DirentStructBytesWithoutName + len(dirent.Name) + size = (size + 7) &^ 7 // round up to multiple of 8 + if size > remaining { + // This is only needed to imitate Linux, since it writes out to the user + // as it's iterating over dirs. We don't do that because we can't take + // the mm.mappingMu while holding the filesystem mutex. + if cb.copied == 0 && cb.userReportedSize >= size { + return linuxerr.EFAULT + } + return linuxerr.EINVAL + } + buf := cb.buf[cb.copied : cb.copied+size] + hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino) + hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff)) + hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size)) + buf[18] = dirent.Type + copy(buf[19:], dirent.Name) + // Zero out all remaining bytes in buf, including the NUL terminator + // after dirent.Name. + bufTail := buf[19+len(dirent.Name):] + for i := range bufTail { + bufTail[i] = 0 + } + cb.copied += size + } else { + // struct linux_dirent { + // unsigned long d_ino; /* Inode number */ + // unsigned long d_off; /* Offset to next linux_dirent */ + // unsigned short d_reclen; /* Length of this linux_dirent */ + // char d_name[]; /* Filename (null-terminated) */ + // /* length is actually (d_reclen - 2 - + // offsetof(struct linux_dirent, d_name)) */ + // /* + // char pad; // Zero padding byte + // char d_type; // File type (only since Linux + // // 2.6.4); offset is (d_reclen - 1) + // */ + // }; + if cb.t.Arch().Width() != 8 { + panic(fmt.Sprintf("unsupported sizeof(unsigned long): %d", cb.t.Arch().Width())) + } + size := DirentStructBytesWithoutName + len(dirent.Name) + size = (size + 7) &^ 7 // round up to multiple of sizeof(long) + if size > remaining { + if cb.copied == 0 && cb.userReportedSize >= size { + return linuxerr.EFAULT + } + return linuxerr.EINVAL + } + buf := cb.buf[cb.copied : cb.copied+size] + hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino) + hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff)) + hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size)) + copy(buf[18:], dirent.Name) + // Zero out all remaining bytes in buf, including the NUL terminator + // after dirent.Name and the zero padding byte between the name and + // dirent type. + bufTail := buf[18+len(dirent.Name) : size-1] + for i := range bufTail { + bufTail[i] = 0 + } + buf[size-1] = dirent.Type + cb.copied += size } - ds.written += n return nil } - -// Written returns the total number of bytes written. -func (ds *direntSerializer) Written() int { - return ds.written -} - -// LINT.ThenChange(vfs2/getdents.go) diff --git a/pkg/sentry/syscalls/linux/sys_inotify.go b/pkg/sentry/syscalls/linux/sys_inotify.go index beebddd3f..edd45a8cd 100644 --- a/pkg/sentry/syscalls/linux/sys_inotify.go +++ b/pkg/sentry/syscalls/linux/sys_inotify.go @@ -18,31 +18,26 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/anon" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) -const allFlags = int(linux.IN_NONBLOCK | linux.IN_CLOEXEC) +const allFlags = linux.IN_NONBLOCK | linux.IN_CLOEXEC // InotifyInit1 implements the inotify_init1() syscalls. func InotifyInit1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - flags := int(args[0].Int()) - + flags := args[0].Int() if flags&^allFlags != 0 { return 0, nil, linuxerr.EINVAL } - dirent := fs.NewDirent(t, anon.NewInode(t), "inotify") - fileFlags := fs.FileFlags{ - Read: true, - Write: true, - NonBlocking: flags&linux.IN_NONBLOCK != 0, + ino, err := vfs.NewInotifyFD(t, t.Kernel().VFS(), uint32(flags)) + if err != nil { + return 0, nil, err } - n := fs.NewFile(t, dirent, fileFlags, fs.NewInotify(t)) - defer n.DecRef(t) + defer ino.DecRef(t) - fd, err := t.NewFDFrom(0, n, kernel.FDFlags{ + fd, err := t.NewFDFromVFS2(0, ino, kernel.FDFlags{ CloseOnExec: flags&linux.IN_CLOEXEC != 0, }) @@ -61,21 +56,21 @@ func InotifyInit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S // fdToInotify resolves an fd to an inotify object. If successful, the file will // have an extra ref and the caller is responsible for releasing the ref. -func fdToInotify(t *kernel.Task, fd int32) (*fs.Inotify, *fs.File, error) { - file := t.GetFile(fd) - if file == nil { +func fdToInotify(t *kernel.Task, fd int32) (*vfs.Inotify, *vfs.FileDescription, error) { + f := t.GetFileVFS2(fd) + if f == nil { // Invalid fd. return nil, nil, linuxerr.EBADF } - ino, ok := file.FileOperations.(*fs.Inotify) + ino, ok := f.Impl().(*vfs.Inotify) if !ok { // Not an inotify fd. - file.DecRef(t) + f.DecRef(t) return nil, nil, linuxerr.EINVAL } - return ino, file, nil + return ino, f, nil } // InotifyAddWatch implements the inotify_add_watch() syscall. @@ -84,39 +79,44 @@ func InotifyAddWatch(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kern addr := args[1].Pointer() mask := args[2].Uint() - // "IN_DONT_FOLLOW: Don't dereference pathname if it is a symbolic link." - // -- inotify(7) - resolve := mask&linux.IN_DONT_FOLLOW == 0 - // "EINVAL: The given event mask contains no valid events." // -- inotify_add_watch(2) - if validBits := mask & linux.ALL_INOTIFY_BITS; validBits == 0 { + if mask&linux.ALL_INOTIFY_BITS == 0 { return 0, nil, linuxerr.EINVAL } - ino, file, err := fdToInotify(t, fd) + // "IN_DONT_FOLLOW: Don't dereference pathname if it is a symbolic link." + // -- inotify(7) + follow := followFinalSymlink + if mask&linux.IN_DONT_FOLLOW != 0 { + follow = nofollowFinalSymlink + } + + ino, f, err := fdToInotify(t, fd) if err != nil { return 0, nil, err } - defer file.DecRef(t) + defer f.DecRef(t) - path, _, err := copyInPath(t, addr, false /* allowEmpty */) + path, err := copyInPath(t, addr) if err != nil { return 0, nil, err } + if mask&linux.IN_ONLYDIR != 0 { + path.Dir = true + } + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, follow) + if err != nil { + return 0, nil, err + } + defer tpop.Release(t) + d, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{}) + if err != nil { + return 0, nil, err + } + defer d.DecRef(t) - err = fileOpOn(t, linux.AT_FDCWD, path, resolve, func(root *fs.Dirent, dirent *fs.Dirent, _ uint) error { - // "IN_ONLYDIR: Only watch pathname if it is a directory." -- inotify(7) - if onlyDir := mask&linux.IN_ONLYDIR != 0; onlyDir && !fs.IsDir(dirent.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - // Copy out to the return frame. - fd = ino.AddWatch(dirent, mask) - - return nil - }) - return uintptr(fd), nil, err // Return from the existing value. + return uintptr(ino.AddWatch(d.Dentry(), mask)), nil, nil } // InotifyRmWatch implements the inotify_rm_watch() syscall. @@ -124,10 +124,10 @@ func InotifyRmWatch(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne fd := args[0].Int() wd := args[1].Int() - ino, file, err := fdToInotify(t, fd) + ino, f, err := fdToInotify(t, fd) if err != nil { return 0, nil, err } - defer file.DecRef(t) + defer f.DecRef(t) return 0, nil, ino.RmWatch(t, wd) } diff --git a/pkg/sentry/syscalls/linux/vfs2/iouringfs.go b/pkg/sentry/syscalls/linux/sys_iouring.go similarity index 99% rename from pkg/sentry/syscalls/linux/vfs2/iouringfs.go rename to pkg/sentry/syscalls/linux/sys_iouring.go index b61da35f1..326dc8f5a 100644 --- a/pkg/sentry/syscalls/linux/vfs2/iouringfs.go +++ b/pkg/sentry/syscalls/linux/sys_iouring.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vfs2 +package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" diff --git a/pkg/sentry/syscalls/linux/sys_lseek.go b/pkg/sentry/syscalls/linux/sys_lseek.go deleted file mode 100644 index 4a5712a29..000000000 --- a/pkg/sentry/syscalls/linux/sys_lseek.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package linux - -import ( - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -// Lseek implements linux syscall lseek(2). -func Lseek(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - offset := args[1].Int64() - whence := args[2].Int() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - var sw fs.SeekWhence - switch whence { - case 0: - sw = fs.SeekSet - case 1: - sw = fs.SeekCurrent - case 2: - sw = fs.SeekEnd - default: - return 0, nil, linuxerr.EINVAL - } - - offset, serr := file.Seek(t, sw, offset) - err := handleIOError(t, false /* partialResult */, serr, linuxerr.ERESTARTSYS, "lseek", file) - if err != nil { - return 0, nil, err - } - return uintptr(offset), nil, err -} - -// LINT.ThenChange(vfs2/read_write.go) diff --git a/pkg/sentry/syscalls/linux/sys_mmap.go b/pkg/sentry/syscalls/linux/sys_mmap.go index 2e0e5c009..611114d1d 100644 --- a/pkg/sentry/syscalls/linux/sys_mmap.go +++ b/pkg/sentry/syscalls/linux/sys_mmap.go @@ -21,6 +21,7 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sentry/mm" @@ -35,9 +36,7 @@ func Brk(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallCo return uintptr(addr), nil, nil } -// LINT.IfChange - -// Mmap implements linux syscall mmap(2). +// Mmap implements Linux syscall mmap(2). func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { prot := args[2].Int() flags := args[3].Int() @@ -81,19 +80,18 @@ func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC if !anon { // Convert the passed FD to a file reference. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - flags := file.Flags() // mmap unconditionally requires that the FD is readable. - if !flags.Read { + if !file.IsReadable() { return 0, nil, linuxerr.EACCES } // MAP_SHARED requires that the FD be writable for PROT_WRITE. - if shared && !flags.Write { + if shared && !file.IsWritable() { opts.MaxPerms.Write = false } @@ -101,22 +99,22 @@ func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC return 0, nil, err } } else if shared { - // Back shared anonymous mappings with a special mappable. + // Back shared anonymous mappings with an anonymous tmpfs file. opts.Offset = 0 - m, err := mm.NewSharedAnonMappable(opts.Length, t.Kernel()) + file, err := tmpfs.NewZeroFile(t, t.Credentials(), t.Kernel().ShmMount(), opts.Length) if err != nil { return 0, nil, err } - opts.MappingIdentity = m // transfers ownership of m to opts - opts.Mappable = m + defer file.DecRef(t) + if err := file.ConfigureMMap(t, &opts); err != nil { + return 0, nil, err + } } rv, err := t.MemoryManager().MMap(t, opts) return uintptr(rv), nil, err } -// LINT.ThenChange(vfs2/mmap.go) - // Munmap implements linux syscall munmap(2). func Munmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { return 0, nil, t.MemoryManager().MUnmap(t, args[0].Pointer(), args[1].Uint64()) diff --git a/pkg/sentry/syscalls/linux/sys_mount.go b/pkg/sentry/syscalls/linux/sys_mount.go index 6d26f89b9..7f20a7741 100644 --- a/pkg/sentry/syscalls/linux/sys_mount.go +++ b/pkg/sentry/syscalls/linux/sys_mount.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,11 +16,13 @@ package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/bits" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) // Mount implements Linux syscall mount(2). @@ -31,21 +33,76 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall flags := args[3].Uint64() dataAddr := args[4].Pointer() + // Must have CAP_SYS_ADMIN in the current mount namespace's associated user + // namespace. + creds := t.Credentials() + if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) { + return 0, nil, linuxerr.EPERM + } + + // Ignore magic value that was required before Linux 2.4. + if flags&linux.MS_MGC_MSK == linux.MS_MGC_VAL { + flags = flags &^ linux.MS_MGC_MSK + } + + // Silently allow MS_NOSUID, since we don't implement set-id bits anyway. + const unsupported = linux.MS_REMOUNT | linux.MS_SLAVE | + linux.MS_UNBINDABLE | linux.MS_MOVE | linux.MS_REC | linux.MS_NODIRATIME | + linux.MS_STRICTATIME + + // Linux just allows passing any flags to mount(2) - it won't fail when + // unknown or unsupported flags are passed. Since we don't implement + // everything, we fail explicitly on flags that are unimplemented. + if flags&(unsupported) != 0 { + return 0, nil, linuxerr.EINVAL + } + + // For null-terminated strings related to mount(2), Linux copies in at most + // a page worth of data. See fs/namespace.c:copy_mount_string(). + targetPath, err := copyInPath(t, targetAddr) + if err != nil { + return 0, nil, err + } + target, err := getTaskPathOperation(t, linux.AT_FDCWD, targetPath, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return 0, nil, err + } + defer target.Release(t) + + if flags&linux.MS_BIND == linux.MS_BIND { + var sourcePath fspath.Path + sourcePath, err = copyInPath(t, sourceAddr) + if err != nil { + return 0, nil, err + } + var sourceTpop taskPathOperation + sourceTpop, err = getTaskPathOperation(t, linux.AT_FDCWD, sourcePath, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return 0, nil, err + } + defer sourceTpop.Release(t) + _, err = t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop) + return 0, nil, err + } + const propagationFlags = linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | linux.MS_UNBINDABLE + if propFlag := flags & propagationFlags; propFlag != 0 { + // Check if flags is a power of 2. If not then more than one flag is set. + if !bits.IsPowerOfTwo64(propFlag) { + return 0, nil, linuxerr.EINVAL + } + propType := vfs.PropagationTypeFromLinux(propFlag) + return 0, nil, t.Kernel().VFS().SetMountPropagationAt(t, creds, &target.pop, propType) + } + + // Only copy in source, fstype, and data if we are doing a normal mount. + source, err := t.CopyInString(sourceAddr, hostarch.PageSize) + if err != nil { + return 0, nil, err + } fsType, err := t.CopyInString(typeAddr, hostarch.PageSize) if err != nil { return 0, nil, err } - - sourcePath, _, err := copyInPath(t, sourceAddr, true /* allowEmpty */) - if err != nil { - return 0, nil, err - } - - targetPath, _, err := copyInPath(t, targetAddr, false /* allowEmpty */) - if err != nil { - return 0, nil, err - } - data := "" if dataAddr != 0 { // In Linux, a full page is always copied in regardless of null @@ -57,69 +114,25 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return 0, nil, err } } - - // Ignore magic value that was required before Linux 2.4. - if flags&linux.MS_MGC_MSK == linux.MS_MGC_VAL { - flags = flags &^ linux.MS_MGC_MSK - } - - // Must have CAP_SYS_ADMIN in the mount namespace's associated user - // namespace. - if !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespace().UserNamespace()) { - return 0, nil, linuxerr.EPERM - } - - const unsupportedOps = linux.MS_REMOUNT | linux.MS_BIND | - linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | - linux.MS_UNBINDABLE | linux.MS_MOVE - - // Silently allow MS_NOSUID, since we don't implement set-id bits - // anyway. - const unsupportedFlags = linux.MS_NODEV | - linux.MS_NODIRATIME | linux.MS_STRICTATIME - - // Linux just allows passing any flags to mount(2) - it won't fail when - // unknown or unsupported flags are passed. Since we don't implement - // everything, we fail explicitly on flags that are unimplemented. - if flags&(unsupportedOps|unsupportedFlags) != 0 { - return 0, nil, linuxerr.EINVAL - } - - rsys, ok := fs.FindFilesystem(fsType) - if !ok { - return 0, nil, linuxerr.ENODEV - } - if !rsys.AllowUserMount() { - return 0, nil, linuxerr.EPERM - } - - var superFlags fs.MountSourceFlags + var opts vfs.MountOptions if flags&linux.MS_NOATIME == linux.MS_NOATIME { - superFlags.NoAtime = true - } - if flags&linux.MS_RDONLY == linux.MS_RDONLY { - superFlags.ReadOnly = true + opts.Flags.NoATime = true } if flags&linux.MS_NOEXEC == linux.MS_NOEXEC { - superFlags.NoExec = true + opts.Flags.NoExec = true } - - rootInode, err := rsys.Mount(t, sourcePath, superFlags, data, nil) - if err != nil { - return 0, nil, linuxerr.EINVAL + if flags&linux.MS_NODEV == linux.MS_NODEV { + opts.Flags.NoDev = true } - - if err := fileOpOn(t, linux.AT_FDCWD, targetPath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - // Mount will take a reference on rootInode if successful. - return t.MountNamespace().Mount(t, d, rootInode) - }); err != nil { - // Something went wrong. Drop our ref on rootInode before - // returning the error. - rootInode.DecRef(t) - return 0, nil, err + if flags&linux.MS_NOSUID == linux.MS_NOSUID { + opts.Flags.NoSUID = true } - - return 0, nil, nil + if flags&linux.MS_RDONLY == linux.MS_RDONLY { + opts.ReadOnly = true + } + opts.GetFilesystemOptions.Data = data + _, err = t.Kernel().VFS().MountAt(t, creds, source, &target.pop, fsType, &opts) + return 0, nil, err } // Umount2 implements Linux syscall umount2(2). @@ -127,28 +140,33 @@ func Umount2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca addr := args[0].Pointer() flags := args[1].Int() + // Must have CAP_SYS_ADMIN in the mount namespace's associated user + // namespace. + // + // Currently, this is always the init task's user namespace. + creds := t.Credentials() + if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) { + return 0, nil, linuxerr.EPERM + } + const unsupported = linux.MNT_FORCE | linux.MNT_EXPIRE if flags&unsupported != 0 { return 0, nil, linuxerr.EINVAL } - path, _, err := copyInPath(t, addr, false /* allowEmpty */) + path, err := copyInPath(t, addr) if err != nil { return 0, nil, err } + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink(flags&linux.UMOUNT_NOFOLLOW == 0)) + if err != nil { + return 0, nil, err + } + defer tpop.Release(t) - // Must have CAP_SYS_ADMIN in the mount namespace's associated user - // namespace. - // - // Currently, this is always the init task's user namespace. - if !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespace().UserNamespace()) { - return 0, nil, linuxerr.EPERM + opts := vfs.UmountOptions{ + Flags: uint32(flags &^ linux.UMOUNT_NOFOLLOW), } - resolve := flags&linux.UMOUNT_NOFOLLOW != linux.UMOUNT_NOFOLLOW - detachOnly := flags&linux.MNT_DETACH == linux.MNT_DETACH - - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, resolve, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - return t.MountNamespace().Unmount(t, d, detachOnly) - }) + return 0, nil, t.Kernel().VFS().UmountAt(t, creds, &tpop.pop, &opts) } diff --git a/pkg/sentry/syscalls/linux/vfs2/mq.go b/pkg/sentry/syscalls/linux/sys_mq.go similarity index 99% rename from pkg/sentry/syscalls/linux/vfs2/mq.go rename to pkg/sentry/syscalls/linux/sys_mq.go index d5d81c6e2..479dbfbfd 100644 --- a/pkg/sentry/syscalls/linux/vfs2/mq.go +++ b/pkg/sentry/syscalls/linux/sys_mq.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vfs2 +package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" diff --git a/pkg/sentry/syscalls/linux/sys_pipe.go b/pkg/sentry/syscalls/linux/sys_pipe.go index 5925c2263..4a203636d 100644 --- a/pkg/sentry/syscalls/linux/sys_pipe.go +++ b/pkg/sentry/syscalls/linux/sys_pipe.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,59 +20,48 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/pipefs" "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) -// LINT.IfChange +// Pipe implements Linux syscall pipe(2). +func Pipe(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + addr := args[0].Pointer() + return 0, nil, pipe2(t, addr, 0) +} -// pipe2 implements the actual system call with flags. -func pipe2(t *kernel.Task, addr hostarch.Addr, flags uint) (uintptr, error) { +// Pipe2 implements Linux syscall pipe2(2). +func Pipe2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + addr := args[0].Pointer() + flags := args[1].Int() + return 0, nil, pipe2(t, addr, flags) +} + +func pipe2(t *kernel.Task, addr hostarch.Addr, flags int32) error { if flags&^(linux.O_NONBLOCK|linux.O_CLOEXEC) != 0 { - return 0, linuxerr.EINVAL + return linuxerr.EINVAL + } + r, w, err := pipefs.NewConnectedPipeFDs(t, t.Kernel().PipeMount(), uint32(flags&linux.O_NONBLOCK)) + if err != nil { + return err } - r, w := pipe.NewConnectedPipe(t, pipe.DefaultPipeSize) - - r.SetFlags(linuxToFlags(flags).Settable()) defer r.DecRef(t) - - w.SetFlags(linuxToFlags(flags).Settable()) defer w.DecRef(t) - fds, err := t.NewFDs(0, []*fs.File{r, w}, kernel.FDFlags{ + fds, err := t.NewFDsVFS2(0, []*vfs.FileDescription{r, w}, kernel.FDFlags{ CloseOnExec: flags&linux.O_CLOEXEC != 0, }) if err != nil { - return 0, err + return err } - if _, err := primitive.CopyInt32SliceOut(t, addr, fds); err != nil { for _, fd := range fds { - if file, _ := t.FDTable().Remove(t, fd); file != nil { + if _, file := t.FDTable().Remove(t, fd); file != nil { file.DecRef(t) } } - return 0, err + return err } - return 0, nil + return nil } - -// Pipe implements linux syscall pipe(2). -func Pipe(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - - n, err := pipe2(t, addr, 0) - return n, nil, err -} - -// Pipe2 implements linux syscall pipe2(2). -func Pipe2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - flags := uint(args[1].Uint()) - - n, err := pipe2(t, addr, flags) - return n, nil, err -} - -// LINT.ThenChange(vfs2/pipe.go) diff --git a/pkg/sentry/syscalls/linux/sys_poll.go b/pkg/sentry/syscalls/linux/sys_poll.go index 0bac94478..ffffeaef4 100644 --- a/pkg/sentry/syscalls/linux/sys_poll.go +++ b/pkg/sentry/syscalls/linux/sys_poll.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,20 +15,23 @@ package linux import ( + "fmt" "time" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/limits" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/waiter" ) -// fileCap is the maximum allowable files for poll & select. +// fileCap is the maximum allowable files for poll & select. This has no +// equivalent in Linux; it exists in gVisor since allocation failure in Go is +// unrecoverable. const fileCap = 1024 * 1024 // Masks for "readable", "writable", and "exceptional" events as defined by @@ -47,9 +50,9 @@ const ( selectExceptEvents = linux.POLLPRI ) -// pollState tracks the associated file descriptor and waiter of a PollFD. +// pollState tracks the associated file description and waiter of a PollFD. type pollState struct { - file *fs.File + file *vfs.FileDescription waiter waiter.Entry } @@ -57,16 +60,16 @@ type pollState struct { // stored in pfd.FD. If a channel is passed in, the waiter entry in "state" is // used to register with the file for event notifications, and a reference to // the file is stored in "state". -func initReadiness(t *kernel.Task, pfd *linux.PollFD, state *pollState, ch chan struct{}) { +func initReadiness(t *kernel.Task, pfd *linux.PollFD, state *pollState, ch chan struct{}) error { if pfd.FD < 0 { pfd.REvents = 0 - return + return nil } - file := t.GetFile(pfd.FD) + file := t.GetFileVFS2(pfd.FD) if file == nil { pfd.REvents = linux.POLLNVAL - return + return nil } if ch == nil { @@ -74,11 +77,14 @@ func initReadiness(t *kernel.Task, pfd *linux.PollFD, state *pollState, ch chan } else { state.file = file state.waiter.Init(waiter.ChannelNotifier(ch), waiter.EventMaskFromLinux(uint32(pfd.Events))) - file.EventRegister(&state.waiter) + if err := file.EventRegister(&state.waiter); err != nil { + return err + } } r := file.Readiness(waiter.EventMaskFromLinux(uint32(pfd.Events))) pfd.REvents = int16(r.ToLinux()) & pfd.Events + return nil } // releaseState releases all the pollState in "state". @@ -110,7 +116,9 @@ func pollBlock(t *kernel.Task, pfd []linux.PollFD, timeout time.Duration) (time. defer releaseState(t, state) n := uintptr(0) for i := range pfd { - initReadiness(t, &pfd[i], &state[i], ch) + if err := initReadiness(t, &pfd[i], &state[i], ch); err != nil { + return timeout, 0, err + } if pfd[i].REvents != 0 { n++ ch = nil @@ -121,12 +129,12 @@ func pollBlock(t *kernel.Task, pfd []linux.PollFD, timeout time.Duration) (time. return timeout, n, nil } - forever := timeout < 0 + haveTimeout := timeout >= 0 for n == 0 { var err error // Wait for a notification. - timeout, err = t.BlockWithTimeout(ch, !forever, timeout) + timeout, err = t.BlockWithTimeout(ch, haveTimeout, timeout) if err != nil { if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { err = nil @@ -262,7 +270,7 @@ func doSelect(t *kernel.Task, nfds int, readFDs, writeFDs, exceptFDs hostarch.Ad // immediately to ensure we don't leak. Note, another thread // might be about to close fd. This is racy, but that's // OK. Linux is racy in the same way. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, linuxerr.EBADF } @@ -371,7 +379,8 @@ func copyOutTimespecRemaining(t *kernel.Task, startNs ktime.Time, timeout time.D } remaining := timeoutRemaining(t, startNs, timeout) tsRemaining := linux.NsecToTimespec(remaining.Nanoseconds()) - return copyTimespecOut(t, timespecAddr, &tsRemaining) + _, err := tsRemaining.CopyOut(t, timespecAddr) + return err } // copyOutTimevalRemaining copies the time remaining in timeout to timevalAddr. @@ -383,7 +392,8 @@ func copyOutTimevalRemaining(t *kernel.Task, startNs ktime.Time, timeout time.Du } remaining := timeoutRemaining(t, startNs, timeout) tvRemaining := linux.NsecToTimeval(remaining.Nanoseconds()) - return copyTimevalOut(t, timevalAddr, &tvRemaining) + _, err := tvRemaining.CopyOut(t, timevalAddr) + return err } // pollRestartBlock encapsulates the state required to restart poll(2) via @@ -442,15 +452,8 @@ func Ppoll(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall startNs = t.Kernel().MonotonicClock().Now() } - if maskAddr != 0 { - mask, err := CopyInSigSet(t, maskAddr, maskSize) - if err != nil { - return 0, nil, err - } - - oldmask := t.SignalMask() - t.SetSignalMask(mask) - t.SetSavedSignalMask(oldmask) + if err := setTempSignalSet(t, maskAddr, maskSize); err != nil { + return 0, nil, err } _, n, err := doPoll(t, pfdAddr, nfds, timeout) @@ -480,8 +483,8 @@ func Select(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal // Use a negative Duration to indicate "no timeout". timeout := time.Duration(-1) if timevalAddr != 0 { - timeval, err := copyTimevalIn(t, timevalAddr) - if err != nil { + var timeval linux.Timeval + if _, err := timeval.CopyIn(t, timevalAddr); err != nil { return 0, nil, err } if timeval.Sec < 0 || timeval.Usec < 0 { @@ -499,6 +502,12 @@ func Select(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal return n, nil, err } +// +marshal +type sigSetWithSize struct { + sigsetAddr uint64 + sizeofSigset uint64 +} + // Pselect implements linux syscall pselect(2). func Pselect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { nfds := int(args[0].Int()) // select(2) uses an int. @@ -519,19 +528,15 @@ func Pselect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca } if maskWithSizeAddr != 0 { - maskAddr, size, err := copyInSigSetWithSize(t, maskWithSizeAddr) - if err != nil { + if t.Arch().Width() != 8 { + panic(fmt.Sprintf("unsupported sizeof(void*): %d", t.Arch().Width())) + } + var maskStruct sigSetWithSize + if _, err := maskStruct.CopyIn(t, maskWithSizeAddr); err != nil { return 0, nil, err } - - if maskAddr != 0 { - mask, err := CopyInSigSet(t, maskAddr, size) - if err != nil { - return 0, nil, err - } - oldmask := t.SignalMask() - t.SetSignalMask(mask) - t.SetSavedSignalMask(oldmask) + if err := setTempSignalSet(t, hostarch.Addr(maskStruct.sigsetAddr), uint(maskStruct.sizeofSigset)); err != nil { + return 0, nil, err } } @@ -543,3 +548,21 @@ func Pselect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca } return n, nil, err } + +func setTempSignalSet(t *kernel.Task, maskAddr hostarch.Addr, maskSize uint) error { + if maskAddr == 0 { + return nil + } + if maskSize != linux.SignalSetSize { + return linuxerr.EINVAL + } + var mask linux.SignalSet + if _, err := mask.CopyIn(t, maskAddr); err != nil { + return err + } + mask &^= kernel.UnblockableSignals + oldmask := t.SignalMask() + t.SetSignalMask(mask) + t.SetSavedSignalMask(oldmask) + return nil +} diff --git a/pkg/sentry/syscalls/linux/sys_prctl.go b/pkg/sentry/syscalls/linux/sys_prctl.go index 2ef1e6404..d855cbc02 100644 --- a/pkg/sentry/syscalls/linux/sys_prctl.go +++ b/pkg/sentry/syscalls/linux/sys_prctl.go @@ -21,11 +21,11 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsbridge" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/mm" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) // Prctl implements linux syscall prctl(2). @@ -125,19 +125,23 @@ func Prctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall case linux.PR_SET_MM_EXE_FILE: fd := args[2].Int() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // They trying to set exe to a non-file? - if !fs.IsFile(file.Dirent.Inode.StableAttr) { + stat, err := file.Stat(t, vfs.StatOptions{Mask: linux.STATX_TYPE}) + if err != nil { + return 0, nil, err + } + if stat.Mask&linux.STATX_TYPE == 0 || stat.Mode&linux.FileTypeMask != linux.ModeRegular { return 0, nil, linuxerr.EBADF } // Set the underlying executable. - t.MemoryManager().SetExecutable(t, fsbridge.NewFSFile(file)) + t.MemoryManager().SetExecutable(t, fsbridge.NewVFSFile(file)) case linux.PR_SET_MM_AUXV, linux.PR_SET_MM_START_CODE, diff --git a/pkg/sentry/syscalls/linux/vfs2/mmap.go b/pkg/sentry/syscalls/linux/sys_process_vm.go similarity index 66% rename from pkg/sentry/syscalls/linux/vfs2/mmap.go rename to pkg/sentry/syscalls/linux/sys_process_vm.go index 4f002f143..96f6ed8b0 100644 --- a/pkg/sentry/syscalls/linux/vfs2/mmap.go +++ b/pkg/sentry/syscalls/linux/sys_process_vm.go @@ -12,98 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vfs2 +package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/usermem" ) -// Mmap implements Linux syscall mmap(2). -func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - prot := args[2].Int() - flags := args[3].Int() - fd := args[4].Int() - fixed := flags&linux.MAP_FIXED != 0 - private := flags&linux.MAP_PRIVATE != 0 - shared := flags&linux.MAP_SHARED != 0 - anon := flags&linux.MAP_ANONYMOUS != 0 - map32bit := flags&linux.MAP_32BIT != 0 - - // Require exactly one of MAP_PRIVATE and MAP_SHARED. - if private == shared { - return 0, nil, linuxerr.EINVAL - } - - opts := memmap.MMapOpts{ - Length: args[1].Uint64(), - Offset: args[5].Uint64(), - Addr: args[0].Pointer(), - Fixed: fixed, - Unmap: fixed, - Map32Bit: map32bit, - Private: private, - Perms: hostarch.AccessType{ - Read: linux.PROT_READ&prot != 0, - Write: linux.PROT_WRITE&prot != 0, - Execute: linux.PROT_EXEC&prot != 0, - }, - MaxPerms: hostarch.AnyAccess, - GrowsDown: linux.MAP_GROWSDOWN&flags != 0, - Precommit: linux.MAP_POPULATE&flags != 0, - } - if linux.MAP_LOCKED&flags != 0 { - opts.MLockMode = memmap.MLockEager - } - defer func() { - if opts.MappingIdentity != nil { - opts.MappingIdentity.DecRef(t) - } - }() - - if !anon { - // Convert the passed FD to a file reference. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // mmap unconditionally requires that the FD is readable. - if !file.IsReadable() { - return 0, nil, linuxerr.EACCES - } - // MAP_SHARED requires that the FD be writable for PROT_WRITE. - if shared && !file.IsWritable() { - opts.MaxPerms.Write = false - } - - if err := file.ConfigureMMap(t, &opts); err != nil { - return 0, nil, err - } - } else if shared { - // Back shared anonymous mappings with an anonymous tmpfs file. - opts.Offset = 0 - file, err := tmpfs.NewZeroFile(t, t.Credentials(), t.Kernel().ShmMount(), opts.Length) - if err != nil { - return 0, nil, err - } - defer file.DecRef(t) - if err := file.ConfigureMMap(t, &opts); err != nil { - return 0, nil, err - } - } - - rv, err := t.MemoryManager().MMap(t, opts) - return uintptr(rv), nil, err -} - // ProcessVMReadv implements process_vm_readv(2). func ProcessVMReadv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { return processVMRW(t, args, false /*isWrite*/) diff --git a/pkg/sentry/syscalls/linux/sys_read.go b/pkg/sentry/syscalls/linux/sys_read.go deleted file mode 100644 index 1b99aa208..000000000 --- a/pkg/sentry/syscalls/linux/sys_read.go +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package linux - -import ( - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -const ( - // EventMaskRead contains events that can be triggered on reads. - EventMaskRead = waiter.ReadableEvents | waiter.EventHUp | waiter.EventErr -) - -// Read implements linux syscall read(2). Note that we try to get a buffer that -// is exactly the size requested because some applications like qemu expect -// they can do large reads all at once. Bug for bug. Same for other read -// calls below. -func Read(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - size := args[2].SizeT() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the file is readable. - if !file.Flags().Read { - return 0, nil, linuxerr.EBADF - } - - // Check that the size is legitimate. - si := int(size) - if si < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get the destination of the read. - dst, err := t.SingleIOSequence(addr, si, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := readv(t, file, dst) - t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "read", file) -} - -// Readahead implements readahead(2). -func Readahead(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - offset := args[1].Int64() - size := args[2].SizeT() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the file is readable. - if !file.Flags().Read { - return 0, nil, linuxerr.EBADF - } - - // Check that the size is valid. - if int(size) < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Check that the offset is legitimate and does not overflow. - if offset < 0 || offset+int64(size) < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Return EINVAL; if the underlying file type does not support readahead, - // then Linux will return EINVAL to indicate as much. In the future, we - // may extend this function to actually support readahead hints. - return 0, nil, linuxerr.EINVAL -} - -// Pread64 implements linux syscall pread64(2). -func Pread64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - size := args[2].SizeT() - offset := args[3].Int64() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the offset is legitimate and does not overflow. - if offset < 0 || offset+int64(size) < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Is reading at an offset supported? - if !file.Flags().Pread { - return 0, nil, linuxerr.ESPIPE - } - - // Check that the file is readable. - if !file.Flags().Read { - return 0, nil, linuxerr.EBADF - } - - // Check that the size is legitimate. - si := int(size) - if si < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get the destination of the read. - dst, err := t.SingleIOSequence(addr, si, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := preadv(t, file, dst, offset) - t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pread64", file) -} - -// Readv implements linux syscall readv(2). -func Readv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - iovcnt := int(args[2].Int()) - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the file is readable. - if !file.Flags().Read { - return 0, nil, linuxerr.EBADF - } - - // Read the iovecs that specify the destination of the read. - dst, err := t.IovecsIOSequence(addr, iovcnt, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := readv(t, file, dst) - t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "readv", file) -} - -// Preadv implements linux syscall preadv(2). -func Preadv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - iovcnt := int(args[2].Int()) - offset := args[3].Int64() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the offset is legitimate. - if offset < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Is reading at an offset supported? - if !file.Flags().Pread { - return 0, nil, linuxerr.ESPIPE - } - - // Check that the file is readable. - if !file.Flags().Read { - return 0, nil, linuxerr.EBADF - } - - // Read the iovecs that specify the destination of the read. - dst, err := t.IovecsIOSequence(addr, iovcnt, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := preadv(t, file, dst, offset) - t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "preadv", file) -} - -// Preadv2 implements linux syscall preadv2(2). -func Preadv2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - // While the syscall is - // preadv2(int fd, struct iovec* iov, int iov_cnt, off_t offset, int flags) - // the linux internal call - // (https://elixir.bootlin.com/linux/v4.18/source/fs/read_write.c#L1248) - // splits the offset argument into a high/low value for compatibility with - // 32-bit architectures. The flags argument is the 5th argument. - - fd := args[0].Int() - addr := args[1].Pointer() - iovcnt := int(args[2].Int()) - offset := args[3].Int64() - flags := int(args[5].Int()) - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the offset is legitimate. - if offset < -1 { - return 0, nil, linuxerr.EINVAL - } - - // Is reading at an offset supported? - if offset > -1 && !file.Flags().Pread { - return 0, nil, linuxerr.ESPIPE - } - - // Check that the file is readable. - if !file.Flags().Read { - return 0, nil, linuxerr.EBADF - } - - // Check flags field. - // Note: gVisor does not implement the RWF_HIPRI feature, but the flag is - // accepted as a valid flag argument for preadv2. - if flags&^linux.RWF_VALID != 0 { - return 0, nil, linuxerr.EOPNOTSUPP - } - - // Read the iovecs that specify the destination of the read. - dst, err := t.IovecsIOSequence(addr, iovcnt, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - // If preadv2 is called with an offset of -1, readv is called. - if offset == -1 { - n, err := readv(t, file, dst) - t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "preadv2", file) - } - - n, err := preadv(t, file, dst, offset) - t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "preadv2", file) -} - -func readv(t *kernel.Task, f *fs.File, dst usermem.IOSequence) (int64, error) { - n, err := f.Readv(t, dst) - if err != linuxerr.ErrWouldBlock || f.Flags().NonBlocking { - if n > 0 { - // Queue notification if we read anything. - f.Dirent.InotifyEvent(linux.IN_ACCESS, 0) - } - return n, err - } - - // Sockets support read timeouts. - var haveDeadline bool - var deadline ktime.Time - if s, ok := f.FileOperations.(socket.Socket); ok { - dl := s.RecvTimeout() - if dl < 0 && err == linuxerr.ErrWouldBlock { - return n, err - } - if dl > 0 { - deadline = t.Kernel().MonotonicClock().Now().Add(time.Duration(dl) * time.Nanosecond) - haveDeadline = true - } - } - - // Register for notifications. - w, ch := waiter.NewChannelEntry(EventMaskRead) - f.EventRegister(&w) - - total := n - for { - // Shorten dst to reflect bytes previously read. - dst = dst.DropFirst64(n) - - // Issue the request and break out if it completes with anything - // other than "would block". - n, err = f.Readv(t, dst) - total += n - if err != linuxerr.ErrWouldBlock { - break - } - - // Wait for a notification that we should retry. - if err = t.BlockWithDeadline(ch, haveDeadline, deadline); err != nil { - if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { - err = linuxerr.ErrWouldBlock - } - break - } - } - - f.EventUnregister(&w) - - if total > 0 { - // Queue notification if we read anything. - f.Dirent.InotifyEvent(linux.IN_ACCESS, 0) - } - - return total, err -} - -func preadv(t *kernel.Task, f *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - n, err := f.Preadv(t, dst, offset) - if err != linuxerr.ErrWouldBlock || f.Flags().NonBlocking { - if n > 0 { - // Queue notification if we read anything. - f.Dirent.InotifyEvent(linux.IN_ACCESS, 0) - } - return n, err - } - - // Register for notifications. - w, ch := waiter.NewChannelEntry(EventMaskRead) - f.EventRegister(&w) - - total := n - for { - // Shorten dst to reflect bytes previously read. - dst = dst.DropFirst64(n) - - // Issue the request and break out if it completes with anything - // other than "would block". - n, err = f.Preadv(t, dst, offset+total) - total += n - if err != linuxerr.ErrWouldBlock { - break - } - - // Wait for a notification that we should retry. - if err = t.Block(ch); err != nil { - break - } - } - - f.EventUnregister(&w) - - if total > 0 { - // Queue notification if we read anything. - f.Dirent.InotifyEvent(linux.IN_ACCESS, 0) - } - - return total, err -} - -// LINT.ThenChange(vfs2/read_write.go) diff --git a/pkg/sentry/syscalls/linux/vfs2/read_write.go b/pkg/sentry/syscalls/linux/sys_read_write.go similarity index 93% rename from pkg/sentry/syscalls/linux/vfs2/read_write.go rename to pkg/sentry/syscalls/linux/sys_read_write.go index 5ade14a89..df85fcb57 100644 --- a/pkg/sentry/syscalls/linux/vfs2/read_write.go +++ b/pkg/sentry/syscalls/linux/sys_read_write.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vfs2 +package linux import ( "time" @@ -23,7 +23,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/socket" - slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/usermem" "gvisor.dev/gvisor/pkg/waiter" @@ -62,7 +61,7 @@ func Read(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC n, err := read(t, file, dst, vfs.ReadOptions{}) t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "read", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "read", file) } // Readv implements Linux syscall readv(2). @@ -87,7 +86,7 @@ func Readv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall n, err := read(t, file, dst, vfs.ReadOptions{}) t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "readv", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "readv", file) } func read(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { @@ -167,7 +166,7 @@ func Pread64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca n, err := pread(t, file, dst, offset, vfs.ReadOptions{}) t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "pread64", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pread64", file) } // Preadv implements Linux syscall preadv(2). @@ -198,7 +197,7 @@ func Preadv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal n, err := pread(t, file, dst, offset, vfs.ReadOptions{}) t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "preadv", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "preadv", file) } // Preadv2 implements Linux syscall preadv2(2). @@ -244,7 +243,7 @@ func Preadv2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca n, err = pread(t, file, dst, offset, opts) } t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "preadv2", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "preadv2", file) } func pread(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { @@ -316,7 +315,7 @@ func Write(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall n, err := write(t, file, src, vfs.WriteOptions{}) t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "write", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "write", file) } // Writev implements Linux syscall writev(2). @@ -341,7 +340,7 @@ func Writev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal n, err := write(t, file, src, vfs.WriteOptions{}) t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "writev", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "writev", file) } func write(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { @@ -420,7 +419,7 @@ func Pwrite64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc n, err := pwrite(t, file, src, offset, vfs.WriteOptions{}) t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "pwrite64", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pwrite64", file) } // Pwritev implements Linux syscall pwritev(2). @@ -451,7 +450,7 @@ func Pwritev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca n, err := pwrite(t, file, src, offset, vfs.WriteOptions{}) t.IOUsage().AccountReadSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "pwritev", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pwritev", file) } // Pwritev2 implements Linux syscall pwritev2(2). @@ -497,7 +496,7 @@ func Pwritev2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc n, err = pwrite(t, file, src, offset, opts) } t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "pwritev2", file) + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pwritev2", file) } func pwrite(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { diff --git a/pkg/sentry/syscalls/linux/sys_signal.go b/pkg/sentry/syscalls/linux/sys_signal.go index 3d3cfe003..c7d77dce8 100644 --- a/pkg/sentry/syscalls/linux/sys_signal.go +++ b/pkg/sentry/syscalls/linux/sys_signal.go @@ -22,9 +22,8 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/signalfd" "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/signalfd" ) // "For a process to have permission to send a signal it must @@ -532,15 +531,15 @@ func sharedSignalfd(t *kernel.Task, fd int32, sigset hostarch.Addr, sigsetsize u // // The spec indicates that this should adjust the mask. if fd != -1 { - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Is this a signalfd? - if s, ok := file.FileOperations.(*signalfd.SignalOperations); ok { - s.SetMask(mask) + if sfd, ok := file.Impl().(*signalfd.SignalFileDescription); ok { + sfd.SetMask(mask) return 0, nil, nil } @@ -548,20 +547,21 @@ func sharedSignalfd(t *kernel.Task, fd int32, sigset hostarch.Addr, sigsetsize u return 0, nil, linuxerr.EINVAL } + fileFlags := uint32(linux.O_RDWR) + if flags&linux.SFD_NONBLOCK != 0 { + fileFlags |= linux.O_NONBLOCK + } + // Create a new file. - file, err := signalfd.New(t, mask) + vfsObj := t.Kernel().VFS() + file, err := signalfd.New(vfsObj, t, mask, fileFlags) if err != nil { return 0, nil, err } defer file.DecRef(t) - // Set appropriate flags. - file.SetFlags(fs.SettableFileFlags{ - NonBlocking: flags&linux.SFD_NONBLOCK != 0, - }) - // Create a new descriptor. - fd, err = t.NewFDFrom(0, file, kernel.FDFlags{ + fd, err = t.NewFDFromVFS2(0, file, kernel.FDFlags{ CloseOnExec: flags&linux.SFD_CLOEXEC != 0, }) if err != nil { diff --git a/pkg/sentry/syscalls/linux/sys_socket.go b/pkg/sentry/syscalls/linux/sys_socket.go index 9219342b4..e4bb151d6 100644 --- a/pkg/sentry/syscalls/linux/sys_socket.go +++ b/pkg/sentry/syscalls/linux/sys_socket.go @@ -15,26 +15,29 @@ package linux import ( + "fmt" "time" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/host" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/socket" "gvisor.dev/gvisor/pkg/sentry/socket/control" "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/syserr" "gvisor.dev/gvisor/pkg/usermem" ) -// LINT.IfChange - // maxAddrLen is the maximum socket address length we're willing to accept. const maxAddrLen = 200 @@ -177,16 +180,17 @@ func Socket(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal } // Create the new socket. - s, e := socket.New(t, domain, linux.SockType(stype&0xf), protocol) + s, e := socket.NewVFS2(t, domain, linux.SockType(stype&0xf), protocol) if e != nil { return 0, nil, e.ToError() } - s.SetFlags(fs.SettableFileFlags{ - NonBlocking: stype&linux.SOCK_NONBLOCK != 0, - }) defer s.DecRef(t) - fd, err := t.NewFDFrom(0, s, kernel.FDFlags{ + if err := s.SetStatusFlags(t, t.Credentials(), uint32(stype&linux.SOCK_NONBLOCK)); err != nil { + return 0, nil, err + } + + fd, err := t.NewFDFromVFS2(0, s, kernel.FDFlags{ CloseOnExec: stype&linux.SOCK_CLOEXEC != 0, }) if err != nil { @@ -201,39 +205,42 @@ func SocketPair(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy domain := int(args[0].Int()) stype := args[1].Int() protocol := int(args[2].Int()) - socks := args[3].Pointer() + addr := args[3].Pointer() // Check and initialize the flags. if stype & ^(0xf|linux.SOCK_NONBLOCK|linux.SOCK_CLOEXEC) != 0 { return 0, nil, linuxerr.EINVAL } - fileFlags := fs.SettableFileFlags{ - NonBlocking: stype&linux.SOCK_NONBLOCK != 0, - } - // Create the socket pair. - s1, s2, e := socket.Pair(t, domain, linux.SockType(stype&0xf), protocol) + s1, s2, e := socket.PairVFS2(t, domain, linux.SockType(stype&0xf), protocol) if e != nil { return 0, nil, e.ToError() } - s1.SetFlags(fileFlags) - s2.SetFlags(fileFlags) + // Adding to the FD table will cause an extra reference to be acquired. defer s1.DecRef(t) defer s2.DecRef(t) + nonblocking := uint32(stype & linux.SOCK_NONBLOCK) + if err := s1.SetStatusFlags(t, t.Credentials(), nonblocking); err != nil { + return 0, nil, err + } + if err := s2.SetStatusFlags(t, t.Credentials(), nonblocking); err != nil { + return 0, nil, err + } + // Create the FDs for the sockets. - fds, err := t.NewFDs(0, []*fs.File{s1, s2}, kernel.FDFlags{ + flags := kernel.FDFlags{ CloseOnExec: stype&linux.SOCK_CLOEXEC != 0, - }) + } + fds, err := t.NewFDsVFS2(0, []*vfs.FileDescription{s1, s2}, flags) if err != nil { return 0, nil, err } - // Copy the file descriptors out. - if _, err := primitive.CopyInt32SliceOut(t, socks, fds); err != nil { + if _, err := primitive.CopyInt32SliceOut(t, addr, fds); err != nil { for _, fd := range fds { - if file, _ := t.FDTable().Remove(t, fd); file != nil { + if _, file := t.FDTable().Remove(t, fd); file != nil { file.DecRef(t) } } @@ -250,14 +257,14 @@ func Connect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca addrlen := args[2].Uint() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -268,7 +275,7 @@ func Connect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca return 0, nil, err } - blocking := !file.Flags().NonBlocking + blocking := (file.StatusFlags() & linux.SOCK_NONBLOCK) == 0 return 0, nil, linuxerr.ConvertIntr(s.Connect(t, a, blocking).ToError(), linuxerr.ERESTARTSYS) } @@ -281,21 +288,21 @@ func accept(t *kernel.Task, fd int32, addr hostarch.Addr, addrLen hostarch.Addr, } // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, linuxerr.ENOTSOCK } // Call the syscall implementation for this socket, then copy the // output address if one is specified. - blocking := !file.Flags().NonBlocking + blocking := (file.StatusFlags() & linux.SOCK_NONBLOCK) == 0 peerRequested := addrLen != 0 nfd, peer, peerLen, e := s.Accept(t, peerRequested, flags, blocking) @@ -340,14 +347,14 @@ func Bind(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC addrlen := args[2].Uint() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -367,14 +374,14 @@ func Listen(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal backlog := args[1].Uint() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -405,14 +412,14 @@ func Shutdown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc how := args[1].Int() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -436,14 +443,14 @@ func GetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy optLenAddr := args[4].Pointer() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -478,7 +485,7 @@ func GetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy // getSockOpt tries to handle common socket options, or dispatches to a specific // socket implementation. -func getSockOpt(t *kernel.Task, s socket.Socket, level, name int, optValAddr hostarch.Addr, len int) (marshal.Marshallable, *syserr.Error) { +func getSockOpt(t *kernel.Task, s socket.SocketVFS2, level, name int, optValAddr hostarch.Addr, len int) (marshal.Marshallable, *syserr.Error) { if level == linux.SOL_SOCKET { switch name { case linux.SO_TYPE, linux.SO_DOMAIN, linux.SO_PROTOCOL: @@ -517,14 +524,14 @@ func SetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy optLen := args[4].Int() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -555,14 +562,14 @@ func GetSockName(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S addrlen := args[2].Pointer() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -583,14 +590,14 @@ func GetPeerName(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S addrlen := args[2].Pointer() // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -616,14 +623,14 @@ func RecvMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca } // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -633,7 +640,7 @@ func RecvMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca return 0, nil, linuxerr.EINVAL } - if file.Flags().NonBlocking { + if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { flags |= linux.MSG_DONTWAIT } @@ -673,27 +680,27 @@ func RecvMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc } // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } - if file.Flags().NonBlocking { + if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { flags |= linux.MSG_DONTWAIT } var haveDeadline bool var deadline ktime.Time if toPtr != 0 { - ts, err := copyTimespecIn(t, toPtr) - if err != nil { + var ts linux.Timespec + if _, err := ts.CopyIn(t, toPtr); err != nil { return 0, nil, err } if !ts.Valid() { @@ -741,7 +748,49 @@ func RecvMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return uintptr(count), nil, nil } -func recvSingleMsg(t *kernel.Task, s socket.Socket, msgPtr hostarch.Addr, flags int32, haveDeadline bool, deadline ktime.Time) (uintptr, error) { +func getSCMRightsVFS2(t *kernel.Task, rights transport.RightsControlMessage) control.SCMRightsVFS2 { + switch v := rights.(type) { + case control.SCMRightsVFS2: + return v + case *transport.SCMRights: + rf := control.RightsFilesVFS2(fdsToHostFiles(t, v.FDs)) + return &rf + default: + panic(fmt.Sprintf("rights of type %T must be *transport.SCMRights or implement SCMRightsVFS2", rights)) + } +} + +// If an error is encountered, only files created before the error will be +// returned. This is what Linux does. +func fdsToHostFiles(ctx context.Context, fds []int) []*vfs.FileDescription { + files := make([]*vfs.FileDescription, 0, len(fds)) + for _, fd := range fds { + // Get flags. We do it here because they may be modified + // by subsequent functions. + fileFlags, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(fd), unix.F_GETFL, 0) + if errno != 0 { + ctx.Warningf("Error retrieving host FD flags: %v", error(errno)) + break + } + + // Create the file backed by hostFD. + file, err := host.NewFD(ctx, kernel.KernelFromContext(ctx).HostMount(), fd, &host.NewFDOptions{}) + if err != nil { + ctx.Warningf("Error creating file from host FD: %v", err) + break + } + + if err := file.SetStatusFlags(ctx, auth.CredentialsFromContext(ctx), uint32(fileFlags&linux.O_NONBLOCK)); err != nil { + ctx.Warningf("Error setting flags on host FD file: %v", err) + break + } + + files = append(files, file) + } + return files +} + +func recvSingleMsg(t *kernel.Task, s socket.SocketVFS2, msgPtr hostarch.Addr, flags int32, haveDeadline bool, deadline ktime.Time) (uintptr, error) { // Capture the message header and io vectors. var msg MessageHeader64 if _, err := msg.CopyIn(t, msgPtr); err != nil { @@ -797,7 +846,8 @@ func recvSingleMsg(t *kernel.Task, s socket.Socket, msgPtr hostarch.Addr, flags } if cms.Unix.Rights != nil { - controlData, mflags = control.PackRights(t, cms.Unix.Rights.(control.SCMRights), flags&linux.MSG_CMSG_CLOEXEC != 0, controlData, mflags) + cms.Unix.Rights = getSCMRightsVFS2(t, cms.Unix.Rights) + controlData, mflags = control.PackRightsVFS2(t, cms.Unix.Rights.(control.SCMRightsVFS2), flags&linux.MSG_CMSG_CLOEXEC != 0, controlData, mflags) } // Copy the address to the caller. @@ -838,19 +888,19 @@ func recvFrom(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, fla } // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, linuxerr.ENOTSOCK } - if file.Flags().NonBlocking { + if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { flags |= linux.MSG_DONTWAIT } @@ -911,14 +961,14 @@ func SendMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca } // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -928,7 +978,7 @@ func SendMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca return 0, nil, linuxerr.EINVAL } - if file.Flags().NonBlocking { + if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { flags |= linux.MSG_DONTWAIT } @@ -953,14 +1003,14 @@ func SendMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc } // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -970,7 +1020,7 @@ func SendMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return 0, nil, linuxerr.EINVAL } - if file.Flags().NonBlocking { + if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { flags |= linux.MSG_DONTWAIT } @@ -1003,7 +1053,7 @@ func SendMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return uintptr(count), nil, nil } -func sendSingleMsg(t *kernel.Task, s socket.Socket, file *fs.File, msgPtr hostarch.Addr, flags int32) (uintptr, error) { +func sendSingleMsg(t *kernel.Task, s socket.SocketVFS2, file *vfs.FileDescription, msgPtr hostarch.Addr, flags int32) (uintptr, error) { // Capture the message header. var msg MessageHeader64 if _, err := msg.CopyIn(t, msgPtr); err != nil { @@ -1059,7 +1109,7 @@ func sendSingleMsg(t *kernel.Task, s socket.Socket, file *fs.File, msgPtr hostar // Call the syscall implementation. n, e := s.SendMsg(t, src, to, int(flags), haveDeadline, deadline, controlMessages) - err = handleIOError(t, n != 0, e.ToError(), linuxerr.ERESTARTSYS, "sendmsg", file) + err = HandleIOError(t, n != 0, e.ToError(), linuxerr.ERESTARTSYS, "sendmsg", file) // Control messages should be released on error as well as for zero-length // messages, which are discarded by the receiver. if n == 0 || err != nil { @@ -1077,19 +1127,19 @@ func sendTo(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, flags } // Get socket from the file descriptor. - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.FileOperations.(socket.Socket) + s, ok := file.Impl().(socket.SocketVFS2) if !ok { return 0, linuxerr.ENOTSOCK } - if file.Flags().NonBlocking { + if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { flags |= linux.MSG_DONTWAIT } @@ -1121,7 +1171,7 @@ func sendTo(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, flags // Call the syscall implementation. n, e := s.SendMsg(t, src, to, int(flags), haveDeadline, deadline, socket.ControlMessages{Unix: control.New(t, s, nil)}) - return uintptr(n), handleIOError(t, n != 0, e.ToError(), linuxerr.ERESTARTSYS, "sendto", file) + return uintptr(n), HandleIOError(t, n != 0, e.ToError(), linuxerr.ERESTARTSYS, "sendto", file) } // SendTo implements the linux syscall sendto(2). @@ -1136,5 +1186,3 @@ func SendTo(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal n, err := sendTo(t, fd, bufPtr, bufLen, flags, namePtr, nameLen) return n, nil, err } - -// LINT.ThenChange(./vfs2/socket.go) diff --git a/pkg/sentry/syscalls/linux/sys_splice.go b/pkg/sentry/syscalls/linux/sys_splice.go index eac50e69e..862431e8d 100644 --- a/pkg/sentry/syscalls/linux/sys_splice.go +++ b/pkg/sentry/syscalls/linux/sys_splice.go @@ -1,4 +1,4 @@ -// Copyright 2019 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,84 +15,255 @@ package linux import ( + "io" + "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" + "gvisor.dev/gvisor/pkg/sentry/vfs" + "gvisor.dev/gvisor/pkg/usermem" "gvisor.dev/gvisor/pkg/waiter" ) -// doSplice implements a blocking splice operation. -func doSplice(t *kernel.Task, outFile, inFile *fs.File, opts fs.SpliceOpts, nonBlocking bool) (int64, error) { - if opts.Length < 0 || opts.SrcStart < 0 || opts.DstStart < 0 || (opts.SrcStart+opts.Length < 0) { - return 0, linuxerr.EINVAL +// Splice implements Linux syscall splice(2). +func Splice(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + inFD := args[0].Int() + inOffsetPtr := args[1].Pointer() + outFD := args[2].Int() + outOffsetPtr := args[3].Pointer() + count := int64(args[4].SizeT()) + flags := args[5].Int() + + if count == 0 { + return 0, nil, nil } - if opts.Length == 0 { - return 0, nil + if count > int64(kernel.MAX_RW_COUNT) { + count = int64(kernel.MAX_RW_COUNT) } - if opts.Length > int64(kernel.MAX_RW_COUNT) { - opts.Length = int64(kernel.MAX_RW_COUNT) + if count < 0 { + return 0, nil, linuxerr.EINVAL } + // Check for invalid flags. + if flags&^(linux.SPLICE_F_MOVE|linux.SPLICE_F_NONBLOCK|linux.SPLICE_F_MORE|linux.SPLICE_F_GIFT) != 0 { + return 0, nil, linuxerr.EINVAL + } + + // Get file descriptions. + inFile := t.GetFileVFS2(inFD) + if inFile == nil { + return 0, nil, linuxerr.EBADF + } + defer inFile.DecRef(t) + outFile := t.GetFileVFS2(outFD) + if outFile == nil { + return 0, nil, linuxerr.EBADF + } + defer outFile.DecRef(t) + + // Check that both files support the required directionality. + if !inFile.IsReadable() || !outFile.IsWritable() { + return 0, nil, linuxerr.EBADF + } + if outFile.Options().DenySpliceIn { + return 0, nil, linuxerr.EINVAL + } + + // The operation is non-blocking if anything is non-blocking. + // + // N.B. This is a rather simplistic heuristic that avoids some + // poor edge case behavior since the exact semantics here are + // underspecified and vary between versions of Linux itself. + nonBlock := ((inFile.StatusFlags()|outFile.StatusFlags())&linux.O_NONBLOCK != 0) || (flags&linux.SPLICE_F_NONBLOCK != 0) + + // At least one file description must represent a pipe. + inPipeFD, inIsPipe := inFile.Impl().(*pipe.VFSPipeFD) + outPipeFD, outIsPipe := outFile.Impl().(*pipe.VFSPipeFD) + if !inIsPipe && !outIsPipe { + return 0, nil, linuxerr.EINVAL + } + + // Copy in offsets. + inOffset := int64(-1) + if inOffsetPtr != 0 { + if inIsPipe { + return 0, nil, linuxerr.ESPIPE + } + if inFile.Options().DenyPRead { + return 0, nil, linuxerr.EINVAL + } + if _, err := primitive.CopyInt64In(t, inOffsetPtr, &inOffset); err != nil { + return 0, nil, err + } + if inOffset < 0 { + return 0, nil, linuxerr.EINVAL + } + } + outOffset := int64(-1) + if outOffsetPtr != 0 { + if outIsPipe { + return 0, nil, linuxerr.ESPIPE + } + if outFile.Options().DenyPWrite { + return 0, nil, linuxerr.EINVAL + } + if _, err := primitive.CopyInt64In(t, outOffsetPtr, &outOffset); err != nil { + return 0, nil, err + } + if outOffset < 0 { + return 0, nil, linuxerr.EINVAL + } + } + + // Move data. var ( - n int64 - err error - inCh chan struct{} - outCh chan struct{} + n int64 + err error ) - + dw := dualWaiter{ + inFile: inFile, + outFile: outFile, + } + defer dw.destroy() for { - n, err = fs.Splice(t, outFile, inFile, opts) - if n != 0 || err != linuxerr.ErrWouldBlock { - break - } else if err == linuxerr.ErrWouldBlock && nonBlocking { - break + // If both input and output are pipes, delegate to the pipe + // implementation. Otherwise, exactly one end is a pipe, which + // we ensure is consistently ordered after the non-pipe FD's + // locks by passing the pipe FD as usermem.IO to the non-pipe + // end. + switch { + case inIsPipe && outIsPipe: + n, err = pipe.Splice(t, outPipeFD, inPipeFD, count) + case inIsPipe: + n, err = inPipeFD.SpliceToNonPipe(t, outFile, outOffset, count) + if outOffset != -1 { + outOffset += n + } + case outIsPipe: + n, err = outPipeFD.SpliceFromNonPipe(t, inFile, inOffset, count) + if inOffset != -1 { + inOffset += n + } + default: + panic("at least one end of splice must be a pipe") } - // Note that the blocking behavior here is a bit different than the - // normal pattern. Because we need to have both data to read and data - // to write simultaneously, we actually explicitly block on both of - // these cases in turn before returning to the splice operation. - if inFile.Readiness(EventMaskRead) == 0 { - if inCh == nil { - var e waiter.Entry - e, inCh = waiter.NewChannelEntry(EventMaskRead) - inFile.EventRegister(&e) - defer inFile.EventUnregister(&e) - // Need to refresh readiness. - continue - } - if err = t.Block(inCh); err != nil { - break - } + if n != 0 || err != linuxerr.ErrWouldBlock || nonBlock { + break } - // Don't bother checking readiness of the outFile, because it's not a - // guarantee that it won't return EWOULDBLOCK. Both pipes and eventfds - // can be "ready" but will reject writes of certain sizes with - // EWOULDBLOCK. - if outCh == nil { - var e waiter.Entry - e, outCh = waiter.NewChannelEntry(EventMaskWrite) - outFile.EventRegister(&e) - defer outFile.EventUnregister(&e) - // We might be ready to write now. Try again before - // blocking. - continue - } - if err = t.Block(outCh); err != nil { + if err = dw.waitForBoth(t); err != nil { break } } - if n > 0 { - // On Linux, inotify behavior is not very consistent with splice(2). We try - // our best to emulate Linux for very basic calls to splice, where for some - // reason, events are generated for output files, but not input files. - outFile.Dirent.InotifyEvent(linux.IN_MODIFY, 0) + // Copy updated offsets out. + if inOffsetPtr != 0 { + if _, err := primitive.CopyInt64Out(t, inOffsetPtr, inOffset); err != nil { + return 0, nil, err + } } - return n, err + if outOffsetPtr != 0 { + if _, err := primitive.CopyInt64Out(t, outOffsetPtr, outOffset); err != nil { + return 0, nil, err + } + } + + // We can only pass a single file to handleIOError, so pick inFile arbitrarily. + // This is used only for debugging purposes. + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "splice", outFile) +} + +// Tee implements Linux syscall tee(2). +func Tee(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + inFD := args[0].Int() + outFD := args[1].Int() + count := int64(args[2].SizeT()) + flags := args[3].Int() + + if count == 0 { + return 0, nil, nil + } + if count > int64(kernel.MAX_RW_COUNT) { + count = int64(kernel.MAX_RW_COUNT) + } + if count < 0 { + return 0, nil, linuxerr.EINVAL + } + + // Check for invalid flags. + if flags&^(linux.SPLICE_F_MOVE|linux.SPLICE_F_NONBLOCK|linux.SPLICE_F_MORE|linux.SPLICE_F_GIFT) != 0 { + return 0, nil, linuxerr.EINVAL + } + + // Get file descriptions. + inFile := t.GetFileVFS2(inFD) + if inFile == nil { + return 0, nil, linuxerr.EBADF + } + defer inFile.DecRef(t) + outFile := t.GetFileVFS2(outFD) + if outFile == nil { + return 0, nil, linuxerr.EBADF + } + defer outFile.DecRef(t) + + // Check that both files support the required directionality. + if !inFile.IsReadable() || !outFile.IsWritable() { + return 0, nil, linuxerr.EBADF + } + if outFile.Options().DenySpliceIn { + return 0, nil, linuxerr.EINVAL + } + + // The operation is non-blocking if anything is non-blocking. + // + // N.B. This is a rather simplistic heuristic that avoids some + // poor edge case behavior since the exact semantics here are + // underspecified and vary between versions of Linux itself. + nonBlock := ((inFile.StatusFlags()|outFile.StatusFlags())&linux.O_NONBLOCK != 0) || (flags&linux.SPLICE_F_NONBLOCK != 0) + + // Both file descriptions must represent pipes. + inPipeFD, inIsPipe := inFile.Impl().(*pipe.VFSPipeFD) + outPipeFD, outIsPipe := outFile.Impl().(*pipe.VFSPipeFD) + if !inIsPipe || !outIsPipe { + return 0, nil, linuxerr.EINVAL + } + + // Copy data. + var ( + n int64 + err error + ) + dw := dualWaiter{ + inFile: inFile, + outFile: outFile, + } + defer dw.destroy() + for { + n, err = pipe.Tee(t, outPipeFD, inPipeFD, count) + if n != 0 || err != linuxerr.ErrWouldBlock || nonBlock { + break + } + if err = dw.waitForBoth(t); err != nil { + break + } + } + + if n != 0 { + // If a partial write is completed, the error is dropped. Log it here. + if err != nil && err != io.EOF && err != linuxerr.ErrWouldBlock { + log.Debugf("tee completed a partial write with error: %v", err) + err = nil + } + } + + // We can only pass a single file to handleIOError, so pick inFile arbitrarily. + // This is used only for debugging purposes. + return uintptr(n), nil, HandleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "tee", inFile) } // Sendfile implements linux system call sendfile(2). @@ -102,242 +273,264 @@ func Sendfile(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc offsetAddr := args[2].Pointer() count := int64(args[3].SizeT()) - // Get files. - inFile := t.GetFile(inFD) + inFile := t.GetFileVFS2(inFD) if inFile == nil { return 0, nil, linuxerr.EBADF } defer inFile.DecRef(t) - - if !inFile.Flags().Read { + if !inFile.IsReadable() { return 0, nil, linuxerr.EBADF } - outFile := t.GetFile(outFD) + outFile := t.GetFileVFS2(outFD) if outFile == nil { return 0, nil, linuxerr.EBADF } defer outFile.DecRef(t) - - if !outFile.Flags().Write { + if !outFile.IsWritable() { return 0, nil, linuxerr.EBADF } - - // Verify that the outfile Append flag is not set. - if outFile.Flags().Append { + if outFile.Options().DenySpliceIn { return 0, nil, linuxerr.EINVAL } - // Verify that we have a regular infile. This is a requirement; the - // same check appears in Linux (fs/splice.c:splice_direct_to_actor). - if !fs.IsRegular(inFile.Dirent.Inode.StableAttr) { + // Verify that the outFile Append flag is not set. + if outFile.StatusFlags()&linux.O_APPEND != 0 { return 0, nil, linuxerr.EINVAL } - var ( - n int64 - err error - ) + // Verify that inFile is a regular file or block device. This is a + // requirement; the same check appears in Linux + // (fs/splice.c:splice_direct_to_actor). + if stat, err := inFile.Stat(t, vfs.StatOptions{Mask: linux.STATX_TYPE}); err != nil { + return 0, nil, err + } else if stat.Mask&linux.STATX_TYPE == 0 || + (stat.Mode&linux.S_IFMT != linux.S_IFREG && stat.Mode&linux.S_IFMT != linux.S_IFBLK) { + return 0, nil, linuxerr.EINVAL + } + + // Copy offset if it exists. + offset := int64(-1) if offsetAddr != 0 { - // Verify that when offset address is not null, infile must be - // seekable. The fs.Splice routine itself validates basic read. - if !inFile.Flags().Pread { + if inFile.Options().DenyPRead { return 0, nil, linuxerr.ESPIPE } - - // Copy in the offset. - var offset int64 - if _, err := primitive.CopyInt64In(t, offsetAddr, &offset); err != nil { + var offsetP primitive.Int64 + if _, err := offsetP.CopyIn(t, offsetAddr); err != nil { return 0, nil, err } + offset = int64(offsetP) - // Do the splice. - n, err = doSplice(t, outFile, inFile, fs.SpliceOpts{ - Length: count, - SrcOffset: true, - SrcStart: int64(offset), - }, outFile.Flags().NonBlocking) - - // Copy out the new offset. - if _, err := primitive.CopyInt64Out(t, offsetAddr, offset+n); err != nil { - return 0, nil, err - } - } else { - // Send data using splice. - n, err = doSplice(t, outFile, inFile, fs.SpliceOpts{ - Length: count, - }, outFile.Flags().NonBlocking) - } - - // Sendfile can't lose any data because inFD is always a regual file. - if n != 0 { - err = nil - } - - // We can only pass a single file to handleIOError, so pick inFile - // arbitrarily. This is used only for debugging purposes. - return uintptr(n), nil, handleIOError(t, false, err, linuxerr.ERESTARTSYS, "sendfile", inFile) -} - -// Splice implements splice(2). -func Splice(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - inFD := args[0].Int() - inOffset := args[1].Pointer() - outFD := args[2].Int() - outOffset := args[3].Pointer() - count := int64(args[4].SizeT()) - flags := args[5].Int() - - // Check for invalid flags. - if flags&^(linux.SPLICE_F_MOVE|linux.SPLICE_F_NONBLOCK|linux.SPLICE_F_MORE|linux.SPLICE_F_GIFT) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get files. - outFile := t.GetFile(outFD) - if outFile == nil { - return 0, nil, linuxerr.EBADF - } - defer outFile.DecRef(t) - - inFile := t.GetFile(inFD) - if inFile == nil { - return 0, nil, linuxerr.EBADF - } - defer inFile.DecRef(t) - - // The operation is non-blocking if anything is non-blocking. - // - // N.B. This is a rather simplistic heuristic that avoids some - // poor edge case behavior since the exact semantics here are - // underspecified and vary between versions of Linux itself. - nonBlock := inFile.Flags().NonBlocking || outFile.Flags().NonBlocking || (flags&linux.SPLICE_F_NONBLOCK != 0) - - // Construct our options. - // - // Note that exactly one of the underlying buffers must be a pipe. We - // don't actually have this constraint internally, but we enforce it - // for the semantics of the call. - opts := fs.SpliceOpts{ - Length: count, - } - inFileAttr := inFile.Dirent.Inode.StableAttr - outFileAttr := outFile.Dirent.Inode.StableAttr - switch { - case fs.IsPipe(inFileAttr) && !fs.IsPipe(outFileAttr): - if inOffset != 0 { - return 0, nil, linuxerr.ESPIPE - } - if outOffset != 0 { - if !outFile.Flags().Pwrite { - return 0, nil, linuxerr.EINVAL - } - - var offset int64 - if _, err := primitive.CopyInt64In(t, outOffset, &offset); err != nil { - return 0, nil, err - } - - // Use the destination offset. - opts.DstOffset = true - opts.DstStart = offset - } - case !fs.IsPipe(inFileAttr) && fs.IsPipe(outFileAttr): - if outOffset != 0 { - return 0, nil, linuxerr.ESPIPE - } - if inOffset != 0 { - if !inFile.Flags().Pread { - return 0, nil, linuxerr.EINVAL - } - - var offset int64 - if _, err := primitive.CopyInt64In(t, inOffset, &offset); err != nil { - return 0, nil, err - } - - // Use the source offset. - opts.SrcOffset = true - opts.SrcStart = offset - } - case fs.IsPipe(inFileAttr) && fs.IsPipe(outFileAttr): - if inOffset != 0 || outOffset != 0 { - return 0, nil, linuxerr.ESPIPE - } - - // We may not refer to the same pipe; otherwise it's a continuous loop. - if inFileAttr.InodeID == outFileAttr.InodeID { + if offset < 0 { return 0, nil, linuxerr.EINVAL } - default: + if offset+count < 0 { + return 0, nil, linuxerr.EINVAL + } + } + + // Validate count. This must come after offset checks. + if count < 0 { return 0, nil, linuxerr.EINVAL } - - // Splice data. - n, err := doSplice(t, outFile, inFile, opts, nonBlock) - - // Special files can have additional requirements for granularity. For - // example, read from eventfd returns EINVAL if a size is less 8 bytes. - // Inotify is another example. read will return EINVAL is a buffer is - // too small to return the next event, but a size of an event isn't - // fixed, it is sizeof(struct inotify_event) + {NAME_LEN} + 1. - if n != 0 && err != nil && (fs.IsAnonymous(inFileAttr) || fs.IsAnonymous(outFileAttr)) { - err = nil + if count == 0 { + return 0, nil, nil + } + if count > int64(kernel.MAX_RW_COUNT) { + count = int64(kernel.MAX_RW_COUNT) } - // See above; inFile is chosen arbitrarily here. - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "splice", inFile) + // Copy data. + var ( + total int64 + err error + ) + dw := dualWaiter{ + inFile: inFile, + outFile: outFile, + } + defer dw.destroy() + outPipeFD, outIsPipe := outFile.Impl().(*pipe.VFSPipeFD) + // Reading from input file should never block, since it is regular or + // block device. We only need to check if writing to the output file + // can block. + nonBlock := outFile.StatusFlags()&linux.O_NONBLOCK != 0 + if outIsPipe { + for { + var n int64 + n, err = outPipeFD.SpliceFromNonPipe(t, inFile, offset, count-total) + if offset != -1 { + offset += n + } + total += n + if total == count { + break + } + if err == nil && t.Interrupted() { + err = linuxerr.ErrInterrupted + break + } + if err == linuxerr.ErrWouldBlock && !nonBlock { + err = dw.waitForBoth(t) + } + if err != nil { + break + } + } + } else { + // Read inFile to buffer, then write the contents to outFile. + // + // The buffer size has to be limited to avoid large memory + // allocations and long delays. In Linux, the buffer size is + // limited by a size of an internl pipe. Here, we repeat this + // behavior. + bufSize := count + if bufSize > pipe.MaximumPipeSize { + bufSize = pipe.MaximumPipeSize + } + buf := make([]byte, bufSize) + for { + if int64(len(buf)) > count-total { + buf = buf[:count-total] + } + var readN int64 + if offset != -1 { + readN, err = inFile.PRead(t, usermem.BytesIOSequence(buf), offset, vfs.ReadOptions{}) + offset += readN + } else { + readN, err = inFile.Read(t, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) + } + + // Write all of the bytes that we read. This may need + // multiple write calls to complete. + wbuf := buf[:readN] + for len(wbuf) > 0 { + var writeN int64 + writeN, err = outFile.Write(t, usermem.BytesIOSequence(wbuf), vfs.WriteOptions{}) + wbuf = wbuf[writeN:] + if err == linuxerr.ErrWouldBlock && !nonBlock { + err = dw.waitForOut(t) + } + if err != nil { + // We didn't complete the write. Only report the bytes that were actually + // written, and rewind offsets as needed. + notWritten := int64(len(wbuf)) + readN -= notWritten + if offset == -1 { + // We modified the offset of the input file itself during the read + // operation. Rewind it. + if _, seekErr := inFile.Seek(t, -notWritten, linux.SEEK_CUR); seekErr != nil { + // Log the error but don't return it, since the write has already + // completed successfully. + log.Warningf("failed to roll back input file offset: %v", seekErr) + } + } else { + // The sendfile call was provided an offset parameter that should be + // adjusted to reflect the number of bytes sent. Rewind it. + offset -= notWritten + } + break + } + } + + total += readN + if total == count { + break + } + if err == nil && t.Interrupted() { + err = linuxerr.ErrInterrupted + break + } + if err == linuxerr.ErrWouldBlock && !nonBlock { + err = dw.waitForBoth(t) + } + if err != nil { + break + } + } + } + + if offsetAddr != 0 { + // Copy out the new offset. + offsetP := primitive.Uint64(offset) + if _, err := offsetP.CopyOut(t, offsetAddr); err != nil { + return 0, nil, err + } + } + + if total != 0 { + if err != nil && err != io.EOF && err != linuxerr.ErrWouldBlock { + // If a partial write is completed, the error is dropped. Log it here. + log.Debugf("sendfile completed a partial write with error: %v", err) + err = nil + } + } + + // We can only pass a single file to handleIOError, so pick inFile arbitrarily. + // This is used only for debugging purposes. + return uintptr(total), nil, HandleIOError(t, total != 0, err, linuxerr.ERESTARTSYS, "sendfile", inFile) } -// Tee imlements tee(2). -func Tee(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - inFD := args[0].Int() - outFD := args[1].Int() - count := int64(args[2].SizeT()) - flags := args[3].Int() +// dualWaiter is used to wait on one or both vfs.FileDescriptions. It is not +// thread-safe, and does not take a reference on the vfs.FileDescriptions. +// +// Users must call destroy() when finished. +type dualWaiter struct { + inFile *vfs.FileDescription + outFile *vfs.FileDescription - // Check for invalid flags. - if flags&^(linux.SPLICE_F_MOVE|linux.SPLICE_F_NONBLOCK|linux.SPLICE_F_MORE|linux.SPLICE_F_GIFT) != 0 { - return 0, nil, linuxerr.EINVAL + inW waiter.Entry + inCh chan struct{} + outW waiter.Entry + outCh chan struct{} +} + +// waitForBoth waits for both dw.inFile and dw.outFile to be ready. +func (dw *dualWaiter) waitForBoth(t *kernel.Task) error { + if dw.inFile.Readiness(eventMaskRead)&eventMaskRead == 0 { + if dw.inCh == nil { + dw.inW, dw.inCh = waiter.NewChannelEntry(eventMaskRead) + if err := dw.inFile.EventRegister(&dw.inW); err != nil { + return err + } + // We might be ready now. Try again before blocking. + return nil + } + if err := t.Block(dw.inCh); err != nil { + return err + } } - - // Get files. - outFile := t.GetFile(outFD) - if outFile == nil { - return 0, nil, linuxerr.EBADF - } - defer outFile.DecRef(t) - - inFile := t.GetFile(inFD) - if inFile == nil { - return 0, nil, linuxerr.EBADF - } - defer inFile.DecRef(t) - - // All files must be pipes. - if !fs.IsPipe(inFile.Dirent.Inode.StableAttr) || !fs.IsPipe(outFile.Dirent.Inode.StableAttr) { - return 0, nil, linuxerr.EINVAL + return dw.waitForOut(t) +} + +// waitForOut waits for dw.outfile to be read. +func (dw *dualWaiter) waitForOut(t *kernel.Task) error { + // Don't bother checking readiness of the outFile, because it's not a + // guarantee that it won't return EWOULDBLOCK. Both pipes and eventfds + // can be "ready" but will reject writes of certain sizes with + // EWOULDBLOCK. See b/172075629, b/170743336. + if dw.outCh == nil { + dw.outW, dw.outCh = waiter.NewChannelEntry(eventMaskWrite) + if err := dw.outFile.EventRegister(&dw.outW); err != nil { + return err + } + // We might be ready to write now. Try again before blocking. + return nil } - - // We may not refer to the same pipe; see above. - if inFile.Dirent.Inode.StableAttr.InodeID == outFile.Dirent.Inode.StableAttr.InodeID { - return 0, nil, linuxerr.EINVAL + return t.Block(dw.outCh) +} + +// destroy cleans up resources help by dw. No more calls to wait* can occur +// after destroy is called. +func (dw *dualWaiter) destroy() { + if dw.inCh != nil { + dw.inFile.EventUnregister(&dw.inW) + dw.inCh = nil } - - // The operation is non-blocking if anything is non-blocking. - nonBlock := inFile.Flags().NonBlocking || outFile.Flags().NonBlocking || (flags&linux.SPLICE_F_NONBLOCK != 0) - - // Splice data. - n, err := doSplice(t, outFile, inFile, fs.SpliceOpts{ - Length: count, - Dup: true, - }, nonBlock) - - // Tee doesn't change a state of inFD, so it can't lose any data. - if n != 0 { - err = nil + if dw.outCh != nil { + dw.outFile.EventUnregister(&dw.outW) + dw.outCh = nil } - - // See above; inFile is chosen arbitrarily here. - return uintptr(n), nil, handleIOError(t, false, err, linuxerr.ERESTARTSYS, "tee", inFile) + dw.inFile = nil + dw.outFile = nil } diff --git a/pkg/sentry/syscalls/linux/sys_stat.go b/pkg/sentry/syscalls/linux/sys_stat.go index 3da385c66..92c3cf0e7 100644 --- a/pkg/sentry/syscalls/linux/sys_stat.go +++ b/pkg/sentry/syscalls/linux/sys_stat.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,275 +16,259 @@ package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/bits" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) -// LINT.IfChange - -// Stat implements linux syscall stat(2). +// Stat implements Linux syscall stat(2). func Stat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() + pathAddr := args[0].Pointer() statAddr := args[1].Pointer() - - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) - if err != nil { - return 0, nil, err - } - - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - return stat(t, d, dirPath, statAddr) - }) + return 0, nil, fstatat(t, linux.AT_FDCWD, pathAddr, statAddr, 0 /* flags */) } -// Fstatat implements linux syscall newfstatat, i.e. fstatat(2). -func Fstatat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() +// Lstat implements Linux syscall lstat(2). +func Lstat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + pathAddr := args[0].Pointer() + statAddr := args[1].Pointer() + return 0, nil, fstatat(t, linux.AT_FDCWD, pathAddr, statAddr, linux.AT_SYMLINK_NOFOLLOW) +} + +// Newfstatat implements Linux syscall newfstatat, which backs fstatat(2). +func Newfstatat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + dirfd := args[0].Int() + pathAddr := args[1].Pointer() statAddr := args[2].Pointer() flags := args[3].Int() + return 0, nil, fstatat(t, dirfd, pathAddr, statAddr, flags) +} - path, dirPath, err := copyInPath(t, addr, flags&linux.AT_EMPTY_PATH != 0) - if err != nil { - return 0, nil, err +func fstatat(t *kernel.Task, dirfd int32, pathAddr, statAddr hostarch.Addr, flags int32) error { + if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 { + return linuxerr.EINVAL } - if path == "" { - // Annoying. What's wrong with fstat? - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF + opts := vfs.StatOptions{ + Mask: linux.STATX_BASIC_STATS, + } + + path, err := copyInPath(t, pathAddr) + if err != nil { + return err + } + + root := t.FSContext().RootDirectoryVFS2() + defer root.DecRef(t) + start := root + if !path.Absolute { + if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 { + return linuxerr.ENOENT + } + if dirfd == linux.AT_FDCWD { + start = t.FSContext().WorkingDirectoryVFS2() + defer start.DecRef(t) + } else { + dirfile := t.GetFileVFS2(dirfd) + if dirfile == nil { + return linuxerr.EBADF + } + if !path.HasComponents() { + // Use FileDescription.Stat() instead of + // VirtualFilesystem.StatAt() for fstatat(fd, ""), since the + // former may be able to use opened file state to expedite the + // Stat. + statx, err := dirfile.Stat(t, opts) + dirfile.DecRef(t) + if err != nil { + return err + } + var stat linux.Stat + convertStatxToUserStat(t, &statx, &stat) + _, err = stat.CopyOut(t, statAddr) + return err + } + start = dirfile.VirtualDentry() + start.IncRef() + defer start.DecRef(t) + dirfile.DecRef(t) } - defer file.DecRef(t) - - return 0, nil, fstat(t, file, statAddr) } - // If the path ends in a slash (i.e. dirPath is true) or if AT_SYMLINK_NOFOLLOW is unset, - // then we must resolve the final component. - resolve := dirPath || flags&linux.AT_SYMLINK_NOFOLLOW == 0 - - return 0, nil, fileOpOn(t, fd, path, resolve, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - return stat(t, d, dirPath, statAddr) - }) -} - -// Lstat implements linux syscall lstat(2). -func Lstat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - statAddr := args[1].Pointer() - - path, dirPath, err := copyInPath(t, addr, false /* allowEmpty */) + statx, err := t.Kernel().VFS().StatAt(t, t.Credentials(), &vfs.PathOperation{ + Root: root, + Start: start, + Path: path, + FollowFinalSymlink: flags&linux.AT_SYMLINK_NOFOLLOW == 0, + }, &opts) if err != nil { - return 0, nil, err + return err } - - // If the path ends in a slash (i.e. dirPath is true), then we *do* - // want to resolve the final component. - resolve := dirPath - - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, resolve, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - return stat(t, d, dirPath, statAddr) - }) + var stat linux.Stat + convertStatxToUserStat(t, &statx, &stat) + _, err = stat.CopyOut(t, statAddr) + return err } -// Fstat implements linux syscall fstat(2). +func timespecFromStatxTimestamp(sxts linux.StatxTimestamp) linux.Timespec { + return linux.Timespec{ + Sec: sxts.Sec, + Nsec: int64(sxts.Nsec), + } +} + +// Fstat implements Linux syscall fstat(2). func Fstat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() statAddr := args[1].Pointer() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - return 0, nil, fstat(t, file, statAddr) -} - -// stat implements stat from the given *fs.Dirent. -func stat(t *kernel.Task, d *fs.Dirent, dirPath bool, statAddr hostarch.Addr) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - uattr, err := d.Inode.UnstableAttr(t) + statx, err := file.Stat(t, vfs.StatOptions{ + Mask: linux.STATX_BASIC_STATS, + }) if err != nil { - return err + return 0, nil, err } - s := statFromAttrs(t, d.Inode.StableAttr, uattr) - _, err = s.CopyOut(t, statAddr) - return err + var stat linux.Stat + convertStatxToUserStat(t, &statx, &stat) + _, err = stat.CopyOut(t, statAddr) + return 0, nil, err } -// fstat implements fstat for the given *fs.File. -func fstat(t *kernel.Task, f *fs.File, statAddr hostarch.Addr) error { - uattr, err := f.UnstableAttr(t) - if err != nil { - return err - } - s := statFromAttrs(t, f.Dirent.Inode.StableAttr, uattr) - _, err = s.CopyOut(t, statAddr) - return err -} - -// Statx implements linux syscall statx(2). +// Statx implements Linux syscall statx(2). func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() + dirfd := args[0].Int() pathAddr := args[1].Pointer() flags := args[2].Int() mask := args[3].Uint() statxAddr := args[4].Pointer() + if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW|linux.AT_STATX_SYNC_TYPE) != 0 { + return 0, nil, linuxerr.EINVAL + } + // Make sure that only one sync type option is set. + syncType := uint32(flags & linux.AT_STATX_SYNC_TYPE) + if syncType != 0 && !bits.IsPowerOfTwo32(syncType) { + return 0, nil, linuxerr.EINVAL + } if mask&linux.STATX__RESERVED != 0 { return 0, nil, linuxerr.EINVAL } - if flags&^(linux.AT_SYMLINK_NOFOLLOW|linux.AT_EMPTY_PATH|linux.AT_STATX_SYNC_TYPE) != 0 { - return 0, nil, linuxerr.EINVAL - } - if flags&linux.AT_STATX_SYNC_TYPE == linux.AT_STATX_SYNC_TYPE { - return 0, nil, linuxerr.EINVAL + + opts := vfs.StatOptions{ + Mask: mask, + Sync: uint32(flags & linux.AT_STATX_SYNC_TYPE), } - path, dirPath, err := copyInPath(t, pathAddr, flags&linux.AT_EMPTY_PATH != 0) + path, err := copyInPath(t, pathAddr) if err != nil { return 0, nil, err } - if path == "" { - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF + root := t.FSContext().RootDirectoryVFS2() + defer root.DecRef(t) + start := root + if !path.Absolute { + if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 { + return 0, nil, linuxerr.ENOENT } - defer file.DecRef(t) - uattr, err := file.UnstableAttr(t) - if err != nil { - return 0, nil, err + if dirfd == linux.AT_FDCWD { + start = t.FSContext().WorkingDirectoryVFS2() + defer start.DecRef(t) + } else { + dirfile := t.GetFileVFS2(dirfd) + if dirfile == nil { + return 0, nil, linuxerr.EBADF + } + if !path.HasComponents() { + // Use FileDescription.Stat() instead of + // VirtualFilesystem.StatAt() for statx(fd, ""), since the + // former may be able to use opened file state to expedite the + // Stat. + statx, err := dirfile.Stat(t, opts) + dirfile.DecRef(t) + if err != nil { + return 0, nil, err + } + userifyStatx(t, &statx) + _, err = statx.CopyOut(t, statxAddr) + return 0, nil, err + } + start = dirfile.VirtualDentry() + start.IncRef() + defer start.DecRef(t) + dirfile.DecRef(t) } - return 0, nil, statx(t, file.Dirent.Inode.StableAttr, uattr, statxAddr) } - resolve := dirPath || flags&linux.AT_SYMLINK_NOFOLLOW == 0 - - return 0, nil, fileOpOn(t, fd, path, resolve, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - uattr, err := d.Inode.UnstableAttr(t) - if err != nil { - return err - } - return statx(t, d.Inode.StableAttr, uattr, statxAddr) - }) -} - -func statx(t *kernel.Task, sattr fs.StableAttr, uattr fs.UnstableAttr, statxAddr hostarch.Addr) error { - // "[T]he kernel may return fields that weren't requested and may fail to - // return fields that were requested, depending on what the backing - // filesystem supports. - // [...] - // A filesystem may also fill in fields that the caller didn't ask for - // if it has values for them available and the information is available - // at no extra cost. If this happens, the corresponding bits will be - // set in stx_mask." -- statx(2) - // - // We fill in all the values we have (which currently does not include - // btime, see b/135608823), regardless of what the user asked for. The - // STATX_BASIC_STATS mask indicates that all fields are present except - // for btime. - - devMajor, devMinor := linux.DecodeDeviceID(uint32(sattr.DeviceID)) - s := linux.Statx{ - // TODO(b/135608823): Support btime, and then change this to - // STATX_ALL to indicate presence of btime. - Mask: linux.STATX_BASIC_STATS, - - // No attributes, and none supported. - Attributes: 0, - AttributesMask: 0, - - Blksize: uint32(sattr.BlockSize), - Nlink: uint32(uattr.Links), - UID: uint32(uattr.Owner.UID.In(t.UserNamespace()).OrOverflow()), - GID: uint32(uattr.Owner.GID.In(t.UserNamespace()).OrOverflow()), - Mode: uint16(sattr.Type.LinuxType()) | uint16(uattr.Perms.LinuxMode()), - Ino: sattr.InodeID, - Size: uint64(uattr.Size), - Blocks: uint64(uattr.Usage) / 512, - Atime: uattr.AccessTime.StatxTimestamp(), - Ctime: uattr.StatusChangeTime.StatxTimestamp(), - Mtime: uattr.ModificationTime.StatxTimestamp(), - RdevMajor: uint32(sattr.DeviceFileMajor), - RdevMinor: sattr.DeviceFileMinor, - DevMajor: uint32(devMajor), - DevMinor: devMinor, + statx, err := t.Kernel().VFS().StatAt(t, t.Credentials(), &vfs.PathOperation{ + Root: root, + Start: start, + Path: path, + FollowFinalSymlink: flags&linux.AT_SYMLINK_NOFOLLOW == 0, + }, &opts) + if err != nil { + return 0, nil, err } - _, err := s.CopyOut(t, statxAddr) - return err + userifyStatx(t, &statx) + _, err = statx.CopyOut(t, statxAddr) + return 0, nil, err } -// Statfs implements linux syscall statfs(2). +func userifyStatx(t *kernel.Task, statx *linux.Statx) { + userns := t.UserNamespace() + statx.UID = uint32(auth.KUID(statx.UID).In(userns).OrOverflow()) + statx.GID = uint32(auth.KGID(statx.GID).In(userns).OrOverflow()) +} + +// Statfs implements Linux syscall statfs(2). func Statfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - statfsAddr := args[1].Pointer() + pathAddr := args[0].Pointer() + bufAddr := args[1].Pointer() - path, _, err := copyInPath(t, addr, false /* allowEmpty */) + path, err := copyInPath(t, pathAddr) if err != nil { return 0, nil, err } + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink) + if err != nil { + return 0, nil, err + } + defer tpop.Release(t) - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent, _ uint) error { - return statfsImpl(t, d, statfsAddr) - }) + statfs, err := t.Kernel().VFS().StatFSAt(t, t.Credentials(), &tpop.pop) + if err != nil { + return 0, nil, err + } + _, err = statfs.CopyOut(t, bufAddr) + return 0, nil, err } -// Fstatfs implements linux syscall fstatfs(2). +// Fstatfs implements Linux syscall fstatfs(2). func Fstatfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - statfsAddr := args[1].Pointer() + bufAddr := args[1].Pointer() - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - return 0, nil, statfsImpl(t, file.Dirent, statfsAddr) -} - -// statfsImpl implements the linux syscall statfs and fstatfs based on a Dirent, -// copying the statfs structure out to addr on success, otherwise an error is -// returned. -func statfsImpl(t *kernel.Task, d *fs.Dirent, addr hostarch.Addr) error { - info, err := d.Inode.StatFS(t) + tpop, err := getTaskPathOperation(t, fd, fspath.Path{}, allowEmptyPath, nofollowFinalSymlink) if err != nil { - return err + return 0, nil, err } - // Construct the statfs structure and copy it out. - statfs := linux.Statfs{ - Type: info.Type, - // Treat block size and fragment size as the same, as - // most consumers of this structure will expect one - // or the other to be filled in. - BlockSize: d.Inode.StableAttr.BlockSize, - Blocks: info.TotalBlocks, - // We don't have the concept of reserved blocks, so - // report blocks free the same as available blocks. - // This is a normal thing for filesystems, to do, see - // udf, hugetlbfs, tmpfs, among others. - BlocksFree: info.FreeBlocks, - BlocksAvailable: info.FreeBlocks, - Files: info.TotalFiles, - FilesFree: info.FreeFiles, - // Same as Linux for simple_statfs, see fs/libfs.c. - NameLength: linux.NAME_MAX, - FragmentSize: d.Inode.StableAttr.BlockSize, - // Leave other fields 0 like simple_statfs does. - } - _, err = statfs.CopyOut(t, addr) - return err -} + defer tpop.Release(t) -// LINT.ThenChange(vfs2/stat.go) + statfs, err := t.Kernel().VFS().StatFSAt(t, t.Credentials(), &tpop.pop) + if err != nil { + return 0, nil, err + } + _, err = statfs.CopyOut(t, bufAddr) + return 0, nil, err +} diff --git a/pkg/sentry/syscalls/linux/sys_stat_amd64.go b/pkg/sentry/syscalls/linux/sys_stat_amd64.go index e38066ea8..31b2692fb 100644 --- a/pkg/sentry/syscalls/linux/sys_stat_amd64.go +++ b/pkg/sentry/syscalls/linux/sys_stat_amd64.go @@ -19,28 +19,29 @@ package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ) -// LINT.IfChange - -func statFromAttrs(t *kernel.Task, sattr fs.StableAttr, uattr fs.UnstableAttr) linux.Stat { - return linux.Stat{ - Dev: sattr.DeviceID, - Ino: sattr.InodeID, - Nlink: uattr.Links, - Mode: sattr.Type.LinuxType() | uint32(uattr.Perms.LinuxMode()), - UID: uint32(uattr.Owner.UID.In(t.UserNamespace()).OrOverflow()), - GID: uint32(uattr.Owner.GID.In(t.UserNamespace()).OrOverflow()), - Rdev: uint64(linux.MakeDeviceID(sattr.DeviceFileMajor, sattr.DeviceFileMinor)), - Size: uattr.Size, - Blksize: sattr.BlockSize, - Blocks: uattr.Usage / 512, - ATime: uattr.AccessTime.Timespec(), - MTime: uattr.ModificationTime.Timespec(), - CTime: uattr.StatusChangeTime.Timespec(), +// This takes both input and output as pointer arguments to avoid copying large +// structs. +func convertStatxToUserStat(t *kernel.Task, statx *linux.Statx, stat *linux.Stat) { + // Linux just copies fields from struct kstat without regard to struct + // kstat::result_mask (fs/stat.c:cp_new_stat()), so we do too. + userns := t.UserNamespace() + *stat = linux.Stat{ + Dev: uint64(linux.MakeDeviceID(uint16(statx.DevMajor), statx.DevMinor)), + Ino: statx.Ino, + Nlink: uint64(statx.Nlink), + Mode: uint32(statx.Mode), + UID: uint32(auth.KUID(statx.UID).In(userns).OrOverflow()), + GID: uint32(auth.KGID(statx.GID).In(userns).OrOverflow()), + Rdev: uint64(linux.MakeDeviceID(uint16(statx.RdevMajor), statx.RdevMinor)), + Size: int64(statx.Size), + Blksize: int64(statx.Blksize), + Blocks: int64(statx.Blocks), + ATime: timespecFromStatxTimestamp(statx.Atime), + MTime: timespecFromStatxTimestamp(statx.Mtime), + CTime: timespecFromStatxTimestamp(statx.Ctime), } } - -// LINT.ThenChange(vfs2/stat_amd64.go) diff --git a/pkg/sentry/syscalls/linux/sys_stat_arm64.go b/pkg/sentry/syscalls/linux/sys_stat_arm64.go index b2ea390c5..8d2b1e0b3 100644 --- a/pkg/sentry/syscalls/linux/sys_stat_arm64.go +++ b/pkg/sentry/syscalls/linux/sys_stat_arm64.go @@ -19,28 +19,29 @@ package linux import ( "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ) -// LINT.IfChange - -func statFromAttrs(t *kernel.Task, sattr fs.StableAttr, uattr fs.UnstableAttr) linux.Stat { - return linux.Stat{ - Dev: sattr.DeviceID, - Ino: sattr.InodeID, - Nlink: uint32(uattr.Links), - Mode: sattr.Type.LinuxType() | uint32(uattr.Perms.LinuxMode()), - UID: uint32(uattr.Owner.UID.In(t.UserNamespace()).OrOverflow()), - GID: uint32(uattr.Owner.GID.In(t.UserNamespace()).OrOverflow()), - Rdev: uint64(linux.MakeDeviceID(sattr.DeviceFileMajor, sattr.DeviceFileMinor)), - Size: uattr.Size, - Blksize: int32(sattr.BlockSize), - Blocks: uattr.Usage / 512, - ATime: uattr.AccessTime.Timespec(), - MTime: uattr.ModificationTime.Timespec(), - CTime: uattr.StatusChangeTime.Timespec(), +// This takes both input and output as pointer arguments to avoid copying large +// structs. +func convertStatxToUserStat(t *kernel.Task, statx *linux.Statx, stat *linux.Stat) { + // Linux just copies fields from struct kstat without regard to struct + // kstat::result_mask (fs/stat.c:cp_new_stat()), so we do too. + userns := t.UserNamespace() + *stat = linux.Stat{ + Dev: uint64(linux.MakeDeviceID(uint16(statx.DevMajor), statx.DevMinor)), + Ino: statx.Ino, + Nlink: uint32(statx.Nlink), + Mode: uint32(statx.Mode), + UID: uint32(auth.KUID(statx.UID).In(userns).OrOverflow()), + GID: uint32(auth.KGID(statx.GID).In(userns).OrOverflow()), + Rdev: uint64(linux.MakeDeviceID(uint16(statx.RdevMajor), statx.RdevMinor)), + Size: int64(statx.Size), + Blksize: int32(statx.Blksize), + Blocks: int64(statx.Blocks), + ATime: timespecFromStatxTimestamp(statx.Atime), + MTime: timespecFromStatxTimestamp(statx.Mtime), + CTime: timespecFromStatxTimestamp(statx.Ctime), } } - -// LINT.ThenChange(vfs2/stat_arm64.go) diff --git a/pkg/sentry/syscalls/linux/sys_sync.go b/pkg/sentry/syscalls/linux/sys_sync.go index 6c238e40f..d46ab31f1 100644 --- a/pkg/sentry/syscalls/linux/sys_sync.go +++ b/pkg/sentry/syscalls/linux/sys_sync.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,125 +18,102 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" ) -// LINT.IfChange - -// Sync implements linux system call sync(2). +// Sync implements Linux syscall sync(2). func Sync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - t.MountNamespace().SyncAll(t) - // Sync is always successful. - return 0, nil, nil + return 0, nil, t.Kernel().VFS().SyncAllFilesystems(t) } -// Syncfs implements linux system call syncfs(2). +// Syncfs implements Linux syscall syncfs(2). func Syncfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - // Use "sync-the-world" for now, it's guaranteed that fd is at least - // on the root filesystem. - return Sync(t, args) + if file.StatusFlags()&linux.O_PATH != 0 { + return 0, nil, linuxerr.EBADF + } + + return 0, nil, file.SyncFS(t) } -// Fsync implements linux syscall fsync(2). +// Fsync implements Linux syscall fsync(2). func Fsync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - err := file.Fsync(t, 0, fs.FileMaxOffset, fs.SyncAll) - return 0, nil, linuxerr.ConvertIntr(err, linuxerr.ERESTARTSYS) + return 0, nil, file.Sync(t) } -// Fdatasync implements linux syscall fdatasync(2). -// -// At the moment, it just calls Fsync, which is a big hammer, but correct. +// Fdatasync implements Linux syscall fdatasync(2). func Fdatasync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - err := file.Fsync(t, 0, fs.FileMaxOffset, fs.SyncData) - return 0, nil, linuxerr.ConvertIntr(err, linuxerr.ERESTARTSYS) + // TODO(gvisor.dev/issue/1897): Avoid writeback of unnecessary metadata. + return Fsync(t, args) } -// SyncFileRange implements linux syscall sync_file_rage(2) +// SyncFileRange implements Linux syscall sync_file_range(2). func SyncFileRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - var err error - fd := args[0].Int() offset := args[1].Int64() nbytes := args[2].Int64() - uflags := args[3].Uint() + flags := args[3].Uint() - if offset < 0 || offset+nbytes < offset { + // Check for negative values and overflow. + if offset < 0 || offset+nbytes < 0 { + return 0, nil, linuxerr.EINVAL + } + if flags&^(linux.SYNC_FILE_RANGE_WAIT_BEFORE|linux.SYNC_FILE_RANGE_WRITE|linux.SYNC_FILE_RANGE_WAIT_AFTER) != 0 { return 0, nil, linuxerr.EINVAL } - if uflags&^(linux.SYNC_FILE_RANGE_WAIT_BEFORE| - linux.SYNC_FILE_RANGE_WRITE| - linux.SYNC_FILE_RANGE_WAIT_AFTER) != 0 { - return 0, nil, linuxerr.EINVAL - } - - if nbytes == 0 { - nbytes = fs.FileMaxOffset - } - - file := t.GetFile(fd) + file := t.GetFileVFS2(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - // SYNC_FILE_RANGE_WAIT_BEFORE waits upon write-out of all pages in the - // specified range that have already been submitted to the device - // driver for write-out before performing any write. - if uflags&linux.SYNC_FILE_RANGE_WAIT_BEFORE != 0 && - uflags&linux.SYNC_FILE_RANGE_WAIT_AFTER == 0 { + // TODO(gvisor.dev/issue/1897): Currently, the only file syncing we support + // is a full-file sync, i.e. fsync(2). As a result, there are severe + // limitations on how much we support sync_file_range: + // - In Linux, sync_file_range(2) doesn't write out the file's metadata, even + // if the file size is changed. We do. + // - We always sync the entire file instead of [offset, offset+nbytes). + // - We do not support the use of WAIT_BEFORE without WAIT_AFTER. For + // correctness, we would have to perform a write-out every time WAIT_BEFORE + // was used, but this would be much more expensive than expected if there + // were no write-out operations in progress. + // - Whenever WAIT_AFTER is used, we sync the file. + // - Ignore WRITE. If this flag is used with WAIT_AFTER, then the file will + // be synced anyway. If this flag is used without WAIT_AFTER, then it is + // safe (and less expensive) to do nothing, because the syscall will not + // wait for the write-out to complete--we only need to make sure that the + // next time WAIT_BEFORE or WAIT_AFTER are used, the write-out completes. + // - According to fs/sync.c, WAIT_BEFORE|WAIT_AFTER "will detect any I/O + // errors or ENOSPC conditions and will return those to the caller, after + // clearing the EIO and ENOSPC flags in the address_space." We don't do + // this. + + if flags&linux.SYNC_FILE_RANGE_WAIT_BEFORE != 0 && + flags&linux.SYNC_FILE_RANGE_WAIT_AFTER == 0 { t.Kernel().EmitUnimplementedEvent(t) return 0, nil, linuxerr.ENOSYS } - // SYNC_FILE_RANGE_WRITE initiates write-out of all dirty pages in the - // specified range which are not presently submitted write-out. - // - // It looks impossible to implement this functionality without a - // massive rework of the vfs subsystem. file.Fsync() take a file lock - // for the entire operation, so even if it is running in a go routing, - // it blocks other file operations instead of flushing data in the - // background. - // - // It should be safe to skipped this flag while nobody uses - // SYNC_FILE_RANGE_WAIT_BEFORE. - _ = nbytes - - // SYNC_FILE_RANGE_WAIT_AFTER waits upon write-out of all pages in the - // range after performing any write. - // - // In Linux, sync_file_range() doesn't writes out the file's - // meta-data, but fdatasync() does if a file size is changed. - if uflags&linux.SYNC_FILE_RANGE_WAIT_AFTER != 0 { - err = file.Fsync(t, offset, fs.FileMaxOffset, fs.SyncData) + if flags&linux.SYNC_FILE_RANGE_WAIT_AFTER != 0 { + if err := file.Sync(t); err != nil { + return 0, nil, linuxerr.ConvertIntr(err, linuxerr.ERESTARTSYS) + } } - - return 0, nil, linuxerr.ConvertIntr(err, linuxerr.ERESTARTSYS) + return 0, nil, nil } - -// LINT.ThenChange(vfs2/sync.go) diff --git a/pkg/sentry/syscalls/linux/sys_thread.go b/pkg/sentry/syscalls/linux/sys_thread.go index 9c3ed555d..b70555297 100644 --- a/pkg/sentry/syscalls/linux/sys_thread.go +++ b/pkg/sentry/syscalls/linux/sys_thread.go @@ -15,18 +15,18 @@ package linux import ( - "path" - "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsbridge" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/sched" "gvisor.dev/gvisor/pkg/sentry/loader" + "gvisor.dev/gvisor/pkg/sentry/seccheck" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/usermem" ) @@ -65,30 +65,31 @@ func Gettid(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal // Execve implements linux syscall execve(2). func Execve(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - filenameAddr := args[0].Pointer() + pathnameAddr := args[0].Pointer() argvAddr := args[1].Pointer() envvAddr := args[2].Pointer() - - return execveat(t, linux.AT_FDCWD, filenameAddr, argvAddr, envvAddr, 0) + return execveat(t, linux.AT_FDCWD, pathnameAddr, argvAddr, envvAddr, 0 /* flags */) } // Execveat implements linux syscall execveat(2). func Execveat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirFD := args[0].Int() + dirfd := args[0].Int() pathnameAddr := args[1].Pointer() argvAddr := args[2].Pointer() envvAddr := args[3].Pointer() flags := args[4].Int() - - return execveat(t, dirFD, pathnameAddr, argvAddr, envvAddr, flags) + return execveat(t, dirfd, pathnameAddr, argvAddr, envvAddr, flags) } -func execveat(t *kernel.Task, dirFD int32, pathnameAddr, argvAddr, envvAddr hostarch.Addr, flags int32) (uintptr, *kernel.SyscallControl, error) { +func execveat(t *kernel.Task, dirfd int32, pathnameAddr, argvAddr, envvAddr hostarch.Addr, flags int32) (uintptr, *kernel.SyscallControl, error) { + if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 { + return 0, nil, linuxerr.EINVAL + } + pathname, err := t.CopyInString(pathnameAddr, linux.PATH_MAX) if err != nil { return 0, nil, err } - var argv, envv []string if argvAddr != 0 { var err error @@ -105,64 +106,59 @@ func execveat(t *kernel.Task, dirFD int32, pathnameAddr, argvAddr, envvAddr host } } - if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 { - return 0, nil, linuxerr.EINVAL - } - atEmptyPath := flags&linux.AT_EMPTY_PATH != 0 - if !atEmptyPath && len(pathname) == 0 { - return 0, nil, linuxerr.ENOENT - } - resolveFinal := flags&linux.AT_SYMLINK_NOFOLLOW == 0 - - root := t.FSContext().RootDirectory() + root := t.FSContext().RootDirectoryVFS2() defer root.DecRef(t) - - var wd *fs.Dirent var executable fsbridge.File - var closeOnExec bool - if dirFD == linux.AT_FDCWD || path.IsAbs(pathname) { - // Even if the pathname is absolute, we may still need the wd - // for interpreter scripts if the path of the interpreter is - // relative. - wd = t.FSContext().WorkingDirectory() - } else { - // Need to extract the given FD. - f, fdFlags := t.FDTable().Get(dirFD) - if f == nil { + defer func() { + if executable != nil { + executable.DecRef(t) + } + }() + closeOnExec := false + if path := fspath.Parse(pathname); dirfd != linux.AT_FDCWD && !path.Absolute { + // We must open the executable ourselves since dirfd is used as the + // starting point while resolving path, but the task working directory + // is used as the starting point while resolving interpreters (Linux: + // fs/binfmt_script.c:load_script() => fs/exec.c:open_exec() => + // do_open_execat(fd=AT_FDCWD)), and the loader package is currently + // incapable of handling this correctly. + if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 { + return 0, nil, linuxerr.ENOENT + } + dirfile, dirfileFlags := t.FDTable().GetVFS2(dirfd) + if dirfile == nil { return 0, nil, linuxerr.EBADF } - defer f.DecRef(t) - closeOnExec = fdFlags.CloseOnExec - - if atEmptyPath && len(pathname) == 0 { - // TODO(gvisor.dev/issue/160): Linux requires only execute permission, - // not read. However, our backing filesystems may prevent us from reading - // the file without read permission. Additionally, a task with a - // non-readable executable has additional constraints on access via - // ptrace and procfs. - if err := f.Dirent.Inode.CheckPermission(t, fs.PermMask{Read: true, Execute: true}); err != nil { - return 0, nil, err - } - executable = fsbridge.NewFSFile(f) - pathname = executable.PathnameWithDeleted(t) - } else { - wd = f.Dirent - wd.IncRef() - if !fs.IsDir(wd.Inode.StableAttr) { - return 0, nil, linuxerr.ENOTDIR - } + start := dirfile.VirtualDentry() + start.IncRef() + dirfile.DecRef(t) + closeOnExec = dirfileFlags.CloseOnExec + file, err := t.Kernel().VFS().OpenAt(t, t.Credentials(), &vfs.PathOperation{ + Root: root, + Start: start, + Path: path, + FollowFinalSymlink: flags&linux.AT_SYMLINK_NOFOLLOW == 0, + }, &vfs.OpenOptions{ + Flags: linux.O_RDONLY, + FileExec: true, + }) + start.DecRef(t) + if err != nil { + return 0, nil, err } - } - if wd != nil { - defer wd.DecRef(t) + executable = fsbridge.NewVFSFile(file) + pathname = executable.PathnameWithDeleted(t) } // Load the new TaskImage. + mntns := t.MountNamespaceVFS2() + wd := t.FSContext().WorkingDirectoryVFS2() + defer wd.DecRef(t) remainingTraversals := uint(linux.MaxSymlinkTraversals) loadArgs := loader.LoadArgs{ - Opener: fsbridge.NewFSLookup(t.MountNamespace(), root, wd), + Opener: fsbridge.NewVFSLookup(mntns, root, wd), RemainingTraversals: &remainingTraversals, - ResolveFinal: resolveFinal, + ResolveFinal: flags&linux.AT_SYMLINK_NOFOLLOW == 0, Filename: pathname, File: executable, CloseOnExec: closeOnExec, @@ -170,13 +166,26 @@ func execveat(t *kernel.Task, dirFD int32, pathnameAddr, argvAddr, envvAddr host Envv: envv, Features: t.Kernel().FeatureSet(), } + if seccheck.Global.Enabled(seccheck.PointExecve) { + // Retain the first executable file that is opened (which may open + // multiple executable files while resolving interpreter scripts). + if executable == nil { + loadArgs.AfterOpen = func(f fsbridge.File) { + if executable == nil { + f.IncRef() + executable = f + pathname = executable.PathnameWithDeleted(t) + } + } + } + } image, se := t.Kernel().LoadTaskImage(t, loadArgs) if se != nil { return 0, nil, se.ToError() } - ctrl, err := t.Execve(image, argv, envv, nil, "") + ctrl, err := t.Execve(image, argv, envv, executable, pathname) return 0, ctrl, err } diff --git a/pkg/sentry/syscalls/linux/sys_timerfd.go b/pkg/sentry/syscalls/linux/sys_timerfd.go index 4eeb94231..5a07456bf 100644 --- a/pkg/sentry/syscalls/linux/sys_timerfd.go +++ b/pkg/sentry/syscalls/linux/sys_timerfd.go @@ -18,8 +18,7 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/timerfd" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/timerfd" "gvisor.dev/gvisor/pkg/sentry/kernel" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" ) @@ -33,28 +32,35 @@ func TimerfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel return 0, nil, linuxerr.EINVAL } - var c ktime.Clock + // Timerfds aren't writable per se (their implementation of Write just + // returns EINVAL), but they are "opened for writing", which is necessary + // to actually reach said implementation of Write. + fileFlags := uint32(linux.O_RDWR) + if flags&linux.TFD_NONBLOCK != 0 { + fileFlags |= linux.O_NONBLOCK + } + + var clock ktime.Clock switch clockID { case linux.CLOCK_REALTIME: - c = t.Kernel().RealtimeClock() + clock = t.Kernel().RealtimeClock() case linux.CLOCK_MONOTONIC, linux.CLOCK_BOOTTIME: - c = t.Kernel().MonotonicClock() + clock = t.Kernel().MonotonicClock() default: return 0, nil, linuxerr.EINVAL } - f := timerfd.NewFile(t, c) - defer f.DecRef(t) - f.SetFlags(fs.SettableFileFlags{ - NonBlocking: flags&linux.TFD_NONBLOCK != 0, - }) - - fd, err := t.NewFDFrom(0, f, kernel.FDFlags{ + vfsObj := t.Kernel().VFS() + file, err := timerfd.New(t, vfsObj, clock, fileFlags) + if err != nil { + return 0, nil, err + } + defer file.DecRef(t) + fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ CloseOnExec: flags&linux.TFD_CLOEXEC != 0, }) if err != nil { return 0, nil, err } - return uintptr(fd), nil, nil } @@ -69,13 +75,13 @@ func TimerfdSettime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne return 0, nil, linuxerr.EINVAL } - f := t.GetFile(fd) - if f == nil { + file := t.GetFileVFS2(fd) + if file == nil { return 0, nil, linuxerr.EBADF } - defer f.DecRef(t) + defer file.DecRef(t) - tf, ok := f.FileOperations.(*timerfd.TimerOperations) + tfd, ok := file.Impl().(*timerfd.TimerFileDescription) if !ok { return 0, nil, linuxerr.EINVAL } @@ -84,11 +90,11 @@ func TimerfdSettime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne if _, err := newVal.CopyIn(t, newValAddr); err != nil { return 0, nil, err } - newS, err := ktime.SettingFromItimerspec(newVal, flags&linux.TFD_TIMER_ABSTIME != 0, tf.Clock()) + newS, err := ktime.SettingFromItimerspec(newVal, flags&linux.TFD_TIMER_ABSTIME != 0, tfd.Clock()) if err != nil { return 0, nil, err } - tm, oldS := tf.SetTime(newS) + tm, oldS := tfd.SetTime(newS) if oldValAddr != 0 { oldVal := ktime.ItimerspecFromSetting(tm, oldS) if _, err := oldVal.CopyOut(t, oldValAddr); err != nil { @@ -103,18 +109,18 @@ func TimerfdGettime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne fd := args[0].Int() curValAddr := args[1].Pointer() - f := t.GetFile(fd) - if f == nil { + file := t.GetFileVFS2(fd) + if file == nil { return 0, nil, linuxerr.EBADF } - defer f.DecRef(t) + defer file.DecRef(t) - tf, ok := f.FileOperations.(*timerfd.TimerOperations) + tfd, ok := file.Impl().(*timerfd.TimerFileDescription) if !ok { return 0, nil, linuxerr.EINVAL } - tm, s := tf.GetTime() + tm, s := tfd.GetTime() curVal := ktime.ItimerspecFromSetting(tm, s) _, err := curVal.CopyOut(t, curValAddr) return 0, nil, err diff --git a/pkg/sentry/syscalls/linux/sys_write.go b/pkg/sentry/syscalls/linux/sys_write.go deleted file mode 100644 index 5bd167689..000000000 --- a/pkg/sentry/syscalls/linux/sys_write.go +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package linux - -import ( - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -const ( - // EventMaskWrite contains events that can be triggered on writes. - // - // Note that EventHUp is not going to happen for pipes but may for - // implementations of poll on some sockets, see net/core/datagram.c. - EventMaskWrite = waiter.EventOut | waiter.EventHUp | waiter.EventErr -) - -// Write implements linux syscall write(2). -func Write(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - size := args[2].SizeT() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the file is writable. - if !file.Flags().Write { - return 0, nil, linuxerr.EBADF - } - - // Check that the size is legitimate. - si := int(size) - if si < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get the source of the write. - src, err := t.SingleIOSequence(addr, si, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := writev(t, file, src) - t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "write", file) -} - -// Pwrite64 implements linux syscall pwrite64(2). -func Pwrite64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - size := args[2].SizeT() - offset := args[3].Int64() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the offset is legitimate and does not overflow. - if offset < 0 || offset+int64(size) < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Is writing at an offset supported? - if !file.Flags().Pwrite { - return 0, nil, linuxerr.ESPIPE - } - - // Check that the file is writable. - if !file.Flags().Write { - return 0, nil, linuxerr.EBADF - } - - // Check that the size is legitimate. - si := int(size) - if si < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get the source of the write. - src, err := t.SingleIOSequence(addr, si, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := pwritev(t, file, src, offset) - t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pwrite64", file) -} - -// Writev implements linux syscall writev(2). -func Writev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - iovcnt := int(args[2].Int()) - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the file is writable. - if !file.Flags().Write { - return 0, nil, linuxerr.EBADF - } - - // Read the iovecs that specify the source of the write. - src, err := t.IovecsIOSequence(addr, iovcnt, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := writev(t, file, src) - t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "writev", file) -} - -// Pwritev implements linux syscall pwritev(2). -func Pwritev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - iovcnt := int(args[2].Int()) - offset := args[3].Int64() - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the offset is legitimate. - if offset < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Is writing at an offset supported? - if !file.Flags().Pwrite { - return 0, nil, linuxerr.ESPIPE - } - - // Check that the file is writable. - if !file.Flags().Write { - return 0, nil, linuxerr.EBADF - } - - // Read the iovecs that specify the source of the write. - src, err := t.IovecsIOSequence(addr, iovcnt, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - n, err := pwritev(t, file, src, offset) - t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pwritev", file) -} - -// Pwritev2 implements linux syscall pwritev2(2). -func Pwritev2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - // While the syscall is - // pwritev2(int fd, struct iovec* iov, int iov_cnt, off_t offset, int flags) - // the linux internal call - // (https://elixir.bootlin.com/linux/v4.18/source/fs/read_write.c#L1354) - // splits the offset argument into a high/low value for compatibility with - // 32-bit architectures. The flags argument is the 5th argument. - - fd := args[0].Int() - addr := args[1].Pointer() - iovcnt := int(args[2].Int()) - offset := args[3].Int64() - flags := int(args[5].Int()) - - if int(args[4].Int())&0x4 == 1 { - return 0, nil, linuxerr.EACCES - } - - file := t.GetFile(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Check that the offset is legitimate. - if offset < -1 { - return 0, nil, linuxerr.EINVAL - } - - // Is writing at an offset supported? - if offset > -1 && !file.Flags().Pwrite { - return 0, nil, linuxerr.ESPIPE - } - - // Note: gVisor does not implement the RWF_HIPRI feature, but the flag is - // accepted as a valid flag argument for pwritev2. - if flags&^linux.RWF_VALID != 0 { - return uintptr(flags), nil, linuxerr.EOPNOTSUPP - } - - // Check that the file is writeable. - if !file.Flags().Write { - return 0, nil, linuxerr.EBADF - } - - // Read the iovecs that specify the source of the write. - src, err := t.IovecsIOSequence(addr, iovcnt, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, nil, err - } - - // If pwritev2 is called with an offset of -1, writev is called. - if offset == -1 { - n, err := writev(t, file, src) - t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pwritev2", file) - } - - n, err := pwritev(t, file, src, offset) - t.IOUsage().AccountWriteSyscall(n) - return uintptr(n), nil, handleIOError(t, n != 0, err, linuxerr.ERESTARTSYS, "pwritev2", file) -} - -func writev(t *kernel.Task, f *fs.File, src usermem.IOSequence) (int64, error) { - n, err := f.Writev(t, src) - if err != linuxerr.ErrWouldBlock || f.Flags().NonBlocking { - if n > 0 { - // Queue notification if we wrote anything. - f.Dirent.InotifyEvent(linux.IN_MODIFY, 0) - } - return n, err - } - - // Sockets support write timeouts. - var haveDeadline bool - var deadline ktime.Time - if s, ok := f.FileOperations.(socket.Socket); ok { - dl := s.SendTimeout() - if dl < 0 && err == linuxerr.ErrWouldBlock { - return n, err - } - if dl > 0 { - deadline = t.Kernel().MonotonicClock().Now().Add(time.Duration(dl) * time.Nanosecond) - haveDeadline = true - } - } - - // Register for notifications. - w, ch := waiter.NewChannelEntry(EventMaskWrite) - f.EventRegister(&w) - - total := n - for { - // Shorten src to reflect bytes previously written. - src = src.DropFirst64(n) - - // Issue the request and break out if it completes with - // anything other than "would block". - n, err = f.Writev(t, src) - total += n - if err != linuxerr.ErrWouldBlock { - break - } - - // Wait for a notification that we should retry. - if err = t.BlockWithDeadline(ch, haveDeadline, deadline); err != nil { - if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { - err = linuxerr.ErrWouldBlock - } - break - } - } - - f.EventUnregister(&w) - - if total > 0 { - // Queue notification if we wrote anything. - f.Dirent.InotifyEvent(linux.IN_MODIFY, 0) - } - - return total, err -} - -func pwritev(t *kernel.Task, f *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - n, err := f.Pwritev(t, src, offset) - if err != linuxerr.ErrWouldBlock || f.Flags().NonBlocking { - if n > 0 { - // Queue notification if we wrote anything. - f.Dirent.InotifyEvent(linux.IN_MODIFY, 0) - } - return n, err - } - - // Register for notifications. - w, ch := waiter.NewChannelEntry(EventMaskWrite) - f.EventRegister(&w) - - total := n - for { - // Shorten src to reflect bytes previously written. - src = src.DropFirst64(n) - - // Issue the request and break out if it completes with - // anything other than "would block". - n, err = f.Pwritev(t, src, offset+total) - total += n - if err != linuxerr.ErrWouldBlock { - break - } - - // Wait for a notification that we should retry. - if err = t.Block(ch); err != nil { - break - } - } - - f.EventUnregister(&w) - - if total > 0 { - // Queue notification if we wrote anything. - f.Dirent.InotifyEvent(linux.IN_MODIFY, 0) - } - - return total, err -} - -// LINT.ThenChange(vfs2/read_write.go) diff --git a/pkg/sentry/syscalls/linux/sys_xattr.go b/pkg/sentry/syscalls/linux/sys_xattr.go index baaf31191..9c157d399 100644 --- a/pkg/sentry/syscalls/linux/sys_xattr.go +++ b/pkg/sentry/syscalls/linux/sys_xattr.go @@ -1,4 +1,4 @@ -// Copyright 2019 The gVisor Authors. +// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,203 +15,280 @@ package linux import ( - "strings" + "bytes" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/gohacks" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) -// LINT.IfChange - -// GetXattr implements linux syscall getxattr(2). -func GetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return getXattrFromPath(t, args, true) +// ListXattr implements Linux syscall listxattr(2). +func ListXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return listxattr(t, args, followFinalSymlink) } -// LGetXattr implements linux syscall lgetxattr(2). -func LGetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return getXattrFromPath(t, args, false) +// Llistxattr implements Linux syscall llistxattr(2). +func Llistxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return listxattr(t, args, nofollowFinalSymlink) } -// FGetXattr implements linux syscall fgetxattr(2). -func FGetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - nameAddr := args[1].Pointer() - valueAddr := args[2].Pointer() - size := uint64(args[3].SizeT()) +func listxattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) (uintptr, *kernel.SyscallControl, error) { + pathAddr := args[0].Pointer() + listAddr := args[1].Pointer() + size := args[2].SizeT() - // TODO(b/113957122): Return EBADF if the fd was opened with O_PATH. - f := t.GetFile(fd) - if f == nil { - return 0, nil, linuxerr.EBADF - } - defer f.DecRef(t) - - n, err := getXattr(t, f.Dirent, nameAddr, valueAddr, size) + path, err := copyInPath(t, pathAddr) if err != nil { return 0, nil, err } + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) + if err != nil { + return 0, nil, err + } + defer tpop.Release(t) + names, err := t.Kernel().VFS().ListXattrAt(t, t.Credentials(), &tpop.pop, uint64(size)) + if err != nil { + return 0, nil, err + } + n, err := copyOutXattrNameList(t, listAddr, size, names) + if err != nil { + return 0, nil, err + } return uintptr(n), nil, nil } -func getXattrFromPath(t *kernel.Task, args arch.SyscallArguments, resolveSymlink bool) (uintptr, *kernel.SyscallControl, error) { +// Flistxattr implements Linux syscall flistxattr(2). +func Flistxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + fd := args[0].Int() + listAddr := args[1].Pointer() + size := args[2].SizeT() + + file := t.GetFileVFS2(fd) + if file == nil { + return 0, nil, linuxerr.EBADF + } + defer file.DecRef(t) + + names, err := file.ListXattr(t, uint64(size)) + if err != nil { + return 0, nil, err + } + n, err := copyOutXattrNameList(t, listAddr, size, names) + if err != nil { + return 0, nil, err + } + return uintptr(n), nil, nil +} + +// GetXattr implements Linux syscall getxattr(2). +func GetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return getxattr(t, args, followFinalSymlink) +} + +// Lgetxattr implements Linux syscall lgetxattr(2). +func Lgetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return getxattr(t, args, nofollowFinalSymlink) +} + +func getxattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) (uintptr, *kernel.SyscallControl, error) { pathAddr := args[0].Pointer() nameAddr := args[1].Pointer() valueAddr := args[2].Pointer() - size := uint64(args[3].SizeT()) + size := args[3].SizeT() - path, dirPath, err := copyInPath(t, pathAddr, false /* allowEmpty */) + path, err := copyInPath(t, pathAddr) if err != nil { return 0, nil, err } - - n := 0 - err = fileOpOn(t, linux.AT_FDCWD, path, resolveSymlink, func(_ *fs.Dirent, d *fs.Dirent, _ uint) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - n, err = getXattr(t, d, nameAddr, valueAddr, size) - return err - }) + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) if err != nil { return 0, nil, err } + defer tpop.Release(t) - return uintptr(n), nil, nil -} - -// getXattr implements getxattr(2) from the given *fs.Dirent. -func getXattr(t *kernel.Task, d *fs.Dirent, nameAddr, valueAddr hostarch.Addr, size uint64) (int, error) { name, err := copyInXattrName(t, nameAddr) if err != nil { - return 0, err + return 0, nil, err } - if err := checkXattrPermissions(t, d.Inode, fs.PermMask{Read: true}); err != nil { - return 0, err - } - - // TODO(b/148380782): Support xattrs in namespaces other than "user". - if !strings.HasPrefix(name, linux.XATTR_USER_PREFIX) { - return 0, linuxerr.EOPNOTSUPP - } - - // If getxattr(2) is called with size 0, the size of the value will be - // returned successfully even if it is nonzero. In that case, we need to - // retrieve the entire attribute value so we can return the correct size. - requestedSize := size - if size == 0 || size > linux.XATTR_SIZE_MAX { - requestedSize = linux.XATTR_SIZE_MAX - } - - value, err := d.Inode.GetXattr(t, name, requestedSize) + value, err := t.Kernel().VFS().GetXattrAt(t, t.Credentials(), &tpop.pop, &vfs.GetXattrOptions{ + Name: name, + Size: uint64(size), + }) if err != nil { - return 0, err + return 0, nil, err } - n := len(value) - if uint64(n) > requestedSize { - return 0, linuxerr.ERANGE + n, err := copyOutXattrValue(t, valueAddr, size, value) + if err != nil { + return 0, nil, err } - - // Don't copy out the attribute value if size is 0. - if size == 0 { - return n, nil - } - - if _, err = t.CopyOutBytes(valueAddr, []byte(value)); err != nil { - return 0, err - } - return n, nil + return uintptr(n), nil, nil } -// SetXattr implements linux syscall setxattr(2). -func SetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return setXattrFromPath(t, args, true) -} - -// LSetXattr implements linux syscall lsetxattr(2). -func LSetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return setXattrFromPath(t, args, false) -} - -// FSetXattr implements linux syscall fsetxattr(2). -func FSetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { +// Fgetxattr implements Linux syscall fgetxattr(2). +func Fgetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() nameAddr := args[1].Pointer() valueAddr := args[2].Pointer() - size := uint64(args[3].SizeT()) - flags := args[4].Uint() + size := args[3].SizeT() - // TODO(b/113957122): Return EBADF if the fd was opened with O_PATH. - f := t.GetFile(fd) - if f == nil { + file := t.GetFileVFS2(fd) + if file == nil { return 0, nil, linuxerr.EBADF } - defer f.DecRef(t) + defer file.DecRef(t) - return 0, nil, setXattr(t, f.Dirent, nameAddr, valueAddr, uint64(size), flags) -} - -func setXattrFromPath(t *kernel.Task, args arch.SyscallArguments, resolveSymlink bool) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - nameAddr := args[1].Pointer() - valueAddr := args[2].Pointer() - size := uint64(args[3].SizeT()) - flags := args[4].Uint() - - path, dirPath, err := copyInPath(t, pathAddr, false /* allowEmpty */) + name, err := copyInXattrName(t, nameAddr) if err != nil { return 0, nil, err } - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, resolveSymlink, func(_ *fs.Dirent, d *fs.Dirent, _ uint) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - return setXattr(t, d, nameAddr, valueAddr, uint64(size), flags) - }) + value, err := file.GetXattr(t, &vfs.GetXattrOptions{Name: name, Size: uint64(size)}) + if err != nil { + return 0, nil, err + } + n, err := copyOutXattrValue(t, valueAddr, size, value) + if err != nil { + return 0, nil, err + } + return uintptr(n), nil, nil } -// setXattr implements setxattr(2) from the given *fs.Dirent. -func setXattr(t *kernel.Task, d *fs.Dirent, nameAddr, valueAddr hostarch.Addr, size uint64, flags uint32) error { +// SetXattr implements Linux syscall setxattr(2). +func SetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return 0, nil, setxattr(t, args, followFinalSymlink) +} + +// Lsetxattr implements Linux syscall lsetxattr(2). +func Lsetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return 0, nil, setxattr(t, args, nofollowFinalSymlink) +} + +func setxattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) error { + pathAddr := args[0].Pointer() + nameAddr := args[1].Pointer() + valueAddr := args[2].Pointer() + size := args[3].SizeT() + flags := args[4].Int() + if flags&^(linux.XATTR_CREATE|linux.XATTR_REPLACE) != 0 { return linuxerr.EINVAL } + path, err := copyInPath(t, pathAddr) + if err != nil { + return err + } + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) + if err != nil { + return err + } + defer tpop.Release(t) + + name, err := copyInXattrName(t, nameAddr) + if err != nil { + return err + } + value, err := copyInXattrValue(t, valueAddr, size) + if err != nil { + return err + } + + return t.Kernel().VFS().SetXattrAt(t, t.Credentials(), &tpop.pop, &vfs.SetXattrOptions{ + Name: name, + Value: value, + Flags: uint32(flags), + }) +} + +// Fsetxattr implements Linux syscall fsetxattr(2). +func Fsetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + fd := args[0].Int() + nameAddr := args[1].Pointer() + valueAddr := args[2].Pointer() + size := args[3].SizeT() + flags := args[4].Int() + + if flags&^(linux.XATTR_CREATE|linux.XATTR_REPLACE) != 0 { + return 0, nil, linuxerr.EINVAL + } + + file := t.GetFileVFS2(fd) + if file == nil { + return 0, nil, linuxerr.EBADF + } + defer file.DecRef(t) + + name, err := copyInXattrName(t, nameAddr) + if err != nil { + return 0, nil, err + } + value, err := copyInXattrValue(t, valueAddr, size) + if err != nil { + return 0, nil, err + } + + return 0, nil, file.SetXattr(t, &vfs.SetXattrOptions{ + Name: name, + Value: value, + Flags: uint32(flags), + }) +} + +// RemoveXattr implements Linux syscall removexattr(2). +func RemoveXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return 0, nil, removexattr(t, args, followFinalSymlink) +} + +// Lremovexattr implements Linux syscall lremovexattr(2). +func Lremovexattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return 0, nil, removexattr(t, args, nofollowFinalSymlink) +} + +func removexattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) error { + pathAddr := args[0].Pointer() + nameAddr := args[1].Pointer() + + path, err := copyInPath(t, pathAddr) + if err != nil { + return err + } + tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) + if err != nil { + return err + } + defer tpop.Release(t) + name, err := copyInXattrName(t, nameAddr) if err != nil { return err } - if err := checkXattrPermissions(t, d.Inode, fs.PermMask{Write: true}); err != nil { - return err + return t.Kernel().VFS().RemoveXattrAt(t, t.Credentials(), &tpop.pop, name) +} + +// Fremovexattr implements Linux syscall fremovexattr(2). +func Fremovexattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + fd := args[0].Int() + nameAddr := args[1].Pointer() + + file := t.GetFileVFS2(fd) + if file == nil { + return 0, nil, linuxerr.EBADF + } + defer file.DecRef(t) + + name, err := copyInXattrName(t, nameAddr) + if err != nil { + return 0, nil, err } - if size > linux.XATTR_SIZE_MAX { - return linuxerr.E2BIG - } - buf := make([]byte, size) - if _, err := t.CopyInBytes(valueAddr, buf); err != nil { - return err - } - value := string(buf) - - if !strings.HasPrefix(name, linux.XATTR_USER_PREFIX) { - return linuxerr.EOPNOTSUPP - } - - if err := d.Inode.SetXattr(t, d, name, value, flags); err != nil { - return err - } - d.InotifyEvent(linux.IN_ATTRIB, 0) - return nil + return 0, nil, file.RemoveXattr(t, name) } func copyInXattrName(t *kernel.Task, nameAddr hostarch.Addr) (string, error) { @@ -228,205 +305,52 @@ func copyInXattrName(t *kernel.Task, nameAddr hostarch.Addr) (string, error) { return name, nil } -// Restrict xattrs to regular files and directories. -// -// TODO(b/148380782): In Linux, this restriction technically only applies to -// xattrs in the "user.*" namespace. Make file type checks specific to the -// namespace once we allow other xattr prefixes. -func xattrFileTypeOk(i *fs.Inode) bool { - return fs.IsRegular(i.StableAttr) || fs.IsDir(i.StableAttr) -} - -func checkXattrPermissions(t *kernel.Task, i *fs.Inode, perms fs.PermMask) error { - // Restrict xattrs to regular files and directories. - if !xattrFileTypeOk(i) { - if perms.Write { - return linuxerr.EPERM +func copyOutXattrNameList(t *kernel.Task, listAddr hostarch.Addr, size uint, names []string) (int, error) { + if size > linux.XATTR_LIST_MAX { + size = linux.XATTR_LIST_MAX + } + var buf bytes.Buffer + for _, name := range names { + buf.WriteString(name) + buf.WriteByte(0) + } + if size == 0 { + // Return the size that would be required to accommodate the list. + return buf.Len(), nil + } + if buf.Len() > int(size) { + if size >= linux.XATTR_LIST_MAX { + return 0, linuxerr.E2BIG } - return linuxerr.ENODATA - } - - return i.CheckPermission(t, perms) -} - -// ListXattr implements linux syscall listxattr(2). -func ListXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return listXattrFromPath(t, args, true) -} - -// LListXattr implements linux syscall llistxattr(2). -func LListXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return listXattrFromPath(t, args, false) -} - -// FListXattr implements linux syscall flistxattr(2). -func FListXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - listAddr := args[1].Pointer() - size := uint64(args[2].SizeT()) - - // TODO(b/113957122): Return EBADF if the fd was opened with O_PATH. - f := t.GetFile(fd) - if f == nil { - return 0, nil, linuxerr.EBADF - } - defer f.DecRef(t) - - n, err := listXattr(t, f.Dirent, listAddr, size) - if err != nil { - return 0, nil, err - } - - return uintptr(n), nil, nil -} - -func listXattrFromPath(t *kernel.Task, args arch.SyscallArguments, resolveSymlink bool) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - listAddr := args[1].Pointer() - size := uint64(args[2].SizeT()) - - path, dirPath, err := copyInPath(t, pathAddr, false /* allowEmpty */) - if err != nil { - return 0, nil, err - } - - n := 0 - err = fileOpOn(t, linux.AT_FDCWD, path, resolveSymlink, func(_ *fs.Dirent, d *fs.Dirent, _ uint) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - - n, err = listXattr(t, d, listAddr, size) - return err - }) - if err != nil { - return 0, nil, err - } - - return uintptr(n), nil, nil -} - -func listXattr(t *kernel.Task, d *fs.Dirent, addr hostarch.Addr, size uint64) (int, error) { - if !xattrFileTypeOk(d.Inode) { - return 0, nil - } - - // If listxattr(2) is called with size 0, the buffer size needed to contain - // the xattr list will be returned successfully even if it is nonzero. In - // that case, we need to retrieve the entire list so we can compute and - // return the correct size. - requestedSize := size - if size == 0 || size > linux.XATTR_SIZE_MAX { - requestedSize = linux.XATTR_SIZE_MAX - } - xattrs, err := d.Inode.ListXattr(t, requestedSize) - if err != nil { - return 0, err - } - - // TODO(b/148380782): support namespaces other than "user". - for x := range xattrs { - if !strings.HasPrefix(x, linux.XATTR_USER_PREFIX) { - delete(xattrs, x) - } - } - - listSize := xattrListSize(xattrs) - if listSize > linux.XATTR_SIZE_MAX { - return 0, linuxerr.E2BIG - } - if uint64(listSize) > requestedSize { return 0, linuxerr.ERANGE } + return t.CopyOutBytes(listAddr, buf.Bytes()) +} - // Don't copy out the attributes if size is 0. +func copyInXattrValue(t *kernel.Task, valueAddr hostarch.Addr, size uint) (string, error) { + if size > linux.XATTR_SIZE_MAX { + return "", linuxerr.E2BIG + } + buf := make([]byte, size) + if _, err := t.CopyInBytes(valueAddr, buf); err != nil { + return "", err + } + return gohacks.StringFromImmutableBytes(buf), nil +} + +func copyOutXattrValue(t *kernel.Task, valueAddr hostarch.Addr, size uint, value string) (int, error) { + if size > linux.XATTR_SIZE_MAX { + size = linux.XATTR_SIZE_MAX + } if size == 0 { - return listSize, nil + // Return the size that would be required to accommodate the value. + return len(value), nil } - - buf := make([]byte, 0, listSize) - for x := range xattrs { - buf = append(buf, []byte(x)...) - buf = append(buf, 0) - } - if _, err := t.CopyOutBytes(addr, buf); err != nil { - return 0, err - } - - return len(buf), nil -} - -func xattrListSize(xattrs map[string]struct{}) int { - size := 0 - for x := range xattrs { - size += len(x) + 1 - } - return size -} - -// RemoveXattr implements linux syscall removexattr(2). -func RemoveXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return removeXattrFromPath(t, args, true) -} - -// LRemoveXattr implements linux syscall lremovexattr(2). -func LRemoveXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return removeXattrFromPath(t, args, false) -} - -// FRemoveXattr implements linux syscall fremovexattr(2). -func FRemoveXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - nameAddr := args[1].Pointer() - - // TODO(b/113957122): Return EBADF if the fd was opened with O_PATH. - f := t.GetFile(fd) - if f == nil { - return 0, nil, linuxerr.EBADF - } - defer f.DecRef(t) - - return 0, nil, removeXattr(t, f.Dirent, nameAddr) -} - -func removeXattrFromPath(t *kernel.Task, args arch.SyscallArguments, resolveSymlink bool) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - nameAddr := args[1].Pointer() - - path, dirPath, err := copyInPath(t, pathAddr, false /* allowEmpty */) - if err != nil { - return 0, nil, err - } - - return 0, nil, fileOpOn(t, linux.AT_FDCWD, path, resolveSymlink, func(_ *fs.Dirent, d *fs.Dirent, _ uint) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR + if len(value) > int(size) { + if size >= linux.XATTR_SIZE_MAX { + return 0, linuxerr.E2BIG } - - return removeXattr(t, d, nameAddr) - }) + return 0, linuxerr.ERANGE + } + return t.CopyOutBytes(valueAddr, gohacks.ImmutableBytesFromString(value)) } - -// removeXattr implements removexattr(2) from the given *fs.Dirent. -func removeXattr(t *kernel.Task, d *fs.Dirent, nameAddr hostarch.Addr) error { - name, err := copyInXattrName(t, nameAddr) - if err != nil { - return err - } - - if err := checkXattrPermissions(t, d.Inode, fs.PermMask{Write: true}); err != nil { - return err - } - - if !strings.HasPrefix(name, linux.XATTR_USER_PREFIX) { - return linuxerr.EOPNOTSUPP - } - - if err := d.Inode.RemoveXattr(t, d, name); err != nil { - return err - } - d.InotifyEvent(linux.IN_ATTRIB, 0) - return nil -} - -// LINT.ThenChange(vfs2/xattr.go) diff --git a/pkg/sentry/syscalls/linux/timespec.go b/pkg/sentry/syscalls/linux/timespec.go index d90652a3f..7b7f956c4 100644 --- a/pkg/sentry/syscalls/linux/timespec.go +++ b/pkg/sentry/syscalls/linux/timespec.go @@ -98,8 +98,8 @@ func copyTimespecInToDuration(t *kernel.Task, timespecAddr hostarch.Addr) (time. // Use a negative Duration to indicate "no timeout". timeout := time.Duration(-1) if timespecAddr != 0 { - timespec, err := copyTimespecIn(t, timespecAddr) - if err != nil { + var timespec linux.Timespec + if _, err := timespec.CopyIn(t, timespecAddr); err != nil { return 0, err } if !timespec.Valid() { diff --git a/pkg/sentry/syscalls/linux/vfs2/BUILD b/pkg/sentry/syscalls/linux/vfs2/BUILD deleted file mode 100644 index bdc504366..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/BUILD +++ /dev/null @@ -1,86 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "vfs2", - srcs = [ - "aio.go", - "epoll.go", - "eventfd.go", - "execve.go", - "fd.go", - "filesystem.go", - "fscontext.go", - "getdents.go", - "inotify.go", - "ioctl.go", - "iouringfs.go", - "lock.go", - "memfd.go", - "mmap.go", - "mount.go", - "mq.go", - "path.go", - "pipe.go", - "poll.go", - "read_write.go", - "setstat.go", - "signal.go", - "socket.go", - "splice.go", - "stat.go", - "stat_amd64.go", - "stat_arm64.go", - "sync.go", - "timerfd.go", - "vfs2.go", - "xattr.go", - ], - marshal = True, - visibility = ["//:sandbox"], - deps = [ - "//pkg/abi/linux", - "//pkg/bits", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/gohacks", - "//pkg/hostarch", - "//pkg/log", - "//pkg/marshal", - "//pkg/marshal/primitive", - "//pkg/sentry/arch", - "//pkg/sentry/fs/lock", - "//pkg/sentry/fsbridge", - "//pkg/sentry/fsimpl/eventfd", - "//pkg/sentry/fsimpl/host", - "//pkg/sentry/fsimpl/iouringfs", - "//pkg/sentry/fsimpl/pipefs", - "//pkg/sentry/fsimpl/signalfd", - "//pkg/sentry/fsimpl/timerfd", - "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/fasync", - "//pkg/sentry/kernel/mq", - "//pkg/sentry/kernel/pipe", - "//pkg/sentry/kernel/time", - "//pkg/sentry/limits", - "//pkg/sentry/loader", - "//pkg/sentry/memmap", - "//pkg/sentry/mm", - "//pkg/sentry/seccheck", - "//pkg/sentry/socket", - "//pkg/sentry/socket/control", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/syscalls", - "//pkg/sentry/syscalls/linux", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/syserr", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/sentry/syscalls/linux/vfs2/aio.go b/pkg/sentry/syscalls/linux/vfs2/aio.go deleted file mode 100644 index 0b57c0f7c..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/aio.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/eventfd" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/mm" - slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/usermem" -) - -// IoSubmit implements linux syscall io_submit(2). -func IoSubmit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - id := args[0].Uint64() - nrEvents := args[1].Int() - addr := args[2].Pointer() - - if nrEvents < 0 { - return 0, nil, linuxerr.EINVAL - } - - for i := int32(0); i < nrEvents; i++ { - // Copy in the callback address. - var cbAddr hostarch.Addr - switch t.Arch().Width() { - case 8: - var cbAddrP primitive.Uint64 - if _, err := cbAddrP.CopyIn(t, addr); err != nil { - if i > 0 { - // Some successful. - return uintptr(i), nil, nil - } - // Nothing done. - return 0, nil, err - } - cbAddr = hostarch.Addr(cbAddrP) - default: - return 0, nil, linuxerr.ENOSYS - } - - // Copy in this callback. - var cb linux.IOCallback - if _, err := cb.CopyIn(t, cbAddr); err != nil { - if i > 0 { - // Some have been successful. - return uintptr(i), nil, nil - } - // Nothing done. - return 0, nil, err - } - - // Process this callback. - if err := submitCallback(t, id, &cb, cbAddr); err != nil { - if i > 0 { - // Partial success. - return uintptr(i), nil, nil - } - // Nothing done. - return 0, nil, err - } - - // Advance to the next one. - addr += hostarch.Addr(t.Arch().Width()) - } - - return uintptr(nrEvents), nil, nil -} - -// submitCallback processes a single callback. -func submitCallback(t *kernel.Task, id uint64, cb *linux.IOCallback, cbAddr hostarch.Addr) error { - if cb.Reserved2 != 0 { - return linuxerr.EINVAL - } - - fd := t.GetFileVFS2(cb.FD) - if fd == nil { - return linuxerr.EBADF - } - defer fd.DecRef(t) - - // Was there an eventFD? Extract it. - var eventFD *vfs.FileDescription - if cb.Flags&linux.IOCB_FLAG_RESFD != 0 { - eventFD = t.GetFileVFS2(cb.ResFD) - if eventFD == nil { - return linuxerr.EBADF - } - defer eventFD.DecRef(t) - - // Check that it is an eventfd. - if _, ok := eventFD.Impl().(*eventfd.EventFileDescription); !ok { - return linuxerr.EINVAL - } - } - - ioseq, err := memoryFor(t, cb) - if err != nil { - return err - } - - // Check offset for reads/writes. - switch cb.OpCode { - case linux.IOCB_CMD_PREAD, linux.IOCB_CMD_PREADV, linux.IOCB_CMD_PWRITE, linux.IOCB_CMD_PWRITEV: - if cb.Offset < 0 { - return linuxerr.EINVAL - } - } - - // Prepare the request. - aioCtx, ok := t.MemoryManager().LookupAIOContext(t, id) - if !ok { - return linuxerr.EINVAL - } - if err := aioCtx.Prepare(); err != nil { - return err - } - - if eventFD != nil { - // The request is set. Make sure there's a ref on the file. - // - // This is necessary when the callback executes on completion, - // which is also what will release this reference. - eventFD.IncRef() - } - - // Perform the request asynchronously. - fd.IncRef() - t.QueueAIO(getAIOCallback(t, fd, eventFD, cbAddr, cb, ioseq, aioCtx)) - return nil -} - -func getAIOCallback(t *kernel.Task, fd, eventFD *vfs.FileDescription, cbAddr hostarch.Addr, cb *linux.IOCallback, ioseq usermem.IOSequence, aioCtx *mm.AIOContext) kernel.AIOCallback { - return func(ctx context.Context) { - // Release references after completing the callback. - defer fd.DecRef(ctx) - if eventFD != nil { - defer eventFD.DecRef(ctx) - } - - if aioCtx.Dead() { - aioCtx.CancelPendingRequest() - return - } - ev := &linux.IOEvent{ - Data: cb.Data, - Obj: uint64(cbAddr), - } - - var err error - switch cb.OpCode { - case linux.IOCB_CMD_PREAD, linux.IOCB_CMD_PREADV: - ev.Result, err = fd.PRead(ctx, ioseq, cb.Offset, vfs.ReadOptions{}) - case linux.IOCB_CMD_PWRITE, linux.IOCB_CMD_PWRITEV: - ev.Result, err = fd.PWrite(ctx, ioseq, cb.Offset, vfs.WriteOptions{}) - case linux.IOCB_CMD_FSYNC, linux.IOCB_CMD_FDSYNC: - err = fd.Sync(ctx) - } - - // Update the result. - if err != nil { - err = slinux.HandleIOErrorVFS2(ctx, ev.Result != 0 /* partial */, err, nil /* never interrupted */, "aio", fd) - ev.Result = -int64(kernel.ExtractErrno(err, 0)) - } - - // Queue the result for delivery. - aioCtx.FinishRequest(ev) - - // Notify the event file if one was specified. This needs to happen - // *after* queueing the result to avoid racing with the thread we may - // wake up. - if eventFD != nil { - eventFD.Impl().(*eventfd.EventFileDescription).Signal(1) - } - } -} - -// memoryFor returns appropriate memory for the given callback. -func memoryFor(t *kernel.Task, cb *linux.IOCallback) (usermem.IOSequence, error) { - bytes := int(cb.Bytes) - if bytes < 0 { - // Linux also requires that this field fit in ssize_t. - return usermem.IOSequence{}, linuxerr.EINVAL - } - - // Since this I/O will be asynchronous with respect to t's task goroutine, - // we have no guarantee that t's AddressSpace will be active during the - // I/O. - switch cb.OpCode { - case linux.IOCB_CMD_PREAD, linux.IOCB_CMD_PWRITE: - return t.SingleIOSequence(hostarch.Addr(cb.Buf), bytes, usermem.IOOpts{ - AddressSpaceActive: false, - }) - - case linux.IOCB_CMD_PREADV, linux.IOCB_CMD_PWRITEV: - return t.IovecsIOSequence(hostarch.Addr(cb.Buf), bytes, usermem.IOOpts{ - AddressSpaceActive: false, - }) - - case linux.IOCB_CMD_FSYNC, linux.IOCB_CMD_FDSYNC, linux.IOCB_CMD_NOOP: - return usermem.IOSequence{}, nil - - default: - // Not a supported command. - return usermem.IOSequence{}, linuxerr.EINVAL - } -} diff --git a/pkg/sentry/syscalls/linux/vfs2/epoll.go b/pkg/sentry/syscalls/linux/vfs2/epoll.go deleted file mode 100644 index 938af4603..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/epoll.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "math" - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/waiter" -) - -var sizeofEpollEvent = (*linux.EpollEvent)(nil).SizeBytes() - -// EpollCreate1 implements Linux syscall epoll_create1(2). -func EpollCreate1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - flags := args[0].Int() - if flags&^linux.EPOLL_CLOEXEC != 0 { - return 0, nil, linuxerr.EINVAL - } - - file, err := t.Kernel().VFS().NewEpollInstanceFD(t) - if err != nil { - return 0, nil, err - } - defer file.DecRef(t) - - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ - CloseOnExec: flags&linux.EPOLL_CLOEXEC != 0, - }) - if err != nil { - return 0, nil, err - } - return uintptr(fd), nil, nil -} - -// EpollCreate implements Linux syscall epoll_create(2). -func EpollCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - size := args[0].Int() - - // "Since Linux 2.6.8, the size argument is ignored, but must be greater - // than zero" - epoll_create(2) - if size <= 0 { - return 0, nil, linuxerr.EINVAL - } - - file, err := t.Kernel().VFS().NewEpollInstanceFD(t) - if err != nil { - return 0, nil, err - } - defer file.DecRef(t) - - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{}) - if err != nil { - return 0, nil, err - } - return uintptr(fd), nil, nil -} - -// EpollCtl implements Linux syscall epoll_ctl(2). -func EpollCtl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - epfd := args[0].Int() - op := args[1].Int() - fd := args[2].Int() - eventAddr := args[3].Pointer() - - epfile := t.GetFileVFS2(epfd) - if epfile == nil { - return 0, nil, linuxerr.EBADF - } - defer epfile.DecRef(t) - ep, ok := epfile.Impl().(*vfs.EpollInstance) - if !ok { - return 0, nil, linuxerr.EINVAL - } - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - if epfile == file { - return 0, nil, linuxerr.EINVAL - } - - var event linux.EpollEvent - switch op { - case linux.EPOLL_CTL_ADD: - if _, err := event.CopyIn(t, eventAddr); err != nil { - return 0, nil, err - } - return 0, nil, ep.AddInterest(file, fd, event) - case linux.EPOLL_CTL_DEL: - return 0, nil, ep.DeleteInterest(file, fd) - case linux.EPOLL_CTL_MOD: - if _, err := event.CopyIn(t, eventAddr); err != nil { - return 0, nil, err - } - return 0, nil, ep.ModifyInterest(file, fd, event) - default: - return 0, nil, linuxerr.EINVAL - } -} - -func waitEpoll(t *kernel.Task, epfd int32, eventsAddr hostarch.Addr, maxEvents int, timeoutInNanos int64) (uintptr, *kernel.SyscallControl, error) { - var _EP_MAX_EVENTS = math.MaxInt32 / sizeofEpollEvent // Linux: fs/eventpoll.c:EP_MAX_EVENTS - if maxEvents <= 0 || maxEvents > _EP_MAX_EVENTS { - return 0, nil, linuxerr.EINVAL - } - - epfile := t.GetFileVFS2(epfd) - if epfile == nil { - return 0, nil, linuxerr.EBADF - } - defer epfile.DecRef(t) - ep, ok := epfile.Impl().(*vfs.EpollInstance) - if !ok { - return 0, nil, linuxerr.EINVAL - } - - // Allocate space for a few events on the stack for the common case in - // which we don't have too many events. - var ( - eventsArr [16]linux.EpollEvent - ch chan struct{} - haveDeadline bool - deadline ktime.Time - ) - for { - events := ep.ReadEvents(eventsArr[:0], maxEvents) - if len(events) != 0 { - copiedBytes, err := linux.CopyEpollEventSliceOut(t, eventsAddr, events) - copiedEvents := copiedBytes / sizeofEpollEvent // rounded down - if copiedEvents != 0 { - return uintptr(copiedEvents), nil, nil - } - return 0, nil, err - } - if timeoutInNanos == 0 { - return 0, nil, nil - } - // In the first iteration of this loop, register with the epoll - // instance for readability events, but then immediately continue the - // loop since we need to retry ReadEvents() before blocking. In all - // subsequent iterations, block until events are available, the timeout - // expires, or an interrupt arrives. - if ch == nil { - var w waiter.Entry - w, ch = waiter.NewChannelEntry(waiter.ReadableEvents) - if err := epfile.EventRegister(&w); err != nil { - return 0, nil, err - } - defer epfile.EventUnregister(&w) - } else { - // Set up the timer if a timeout was specified. - if timeoutInNanos > 0 && !haveDeadline { - timeoutDur := time.Duration(timeoutInNanos) * time.Nanosecond - deadline = t.Kernel().MonotonicClock().Now().Add(timeoutDur) - haveDeadline = true - } - if err := t.BlockWithDeadline(ch, haveDeadline, deadline); err != nil { - if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { - err = nil - } - return 0, nil, err - } - } - } - -} - -// EpollWait implements Linux syscall epoll_wait(2). -func EpollWait(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - epfd := args[0].Int() - eventsAddr := args[1].Pointer() - maxEvents := int(args[2].Int()) - timeoutInNanos := int64(args[3].Int()) * 1000000 - - return waitEpoll(t, epfd, eventsAddr, maxEvents, timeoutInNanos) -} - -// EpollPwait implements Linux syscall epoll_pwait(2). -func EpollPwait(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - maskAddr := args[4].Pointer() - maskSize := uint(args[5].Uint()) - - if err := setTempSignalSet(t, maskAddr, maskSize); err != nil { - return 0, nil, err - } - - return EpollWait(t, args) -} - -// EpollPwait2 implements Linux syscall epoll_pwait(2). -func EpollPwait2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - epfd := args[0].Int() - eventsAddr := args[1].Pointer() - maxEvents := int(args[2].Int()) - timeoutPtr := args[3].Pointer() - maskAddr := args[4].Pointer() - maskSize := uint(args[5].Uint()) - haveTimeout := timeoutPtr != 0 - - var timeoutInNanos int64 = -1 - if haveTimeout { - var timeout linux.Timespec - if _, err := timeout.CopyIn(t, timeoutPtr); err != nil { - return 0, nil, err - } - timeoutInNanos = timeout.ToNsec() - } - - if err := setTempSignalSet(t, maskAddr, maskSize); err != nil { - return 0, nil, err - } - - return waitEpoll(t, epfd, eventsAddr, maxEvents, timeoutInNanos) -} diff --git a/pkg/sentry/syscalls/linux/vfs2/eventfd.go b/pkg/sentry/syscalls/linux/vfs2/eventfd.go deleted file mode 100644 index 0dcf1fbff..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/eventfd.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/eventfd" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// Eventfd2 implements linux syscall eventfd2(2). -func Eventfd2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - initVal := uint64(args[0].Uint()) - flags := uint(args[1].Uint()) - allOps := uint(linux.EFD_SEMAPHORE | linux.EFD_NONBLOCK | linux.EFD_CLOEXEC) - - if flags & ^allOps != 0 { - return 0, nil, linuxerr.EINVAL - } - - vfsObj := t.Kernel().VFS() - fileFlags := uint32(linux.O_RDWR) - if flags&linux.EFD_NONBLOCK != 0 { - fileFlags |= linux.O_NONBLOCK - } - semMode := flags&linux.EFD_SEMAPHORE != 0 - eventfd, err := eventfd.New(t, vfsObj, initVal, semMode, fileFlags) - if err != nil { - return 0, nil, err - } - defer eventfd.DecRef(t) - - fd, err := t.NewFDFromVFS2(0, eventfd, kernel.FDFlags{ - CloseOnExec: flags&linux.EFD_CLOEXEC != 0, - }) - if err != nil { - return 0, nil, err - } - - return uintptr(fd), nil, nil -} - -// Eventfd implements linux syscall eventfd(2). -func Eventfd(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - args[1].Value = 0 - return Eventfd2(t, args) -} diff --git a/pkg/sentry/syscalls/linux/vfs2/execve.go b/pkg/sentry/syscalls/linux/vfs2/execve.go deleted file mode 100644 index deab66bc1..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/execve.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsbridge" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/loader" - "gvisor.dev/gvisor/pkg/sentry/seccheck" - slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Execve implements linux syscall execve(2). -func Execve(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathnameAddr := args[0].Pointer() - argvAddr := args[1].Pointer() - envvAddr := args[2].Pointer() - return execveat(t, linux.AT_FDCWD, pathnameAddr, argvAddr, envvAddr, 0 /* flags */) -} - -// Execveat implements linux syscall execveat(2). -func Execveat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathnameAddr := args[1].Pointer() - argvAddr := args[2].Pointer() - envvAddr := args[3].Pointer() - flags := args[4].Int() - return execveat(t, dirfd, pathnameAddr, argvAddr, envvAddr, flags) -} - -func execveat(t *kernel.Task, dirfd int32, pathnameAddr, argvAddr, envvAddr hostarch.Addr, flags int32) (uintptr, *kernel.SyscallControl, error) { - if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 { - return 0, nil, linuxerr.EINVAL - } - - pathname, err := t.CopyInString(pathnameAddr, linux.PATH_MAX) - if err != nil { - return 0, nil, err - } - var argv, envv []string - if argvAddr != 0 { - var err error - argv, err = t.CopyInVector(argvAddr, slinux.ExecMaxElemSize, slinux.ExecMaxTotalSize) - if err != nil { - return 0, nil, err - } - } - if envvAddr != 0 { - var err error - envv, err = t.CopyInVector(envvAddr, slinux.ExecMaxElemSize, slinux.ExecMaxTotalSize) - if err != nil { - return 0, nil, err - } - } - - root := t.FSContext().RootDirectoryVFS2() - defer root.DecRef(t) - var executable fsbridge.File - defer func() { - if executable != nil { - executable.DecRef(t) - } - }() - closeOnExec := false - if path := fspath.Parse(pathname); dirfd != linux.AT_FDCWD && !path.Absolute { - // We must open the executable ourselves since dirfd is used as the - // starting point while resolving path, but the task working directory - // is used as the starting point while resolving interpreters (Linux: - // fs/binfmt_script.c:load_script() => fs/exec.c:open_exec() => - // do_open_execat(fd=AT_FDCWD)), and the loader package is currently - // incapable of handling this correctly. - if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 { - return 0, nil, linuxerr.ENOENT - } - dirfile, dirfileFlags := t.FDTable().GetVFS2(dirfd) - if dirfile == nil { - return 0, nil, linuxerr.EBADF - } - start := dirfile.VirtualDentry() - start.IncRef() - dirfile.DecRef(t) - closeOnExec = dirfileFlags.CloseOnExec - file, err := t.Kernel().VFS().OpenAt(t, t.Credentials(), &vfs.PathOperation{ - Root: root, - Start: start, - Path: path, - FollowFinalSymlink: flags&linux.AT_SYMLINK_NOFOLLOW == 0, - }, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - FileExec: true, - }) - start.DecRef(t) - if err != nil { - return 0, nil, err - } - executable = fsbridge.NewVFSFile(file) - pathname = executable.PathnameWithDeleted(t) - } - - // Load the new TaskImage. - mntns := t.MountNamespaceVFS2() - wd := t.FSContext().WorkingDirectoryVFS2() - defer wd.DecRef(t) - remainingTraversals := uint(linux.MaxSymlinkTraversals) - loadArgs := loader.LoadArgs{ - Opener: fsbridge.NewVFSLookup(mntns, root, wd), - RemainingTraversals: &remainingTraversals, - ResolveFinal: flags&linux.AT_SYMLINK_NOFOLLOW == 0, - Filename: pathname, - File: executable, - CloseOnExec: closeOnExec, - Argv: argv, - Envv: envv, - Features: t.Kernel().FeatureSet(), - } - if seccheck.Global.Enabled(seccheck.PointExecve) { - // Retain the first executable file that is opened (which may open - // multiple executable files while resolving interpreter scripts). - if executable == nil { - loadArgs.AfterOpen = func(f fsbridge.File) { - if executable == nil { - f.IncRef() - executable = f - pathname = executable.PathnameWithDeleted(t) - } - } - } - } - - image, se := t.Kernel().LoadTaskImage(t, loadArgs) - if se != nil { - return 0, nil, se.ToError() - } - - ctrl, err := t.Execve(image, argv, envv, executable, pathname) - return 0, ctrl, err -} diff --git a/pkg/sentry/syscalls/linux/vfs2/fd.go b/pkg/sentry/syscalls/linux/vfs2/fd.go deleted file mode 100644 index 455177fe0..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/fd.go +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "math" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/fasync" - "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" - slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Close implements Linux syscall close(2). -func Close(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - // Note that Remove provides a reference on the file that we may use to - // flush. It is still active until we drop the final reference below - // (and other reference-holding operations complete). - _, file := t.FDTable().Remove(t, fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - err := file.OnClose(t) - return 0, nil, slinux.HandleIOErrorVFS2(t, false /* partial */, err, linuxerr.EINTR, "close", file) -} - -// CloseRange implements linux syscall close_range(2). -func CloseRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - first := args[0].Uint() - last := args[1].Uint() - flags := args[2].Uint() - - if (first > last) || (last > math.MaxInt32) { - return 0, nil, linuxerr.EINVAL - } - - if (flags & ^(linux.CLOSE_RANGE_CLOEXEC | linux.CLOSE_RANGE_UNSHARE)) != 0 { - return 0, nil, linuxerr.EINVAL - } - - cloexec := flags & linux.CLOSE_RANGE_CLOEXEC - unshare := flags & linux.CLOSE_RANGE_UNSHARE - - if unshare != 0 { - // If possible, we don't want to copy FDs to the new unshared table, because those FDs will - // be promptly closed and no longer used. So in the case where we know the range extends all - // the way to the end of the FdTable, we can simply copy the FdTable only up to the start of - // the range that we are closing. - if cloexec == 0 && int32(last) >= t.FDTable().GetLastFd() { - t.UnshareFdTable(int32(first)) - } else { - t.UnshareFdTable(math.MaxInt32) - } - } - - if cloexec != 0 { - flagToApply := kernel.FDFlags{ - CloseOnExec: true, - } - t.FDTable().SetFlagsForRangeVFS2(t.AsyncContext(), int32(first), int32(last), flagToApply) - return 0, nil, nil - } - - fdTable := t.FDTable() - fd := int32(first) - for { - fd, _, file := fdTable.RemoveNextInRange(t, fd, int32(last)) - if file == nil { - break - } - - fd++ - // Per the close_range(2) documentation, errors upon closing file descriptors are ignored. - _ = file.OnClose(t) - file.DecRef(t) - } - - return 0, nil, nil -} - -// Dup implements Linux syscall dup(2). -func Dup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - newFD, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{}) - if err != nil { - return 0, nil, linuxerr.EMFILE - } - return uintptr(newFD), nil, nil -} - -// Dup2 implements Linux syscall dup2(2). -func Dup2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldfd := args[0].Int() - newfd := args[1].Int() - - if oldfd == newfd { - // As long as oldfd is valid, dup2() does nothing and returns newfd. - file := t.GetFileVFS2(oldfd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - file.DecRef(t) - return uintptr(newfd), nil, nil - } - - return dup3(t, oldfd, newfd, 0) -} - -// Dup3 implements Linux syscall dup3(2). -func Dup3(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldfd := args[0].Int() - newfd := args[1].Int() - flags := args[2].Uint() - - if oldfd == newfd { - return 0, nil, linuxerr.EINVAL - } - - return dup3(t, oldfd, newfd, flags) -} - -func dup3(t *kernel.Task, oldfd, newfd int32, flags uint32) (uintptr, *kernel.SyscallControl, error) { - if flags&^linux.O_CLOEXEC != 0 { - return 0, nil, linuxerr.EINVAL - } - - file := t.GetFileVFS2(oldfd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - err := t.NewFDAtVFS2(newfd, file, kernel.FDFlags{ - CloseOnExec: flags&linux.O_CLOEXEC != 0, - }) - if err != nil { - return 0, nil, err - } - return uintptr(newfd), nil, nil -} - -// Fcntl implements linux syscall fcntl(2). -func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - cmd := args[1].Int() - - file, flags := t.FDTable().GetVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - if file.StatusFlags()&linux.O_PATH != 0 { - switch cmd { - case linux.F_DUPFD, linux.F_DUPFD_CLOEXEC, linux.F_GETFD, linux.F_SETFD, linux.F_GETFL: - // allowed - default: - return 0, nil, linuxerr.EBADF - } - } - - switch cmd { - case linux.F_DUPFD, linux.F_DUPFD_CLOEXEC: - minfd := args[2].Int() - fd, err := t.NewFDFromVFS2(minfd, file, kernel.FDFlags{ - CloseOnExec: cmd == linux.F_DUPFD_CLOEXEC, - }) - if err != nil { - return 0, nil, err - } - return uintptr(fd), nil, nil - case linux.F_GETFD: - return uintptr(flags.ToLinuxFDFlags()), nil, nil - case linux.F_SETFD: - flags := args[2].Uint() - err := t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ - CloseOnExec: flags&linux.FD_CLOEXEC != 0, - }) - return 0, nil, err - case linux.F_GETFL: - return uintptr(file.StatusFlags()), nil, nil - case linux.F_SETFL: - return 0, nil, file.SetStatusFlags(t, t.Credentials(), args[2].Uint()) - case linux.F_GETOWN: - owner, hasOwner := getAsyncOwner(t, file) - if !hasOwner { - return 0, nil, nil - } - if owner.Type == linux.F_OWNER_PGRP { - return uintptr(-owner.PID), nil, nil - } - return uintptr(owner.PID), nil, nil - case linux.F_SETOWN: - who := args[2].Int() - ownerType := int32(linux.F_OWNER_PID) - if who < 0 { - // Check for overflow before flipping the sign. - if who-1 > who { - return 0, nil, linuxerr.EINVAL - } - ownerType = linux.F_OWNER_PGRP - who = -who - } - return 0, nil, setAsyncOwner(t, int(fd), file, ownerType, who) - case linux.F_GETOWN_EX: - owner, hasOwner := getAsyncOwner(t, file) - if !hasOwner { - return 0, nil, nil - } - _, err := owner.CopyOut(t, args[2].Pointer()) - return 0, nil, err - case linux.F_SETOWN_EX: - var owner linux.FOwnerEx - _, err := owner.CopyIn(t, args[2].Pointer()) - if err != nil { - return 0, nil, err - } - return 0, nil, setAsyncOwner(t, int(fd), file, owner.Type, owner.PID) - case linux.F_SETPIPE_SZ: - pipefile, ok := file.Impl().(*pipe.VFSPipeFD) - if !ok { - return 0, nil, linuxerr.EBADF - } - n, err := pipefile.SetPipeSize(int64(args[2].Int())) - if err != nil { - return 0, nil, err - } - return uintptr(n), nil, nil - case linux.F_GETPIPE_SZ: - pipefile, ok := file.Impl().(*pipe.VFSPipeFD) - if !ok { - return 0, nil, linuxerr.EBADF - } - return uintptr(pipefile.PipeSize()), nil, nil - case linux.F_GET_SEALS: - val, err := tmpfs.GetSeals(file) - return uintptr(val), nil, err - case linux.F_ADD_SEALS: - if !file.IsWritable() { - return 0, nil, linuxerr.EPERM - } - err := tmpfs.AddSeals(file, args[2].Uint()) - return 0, nil, err - case linux.F_SETLK: - return 0, nil, posixLock(t, args, file, false /* block */) - case linux.F_SETLKW: - return 0, nil, posixLock(t, args, file, true /* block */) - case linux.F_GETLK: - return 0, nil, posixTestLock(t, args, file) - case linux.F_GETSIG: - a := file.AsyncHandler() - if a == nil { - // Default behavior aka SIGIO. - return 0, nil, nil - } - return uintptr(a.(*fasync.FileAsync).Signal()), nil, nil - case linux.F_SETSIG: - a, err := file.SetAsyncHandler(fasync.NewVFS2(int(fd))) - if err != nil { - return 0, nil, err - } - async := a.(*fasync.FileAsync) - return 0, nil, async.SetSignal(linux.Signal(args[2].Int())) - default: - // Everything else is not yet supported. - return 0, nil, linuxerr.EINVAL - } -} - -func getAsyncOwner(t *kernel.Task, fd *vfs.FileDescription) (ownerEx linux.FOwnerEx, hasOwner bool) { - a := fd.AsyncHandler() - if a == nil { - return linux.FOwnerEx{}, false - } - - ot, otg, opg := a.(*fasync.FileAsync).Owner() - switch { - case ot != nil: - return linux.FOwnerEx{ - Type: linux.F_OWNER_TID, - PID: int32(t.PIDNamespace().IDOfTask(ot)), - }, true - case otg != nil: - return linux.FOwnerEx{ - Type: linux.F_OWNER_PID, - PID: int32(t.PIDNamespace().IDOfThreadGroup(otg)), - }, true - case opg != nil: - return linux.FOwnerEx{ - Type: linux.F_OWNER_PGRP, - PID: int32(t.PIDNamespace().IDOfProcessGroup(opg)), - }, true - default: - return linux.FOwnerEx{}, true - } -} - -func setAsyncOwner(t *kernel.Task, fd int, file *vfs.FileDescription, ownerType, pid int32) error { - switch ownerType { - case linux.F_OWNER_TID, linux.F_OWNER_PID, linux.F_OWNER_PGRP: - // Acceptable type. - default: - return linuxerr.EINVAL - } - - a, err := file.SetAsyncHandler(fasync.NewVFS2(fd)) - if err != nil { - return err - } - async := a.(*fasync.FileAsync) - if pid == 0 { - async.ClearOwner() - return nil - } - - switch ownerType { - case linux.F_OWNER_TID: - task := t.PIDNamespace().TaskWithID(kernel.ThreadID(pid)) - if task == nil { - return linuxerr.ESRCH - } - async.SetOwnerTask(t, task) - return nil - case linux.F_OWNER_PID: - tg := t.PIDNamespace().ThreadGroupWithID(kernel.ThreadID(pid)) - if tg == nil { - return linuxerr.ESRCH - } - async.SetOwnerThreadGroup(t, tg) - return nil - case linux.F_OWNER_PGRP: - pg := t.PIDNamespace().ProcessGroupWithID(kernel.ProcessGroupID(pid)) - if pg == nil { - return linuxerr.ESRCH - } - async.SetOwnerProcessGroup(t, pg) - return nil - default: - return linuxerr.EINVAL - } -} - -func posixTestLock(t *kernel.Task, args arch.SyscallArguments, file *vfs.FileDescription) error { - // Copy in the lock request. - flockAddr := args[2].Pointer() - var flock linux.Flock - if _, err := flock.CopyIn(t, flockAddr); err != nil { - return err - } - var typ lock.LockType - switch flock.Type { - case linux.F_RDLCK: - typ = lock.ReadLock - case linux.F_WRLCK: - typ = lock.WriteLock - default: - return linuxerr.EINVAL - } - r, err := file.ComputeLockRange(t, uint64(flock.Start), uint64(flock.Len), flock.Whence) - if err != nil { - return err - } - - newFlock, err := file.TestPOSIX(t, t.FDTable(), typ, r) - if err != nil { - return err - } - newFlock.PID = translatePID(t.PIDNamespace().Root(), t.PIDNamespace(), newFlock.PID) - if _, err = newFlock.CopyOut(t, flockAddr); err != nil { - return err - } - return nil -} - -// translatePID translates a pid from one namespace to another. Note that this -// may race with task termination/creation, in which case the original task -// corresponding to pid may no longer exist. This is used to implement the -// F_GETLK fcntl, which has the same potential race in Linux as well (i.e., -// there is no synchronization between retrieving the lock PID and translating -// it). See fs/locks.c:posix_lock_to_flock. -func translatePID(old, new *kernel.PIDNamespace, pid int32) int32 { - return int32(new.IDOfTask(old.TaskWithID(kernel.ThreadID(pid)))) -} - -func posixLock(t *kernel.Task, args arch.SyscallArguments, file *vfs.FileDescription, block bool) error { - // Copy in the lock request. - flockAddr := args[2].Pointer() - var flock linux.Flock - if _, err := flock.CopyIn(t, flockAddr); err != nil { - return err - } - - r, err := file.ComputeLockRange(t, uint64(flock.Start), uint64(flock.Len), flock.Whence) - if err != nil { - return err - } - - switch flock.Type { - case linux.F_RDLCK: - if !file.IsReadable() { - return linuxerr.EBADF - } - return file.LockPOSIX(t, t.FDTable(), int32(t.TGIDInRoot()), lock.ReadLock, r, block) - - case linux.F_WRLCK: - if !file.IsWritable() { - return linuxerr.EBADF - } - return file.LockPOSIX(t, t.FDTable(), int32(t.TGIDInRoot()), lock.WriteLock, r, block) - - case linux.F_UNLCK: - return file.UnlockPOSIX(t, t.FDTable(), r) - - default: - return linuxerr.EINVAL - } -} - -// Fadvise64 implements fadvise64(2). -// This implementation currently ignores the provided advice. -func Fadvise64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - length := args[2].Int64() - advice := args[3].Int() - - // Note: offset is allowed to be negative. - if length < 0 { - return 0, nil, linuxerr.EINVAL - } - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - if file.StatusFlags()&linux.O_PATH != 0 { - return 0, nil, linuxerr.EBADF - } - - // If the FD refers to a pipe or FIFO, return error. - if _, isPipe := file.Impl().(*pipe.VFSPipeFD); isPipe { - return 0, nil, linuxerr.ESPIPE - } - - switch advice { - case linux.POSIX_FADV_NORMAL: - case linux.POSIX_FADV_RANDOM: - case linux.POSIX_FADV_SEQUENTIAL: - case linux.POSIX_FADV_WILLNEED: - case linux.POSIX_FADV_DONTNEED: - case linux.POSIX_FADV_NOREUSE: - default: - return 0, nil, linuxerr.EINVAL - } - - // Sure, whatever. - return 0, nil, nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/filesystem.go b/pkg/sentry/syscalls/linux/vfs2/filesystem.go deleted file mode 100644 index f19f0fd41..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/filesystem.go +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Link implements Linux syscall link(2). -func Link(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldpathAddr := args[0].Pointer() - newpathAddr := args[1].Pointer() - return 0, nil, linkat(t, linux.AT_FDCWD, oldpathAddr, linux.AT_FDCWD, newpathAddr, 0 /* flags */) -} - -// Linkat implements Linux syscall linkat(2). -func Linkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - olddirfd := args[0].Int() - oldpathAddr := args[1].Pointer() - newdirfd := args[2].Int() - newpathAddr := args[3].Pointer() - flags := args[4].Int() - return 0, nil, linkat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, flags) -} - -func linkat(t *kernel.Task, olddirfd int32, oldpathAddr hostarch.Addr, newdirfd int32, newpathAddr hostarch.Addr, flags int32) error { - if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_FOLLOW) != 0 { - return linuxerr.EINVAL - } - if flags&linux.AT_EMPTY_PATH != 0 && !t.HasCapability(linux.CAP_DAC_READ_SEARCH) { - return linuxerr.ENOENT - } - - oldpath, err := copyInPath(t, oldpathAddr) - if err != nil { - return err - } - oldtpop, err := getTaskPathOperation(t, olddirfd, oldpath, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_FOLLOW != 0)) - if err != nil { - return err - } - defer oldtpop.Release(t) - - newpath, err := copyInPath(t, newpathAddr) - if err != nil { - return err - } - newtpop, err := getTaskPathOperation(t, newdirfd, newpath, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer newtpop.Release(t) - - return t.Kernel().VFS().LinkAt(t, t.Credentials(), &oldtpop.pop, &newtpop.pop) -} - -// Mkdir implements Linux syscall mkdir(2). -func Mkdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - mode := args[1].ModeT() - return 0, nil, mkdirat(t, linux.AT_FDCWD, addr, mode) -} - -// Mkdirat implements Linux syscall mkdirat(2). -func Mkdirat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - addr := args[1].Pointer() - mode := args[2].ModeT() - return 0, nil, mkdirat(t, dirfd, addr, mode) -} - -func mkdirat(t *kernel.Task, dirfd int32, addr hostarch.Addr, mode uint) error { - path, err := copyInPath(t, addr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer tpop.Release(t) - return t.Kernel().VFS().MkdirAt(t, t.Credentials(), &tpop.pop, &vfs.MkdirOptions{ - Mode: linux.FileMode(mode & (0777 | linux.S_ISVTX) &^ t.FSContext().Umask()), - }) -} - -// Mknod implements Linux syscall mknod(2). -func Mknod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - mode := args[1].ModeT() - dev := args[2].Uint() - return 0, nil, mknodat(t, linux.AT_FDCWD, addr, linux.FileMode(mode), dev) -} - -// Mknodat implements Linux syscall mknodat(2). -func Mknodat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - addr := args[1].Pointer() - mode := args[2].ModeT() - dev := args[3].Uint() - return 0, nil, mknodat(t, dirfd, addr, linux.FileMode(mode), dev) -} - -func mknodat(t *kernel.Task, dirfd int32, addr hostarch.Addr, mode linux.FileMode, dev uint32) error { - path, err := copyInPath(t, addr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer tpop.Release(t) - - // "Zero file type is equivalent to type S_IFREG." - mknod(2) - if mode.FileType() == 0 { - mode |= linux.ModeRegular - } - major, minor := linux.DecodeDeviceID(dev) - return t.Kernel().VFS().MknodAt(t, t.Credentials(), &tpop.pop, &vfs.MknodOptions{ - Mode: mode &^ linux.FileMode(t.FSContext().Umask()), - DevMajor: uint32(major), - DevMinor: minor, - }) -} - -// Open implements Linux syscall open(2). -func Open(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - flags := args[1].Uint() - mode := args[2].ModeT() - return openat(t, linux.AT_FDCWD, addr, flags, mode) -} - -// Openat implements Linux syscall openat(2). -func Openat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - addr := args[1].Pointer() - flags := args[2].Uint() - mode := args[3].ModeT() - return openat(t, dirfd, addr, flags, mode) -} - -// Creat implements Linux syscall creat(2). -func Creat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - mode := args[1].ModeT() - return openat(t, linux.AT_FDCWD, addr, linux.O_WRONLY|linux.O_CREAT|linux.O_TRUNC, mode) -} - -func openat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, flags uint32, mode uint) (uintptr, *kernel.SyscallControl, error) { - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, shouldFollowFinalSymlink(flags&linux.O_NOFOLLOW == 0)) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - file, err := t.Kernel().VFS().OpenAt(t, t.Credentials(), &tpop.pop, &vfs.OpenOptions{ - Flags: flags | linux.O_LARGEFILE, - Mode: linux.FileMode(mode & (0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX) &^ t.FSContext().Umask()), - }) - if err != nil { - return 0, nil, err - } - defer file.DecRef(t) - - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ - CloseOnExec: flags&linux.O_CLOEXEC != 0, - }) - return uintptr(fd), nil, err -} - -// Rename implements Linux syscall rename(2). -func Rename(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - oldpathAddr := args[0].Pointer() - newpathAddr := args[1].Pointer() - return 0, nil, renameat(t, linux.AT_FDCWD, oldpathAddr, linux.AT_FDCWD, newpathAddr, 0 /* flags */) -} - -// Renameat implements Linux syscall renameat(2). -func Renameat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - olddirfd := args[0].Int() - oldpathAddr := args[1].Pointer() - newdirfd := args[2].Int() - newpathAddr := args[3].Pointer() - return 0, nil, renameat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, 0 /* flags */) -} - -// Renameat2 implements Linux syscall renameat2(2). -func Renameat2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - olddirfd := args[0].Int() - oldpathAddr := args[1].Pointer() - newdirfd := args[2].Int() - newpathAddr := args[3].Pointer() - flags := args[4].Uint() - return 0, nil, renameat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, flags) -} - -func renameat(t *kernel.Task, olddirfd int32, oldpathAddr hostarch.Addr, newdirfd int32, newpathAddr hostarch.Addr, flags uint32) error { - oldpath, err := copyInPath(t, oldpathAddr) - if err != nil { - return err - } - // "If oldpath refers to a symbolic link, the link is renamed" - rename(2) - oldtpop, err := getTaskPathOperation(t, olddirfd, oldpath, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer oldtpop.Release(t) - - newpath, err := copyInPath(t, newpathAddr) - if err != nil { - return err - } - newtpop, err := getTaskPathOperation(t, newdirfd, newpath, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer newtpop.Release(t) - - return t.Kernel().VFS().RenameAt(t, t.Credentials(), &oldtpop.pop, &newtpop.pop, &vfs.RenameOptions{ - Flags: flags, - }) -} - -// Rmdir implements Linux syscall rmdir(2). -func Rmdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - return 0, nil, rmdirat(t, linux.AT_FDCWD, pathAddr) -} - -func rmdirat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr) error { - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer tpop.Release(t) - return t.Kernel().VFS().RmdirAt(t, t.Credentials(), &tpop.pop) -} - -// Unlink implements Linux syscall unlink(2). -func Unlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - return 0, nil, unlinkat(t, linux.AT_FDCWD, pathAddr) -} - -func unlinkat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr) error { - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer tpop.Release(t) - return t.Kernel().VFS().UnlinkAt(t, t.Credentials(), &tpop.pop) -} - -// Unlinkat implements Linux syscall unlinkat(2). -func Unlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - flags := args[2].Int() - - if flags&^linux.AT_REMOVEDIR != 0 { - return 0, nil, linuxerr.EINVAL - } - - if flags&linux.AT_REMOVEDIR != 0 { - return 0, nil, rmdirat(t, dirfd, pathAddr) - } - return 0, nil, unlinkat(t, dirfd, pathAddr) -} - -// Symlink implements Linux syscall symlink(2). -func Symlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - targetAddr := args[0].Pointer() - linkpathAddr := args[1].Pointer() - return 0, nil, symlinkat(t, targetAddr, linux.AT_FDCWD, linkpathAddr) -} - -// Symlinkat implements Linux syscall symlinkat(2). -func Symlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - targetAddr := args[0].Pointer() - newdirfd := args[1].Int() - linkpathAddr := args[2].Pointer() - return 0, nil, symlinkat(t, targetAddr, newdirfd, linkpathAddr) -} - -func symlinkat(t *kernel.Task, targetAddr hostarch.Addr, newdirfd int32, linkpathAddr hostarch.Addr) error { - target, err := t.CopyInString(targetAddr, linux.PATH_MAX) - if err != nil { - return err - } - if len(target) == 0 { - return linuxerr.ENOENT - } - linkpath, err := copyInPath(t, linkpathAddr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, newdirfd, linkpath, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return err - } - defer tpop.Release(t) - return t.Kernel().VFS().SymlinkAt(t, t.Credentials(), &tpop.pop, target) -} diff --git a/pkg/sentry/syscalls/linux/vfs2/fscontext.go b/pkg/sentry/syscalls/linux/vfs2/fscontext.go deleted file mode 100644 index 8d8634331..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/fscontext.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Getcwd implements Linux syscall getcwd(2). -func Getcwd(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - size := args[1].SizeT() - - root := t.FSContext().RootDirectoryVFS2() - wd := t.FSContext().WorkingDirectoryVFS2() - s, err := t.Kernel().VFS().PathnameForGetcwd(t, root, wd) - root.DecRef(t) - wd.DecRef(t) - if err != nil { - return 0, nil, err - } - - // Note this is >= because we need a terminator. - if uint(len(s)) >= size { - return 0, nil, linuxerr.ERANGE - } - - // Construct a byte slice containing a NUL terminator. - buf := t.CopyScratchBuffer(len(s) + 1) - copy(buf, s) - buf[len(buf)-1] = 0 - - // Write the pathname slice. - n, err := t.CopyOutBytes(addr, buf) - if err != nil { - return 0, nil, err - } - return uintptr(n), nil, nil -} - -// Chdir implements Linux syscall chdir(2). -func Chdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - - path, err := copyInPath(t, addr) - if err != nil { - return 0, nil, err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - vd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{ - CheckSearchable: true, - }) - if err != nil { - return 0, nil, err - } - t.FSContext().SetWorkingDirectoryVFS2(t, vd) - vd.DecRef(t) - return 0, nil, nil -} - -// Fchdir implements Linux syscall fchdir(2). -func Fchdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - tpop, err := getTaskPathOperation(t, fd, fspath.Path{}, allowEmptyPath, nofollowFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - vd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{ - CheckSearchable: true, - }) - if err != nil { - return 0, nil, err - } - t.FSContext().SetWorkingDirectoryVFS2(t, vd) - vd.DecRef(t) - return 0, nil, nil -} - -// Chroot implements Linux syscall chroot(2). -func Chroot(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - - if !t.HasCapability(linux.CAP_SYS_CHROOT) { - return 0, nil, linuxerr.EPERM - } - - path, err := copyInPath(t, addr) - if err != nil { - return 0, nil, err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - vd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{ - CheckSearchable: true, - }) - if err != nil { - return 0, nil, err - } - t.FSContext().SetRootDirectoryVFS2(t, vd) - vd.DecRef(t) - return 0, nil, nil -} - -// PivotRoot implements Linux syscall pivot_root(2). -func PivotRoot(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr1 := args[0].Pointer() - addr2 := args[1].Pointer() - - if !t.HasCapability(linux.CAP_SYS_ADMIN) { - return 0, nil, linuxerr.EPERM - } - - newRootPath, err := copyInPath(t, addr1) - if err != nil { - return 0, nil, err - } - newRootTpop, err := getTaskPathOperation(t, linux.AT_FDCWD, newRootPath, disallowEmptyPath, followFinalSymlink) - if err != nil { - return 0, nil, err - } - defer newRootTpop.Release(t) - putOldPath, err := copyInPath(t, addr2) - if err != nil { - return 0, nil, err - } - putOldTpop, err := getTaskPathOperation(t, linux.AT_FDCWD, putOldPath, disallowEmptyPath, followFinalSymlink) - if err != nil { - return 0, nil, err - } - defer putOldTpop.Release(t) - - oldRootVd := t.FSContext().RootDirectoryVFS2() - defer oldRootVd.DecRef(t) - newRootVd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &newRootTpop.pop, &vfs.GetDentryOptions{ - CheckSearchable: true, - }) - if err != nil { - return 0, nil, err - } - defer newRootVd.DecRef(t) - - if err := t.Kernel().VFS().PivotRoot(t, t.Credentials(), &newRootTpop.pop, &putOldTpop.pop); err != nil { - return 0, nil, err - } - t.Kernel().ReplaceFSContextRoots(t, oldRootVd, newRootVd) - return 0, nil, nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/getdents.go b/pkg/sentry/syscalls/linux/vfs2/getdents.go deleted file mode 100644 index 08e47abde..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/getdents.go +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -// Getdents implements Linux syscall getdents(2). -func Getdents(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return getdents(t, args, false /* isGetdents64 */) -} - -// Getdents64 implements Linux syscall getdents64(2). -func Getdents64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return getdents(t, args, true /* isGetdents64 */) -} - -// DirentStructBytesWithoutName is enough to fit (struct linux_dirent) and -// (struct linux_dirent64) without accounting for the name parameter. -const DirentStructBytesWithoutName = 8 + 8 + 2 + 1 + 1 - -func getdents(t *kernel.Task, args arch.SyscallArguments, isGetdents64 bool) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - size := int(args[2].Uint()) - if size < DirentStructBytesWithoutName { - return 0, nil, linuxerr.EINVAL - } - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // We want to be sure of the allowed buffer size before calling IterDirents, - // because this function depends on IterDirents saving state of which dirent - // was the last one that was successfully operated on. - allowedSize, err := t.MemoryManager().EnsurePMAsExist(t, addr, int64(size), usermem.IOOpts{ - AddressSpaceActive: true, - }) - if allowedSize == 0 { - return 0, nil, err - } - - cb := getGetdentsCallback(t, int(allowedSize), size, isGetdents64) - err = file.IterDirents(t, cb) - n, _ := t.CopyOutBytes(addr, cb.buf[:cb.copied]) - - putGetdentsCallback(cb) - - // Only report an error in case we didn't copy anything. - // If we did manage to give _something_ to the caller then the correct - // behaviour is to return success. - if n == 0 { - return 0, nil, err - } - - return uintptr(n), nil, nil -} - -type getdentsCallback struct { - t *kernel.Task - buf []byte - copied int - userReportedSize int - isGetdents64 bool -} - -var getdentsCallbackPool = sync.Pool{ - New: func() any { - return &getdentsCallback{} - }, -} - -func getGetdentsCallback(t *kernel.Task, size int, userReportedSize int, isGetdents64 bool) *getdentsCallback { - cb := getdentsCallbackPool.Get().(*getdentsCallback) - buf := cb.buf - if cap(buf) < size { - buf = make([]byte, size) - } else { - buf = buf[:size] - } - - *cb = getdentsCallback{ - t: t, - buf: buf, - copied: 0, - userReportedSize: userReportedSize, - isGetdents64: isGetdents64, - } - return cb -} - -func putGetdentsCallback(cb *getdentsCallback) { - cb.t = nil - cb.buf = cb.buf[:0] - getdentsCallbackPool.Put(cb) -} - -// Handle implements vfs.IterDirentsCallback.Handle. -func (cb *getdentsCallback) Handle(dirent vfs.Dirent) error { - remaining := len(cb.buf) - cb.copied - if cb.isGetdents64 { - // struct linux_dirent64 { - // ino64_t d_ino; /* 64-bit inode number */ - // off64_t d_off; /* 64-bit offset to next structure */ - // unsigned short d_reclen; /* Size of this dirent */ - // unsigned char d_type; /* File type */ - // char d_name[]; /* Filename (null-terminated) */ - // }; - size := DirentStructBytesWithoutName + len(dirent.Name) - size = (size + 7) &^ 7 // round up to multiple of 8 - if size > remaining { - // This is only needed to imitate Linux, since it writes out to the user - // as it's iterating over dirs. We don't do that because we can't take - // the mm.mappingMu while holding the filesystem mutex. - if cb.copied == 0 && cb.userReportedSize >= size { - return linuxerr.EFAULT - } - return linuxerr.EINVAL - } - buf := cb.buf[cb.copied : cb.copied+size] - hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino) - hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff)) - hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size)) - buf[18] = dirent.Type - copy(buf[19:], dirent.Name) - // Zero out all remaining bytes in buf, including the NUL terminator - // after dirent.Name. - bufTail := buf[19+len(dirent.Name):] - for i := range bufTail { - bufTail[i] = 0 - } - cb.copied += size - } else { - // struct linux_dirent { - // unsigned long d_ino; /* Inode number */ - // unsigned long d_off; /* Offset to next linux_dirent */ - // unsigned short d_reclen; /* Length of this linux_dirent */ - // char d_name[]; /* Filename (null-terminated) */ - // /* length is actually (d_reclen - 2 - - // offsetof(struct linux_dirent, d_name)) */ - // /* - // char pad; // Zero padding byte - // char d_type; // File type (only since Linux - // // 2.6.4); offset is (d_reclen - 1) - // */ - // }; - if cb.t.Arch().Width() != 8 { - panic(fmt.Sprintf("unsupported sizeof(unsigned long): %d", cb.t.Arch().Width())) - } - size := DirentStructBytesWithoutName + len(dirent.Name) - size = (size + 7) &^ 7 // round up to multiple of sizeof(long) - if size > remaining { - if cb.copied == 0 && cb.userReportedSize >= size { - return linuxerr.EFAULT - } - return linuxerr.EINVAL - } - buf := cb.buf[cb.copied : cb.copied+size] - hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino) - hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff)) - hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size)) - copy(buf[18:], dirent.Name) - // Zero out all remaining bytes in buf, including the NUL terminator - // after dirent.Name and the zero padding byte between the name and - // dirent type. - bufTail := buf[18+len(dirent.Name) : size-1] - for i := range bufTail { - bufTail[i] = 0 - } - buf[size-1] = dirent.Type - cb.copied += size - } - - return nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/inotify.go b/pkg/sentry/syscalls/linux/vfs2/inotify.go deleted file mode 100644 index 739be9463..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/inotify.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -const allFlags = linux.IN_NONBLOCK | linux.IN_CLOEXEC - -// InotifyInit1 implements the inotify_init1() syscalls. -func InotifyInit1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - flags := args[0].Int() - if flags&^allFlags != 0 { - return 0, nil, linuxerr.EINVAL - } - - ino, err := vfs.NewInotifyFD(t, t.Kernel().VFS(), uint32(flags)) - if err != nil { - return 0, nil, err - } - defer ino.DecRef(t) - - fd, err := t.NewFDFromVFS2(0, ino, kernel.FDFlags{ - CloseOnExec: flags&linux.IN_CLOEXEC != 0, - }) - - if err != nil { - return 0, nil, err - } - - return uintptr(fd), nil, nil -} - -// InotifyInit implements the inotify_init() syscalls. -func InotifyInit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - args[0].Value = 0 - return InotifyInit1(t, args) -} - -// fdToInotify resolves an fd to an inotify object. If successful, the file will -// have an extra ref and the caller is responsible for releasing the ref. -func fdToInotify(t *kernel.Task, fd int32) (*vfs.Inotify, *vfs.FileDescription, error) { - f := t.GetFileVFS2(fd) - if f == nil { - // Invalid fd. - return nil, nil, linuxerr.EBADF - } - - ino, ok := f.Impl().(*vfs.Inotify) - if !ok { - // Not an inotify fd. - f.DecRef(t) - return nil, nil, linuxerr.EINVAL - } - - return ino, f, nil -} - -// InotifyAddWatch implements the inotify_add_watch() syscall. -func InotifyAddWatch(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - mask := args[2].Uint() - - // "EINVAL: The given event mask contains no valid events." - // -- inotify_add_watch(2) - if mask&linux.ALL_INOTIFY_BITS == 0 { - return 0, nil, linuxerr.EINVAL - } - - // "IN_DONT_FOLLOW: Don't dereference pathname if it is a symbolic link." - // -- inotify(7) - follow := followFinalSymlink - if mask&linux.IN_DONT_FOLLOW != 0 { - follow = nofollowFinalSymlink - } - - ino, f, err := fdToInotify(t, fd) - if err != nil { - return 0, nil, err - } - defer f.DecRef(t) - - path, err := copyInPath(t, addr) - if err != nil { - return 0, nil, err - } - if mask&linux.IN_ONLYDIR != 0 { - path.Dir = true - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, follow) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - d, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{}) - if err != nil { - return 0, nil, err - } - defer d.DecRef(t) - - return uintptr(ino.AddWatch(d.Dentry(), mask)), nil, nil -} - -// InotifyRmWatch implements the inotify_rm_watch() syscall. -func InotifyRmWatch(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - wd := args[1].Int() - - ino, f, err := fdToInotify(t, fd) - if err != nil { - return 0, nil, err - } - defer f.DecRef(t) - return 0, nil, ino.RmWatch(t, wd) -} diff --git a/pkg/sentry/syscalls/linux/vfs2/ioctl.go b/pkg/sentry/syscalls/linux/vfs2/ioctl.go deleted file mode 100644 index b806120cd..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/ioctl.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// Ioctl implements Linux syscall ioctl(2). -func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - if file.StatusFlags()&linux.O_PATH != 0 { - return 0, nil, linuxerr.EBADF - } - - // Handle ioctls that apply to all FDs. - switch args[1].Int() { - case linux.FIONCLEX: - t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ - CloseOnExec: false, - }) - return 0, nil, nil - - case linux.FIOCLEX: - t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ - CloseOnExec: true, - }) - return 0, nil, nil - - case linux.FIONBIO: - var set int32 - if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &set); err != nil { - return 0, nil, err - } - flags := file.StatusFlags() - if set != 0 { - flags |= linux.O_NONBLOCK - } else { - flags &^= linux.O_NONBLOCK - } - return 0, nil, file.SetStatusFlags(t, t.Credentials(), flags) - - case linux.FIOASYNC: - var set int32 - if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &set); err != nil { - return 0, nil, err - } - flags := file.StatusFlags() - if set != 0 { - flags |= linux.O_ASYNC - } else { - flags &^= linux.O_ASYNC - } - file.SetStatusFlags(t, t.Credentials(), flags) - return 0, nil, nil - - case linux.FIOGETOWN, linux.SIOCGPGRP: - var who int32 - owner, hasOwner := getAsyncOwner(t, file) - if hasOwner { - if owner.Type == linux.F_OWNER_PGRP { - who = -owner.PID - } else { - who = owner.PID - } - } - _, err := primitive.CopyInt32Out(t, args[2].Pointer(), who) - return 0, nil, err - - case linux.FIOSETOWN, linux.SIOCSPGRP: - var who int32 - if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &who); err != nil { - return 0, nil, err - } - ownerType := int32(linux.F_OWNER_PID) - if who < 0 { - // Check for overflow before flipping the sign. - if who-1 > who { - return 0, nil, linuxerr.EINVAL - } - ownerType = linux.F_OWNER_PGRP - who = -who - } - return 0, nil, setAsyncOwner(t, int(fd), file, ownerType, who) - } - - ret, err := file.Ioctl(t, t.MemoryManager(), args) - return ret, nil, err -} diff --git a/pkg/sentry/syscalls/linux/vfs2/lock.go b/pkg/sentry/syscalls/linux/vfs2/lock.go deleted file mode 100644 index 0d1cce6d8..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/lock.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// Flock implements linux syscall flock(2). -func Flock(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - operation := args[1].Int() - - file := t.GetFileVFS2(fd) - if file == nil { - // flock(2): EBADF fd is not an open file descriptor. - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - nonblocking := operation&linux.LOCK_NB != 0 - operation &^= linux.LOCK_NB - - switch operation { - case linux.LOCK_EX: - if err := file.LockBSD(t, int32(t.TGIDInRoot()), lock.WriteLock, !nonblocking /* block */); err != nil { - return 0, nil, err - } - case linux.LOCK_SH: - if err := file.LockBSD(t, int32(t.TGIDInRoot()), lock.ReadLock, !nonblocking /* block */); err != nil { - return 0, nil, err - } - case linux.LOCK_UN: - if err := file.UnlockBSD(t); err != nil { - return 0, nil, err - } - default: - // flock(2): EINVAL operation is invalid. - return 0, nil, linuxerr.EINVAL - } - - return 0, nil, nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/memfd.go b/pkg/sentry/syscalls/linux/vfs2/memfd.go deleted file mode 100644 index 70c2cf5a5..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/memfd.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -const ( - memfdPrefix = "memfd:" - memfdMaxNameLen = linux.NAME_MAX - len(memfdPrefix) - memfdAllFlags = uint32(linux.MFD_CLOEXEC | linux.MFD_ALLOW_SEALING) -) - -// MemfdCreate implements the linux syscall memfd_create(2). -func MemfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - flags := args[1].Uint() - - if flags&^memfdAllFlags != 0 { - // Unknown bits in flags. - return 0, nil, linuxerr.EINVAL - } - - allowSeals := flags&linux.MFD_ALLOW_SEALING != 0 - cloExec := flags&linux.MFD_CLOEXEC != 0 - - name, err := t.CopyInString(addr, memfdMaxNameLen) - if err != nil { - return 0, nil, err - } - - shmMount := t.Kernel().ShmMount() - file, err := tmpfs.NewMemfd(t, t.Credentials(), shmMount, allowSeals, memfdPrefix+name) - if err != nil { - return 0, nil, err - } - defer file.DecRef(t) - - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ - CloseOnExec: cloExec, - }) - if err != nil { - return 0, nil, err - } - - return uintptr(fd), nil, nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/mount.go b/pkg/sentry/syscalls/linux/vfs2/mount.go deleted file mode 100644 index faa8205cf..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/mount.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/bits" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Mount implements Linux syscall mount(2). -func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - sourceAddr := args[0].Pointer() - targetAddr := args[1].Pointer() - typeAddr := args[2].Pointer() - flags := args[3].Uint64() - dataAddr := args[4].Pointer() - - // Must have CAP_SYS_ADMIN in the current mount namespace's associated user - // namespace. - creds := t.Credentials() - if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) { - return 0, nil, linuxerr.EPERM - } - - // Ignore magic value that was required before Linux 2.4. - if flags&linux.MS_MGC_MSK == linux.MS_MGC_VAL { - flags = flags &^ linux.MS_MGC_MSK - } - - // Silently allow MS_NOSUID, since we don't implement set-id bits anyway. - const unsupported = linux.MS_REMOUNT | linux.MS_SLAVE | - linux.MS_UNBINDABLE | linux.MS_MOVE | linux.MS_REC | linux.MS_NODIRATIME | - linux.MS_STRICTATIME - - // Linux just allows passing any flags to mount(2) - it won't fail when - // unknown or unsupported flags are passed. Since we don't implement - // everything, we fail explicitly on flags that are unimplemented. - if flags&(unsupported) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // For null-terminated strings related to mount(2), Linux copies in at most - // a page worth of data. See fs/namespace.c:copy_mount_string(). - targetPath, err := copyInPath(t, targetAddr) - if err != nil { - return 0, nil, err - } - target, err := getTaskPathOperation(t, linux.AT_FDCWD, targetPath, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return 0, nil, err - } - defer target.Release(t) - - if flags&linux.MS_BIND == linux.MS_BIND { - var sourcePath fspath.Path - sourcePath, err = copyInPath(t, sourceAddr) - if err != nil { - return 0, nil, err - } - var sourceTpop taskPathOperation - sourceTpop, err = getTaskPathOperation(t, linux.AT_FDCWD, sourcePath, disallowEmptyPath, nofollowFinalSymlink) - if err != nil { - return 0, nil, err - } - defer sourceTpop.Release(t) - _, err = t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop) - return 0, nil, err - } - const propagationFlags = linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | linux.MS_UNBINDABLE - if propFlag := flags & propagationFlags; propFlag != 0 { - // Check if flags is a power of 2. If not then more than one flag is set. - if !bits.IsPowerOfTwo64(propFlag) { - return 0, nil, linuxerr.EINVAL - } - propType := vfs.PropagationTypeFromLinux(propFlag) - return 0, nil, t.Kernel().VFS().SetMountPropagationAt(t, creds, &target.pop, propType) - } - - // Only copy in source, fstype, and data if we are doing a normal mount. - source, err := t.CopyInString(sourceAddr, hostarch.PageSize) - if err != nil { - return 0, nil, err - } - fsType, err := t.CopyInString(typeAddr, hostarch.PageSize) - if err != nil { - return 0, nil, err - } - data := "" - if dataAddr != 0 { - // In Linux, a full page is always copied in regardless of null - // character placement, and the address is passed to each file system. - // Most file systems always treat this data as a string, though, and so - // do all of the ones we implement. - data, err = t.CopyInString(dataAddr, hostarch.PageSize) - if err != nil { - return 0, nil, err - } - } - var opts vfs.MountOptions - if flags&linux.MS_NOATIME == linux.MS_NOATIME { - opts.Flags.NoATime = true - } - if flags&linux.MS_NOEXEC == linux.MS_NOEXEC { - opts.Flags.NoExec = true - } - if flags&linux.MS_NODEV == linux.MS_NODEV { - opts.Flags.NoDev = true - } - if flags&linux.MS_NOSUID == linux.MS_NOSUID { - opts.Flags.NoSUID = true - } - if flags&linux.MS_RDONLY == linux.MS_RDONLY { - opts.ReadOnly = true - } - opts.GetFilesystemOptions.Data = data - _, err = t.Kernel().VFS().MountAt(t, creds, source, &target.pop, fsType, &opts) - return 0, nil, err -} - -// Umount2 implements Linux syscall umount2(2). -func Umount2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - flags := args[1].Int() - - // Must have CAP_SYS_ADMIN in the mount namespace's associated user - // namespace. - // - // Currently, this is always the init task's user namespace. - creds := t.Credentials() - if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) { - return 0, nil, linuxerr.EPERM - } - - const unsupported = linux.MNT_FORCE | linux.MNT_EXPIRE - if flags&unsupported != 0 { - return 0, nil, linuxerr.EINVAL - } - - path, err := copyInPath(t, addr) - if err != nil { - return 0, nil, err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink(flags&linux.UMOUNT_NOFOLLOW == 0)) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - opts := vfs.UmountOptions{ - Flags: uint32(flags &^ linux.UMOUNT_NOFOLLOW), - } - - return 0, nil, t.Kernel().VFS().UmountAt(t, creds, &tpop.pop, &opts) -} diff --git a/pkg/sentry/syscalls/linux/vfs2/pipe.go b/pkg/sentry/syscalls/linux/vfs2/pipe.go deleted file mode 100644 index 07a89cf4e..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/pipe.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/pipefs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Pipe implements Linux syscall pipe(2). -func Pipe(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - return 0, nil, pipe2(t, addr, 0) -} - -// Pipe2 implements Linux syscall pipe2(2). -func Pipe2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - flags := args[1].Int() - return 0, nil, pipe2(t, addr, flags) -} - -func pipe2(t *kernel.Task, addr hostarch.Addr, flags int32) error { - if flags&^(linux.O_NONBLOCK|linux.O_CLOEXEC) != 0 { - return linuxerr.EINVAL - } - r, w, err := pipefs.NewConnectedPipeFDs(t, t.Kernel().PipeMount(), uint32(flags&linux.O_NONBLOCK)) - if err != nil { - return err - } - defer r.DecRef(t) - defer w.DecRef(t) - - fds, err := t.NewFDsVFS2(0, []*vfs.FileDescription{r, w}, kernel.FDFlags{ - CloseOnExec: flags&linux.O_CLOEXEC != 0, - }) - if err != nil { - return err - } - if _, err := primitive.CopyInt32SliceOut(t, addr, fds); err != nil { - for _, fd := range fds { - if _, file := t.FDTable().Remove(t, fd); file != nil { - file.DecRef(t) - } - } - return err - } - return nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/poll.go b/pkg/sentry/syscalls/linux/vfs2/poll.go deleted file mode 100644 index 32726952c..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/poll.go +++ /dev/null @@ -1,591 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "fmt" - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/limits" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/waiter" -) - -// fileCap is the maximum allowable files for poll & select. This has no -// equivalent in Linux; it exists in gVisor since allocation failure in Go is -// unrecoverable. -const fileCap = 1024 * 1024 - -// Masks for "readable", "writable", and "exceptional" events as defined by -// select(2). -const ( - // selectReadEvents is analogous to the Linux kernel's - // fs/select.c:POLLIN_SET. - selectReadEvents = linux.POLLIN | linux.POLLHUP | linux.POLLERR - - // selectWriteEvents is analogous to the Linux kernel's - // fs/select.c:POLLOUT_SET. - selectWriteEvents = linux.POLLOUT | linux.POLLERR - - // selectExceptEvents is analogous to the Linux kernel's - // fs/select.c:POLLEX_SET. - selectExceptEvents = linux.POLLPRI -) - -// pollState tracks the associated file description and waiter of a PollFD. -type pollState struct { - file *vfs.FileDescription - waiter waiter.Entry -} - -// initReadiness gets the current ready mask for the file represented by the FD -// stored in pfd.FD. If a channel is passed in, the waiter entry in "state" is -// used to register with the file for event notifications, and a reference to -// the file is stored in "state". -func initReadiness(t *kernel.Task, pfd *linux.PollFD, state *pollState, ch chan struct{}) error { - if pfd.FD < 0 { - pfd.REvents = 0 - return nil - } - - file := t.GetFileVFS2(pfd.FD) - if file == nil { - pfd.REvents = linux.POLLNVAL - return nil - } - - if ch == nil { - defer file.DecRef(t) - } else { - state.file = file - state.waiter.Init(waiter.ChannelNotifier(ch), waiter.EventMaskFromLinux(uint32(pfd.Events))) - if err := file.EventRegister(&state.waiter); err != nil { - return err - } - } - - r := file.Readiness(waiter.EventMaskFromLinux(uint32(pfd.Events))) - pfd.REvents = int16(r.ToLinux()) & pfd.Events - return nil -} - -// releaseState releases all the pollState in "state". -func releaseState(t *kernel.Task, state []pollState) { - for i := range state { - if state[i].file != nil { - state[i].file.EventUnregister(&state[i].waiter) - state[i].file.DecRef(t) - } - } -} - -// pollBlock polls the PollFDs in "pfd" with a bounded time specified in "timeout" -// when "timeout" is greater than zero. -// -// pollBlock returns the remaining timeout, which is always 0 on a timeout; and 0 or -// positive if interrupted by a signal. -func pollBlock(t *kernel.Task, pfd []linux.PollFD, timeout time.Duration) (time.Duration, uintptr, error) { - var ch chan struct{} - if timeout != 0 { - ch = make(chan struct{}, 1) - } - - // Register for event notification in the files involved if we may - // block (timeout not zero). Once we find a file that has a non-zero - // result, we stop registering for events but still go through all files - // to get their ready masks. - state := make([]pollState, len(pfd)) - defer releaseState(t, state) - n := uintptr(0) - for i := range pfd { - if err := initReadiness(t, &pfd[i], &state[i], ch); err != nil { - return timeout, 0, err - } - if pfd[i].REvents != 0 { - n++ - ch = nil - } - } - - if timeout == 0 { - return timeout, n, nil - } - - haveTimeout := timeout >= 0 - - for n == 0 { - var err error - // Wait for a notification. - timeout, err = t.BlockWithTimeout(ch, haveTimeout, timeout) - if err != nil { - if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { - err = nil - } - return timeout, 0, err - } - - // We got notified, count how many files are ready. If none, - // then this was a spurious notification, and we just go back - // to sleep with the remaining timeout. - for i := range state { - if state[i].file == nil { - continue - } - - r := state[i].file.Readiness(waiter.EventMaskFromLinux(uint32(pfd[i].Events))) - rl := int16(r.ToLinux()) & pfd[i].Events - if rl != 0 { - pfd[i].REvents = rl - n++ - } - } - } - - return timeout, n, nil -} - -// copyInPollFDs copies an array of struct pollfd unless nfds exceeds the max. -func copyInPollFDs(t *kernel.Task, addr hostarch.Addr, nfds uint) ([]linux.PollFD, error) { - if uint64(nfds) > t.ThreadGroup().Limits().GetCapped(limits.NumberOfFiles, fileCap) { - return nil, linuxerr.EINVAL - } - - pfd := make([]linux.PollFD, nfds) - if nfds > 0 { - if _, err := linux.CopyPollFDSliceIn(t, addr, pfd); err != nil { - return nil, err - } - } - - return pfd, nil -} - -func doPoll(t *kernel.Task, addr hostarch.Addr, nfds uint, timeout time.Duration) (time.Duration, uintptr, error) { - pfd, err := copyInPollFDs(t, addr, nfds) - if err != nil { - return timeout, 0, err - } - - // Compatibility warning: Linux adds POLLHUP and POLLERR just before - // polling, in fs/select.c:do_pollfd(). Since pfd is copied out after - // polling, changing event masks here is an application-visible difference. - // (Linux also doesn't copy out event masks at all, only revents.) - for i := range pfd { - pfd[i].Events |= linux.POLLHUP | linux.POLLERR - } - remainingTimeout, n, err := pollBlock(t, pfd, timeout) - err = linuxerr.ConvertIntr(err, linuxerr.EINTR) - - // The poll entries are copied out regardless of whether - // any are set or not. This aligns with the Linux behavior. - if nfds > 0 && err == nil { - if _, err := linux.CopyPollFDSliceOut(t, addr, pfd); err != nil { - return remainingTimeout, 0, err - } - } - - return remainingTimeout, n, err -} - -// CopyInFDSet copies an fd set from select(2)/pselect(2). -func CopyInFDSet(t *kernel.Task, addr hostarch.Addr, nBytes, nBitsInLastPartialByte int) ([]byte, error) { - set := make([]byte, nBytes) - - if addr != 0 { - if _, err := t.CopyInBytes(addr, set); err != nil { - return nil, err - } - // If we only use part of the last byte, mask out the extraneous bits. - // - // N.B. This only works on little-endian architectures. - if nBitsInLastPartialByte != 0 { - set[nBytes-1] &^= byte(0xff) << nBitsInLastPartialByte - } - } - return set, nil -} - -func doSelect(t *kernel.Task, nfds int, readFDs, writeFDs, exceptFDs hostarch.Addr, timeout time.Duration) (uintptr, error) { - if nfds < 0 || nfds > fileCap { - return 0, linuxerr.EINVAL - } - - // Calculate the size of the fd sets (one bit per fd). - nBytes := (nfds + 7) / 8 - nBitsInLastPartialByte := nfds % 8 - - // Capture all the provided input vectors. - r, err := CopyInFDSet(t, readFDs, nBytes, nBitsInLastPartialByte) - if err != nil { - return 0, err - } - w, err := CopyInFDSet(t, writeFDs, nBytes, nBitsInLastPartialByte) - if err != nil { - return 0, err - } - e, err := CopyInFDSet(t, exceptFDs, nBytes, nBitsInLastPartialByte) - if err != nil { - return 0, err - } - - // Count how many FDs are actually being requested so that we can build - // a PollFD array. - fdCount := 0 - for i := 0; i < nBytes; i++ { - v := r[i] | w[i] | e[i] - for v != 0 { - v &= (v - 1) - fdCount++ - } - } - - // Build the PollFD array. - pfd := make([]linux.PollFD, 0, fdCount) - var fd int32 - for i := 0; i < nBytes; i++ { - rV, wV, eV := r[i], w[i], e[i] - v := rV | wV | eV - m := byte(1) - for j := 0; j < 8; j++ { - if (v & m) != 0 { - // Make sure the fd is valid and decrement the reference - // immediately to ensure we don't leak. Note, another thread - // might be about to close fd. This is racy, but that's - // OK. Linux is racy in the same way. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, linuxerr.EBADF - } - file.DecRef(t) - - var mask int16 - if (rV & m) != 0 { - mask |= selectReadEvents - } - - if (wV & m) != 0 { - mask |= selectWriteEvents - } - - if (eV & m) != 0 { - mask |= selectExceptEvents - } - - pfd = append(pfd, linux.PollFD{ - FD: fd, - Events: mask, - }) - } - - fd++ - m <<= 1 - } - } - - // Do the syscall, then count the number of bits set. - if _, _, err = pollBlock(t, pfd, timeout); err != nil { - return 0, linuxerr.ConvertIntr(err, linuxerr.EINTR) - } - - // r, w, and e are currently event mask bitsets; unset bits corresponding - // to events that *didn't* occur. - bitSetCount := uintptr(0) - for idx := range pfd { - events := pfd[idx].REvents - i, j := pfd[idx].FD/8, uint(pfd[idx].FD%8) - m := byte(1) << j - if r[i]&m != 0 { - if (events & selectReadEvents) != 0 { - bitSetCount++ - } else { - r[i] &^= m - } - } - if w[i]&m != 0 { - if (events & selectWriteEvents) != 0 { - bitSetCount++ - } else { - w[i] &^= m - } - } - if e[i]&m != 0 { - if (events & selectExceptEvents) != 0 { - bitSetCount++ - } else { - e[i] &^= m - } - } - } - - // Copy updated vectors back. - if readFDs != 0 { - if _, err := t.CopyOutBytes(readFDs, r); err != nil { - return 0, err - } - } - - if writeFDs != 0 { - if _, err := t.CopyOutBytes(writeFDs, w); err != nil { - return 0, err - } - } - - if exceptFDs != 0 { - if _, err := t.CopyOutBytes(exceptFDs, e); err != nil { - return 0, err - } - } - - return bitSetCount, nil -} - -// timeoutRemaining returns the amount of time remaining for the specified -// timeout or 0 if it has elapsed. -// -// startNs must be from CLOCK_MONOTONIC. -func timeoutRemaining(t *kernel.Task, startNs ktime.Time, timeout time.Duration) time.Duration { - now := t.Kernel().MonotonicClock().Now() - remaining := timeout - now.Sub(startNs) - if remaining < 0 { - remaining = 0 - } - return remaining -} - -// copyOutTimespecRemaining copies the time remaining in timeout to timespecAddr. -// -// startNs must be from CLOCK_MONOTONIC. -func copyOutTimespecRemaining(t *kernel.Task, startNs ktime.Time, timeout time.Duration, timespecAddr hostarch.Addr) error { - if timeout <= 0 { - return nil - } - remaining := timeoutRemaining(t, startNs, timeout) - tsRemaining := linux.NsecToTimespec(remaining.Nanoseconds()) - _, err := tsRemaining.CopyOut(t, timespecAddr) - return err -} - -// copyOutTimevalRemaining copies the time remaining in timeout to timevalAddr. -// -// startNs must be from CLOCK_MONOTONIC. -func copyOutTimevalRemaining(t *kernel.Task, startNs ktime.Time, timeout time.Duration, timevalAddr hostarch.Addr) error { - if timeout <= 0 { - return nil - } - remaining := timeoutRemaining(t, startNs, timeout) - tvRemaining := linux.NsecToTimeval(remaining.Nanoseconds()) - _, err := tvRemaining.CopyOut(t, timevalAddr) - return err -} - -// pollRestartBlock encapsulates the state required to restart poll(2) via -// restart_syscall(2). -// -// +stateify savable -type pollRestartBlock struct { - pfdAddr hostarch.Addr - nfds uint - timeout time.Duration -} - -// Restart implements kernel.SyscallRestartBlock.Restart. -func (p *pollRestartBlock) Restart(t *kernel.Task) (uintptr, error) { - return poll(t, p.pfdAddr, p.nfds, p.timeout) -} - -func poll(t *kernel.Task, pfdAddr hostarch.Addr, nfds uint, timeout time.Duration) (uintptr, error) { - remainingTimeout, n, err := doPoll(t, pfdAddr, nfds, timeout) - // On an interrupt poll(2) is restarted with the remaining timeout. - if linuxerr.Equals(linuxerr.EINTR, err) { - t.SetSyscallRestartBlock(&pollRestartBlock{ - pfdAddr: pfdAddr, - nfds: nfds, - timeout: remainingTimeout, - }) - return 0, linuxerr.ERESTART_RESTARTBLOCK - } - return n, err -} - -// Poll implements linux syscall poll(2). -func Poll(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pfdAddr := args[0].Pointer() - nfds := uint(args[1].Uint()) // poll(2) uses unsigned long. - timeout := time.Duration(args[2].Int()) * time.Millisecond - n, err := poll(t, pfdAddr, nfds, timeout) - return n, nil, err -} - -// Ppoll implements linux syscall ppoll(2). -func Ppoll(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pfdAddr := args[0].Pointer() - nfds := uint(args[1].Uint()) // poll(2) uses unsigned long. - timespecAddr := args[2].Pointer() - maskAddr := args[3].Pointer() - maskSize := uint(args[4].Uint()) - - timeout, err := copyTimespecInToDuration(t, timespecAddr) - if err != nil { - return 0, nil, err - } - - var startNs ktime.Time - if timeout > 0 { - startNs = t.Kernel().MonotonicClock().Now() - } - - if err := setTempSignalSet(t, maskAddr, maskSize); err != nil { - return 0, nil, err - } - - _, n, err := doPoll(t, pfdAddr, nfds, timeout) - copyErr := copyOutTimespecRemaining(t, startNs, timeout, timespecAddr) - // doPoll returns EINTR if interrupted, but ppoll is normally restartable - // if interrupted by something other than a signal handled by the - // application (i.e. returns ERESTARTNOHAND). However, if - // copyOutTimespecRemaining failed, then the restarted ppoll would use the - // wrong timeout, so the error should be left as EINTR. - // - // Note that this means that if err is nil but copyErr is not, copyErr is - // ignored. This is consistent with Linux. - if linuxerr.Equals(linuxerr.EINTR, err) && copyErr == nil { - err = linuxerr.ERESTARTNOHAND - } - return n, nil, err -} - -// Select implements linux syscall select(2). -func Select(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - nfds := int(args[0].Int()) // select(2) uses an int. - readFDs := args[1].Pointer() - writeFDs := args[2].Pointer() - exceptFDs := args[3].Pointer() - timevalAddr := args[4].Pointer() - - // Use a negative Duration to indicate "no timeout". - timeout := time.Duration(-1) - if timevalAddr != 0 { - var timeval linux.Timeval - if _, err := timeval.CopyIn(t, timevalAddr); err != nil { - return 0, nil, err - } - if timeval.Sec < 0 || timeval.Usec < 0 { - return 0, nil, linuxerr.EINVAL - } - timeout = time.Duration(timeval.ToNsecCapped()) - } - startNs := t.Kernel().MonotonicClock().Now() - n, err := doSelect(t, nfds, readFDs, writeFDs, exceptFDs, timeout) - copyErr := copyOutTimevalRemaining(t, startNs, timeout, timevalAddr) - // See comment in Ppoll. - if linuxerr.Equals(linuxerr.EINTR, err) && copyErr == nil { - err = linuxerr.ERESTARTNOHAND - } - return n, nil, err -} - -// +marshal -type sigSetWithSize struct { - sigsetAddr uint64 - sizeofSigset uint64 -} - -// Pselect implements linux syscall pselect(2). -func Pselect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - nfds := int(args[0].Int()) // select(2) uses an int. - readFDs := args[1].Pointer() - writeFDs := args[2].Pointer() - exceptFDs := args[3].Pointer() - timespecAddr := args[4].Pointer() - maskWithSizeAddr := args[5].Pointer() - - timeout, err := copyTimespecInToDuration(t, timespecAddr) - if err != nil { - return 0, nil, err - } - - var startNs ktime.Time - if timeout > 0 { - startNs = t.Kernel().MonotonicClock().Now() - } - - if maskWithSizeAddr != 0 { - if t.Arch().Width() != 8 { - panic(fmt.Sprintf("unsupported sizeof(void*): %d", t.Arch().Width())) - } - var maskStruct sigSetWithSize - if _, err := maskStruct.CopyIn(t, maskWithSizeAddr); err != nil { - return 0, nil, err - } - if err := setTempSignalSet(t, hostarch.Addr(maskStruct.sigsetAddr), uint(maskStruct.sizeofSigset)); err != nil { - return 0, nil, err - } - } - - n, err := doSelect(t, nfds, readFDs, writeFDs, exceptFDs, timeout) - copyErr := copyOutTimespecRemaining(t, startNs, timeout, timespecAddr) - // See comment in Ppoll. - if linuxerr.Equals(linuxerr.EINTR, err) && copyErr == nil { - err = linuxerr.ERESTARTNOHAND - } - return n, nil, err -} - -// copyTimespecInToDuration copies a Timespec from the untrusted app range, -// validates it and converts it to a Duration. -// -// If the Timespec is larger than what can be represented in a Duration, the -// returned value is the maximum that Duration will allow. -// -// If timespecAddr is NULL, the returned value is negative. -func copyTimespecInToDuration(t *kernel.Task, timespecAddr hostarch.Addr) (time.Duration, error) { - // Use a negative Duration to indicate "no timeout". - timeout := time.Duration(-1) - if timespecAddr != 0 { - var timespec linux.Timespec - if _, err := timespec.CopyIn(t, timespecAddr); err != nil { - return 0, err - } - if !timespec.Valid() { - return 0, linuxerr.EINVAL - } - timeout = time.Duration(timespec.ToNsecCapped()) - } - return timeout, nil -} - -func setTempSignalSet(t *kernel.Task, maskAddr hostarch.Addr, maskSize uint) error { - if maskAddr == 0 { - return nil - } - if maskSize != linux.SignalSetSize { - return linuxerr.EINVAL - } - var mask linux.SignalSet - if _, err := mask.CopyIn(t, maskAddr); err != nil { - return err - } - mask &^= kernel.UnblockableSignals - oldmask := t.SignalMask() - t.SetSignalMask(mask) - t.SetSavedSignalMask(oldmask) - return nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/setstat.go b/pkg/sentry/syscalls/linux/vfs2/setstat.go deleted file mode 100644 index e608572b4..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/setstat.go +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/limits" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -const chmodMask = 0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX - -// Chmod implements Linux syscall chmod(2). -func Chmod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - mode := args[1].ModeT() - return 0, nil, fchmodat(t, linux.AT_FDCWD, pathAddr, mode) -} - -// Fchmodat implements Linux syscall fchmodat(2). -func Fchmodat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - mode := args[2].ModeT() - return 0, nil, fchmodat(t, dirfd, pathAddr, mode) -} - -func fchmodat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint) error { - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - - return setstatat(t, dirfd, path, disallowEmptyPath, followFinalSymlink, &vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_MODE, - Mode: uint16(mode & chmodMask), - }, - }) -} - -// Fchmod implements Linux syscall fchmod(2). -func Fchmod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - mode := args[1].ModeT() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - return 0, nil, file.SetStat(t, vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_MODE, - Mode: uint16(mode & chmodMask), - }, - }) -} - -// Chown implements Linux syscall chown(2). -func Chown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - owner := args[1].Int() - group := args[2].Int() - return 0, nil, fchownat(t, linux.AT_FDCWD, pathAddr, owner, group, 0 /* flags */) -} - -// Lchown implements Linux syscall lchown(2). -func Lchown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - owner := args[1].Int() - group := args[2].Int() - return 0, nil, fchownat(t, linux.AT_FDCWD, pathAddr, owner, group, linux.AT_SYMLINK_NOFOLLOW) -} - -// Fchownat implements Linux syscall fchownat(2). -func Fchownat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - owner := args[2].Int() - group := args[3].Int() - flags := args[4].Int() - return 0, nil, fchownat(t, dirfd, pathAddr, owner, group, flags) -} - -func fchownat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, owner, group, flags int32) error { - if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 { - return linuxerr.EINVAL - } - - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - - var opts vfs.SetStatOptions - if err := populateSetStatOptionsForChown(t, owner, group, &opts); err != nil { - return err - } - - return setstatat(t, dirfd, path, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_NOFOLLOW == 0), &opts) -} - -func populateSetStatOptionsForChown(t *kernel.Task, owner, group int32, opts *vfs.SetStatOptions) error { - userns := t.UserNamespace() - if owner != -1 { - kuid := userns.MapToKUID(auth.UID(owner)) - if !kuid.Ok() { - return linuxerr.EINVAL - } - opts.Stat.Mask |= linux.STATX_UID - opts.Stat.UID = uint32(kuid) - } - if group != -1 { - kgid := userns.MapToKGID(auth.GID(group)) - if !kgid.Ok() { - return linuxerr.EINVAL - } - opts.Stat.Mask |= linux.STATX_GID - opts.Stat.GID = uint32(kgid) - } - return nil -} - -// Fchown implements Linux syscall fchown(2). -func Fchown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - owner := args[1].Int() - group := args[2].Int() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - var opts vfs.SetStatOptions - if err := populateSetStatOptionsForChown(t, owner, group, &opts); err != nil { - return 0, nil, err - } - return 0, nil, file.SetStat(t, opts) -} - -// Truncate implements Linux syscall truncate(2). -func Truncate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - length := args[1].Int64() - - if length < 0 { - return 0, nil, linuxerr.EINVAL - } - - path, err := copyInPath(t, addr) - if err != nil { - return 0, nil, err - } - - err = setstatat(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink, &vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_SIZE, - Size: uint64(length), - }, - NeedWritePerm: true, - }) - return 0, nil, handleSetSizeError(t, err) -} - -// Ftruncate implements Linux syscall ftruncate(2). -func Ftruncate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - length := args[1].Int64() - - if length < 0 { - return 0, nil, linuxerr.EINVAL - } - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - if !file.IsWritable() { - return 0, nil, linuxerr.EINVAL - } - - err := file.SetStat(t, vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_SIZE, - Size: uint64(length), - }, - }) - return 0, nil, handleSetSizeError(t, err) -} - -// Fallocate implements linux system call fallocate(2). -func Fallocate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - mode := args[1].Uint64() - offset := args[2].Int64() - length := args[3].Int64() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - if !file.IsWritable() { - return 0, nil, linuxerr.EBADF - } - if mode != 0 { - return 0, nil, linuxerr.ENOTSUP - } - if offset < 0 || length <= 0 { - return 0, nil, linuxerr.EINVAL - } - - size := offset + length - if size < 0 { - return 0, nil, linuxerr.EFBIG - } - limit := limits.FromContext(t).Get(limits.FileSize).Cur - if uint64(size) >= limit { - t.SendSignal(&linux.SignalInfo{ - Signo: int32(linux.SIGXFSZ), - Code: linux.SI_USER, - }) - return 0, nil, linuxerr.EFBIG - } - - return 0, nil, file.Allocate(t, mode, uint64(offset), uint64(length)) -} - -// Utime implements Linux syscall utime(2). -func Utime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - timesAddr := args[1].Pointer() - - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - - opts := vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_ATIME | linux.STATX_MTIME, - }, - } - if timesAddr == 0 { - opts.Stat.Atime.Nsec = linux.UTIME_NOW - opts.Stat.Mtime.Nsec = linux.UTIME_NOW - } else { - var times linux.Utime - if _, err := times.CopyIn(t, timesAddr); err != nil { - return 0, nil, err - } - opts.Stat.Atime.Sec = times.Actime - opts.Stat.Mtime.Sec = times.Modtime - } - - return 0, nil, setstatat(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink, &opts) -} - -// Utimes implements Linux syscall utimes(2). -func Utimes(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - timesAddr := args[1].Pointer() - - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - - var opts vfs.SetStatOptions - if err := populateSetStatOptionsForUtimes(t, timesAddr, &opts); err != nil { - return 0, nil, err - } - - return 0, nil, setstatat(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink, &opts) -} - -// Futimesat implements Linux syscall futimesat(2). -func Futimesat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - timesAddr := args[2].Pointer() - - // "If filename is NULL and dfd refers to an open file, then operate on the - // file. Otherwise look up filename, possibly using dfd as a starting - // point." - fs/utimes.c - var path fspath.Path - shouldAllowEmptyPath := allowEmptyPath - if dirfd == linux.AT_FDCWD || pathAddr != 0 { - var err error - path, err = copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - shouldAllowEmptyPath = disallowEmptyPath - } - - var opts vfs.SetStatOptions - if err := populateSetStatOptionsForUtimes(t, timesAddr, &opts); err != nil { - return 0, nil, err - } - - return 0, nil, setstatat(t, dirfd, path, shouldAllowEmptyPath, followFinalSymlink, &opts) -} - -func populateSetStatOptionsForUtimes(t *kernel.Task, timesAddr hostarch.Addr, opts *vfs.SetStatOptions) error { - if timesAddr == 0 { - opts.Stat.Mask = linux.STATX_ATIME | linux.STATX_MTIME - opts.Stat.Atime.Nsec = linux.UTIME_NOW - opts.Stat.Mtime.Nsec = linux.UTIME_NOW - return nil - } - var times [2]linux.Timeval - if _, err := linux.CopyTimevalSliceIn(t, timesAddr, times[:]); err != nil { - return err - } - if times[0].Usec < 0 || times[0].Usec > 999999 || times[1].Usec < 0 || times[1].Usec > 999999 { - return linuxerr.EINVAL - } - opts.Stat.Mask = linux.STATX_ATIME | linux.STATX_MTIME - opts.Stat.Atime = linux.StatxTimestamp{ - Sec: times[0].Sec, - Nsec: uint32(times[0].Usec * 1000), - } - opts.Stat.Mtime = linux.StatxTimestamp{ - Sec: times[1].Sec, - Nsec: uint32(times[1].Usec * 1000), - } - return nil -} - -// Utimensat implements Linux syscall utimensat(2). -func Utimensat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - timesAddr := args[2].Pointer() - flags := args[3].Int() - - // Linux requires that the UTIME_OMIT check occur before checking path or - // flags. - var opts vfs.SetStatOptions - if err := populateSetStatOptionsForUtimens(t, timesAddr, &opts); err != nil { - return 0, nil, err - } - if opts.Stat.Mask == 0 { - return 0, nil, nil - } - - if flags&^linux.AT_SYMLINK_NOFOLLOW != 0 { - return 0, nil, linuxerr.EINVAL - } - - // "If filename is NULL and dfd refers to an open file, then operate on the - // file. Otherwise look up filename, possibly using dfd as a starting - // point." - fs/utimes.c - var path fspath.Path - shouldAllowEmptyPath := allowEmptyPath - if dirfd == linux.AT_FDCWD || pathAddr != 0 { - var err error - path, err = copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - shouldAllowEmptyPath = disallowEmptyPath - } - - return 0, nil, setstatat(t, dirfd, path, shouldAllowEmptyPath, shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_NOFOLLOW == 0), &opts) -} - -func populateSetStatOptionsForUtimens(t *kernel.Task, timesAddr hostarch.Addr, opts *vfs.SetStatOptions) error { - if timesAddr == 0 { - opts.Stat.Mask = linux.STATX_ATIME | linux.STATX_MTIME - opts.Stat.Atime.Nsec = linux.UTIME_NOW - opts.Stat.Mtime.Nsec = linux.UTIME_NOW - return nil - } - var times [2]linux.Timespec - if _, err := linux.CopyTimespecSliceIn(t, timesAddr, times[:]); err != nil { - return err - } - if times[0].Nsec != linux.UTIME_OMIT { - if times[0].Nsec != linux.UTIME_NOW && (times[0].Nsec < 0 || times[0].Nsec > 999999999) { - return linuxerr.EINVAL - } - opts.Stat.Mask |= linux.STATX_ATIME - opts.Stat.Atime = linux.StatxTimestamp{ - Sec: times[0].Sec, - Nsec: uint32(times[0].Nsec), - } - } - if times[1].Nsec != linux.UTIME_OMIT { - if times[1].Nsec != linux.UTIME_NOW && (times[1].Nsec < 0 || times[1].Nsec > 999999999) { - return linuxerr.EINVAL - } - opts.Stat.Mask |= linux.STATX_MTIME - opts.Stat.Mtime = linux.StatxTimestamp{ - Sec: times[1].Sec, - Nsec: uint32(times[1].Nsec), - } - } - return nil -} - -func setstatat(t *kernel.Task, dirfd int32, path fspath.Path, shouldAllowEmptyPath shouldAllowEmptyPath, shouldFollowFinalSymlink shouldFollowFinalSymlink, opts *vfs.SetStatOptions) error { - root := t.FSContext().RootDirectoryVFS2() - defer root.DecRef(t) - start := root - if !path.Absolute { - if !path.HasComponents() && !bool(shouldAllowEmptyPath) { - return linuxerr.ENOENT - } - if dirfd == linux.AT_FDCWD { - start = t.FSContext().WorkingDirectoryVFS2() - defer start.DecRef(t) - } else { - dirfile := t.GetFileVFS2(dirfd) - if dirfile == nil { - return linuxerr.EBADF - } - if !path.HasComponents() { - // Use FileDescription.SetStat() instead of - // VirtualFilesystem.SetStatAt(), since the former may be able - // to use opened file state to expedite the SetStat. - err := dirfile.SetStat(t, *opts) - dirfile.DecRef(t) - return err - } - start = dirfile.VirtualDentry() - start.IncRef() - defer start.DecRef(t) - dirfile.DecRef(t) - } - } - return t.Kernel().VFS().SetStatAt(t, t.Credentials(), &vfs.PathOperation{ - Root: root, - Start: start, - Path: path, - FollowFinalSymlink: bool(shouldFollowFinalSymlink), - }, opts) -} - -func handleSetSizeError(t *kernel.Task, err error) error { - if err == linuxerr.ErrExceedsFileSizeLimit { - // Convert error to EFBIG and send a SIGXFSZ per setrlimit(2). - t.SendSignal(kernel.SignalInfoNoInfo(linux.SIGXFSZ, t, t)) - return linuxerr.EFBIG - } - return err -} diff --git a/pkg/sentry/syscalls/linux/vfs2/signal.go b/pkg/sentry/syscalls/linux/vfs2/signal.go deleted file mode 100644 index 27fb2139b..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/signal.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/signalfd" - "gvisor.dev/gvisor/pkg/sentry/kernel" - slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" -) - -// sharedSignalfd is shared between the two calls. -func sharedSignalfd(t *kernel.Task, fd int32, sigset hostarch.Addr, sigsetsize uint, flags int32) (uintptr, *kernel.SyscallControl, error) { - // Copy in the signal mask. - mask, err := slinux.CopyInSigSet(t, sigset, sigsetsize) - if err != nil { - return 0, nil, err - } - - // Always check for valid flags, even if not creating. - if flags&^(linux.SFD_NONBLOCK|linux.SFD_CLOEXEC) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Is this a change to an existing signalfd? - // - // The spec indicates that this should adjust the mask. - if fd != -1 { - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Is this a signalfd? - if sfd, ok := file.Impl().(*signalfd.SignalFileDescription); ok { - sfd.SetMask(mask) - return 0, nil, nil - } - - // Not a signalfd. - return 0, nil, linuxerr.EINVAL - } - - fileFlags := uint32(linux.O_RDWR) - if flags&linux.SFD_NONBLOCK != 0 { - fileFlags |= linux.O_NONBLOCK - } - - // Create a new file. - vfsObj := t.Kernel().VFS() - file, err := signalfd.New(vfsObj, t, mask, fileFlags) - if err != nil { - return 0, nil, err - } - defer file.DecRef(t) - - // Create a new descriptor. - fd, err = t.NewFDFromVFS2(0, file, kernel.FDFlags{ - CloseOnExec: flags&linux.SFD_CLOEXEC != 0, - }) - if err != nil { - return 0, nil, err - } - - // Done. - return uintptr(fd), nil, nil -} - -// Signalfd implements the linux syscall signalfd(2). -func Signalfd(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - sigset := args[1].Pointer() - sigsetsize := args[2].SizeT() - return sharedSignalfd(t, fd, sigset, sigsetsize, 0) -} - -// Signalfd4 implements the linux syscall signalfd4(2). -func Signalfd4(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - sigset := args[1].Pointer() - sigsetsize := args[2].SizeT() - flags := args[3].Int() - return sharedSignalfd(t, fd, sigset, sigsetsize, flags) -} diff --git a/pkg/sentry/syscalls/linux/vfs2/socket.go b/pkg/sentry/syscalls/linux/vfs2/socket.go deleted file mode 100644 index 3eae2bd10..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/socket.go +++ /dev/null @@ -1,1189 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "fmt" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/marshal" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/host" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/socket/control" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/usermem" -) - -// maxAddrLen is the maximum socket address length we're willing to accept. -const maxAddrLen = 200 - -// maxOptLen is the maximum sockopt parameter length we're willing to accept. -const maxOptLen = 1024 * 8 - -// maxControlLen is the maximum length of the msghdr.msg_control buffer we're -// willing to accept. Note that this limit is smaller than Linux, which allows -// buffers upto INT_MAX. -const maxControlLen = 10 * 1024 * 1024 - -// maxListenBacklog is the maximum limit of listen backlog supported. -const maxListenBacklog = 1024 - -// nameLenOffset is the offset from the start of the MessageHeader64 struct to -// the NameLen field. -const nameLenOffset = 8 - -// controlLenOffset is the offset form the start of the MessageHeader64 struct -// to the ControlLen field. -const controlLenOffset = 40 - -// flagsOffset is the offset form the start of the MessageHeader64 struct -// to the Flags field. -const flagsOffset = 48 - -const sizeOfInt32 = 4 - -// messageHeader64Len is the length of a MessageHeader64 struct. -var messageHeader64Len = uint64((*MessageHeader64)(nil).SizeBytes()) - -// multipleMessageHeader64Len is the length of a multipeMessageHeader64 struct. -var multipleMessageHeader64Len = uint64((*multipleMessageHeader64)(nil).SizeBytes()) - -// baseRecvFlags are the flags that are accepted across recvmsg(2), -// recvmmsg(2), and recvfrom(2). -const baseRecvFlags = linux.MSG_OOB | linux.MSG_DONTROUTE | linux.MSG_DONTWAIT | linux.MSG_NOSIGNAL | linux.MSG_WAITALL | linux.MSG_TRUNC | linux.MSG_CTRUNC - -// MessageHeader64 is the 64-bit representation of the msghdr struct used in -// the recvmsg and sendmsg syscalls. -// -// +marshal -type MessageHeader64 struct { - // Name is the optional pointer to a network address buffer. - Name uint64 - - // NameLen is the length of the buffer pointed to by Name. - NameLen uint32 - _ uint32 - - // Iov is a pointer to an array of io vectors that describe the memory - // locations involved in the io operation. - Iov uint64 - - // IovLen is the length of the array pointed to by Iov. - IovLen uint64 - - // Control is the optional pointer to ancillary control data. - Control uint64 - - // ControlLen is the length of the data pointed to by Control. - ControlLen uint64 - - // Flags on the sent/received message. - Flags int32 - _ int32 -} - -// multipleMessageHeader64 is the 64-bit representation of the mmsghdr struct used in -// the recvmmsg and sendmmsg syscalls. -// -// +marshal -type multipleMessageHeader64 struct { - msgHdr MessageHeader64 - msgLen uint32 - _ int32 -} - -// CaptureAddress allocates memory for and copies a socket address structure -// from the untrusted address space range. -func CaptureAddress(t *kernel.Task, addr hostarch.Addr, addrlen uint32) ([]byte, error) { - if addrlen > maxAddrLen { - return nil, linuxerr.EINVAL - } - - addrBuf := make([]byte, addrlen) - if _, err := t.CopyInBytes(addr, addrBuf); err != nil { - return nil, err - } - - return addrBuf, nil -} - -// writeAddress writes a sockaddr structure and its length to an output buffer -// in the unstrusted address space range. If the address is bigger than the -// buffer, it is truncated. -func writeAddress(t *kernel.Task, addr linux.SockAddr, addrLen uint32, addrPtr hostarch.Addr, addrLenPtr hostarch.Addr) error { - // Get the buffer length. - var bufLen uint32 - if _, err := primitive.CopyUint32In(t, addrLenPtr, &bufLen); err != nil { - return err - } - - if int32(bufLen) < 0 { - return linuxerr.EINVAL - } - - // Write the length unconditionally. - if _, err := primitive.CopyUint32Out(t, addrLenPtr, addrLen); err != nil { - return err - } - - if addr == nil { - return nil - } - - if bufLen > addrLen { - bufLen = addrLen - } - - // Copy as much of the address as will fit in the buffer. - encodedAddr := t.CopyScratchBuffer(addr.SizeBytes()) - addr.MarshalUnsafe(encodedAddr) - if bufLen > uint32(len(encodedAddr)) { - bufLen = uint32(len(encodedAddr)) - } - _, err := t.CopyOutBytes(addrPtr, encodedAddr[:int(bufLen)]) - return err -} - -// Socket implements the linux syscall socket(2). -func Socket(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - domain := int(args[0].Int()) - stype := args[1].Int() - protocol := int(args[2].Int()) - - // Check and initialize the flags. - if stype & ^(0xf|linux.SOCK_NONBLOCK|linux.SOCK_CLOEXEC) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Create the new socket. - s, e := socket.NewVFS2(t, domain, linux.SockType(stype&0xf), protocol) - if e != nil { - return 0, nil, e.ToError() - } - defer s.DecRef(t) - - if err := s.SetStatusFlags(t, t.Credentials(), uint32(stype&linux.SOCK_NONBLOCK)); err != nil { - return 0, nil, err - } - - fd, err := t.NewFDFromVFS2(0, s, kernel.FDFlags{ - CloseOnExec: stype&linux.SOCK_CLOEXEC != 0, - }) - if err != nil { - return 0, nil, err - } - - return uintptr(fd), nil, nil -} - -// SocketPair implements the linux syscall socketpair(2). -func SocketPair(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - domain := int(args[0].Int()) - stype := args[1].Int() - protocol := int(args[2].Int()) - addr := args[3].Pointer() - - // Check and initialize the flags. - if stype & ^(0xf|linux.SOCK_NONBLOCK|linux.SOCK_CLOEXEC) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Create the socket pair. - s1, s2, e := socket.PairVFS2(t, domain, linux.SockType(stype&0xf), protocol) - if e != nil { - return 0, nil, e.ToError() - } - // Adding to the FD table will cause an extra reference to be acquired. - defer s1.DecRef(t) - defer s2.DecRef(t) - - nonblocking := uint32(stype & linux.SOCK_NONBLOCK) - if err := s1.SetStatusFlags(t, t.Credentials(), nonblocking); err != nil { - return 0, nil, err - } - if err := s2.SetStatusFlags(t, t.Credentials(), nonblocking); err != nil { - return 0, nil, err - } - - // Create the FDs for the sockets. - flags := kernel.FDFlags{ - CloseOnExec: stype&linux.SOCK_CLOEXEC != 0, - } - fds, err := t.NewFDsVFS2(0, []*vfs.FileDescription{s1, s2}, flags) - if err != nil { - return 0, nil, err - } - - if _, err := primitive.CopyInt32SliceOut(t, addr, fds); err != nil { - for _, fd := range fds { - if _, file := t.FDTable().Remove(t, fd); file != nil { - file.DecRef(t) - } - } - return 0, nil, err - } - - return 0, nil, nil -} - -// Connect implements the linux syscall connect(2). -func Connect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - addrlen := args[2].Uint() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Capture address and call syscall implementation. - a, err := CaptureAddress(t, addr, addrlen) - if err != nil { - return 0, nil, err - } - - blocking := (file.StatusFlags() & linux.SOCK_NONBLOCK) == 0 - return 0, nil, linuxerr.ConvertIntr(s.Connect(t, a, blocking).ToError(), linuxerr.ERESTARTSYS) -} - -// accept is the implementation of the accept syscall. It is called by accept -// and accept4 syscall handlers. -func accept(t *kernel.Task, fd int32, addr hostarch.Addr, addrLen hostarch.Addr, flags int) (uintptr, error) { - // Check that no unsupported flags are passed in. - if flags & ^(linux.SOCK_NONBLOCK|linux.SOCK_CLOEXEC) != 0 { - return 0, linuxerr.EINVAL - } - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, linuxerr.ENOTSOCK - } - - // Call the syscall implementation for this socket, then copy the - // output address if one is specified. - blocking := (file.StatusFlags() & linux.SOCK_NONBLOCK) == 0 - - peerRequested := addrLen != 0 - nfd, peer, peerLen, e := s.Accept(t, peerRequested, flags, blocking) - if e != nil { - return 0, linuxerr.ConvertIntr(e.ToError(), linuxerr.ERESTARTSYS) - } - if peerRequested { - // NOTE(magi): Linux does not give you an error if it can't - // write the data back out so neither do we. - if err := writeAddress(t, peer, peerLen, addr, addrLen); linuxerr.Equals(linuxerr.EINVAL, err) { - return 0, err - } - } - return uintptr(nfd), nil -} - -// Accept4 implements the linux syscall accept4(2). -func Accept4(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - addrlen := args[2].Pointer() - flags := int(args[3].Int()) - - n, err := accept(t, fd, addr, addrlen, flags) - return n, nil, err -} - -// Accept implements the linux syscall accept(2). -func Accept(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - addrlen := args[2].Pointer() - - n, err := accept(t, fd, addr, addrlen, 0) - return n, nil, err -} - -// Bind implements the linux syscall bind(2). -func Bind(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - addrlen := args[2].Uint() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Capture address and call syscall implementation. - a, err := CaptureAddress(t, addr, addrlen) - if err != nil { - return 0, nil, err - } - - return 0, nil, s.Bind(t, a).ToError() -} - -// Listen implements the linux syscall listen(2). -func Listen(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - backlog := args[1].Uint() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - if backlog > maxListenBacklog { - // Linux treats incoming backlog as uint with a limit defined by - // sysctl_somaxconn. - // https://github.com/torvalds/linux/blob/7acac4b3196/net/socket.c#L1666 - backlog = maxListenBacklog - } - - // Accept one more than the configured listen backlog to keep in parity with - // Linux. Ref, because of missing equality check here: - // https://github.com/torvalds/linux/blob/7acac4b3196/include/net/sock.h#L937 - // - // In case of unix domain sockets, the following check - // https://github.com/torvalds/linux/blob/7d6beb71da3/net/unix/af_unix.c#L1293 - // will allow 1 connect through since it checks for a receive queue len > - // backlog and not >=. - backlog++ - - return 0, nil, s.Listen(t, int(backlog)).ToError() -} - -// Shutdown implements the linux syscall shutdown(2). -func Shutdown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - how := args[1].Int() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Validate how, then call syscall implementation. - switch how { - case linux.SHUT_RD, linux.SHUT_WR, linux.SHUT_RDWR: - default: - return 0, nil, linuxerr.EINVAL - } - - return 0, nil, s.Shutdown(t, int(how)).ToError() -} - -// GetSockOpt implements the linux syscall getsockopt(2). -func GetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - level := args[1].Int() - name := args[2].Int() - optValAddr := args[3].Pointer() - optLenAddr := args[4].Pointer() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Read the length. Reject negative values. - var optLen int32 - if _, err := primitive.CopyInt32In(t, optLenAddr, &optLen); err != nil { - return 0, nil, err - } - if optLen < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Call syscall implementation then copy both value and value len out. - v, e := getSockOpt(t, s, int(level), int(name), optValAddr, int(optLen)) - if e != nil { - return 0, nil, e.ToError() - } - - if _, err := primitive.CopyInt32Out(t, optLenAddr, int32(v.SizeBytes())); err != nil { - return 0, nil, err - } - - if v != nil { - if _, err := v.CopyOut(t, optValAddr); err != nil { - return 0, nil, err - } - } - - return 0, nil, nil -} - -// getSockOpt tries to handle common socket options, or dispatches to a specific -// socket implementation. -func getSockOpt(t *kernel.Task, s socket.SocketVFS2, level, name int, optValAddr hostarch.Addr, len int) (marshal.Marshallable, *syserr.Error) { - if level == linux.SOL_SOCKET { - switch name { - case linux.SO_TYPE, linux.SO_DOMAIN, linux.SO_PROTOCOL: - if len < sizeOfInt32 { - return nil, syserr.ErrInvalidArgument - } - } - - switch name { - case linux.SO_TYPE: - _, skType, _ := s.Type() - v := primitive.Int32(skType) - return &v, nil - case linux.SO_DOMAIN: - family, _, _ := s.Type() - v := primitive.Int32(family) - return &v, nil - case linux.SO_PROTOCOL: - _, _, protocol := s.Type() - v := primitive.Int32(protocol) - return &v, nil - } - } - - return s.GetSockOpt(t, level, name, optValAddr, len) -} - -// SetSockOpt implements the linux syscall setsockopt(2). -// -// Note that unlike Linux, enabling SO_PASSCRED does not autobind the socket. -func SetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - level := args[1].Int() - name := args[2].Int() - optValAddr := args[3].Pointer() - optLen := args[4].Int() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - if optLen < 0 { - return 0, nil, linuxerr.EINVAL - } - if optLen > maxOptLen { - return 0, nil, linuxerr.EINVAL - } - buf := t.CopyScratchBuffer(int(optLen)) - if _, err := t.CopyInBytes(optValAddr, buf); err != nil { - return 0, nil, err - } - - // Call syscall implementation. - if err := s.SetSockOpt(t, int(level), int(name), buf); err != nil { - return 0, nil, err.ToError() - } - - return 0, nil, nil -} - -// GetSockName implements the linux syscall getsockname(2). -func GetSockName(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - addrlen := args[2].Pointer() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Get the socket name and copy it to the caller. - v, vl, err := s.GetSockName(t) - if err != nil { - return 0, nil, err.ToError() - } - - return 0, nil, writeAddress(t, v, vl, addr, addrlen) -} - -// GetPeerName implements the linux syscall getpeername(2). -func GetPeerName(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - addr := args[1].Pointer() - addrlen := args[2].Pointer() - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Get the socket peer name and copy it to the caller. - v, vl, err := s.GetPeerName(t) - if err != nil { - return 0, nil, err.ToError() - } - - return 0, nil, writeAddress(t, v, vl, addr, addrlen) -} - -// RecvMsg implements the linux syscall recvmsg(2). -func RecvMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - msgPtr := args[1].Pointer() - flags := args[2].Int() - - if t.Arch().Width() != 8 { - // We only handle 64-bit for now. - return 0, nil, linuxerr.EINVAL - } - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Reject flags that we don't handle yet. - if flags & ^(baseRecvFlags|linux.MSG_PEEK|linux.MSG_CMSG_CLOEXEC|linux.MSG_ERRQUEUE) != 0 { - return 0, nil, linuxerr.EINVAL - } - - if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { - flags |= linux.MSG_DONTWAIT - } - - var haveDeadline bool - var deadline ktime.Time - if dl := s.RecvTimeout(); dl > 0 { - deadline = t.Kernel().MonotonicClock().Now().Add(time.Duration(dl) * time.Nanosecond) - haveDeadline = true - } else if dl < 0 { - flags |= linux.MSG_DONTWAIT - } - - n, err := recvSingleMsg(t, s, msgPtr, flags, haveDeadline, deadline) - return n, nil, err -} - -// RecvMMsg implements the linux syscall recvmmsg(2). -func RecvMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - msgPtr := args[1].Pointer() - vlen := args[2].Uint() - flags := args[3].Int() - toPtr := args[4].Pointer() - - if t.Arch().Width() != 8 { - // We only handle 64-bit for now. - return 0, nil, linuxerr.EINVAL - } - - if vlen > linux.UIO_MAXIOV { - vlen = linux.UIO_MAXIOV - } - - // Reject flags that we don't handle yet. - if flags & ^(baseRecvFlags|linux.MSG_CMSG_CLOEXEC|linux.MSG_ERRQUEUE) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { - flags |= linux.MSG_DONTWAIT - } - - var haveDeadline bool - var deadline ktime.Time - if toPtr != 0 { - var ts linux.Timespec - if _, err := ts.CopyIn(t, toPtr); err != nil { - return 0, nil, err - } - if !ts.Valid() { - return 0, nil, linuxerr.EINVAL - } - deadline = t.Kernel().MonotonicClock().Now().Add(ts.ToDuration()) - haveDeadline = true - } - - if !haveDeadline { - if dl := s.RecvTimeout(); dl > 0 { - deadline = t.Kernel().MonotonicClock().Now().Add(time.Duration(dl) * time.Nanosecond) - haveDeadline = true - } else if dl < 0 { - flags |= linux.MSG_DONTWAIT - } - } - - var count uint32 - var err error - for i := uint64(0); i < uint64(vlen); i++ { - mp, ok := msgPtr.AddLength(i * multipleMessageHeader64Len) - if !ok { - return 0, nil, linuxerr.EFAULT - } - var n uintptr - if n, err = recvSingleMsg(t, s, mp, flags, haveDeadline, deadline); err != nil { - break - } - - // Copy the received length to the caller. - lp, ok := mp.AddLength(messageHeader64Len) - if !ok { - return 0, nil, linuxerr.EFAULT - } - if _, err = primitive.CopyUint32Out(t, lp, uint32(n)); err != nil { - break - } - count++ - } - - if count == 0 { - return 0, nil, err - } - return uintptr(count), nil, nil -} - -func getSCMRightsVFS2(t *kernel.Task, rights transport.RightsControlMessage) control.SCMRightsVFS2 { - switch v := rights.(type) { - case control.SCMRightsVFS2: - return v - case *transport.SCMRights: - rf := control.RightsFilesVFS2(fdsToHostFiles(t, v.FDs)) - return &rf - default: - panic(fmt.Sprintf("rights of type %T must be *transport.SCMRights or implement SCMRightsVFS2", rights)) - } -} - -// If an error is encountered, only files created before the error will be -// returned. This is what Linux does. -func fdsToHostFiles(ctx context.Context, fds []int) []*vfs.FileDescription { - files := make([]*vfs.FileDescription, 0, len(fds)) - for _, fd := range fds { - // Get flags. We do it here because they may be modified - // by subsequent functions. - fileFlags, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(fd), unix.F_GETFL, 0) - if errno != 0 { - ctx.Warningf("Error retrieving host FD flags: %v", error(errno)) - break - } - - // Create the file backed by hostFD. - file, err := host.NewFD(ctx, kernel.KernelFromContext(ctx).HostMount(), fd, &host.NewFDOptions{}) - if err != nil { - ctx.Warningf("Error creating file from host FD: %v", err) - break - } - - if err := file.SetStatusFlags(ctx, auth.CredentialsFromContext(ctx), uint32(fileFlags&linux.O_NONBLOCK)); err != nil { - ctx.Warningf("Error setting flags on host FD file: %v", err) - break - } - - files = append(files, file) - } - return files -} - -func recvSingleMsg(t *kernel.Task, s socket.SocketVFS2, msgPtr hostarch.Addr, flags int32, haveDeadline bool, deadline ktime.Time) (uintptr, error) { - // Capture the message header and io vectors. - var msg MessageHeader64 - if _, err := msg.CopyIn(t, msgPtr); err != nil { - return 0, err - } - - if msg.IovLen > linux.UIO_MAXIOV { - return 0, linuxerr.EMSGSIZE - } - dst, err := t.IovecsIOSequence(hostarch.Addr(msg.Iov), int(msg.IovLen), usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, err - } - - // Fast path when no control message nor name buffers are provided. - if msg.ControlLen == 0 && msg.NameLen == 0 { - n, mflags, _, _, cms, err := s.RecvMsg(t, dst, int(flags), haveDeadline, deadline, false, 0) - if err != nil { - return 0, linuxerr.ConvertIntr(err.ToError(), linuxerr.ERESTARTSYS) - } - if !cms.Unix.Empty() { - mflags |= linux.MSG_CTRUNC - cms.Release(t) - } - - if int(msg.Flags) != mflags { - // Copy out the flags to the caller. - if _, err := primitive.CopyInt32Out(t, msgPtr+flagsOffset, int32(mflags)); err != nil { - return 0, err - } - } - - return uintptr(n), nil - } - - if msg.ControlLen > maxControlLen { - return 0, linuxerr.ENOBUFS - } - n, mflags, sender, senderLen, cms, e := s.RecvMsg(t, dst, int(flags), haveDeadline, deadline, msg.NameLen != 0, msg.ControlLen) - if e != nil { - return 0, linuxerr.ConvertIntr(e.ToError(), linuxerr.ERESTARTSYS) - } - defer cms.Release(t) - - controlData := make([]byte, 0, msg.ControlLen) - controlData = control.PackControlMessages(t, cms, controlData) - - if cr, ok := s.(transport.Credentialer); ok && cr.Passcred() { - creds, _ := cms.Unix.Credentials.(control.SCMCredentials) - controlData, mflags = control.PackCredentials(t, creds, controlData, mflags) - } - - if cms.Unix.Rights != nil { - cms.Unix.Rights = getSCMRightsVFS2(t, cms.Unix.Rights) - controlData, mflags = control.PackRightsVFS2(t, cms.Unix.Rights.(control.SCMRightsVFS2), flags&linux.MSG_CMSG_CLOEXEC != 0, controlData, mflags) - } - - // Copy the address to the caller. - if msg.NameLen != 0 { - if err := writeAddress(t, sender, senderLen, hostarch.Addr(msg.Name), hostarch.Addr(msgPtr+nameLenOffset)); err != nil { - return 0, err - } - } - - // Copy the control data to the caller. - if _, err := primitive.CopyUint64Out(t, msgPtr+controlLenOffset, uint64(len(controlData))); err != nil { - return 0, err - } - if len(controlData) > 0 { - if _, err := t.CopyOutBytes(hostarch.Addr(msg.Control), controlData); err != nil { - return 0, err - } - } - - // Copy out the flags to the caller. - if _, err := primitive.CopyInt32Out(t, msgPtr+flagsOffset, int32(mflags)); err != nil { - return 0, err - } - - return uintptr(n), nil -} - -// recvFrom is the implementation of the recvfrom syscall. It is called by -// recvfrom and recv syscall handlers. -func recvFrom(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, flags int32, namePtr hostarch.Addr, nameLenPtr hostarch.Addr) (uintptr, error) { - if int(bufLen) < 0 { - return 0, linuxerr.EINVAL - } - - // Reject flags that we don't handle yet. - if flags & ^(baseRecvFlags|linux.MSG_PEEK|linux.MSG_CONFIRM) != 0 { - return 0, linuxerr.EINVAL - } - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, linuxerr.ENOTSOCK - } - - if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { - flags |= linux.MSG_DONTWAIT - } - - dst, err := t.SingleIOSequence(bufPtr, int(bufLen), usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, err - } - - var haveDeadline bool - var deadline ktime.Time - if dl := s.RecvTimeout(); dl > 0 { - deadline = t.Kernel().MonotonicClock().Now().Add(time.Duration(dl) * time.Nanosecond) - haveDeadline = true - } else if dl < 0 { - flags |= linux.MSG_DONTWAIT - } - - n, _, sender, senderLen, cm, e := s.RecvMsg(t, dst, int(flags), haveDeadline, deadline, nameLenPtr != 0, 0) - cm.Release(t) - if e != nil { - return 0, linuxerr.ConvertIntr(e.ToError(), linuxerr.ERESTARTSYS) - } - - // Copy the address to the caller. - if nameLenPtr != 0 { - if err := writeAddress(t, sender, senderLen, namePtr, nameLenPtr); err != nil { - return 0, err - } - } - - return uintptr(n), nil -} - -// RecvFrom implements the linux syscall recvfrom(2). -func RecvFrom(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - bufPtr := args[1].Pointer() - bufLen := args[2].Uint64() - flags := args[3].Int() - namePtr := args[4].Pointer() - nameLenPtr := args[5].Pointer() - - n, err := recvFrom(t, fd, bufPtr, bufLen, flags, namePtr, nameLenPtr) - return n, nil, err -} - -// SendMsg implements the linux syscall sendmsg(2). -func SendMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - msgPtr := args[1].Pointer() - flags := args[2].Int() - - if t.Arch().Width() != 8 { - // We only handle 64-bit for now. - return 0, nil, linuxerr.EINVAL - } - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Reject flags that we don't handle yet. - if flags & ^(linux.MSG_DONTWAIT|linux.MSG_EOR|linux.MSG_MORE|linux.MSG_NOSIGNAL) != 0 { - return 0, nil, linuxerr.EINVAL - } - - if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { - flags |= linux.MSG_DONTWAIT - } - - n, err := sendSingleMsg(t, s, file, msgPtr, flags) - return n, nil, err -} - -// SendMMsg implements the linux syscall sendmmsg(2). -func SendMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - msgPtr := args[1].Pointer() - vlen := args[2].Uint() - flags := args[3].Int() - - if t.Arch().Width() != 8 { - // We only handle 64-bit for now. - return 0, nil, linuxerr.EINVAL - } - - if vlen > linux.UIO_MAXIOV { - vlen = linux.UIO_MAXIOV - } - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, nil, linuxerr.ENOTSOCK - } - - // Reject flags that we don't handle yet. - if flags & ^(linux.MSG_DONTWAIT|linux.MSG_EOR|linux.MSG_MORE|linux.MSG_NOSIGNAL) != 0 { - return 0, nil, linuxerr.EINVAL - } - - if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { - flags |= linux.MSG_DONTWAIT - } - - var count uint32 - var err error - for i := uint64(0); i < uint64(vlen); i++ { - mp, ok := msgPtr.AddLength(i * multipleMessageHeader64Len) - if !ok { - return 0, nil, linuxerr.EFAULT - } - var n uintptr - if n, err = sendSingleMsg(t, s, file, mp, flags); err != nil { - break - } - - // Copy the received length to the caller. - lp, ok := mp.AddLength(messageHeader64Len) - if !ok { - return 0, nil, linuxerr.EFAULT - } - if _, err = primitive.CopyUint32Out(t, lp, uint32(n)); err != nil { - break - } - count++ - } - - if count == 0 { - return 0, nil, err - } - return uintptr(count), nil, nil -} - -func sendSingleMsg(t *kernel.Task, s socket.SocketVFS2, file *vfs.FileDescription, msgPtr hostarch.Addr, flags int32) (uintptr, error) { - // Capture the message header. - var msg MessageHeader64 - if _, err := msg.CopyIn(t, msgPtr); err != nil { - return 0, err - } - - var controlData []byte - if msg.ControlLen > 0 { - // Put an upper bound to prevent large allocations. - if msg.ControlLen > maxControlLen { - return 0, linuxerr.ENOBUFS - } - controlData = make([]byte, msg.ControlLen) - if _, err := t.CopyInBytes(hostarch.Addr(msg.Control), controlData); err != nil { - return 0, err - } - } - - // Read the destination address if one is specified. - var to []byte - if msg.NameLen != 0 { - var err error - to, err = CaptureAddress(t, hostarch.Addr(msg.Name), msg.NameLen) - if err != nil { - return 0, err - } - } - - // Read data then call the sendmsg implementation. - if msg.IovLen > linux.UIO_MAXIOV { - return 0, linuxerr.EMSGSIZE - } - src, err := t.IovecsIOSequence(hostarch.Addr(msg.Iov), int(msg.IovLen), usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, err - } - - controlMessages, err := control.Parse(t, s, controlData, t.Arch().Width()) - if err != nil { - return 0, err - } - - var haveDeadline bool - var deadline ktime.Time - if dl := s.SendTimeout(); dl > 0 { - deadline = t.Kernel().MonotonicClock().Now().Add(time.Duration(dl) * time.Nanosecond) - haveDeadline = true - } else if dl < 0 { - flags |= linux.MSG_DONTWAIT - } - - // Call the syscall implementation. - n, e := s.SendMsg(t, src, to, int(flags), haveDeadline, deadline, controlMessages) - err = slinux.HandleIOErrorVFS2(t, n != 0, e.ToError(), linuxerr.ERESTARTSYS, "sendmsg", file) - // Control messages should be released on error as well as for zero-length - // messages, which are discarded by the receiver. - if n == 0 || err != nil { - controlMessages.Release(t) - } - return uintptr(n), err -} - -// sendTo is the implementation of the sendto syscall. It is called by sendto -// and send syscall handlers. -func sendTo(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, flags int32, namePtr hostarch.Addr, nameLen uint32) (uintptr, error) { - bl := int(bufLen) - if bl < 0 { - return 0, linuxerr.EINVAL - } - - // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) - if file == nil { - return 0, linuxerr.EBADF - } - defer file.DecRef(t) - - // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) - if !ok { - return 0, linuxerr.ENOTSOCK - } - - if (file.StatusFlags() & linux.SOCK_NONBLOCK) != 0 { - flags |= linux.MSG_DONTWAIT - } - - // Read the destination address if one is specified. - var to []byte - var err error - if namePtr != 0 { - to, err = CaptureAddress(t, namePtr, nameLen) - if err != nil { - return 0, err - } - } - - src, err := t.SingleIOSequence(bufPtr, bl, usermem.IOOpts{ - AddressSpaceActive: true, - }) - if err != nil { - return 0, err - } - - var haveDeadline bool - var deadline ktime.Time - if dl := s.SendTimeout(); dl > 0 { - deadline = t.Kernel().MonotonicClock().Now().Add(time.Duration(dl) * time.Nanosecond) - haveDeadline = true - } else if dl < 0 { - flags |= linux.MSG_DONTWAIT - } - - // Call the syscall implementation. - n, e := s.SendMsg(t, src, to, int(flags), haveDeadline, deadline, socket.ControlMessages{Unix: control.New(t, s, nil)}) - return uintptr(n), slinux.HandleIOErrorVFS2(t, n != 0, e.ToError(), linuxerr.ERESTARTSYS, "sendto", file) -} - -// SendTo implements the linux syscall sendto(2). -func SendTo(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - bufPtr := args[1].Pointer() - bufLen := args[2].Uint64() - flags := args[3].Int() - namePtr := args[4].Pointer() - nameLen := args[5].Uint() - - n, err := sendTo(t, fd, bufPtr, bufLen, flags, namePtr, nameLen) - return n, nil, err -} diff --git a/pkg/sentry/syscalls/linux/vfs2/splice.go b/pkg/sentry/syscalls/linux/vfs2/splice.go deleted file mode 100644 index e7ebdbebc..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/splice.go +++ /dev/null @@ -1,537 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "io" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" - slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// Splice implements Linux syscall splice(2). -func Splice(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - inFD := args[0].Int() - inOffsetPtr := args[1].Pointer() - outFD := args[2].Int() - outOffsetPtr := args[3].Pointer() - count := int64(args[4].SizeT()) - flags := args[5].Int() - - if count == 0 { - return 0, nil, nil - } - if count > int64(kernel.MAX_RW_COUNT) { - count = int64(kernel.MAX_RW_COUNT) - } - if count < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Check for invalid flags. - if flags&^(linux.SPLICE_F_MOVE|linux.SPLICE_F_NONBLOCK|linux.SPLICE_F_MORE|linux.SPLICE_F_GIFT) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get file descriptions. - inFile := t.GetFileVFS2(inFD) - if inFile == nil { - return 0, nil, linuxerr.EBADF - } - defer inFile.DecRef(t) - outFile := t.GetFileVFS2(outFD) - if outFile == nil { - return 0, nil, linuxerr.EBADF - } - defer outFile.DecRef(t) - - // Check that both files support the required directionality. - if !inFile.IsReadable() || !outFile.IsWritable() { - return 0, nil, linuxerr.EBADF - } - if outFile.Options().DenySpliceIn { - return 0, nil, linuxerr.EINVAL - } - - // The operation is non-blocking if anything is non-blocking. - // - // N.B. This is a rather simplistic heuristic that avoids some - // poor edge case behavior since the exact semantics here are - // underspecified and vary between versions of Linux itself. - nonBlock := ((inFile.StatusFlags()|outFile.StatusFlags())&linux.O_NONBLOCK != 0) || (flags&linux.SPLICE_F_NONBLOCK != 0) - - // At least one file description must represent a pipe. - inPipeFD, inIsPipe := inFile.Impl().(*pipe.VFSPipeFD) - outPipeFD, outIsPipe := outFile.Impl().(*pipe.VFSPipeFD) - if !inIsPipe && !outIsPipe { - return 0, nil, linuxerr.EINVAL - } - - // Copy in offsets. - inOffset := int64(-1) - if inOffsetPtr != 0 { - if inIsPipe { - return 0, nil, linuxerr.ESPIPE - } - if inFile.Options().DenyPRead { - return 0, nil, linuxerr.EINVAL - } - if _, err := primitive.CopyInt64In(t, inOffsetPtr, &inOffset); err != nil { - return 0, nil, err - } - if inOffset < 0 { - return 0, nil, linuxerr.EINVAL - } - } - outOffset := int64(-1) - if outOffsetPtr != 0 { - if outIsPipe { - return 0, nil, linuxerr.ESPIPE - } - if outFile.Options().DenyPWrite { - return 0, nil, linuxerr.EINVAL - } - if _, err := primitive.CopyInt64In(t, outOffsetPtr, &outOffset); err != nil { - return 0, nil, err - } - if outOffset < 0 { - return 0, nil, linuxerr.EINVAL - } - } - - // Move data. - var ( - n int64 - err error - ) - dw := dualWaiter{ - inFile: inFile, - outFile: outFile, - } - defer dw.destroy() - for { - // If both input and output are pipes, delegate to the pipe - // implementation. Otherwise, exactly one end is a pipe, which - // we ensure is consistently ordered after the non-pipe FD's - // locks by passing the pipe FD as usermem.IO to the non-pipe - // end. - switch { - case inIsPipe && outIsPipe: - n, err = pipe.Splice(t, outPipeFD, inPipeFD, count) - case inIsPipe: - n, err = inPipeFD.SpliceToNonPipe(t, outFile, outOffset, count) - if outOffset != -1 { - outOffset += n - } - case outIsPipe: - n, err = outPipeFD.SpliceFromNonPipe(t, inFile, inOffset, count) - if inOffset != -1 { - inOffset += n - } - default: - panic("at least one end of splice must be a pipe") - } - - if n != 0 || err != linuxerr.ErrWouldBlock || nonBlock { - break - } - if err = dw.waitForBoth(t); err != nil { - break - } - } - - // Copy updated offsets out. - if inOffsetPtr != 0 { - if _, err := primitive.CopyInt64Out(t, inOffsetPtr, inOffset); err != nil { - return 0, nil, err - } - } - if outOffsetPtr != 0 { - if _, err := primitive.CopyInt64Out(t, outOffsetPtr, outOffset); err != nil { - return 0, nil, err - } - } - - // We can only pass a single file to handleIOError, so pick inFile arbitrarily. - // This is used only for debugging purposes. - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "splice", outFile) -} - -// Tee implements Linux syscall tee(2). -func Tee(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - inFD := args[0].Int() - outFD := args[1].Int() - count := int64(args[2].SizeT()) - flags := args[3].Int() - - if count == 0 { - return 0, nil, nil - } - if count > int64(kernel.MAX_RW_COUNT) { - count = int64(kernel.MAX_RW_COUNT) - } - if count < 0 { - return 0, nil, linuxerr.EINVAL - } - - // Check for invalid flags. - if flags&^(linux.SPLICE_F_MOVE|linux.SPLICE_F_NONBLOCK|linux.SPLICE_F_MORE|linux.SPLICE_F_GIFT) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Get file descriptions. - inFile := t.GetFileVFS2(inFD) - if inFile == nil { - return 0, nil, linuxerr.EBADF - } - defer inFile.DecRef(t) - outFile := t.GetFileVFS2(outFD) - if outFile == nil { - return 0, nil, linuxerr.EBADF - } - defer outFile.DecRef(t) - - // Check that both files support the required directionality. - if !inFile.IsReadable() || !outFile.IsWritable() { - return 0, nil, linuxerr.EBADF - } - if outFile.Options().DenySpliceIn { - return 0, nil, linuxerr.EINVAL - } - - // The operation is non-blocking if anything is non-blocking. - // - // N.B. This is a rather simplistic heuristic that avoids some - // poor edge case behavior since the exact semantics here are - // underspecified and vary between versions of Linux itself. - nonBlock := ((inFile.StatusFlags()|outFile.StatusFlags())&linux.O_NONBLOCK != 0) || (flags&linux.SPLICE_F_NONBLOCK != 0) - - // Both file descriptions must represent pipes. - inPipeFD, inIsPipe := inFile.Impl().(*pipe.VFSPipeFD) - outPipeFD, outIsPipe := outFile.Impl().(*pipe.VFSPipeFD) - if !inIsPipe || !outIsPipe { - return 0, nil, linuxerr.EINVAL - } - - // Copy data. - var ( - n int64 - err error - ) - dw := dualWaiter{ - inFile: inFile, - outFile: outFile, - } - defer dw.destroy() - for { - n, err = pipe.Tee(t, outPipeFD, inPipeFD, count) - if n != 0 || err != linuxerr.ErrWouldBlock || nonBlock { - break - } - if err = dw.waitForBoth(t); err != nil { - break - } - } - - if n != 0 { - // If a partial write is completed, the error is dropped. Log it here. - if err != nil && err != io.EOF && err != linuxerr.ErrWouldBlock { - log.Debugf("tee completed a partial write with error: %v", err) - err = nil - } - } - - // We can only pass a single file to handleIOError, so pick inFile arbitrarily. - // This is used only for debugging purposes. - return uintptr(n), nil, slinux.HandleIOErrorVFS2(t, n != 0, err, linuxerr.ERESTARTSYS, "tee", inFile) -} - -// Sendfile implements linux system call sendfile(2). -func Sendfile(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - outFD := args[0].Int() - inFD := args[1].Int() - offsetAddr := args[2].Pointer() - count := int64(args[3].SizeT()) - - inFile := t.GetFileVFS2(inFD) - if inFile == nil { - return 0, nil, linuxerr.EBADF - } - defer inFile.DecRef(t) - if !inFile.IsReadable() { - return 0, nil, linuxerr.EBADF - } - - outFile := t.GetFileVFS2(outFD) - if outFile == nil { - return 0, nil, linuxerr.EBADF - } - defer outFile.DecRef(t) - if !outFile.IsWritable() { - return 0, nil, linuxerr.EBADF - } - if outFile.Options().DenySpliceIn { - return 0, nil, linuxerr.EINVAL - } - - // Verify that the outFile Append flag is not set. - if outFile.StatusFlags()&linux.O_APPEND != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Verify that inFile is a regular file or block device. This is a - // requirement; the same check appears in Linux - // (fs/splice.c:splice_direct_to_actor). - if stat, err := inFile.Stat(t, vfs.StatOptions{Mask: linux.STATX_TYPE}); err != nil { - return 0, nil, err - } else if stat.Mask&linux.STATX_TYPE == 0 || - (stat.Mode&linux.S_IFMT != linux.S_IFREG && stat.Mode&linux.S_IFMT != linux.S_IFBLK) { - return 0, nil, linuxerr.EINVAL - } - - // Copy offset if it exists. - offset := int64(-1) - if offsetAddr != 0 { - if inFile.Options().DenyPRead { - return 0, nil, linuxerr.ESPIPE - } - var offsetP primitive.Int64 - if _, err := offsetP.CopyIn(t, offsetAddr); err != nil { - return 0, nil, err - } - offset = int64(offsetP) - - if offset < 0 { - return 0, nil, linuxerr.EINVAL - } - if offset+count < 0 { - return 0, nil, linuxerr.EINVAL - } - } - - // Validate count. This must come after offset checks. - if count < 0 { - return 0, nil, linuxerr.EINVAL - } - if count == 0 { - return 0, nil, nil - } - if count > int64(kernel.MAX_RW_COUNT) { - count = int64(kernel.MAX_RW_COUNT) - } - - // Copy data. - var ( - total int64 - err error - ) - dw := dualWaiter{ - inFile: inFile, - outFile: outFile, - } - defer dw.destroy() - outPipeFD, outIsPipe := outFile.Impl().(*pipe.VFSPipeFD) - // Reading from input file should never block, since it is regular or - // block device. We only need to check if writing to the output file - // can block. - nonBlock := outFile.StatusFlags()&linux.O_NONBLOCK != 0 - if outIsPipe { - for { - var n int64 - n, err = outPipeFD.SpliceFromNonPipe(t, inFile, offset, count-total) - if offset != -1 { - offset += n - } - total += n - if total == count { - break - } - if err == nil && t.Interrupted() { - err = linuxerr.ErrInterrupted - break - } - if err == linuxerr.ErrWouldBlock && !nonBlock { - err = dw.waitForBoth(t) - } - if err != nil { - break - } - } - } else { - // Read inFile to buffer, then write the contents to outFile. - // - // The buffer size has to be limited to avoid large memory - // allocations and long delays. In Linux, the buffer size is - // limited by a size of an internl pipe. Here, we repeat this - // behavior. - bufSize := count - if bufSize > pipe.MaximumPipeSize { - bufSize = pipe.MaximumPipeSize - } - buf := make([]byte, bufSize) - for { - if int64(len(buf)) > count-total { - buf = buf[:count-total] - } - var readN int64 - if offset != -1 { - readN, err = inFile.PRead(t, usermem.BytesIOSequence(buf), offset, vfs.ReadOptions{}) - offset += readN - } else { - readN, err = inFile.Read(t, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) - } - - // Write all of the bytes that we read. This may need - // multiple write calls to complete. - wbuf := buf[:readN] - for len(wbuf) > 0 { - var writeN int64 - writeN, err = outFile.Write(t, usermem.BytesIOSequence(wbuf), vfs.WriteOptions{}) - wbuf = wbuf[writeN:] - if err == linuxerr.ErrWouldBlock && !nonBlock { - err = dw.waitForOut(t) - } - if err != nil { - // We didn't complete the write. Only report the bytes that were actually - // written, and rewind offsets as needed. - notWritten := int64(len(wbuf)) - readN -= notWritten - if offset == -1 { - // We modified the offset of the input file itself during the read - // operation. Rewind it. - if _, seekErr := inFile.Seek(t, -notWritten, linux.SEEK_CUR); seekErr != nil { - // Log the error but don't return it, since the write has already - // completed successfully. - log.Warningf("failed to roll back input file offset: %v", seekErr) - } - } else { - // The sendfile call was provided an offset parameter that should be - // adjusted to reflect the number of bytes sent. Rewind it. - offset -= notWritten - } - break - } - } - - total += readN - if total == count { - break - } - if err == nil && t.Interrupted() { - err = linuxerr.ErrInterrupted - break - } - if err == linuxerr.ErrWouldBlock && !nonBlock { - err = dw.waitForBoth(t) - } - if err != nil { - break - } - } - } - - if offsetAddr != 0 { - // Copy out the new offset. - offsetP := primitive.Uint64(offset) - if _, err := offsetP.CopyOut(t, offsetAddr); err != nil { - return 0, nil, err - } - } - - if total != 0 { - if err != nil && err != io.EOF && err != linuxerr.ErrWouldBlock { - // If a partial write is completed, the error is dropped. Log it here. - log.Debugf("sendfile completed a partial write with error: %v", err) - err = nil - } - } - - // We can only pass a single file to handleIOError, so pick inFile arbitrarily. - // This is used only for debugging purposes. - return uintptr(total), nil, slinux.HandleIOErrorVFS2(t, total != 0, err, linuxerr.ERESTARTSYS, "sendfile", inFile) -} - -// dualWaiter is used to wait on one or both vfs.FileDescriptions. It is not -// thread-safe, and does not take a reference on the vfs.FileDescriptions. -// -// Users must call destroy() when finished. -type dualWaiter struct { - inFile *vfs.FileDescription - outFile *vfs.FileDescription - - inW waiter.Entry - inCh chan struct{} - outW waiter.Entry - outCh chan struct{} -} - -// waitForBoth waits for both dw.inFile and dw.outFile to be ready. -func (dw *dualWaiter) waitForBoth(t *kernel.Task) error { - if dw.inFile.Readiness(eventMaskRead)&eventMaskRead == 0 { - if dw.inCh == nil { - dw.inW, dw.inCh = waiter.NewChannelEntry(eventMaskRead) - if err := dw.inFile.EventRegister(&dw.inW); err != nil { - return err - } - // We might be ready now. Try again before blocking. - return nil - } - if err := t.Block(dw.inCh); err != nil { - return err - } - } - return dw.waitForOut(t) -} - -// waitForOut waits for dw.outfile to be read. -func (dw *dualWaiter) waitForOut(t *kernel.Task) error { - // Don't bother checking readiness of the outFile, because it's not a - // guarantee that it won't return EWOULDBLOCK. Both pipes and eventfds - // can be "ready" but will reject writes of certain sizes with - // EWOULDBLOCK. See b/172075629, b/170743336. - if dw.outCh == nil { - dw.outW, dw.outCh = waiter.NewChannelEntry(eventMaskWrite) - if err := dw.outFile.EventRegister(&dw.outW); err != nil { - return err - } - // We might be ready to write now. Try again before blocking. - return nil - } - return t.Block(dw.outCh) -} - -// destroy cleans up resources help by dw. No more calls to wait* can occur -// after destroy is called. -func (dw *dualWaiter) destroy() { - if dw.inCh != nil { - dw.inFile.EventUnregister(&dw.inW) - dw.inCh = nil - } - if dw.outCh != nil { - dw.outFile.EventUnregister(&dw.outW) - dw.outCh = nil - } - dw.inFile = nil - dw.outFile = nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/stat.go b/pkg/sentry/syscalls/linux/vfs2/stat.go deleted file mode 100644 index 5ba566c55..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/stat.go +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/bits" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/gohacks" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Stat implements Linux syscall stat(2). -func Stat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - statAddr := args[1].Pointer() - return 0, nil, fstatat(t, linux.AT_FDCWD, pathAddr, statAddr, 0 /* flags */) -} - -// Lstat implements Linux syscall lstat(2). -func Lstat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - statAddr := args[1].Pointer() - return 0, nil, fstatat(t, linux.AT_FDCWD, pathAddr, statAddr, linux.AT_SYMLINK_NOFOLLOW) -} - -// Newfstatat implements Linux syscall newfstatat, which backs fstatat(2). -func Newfstatat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - statAddr := args[2].Pointer() - flags := args[3].Int() - return 0, nil, fstatat(t, dirfd, pathAddr, statAddr, flags) -} - -func fstatat(t *kernel.Task, dirfd int32, pathAddr, statAddr hostarch.Addr, flags int32) error { - if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 { - return linuxerr.EINVAL - } - - opts := vfs.StatOptions{ - Mask: linux.STATX_BASIC_STATS, - } - - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - - root := t.FSContext().RootDirectoryVFS2() - defer root.DecRef(t) - start := root - if !path.Absolute { - if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 { - return linuxerr.ENOENT - } - if dirfd == linux.AT_FDCWD { - start = t.FSContext().WorkingDirectoryVFS2() - defer start.DecRef(t) - } else { - dirfile := t.GetFileVFS2(dirfd) - if dirfile == nil { - return linuxerr.EBADF - } - if !path.HasComponents() { - // Use FileDescription.Stat() instead of - // VirtualFilesystem.StatAt() for fstatat(fd, ""), since the - // former may be able to use opened file state to expedite the - // Stat. - statx, err := dirfile.Stat(t, opts) - dirfile.DecRef(t) - if err != nil { - return err - } - var stat linux.Stat - convertStatxToUserStat(t, &statx, &stat) - _, err = stat.CopyOut(t, statAddr) - return err - } - start = dirfile.VirtualDentry() - start.IncRef() - defer start.DecRef(t) - dirfile.DecRef(t) - } - } - - statx, err := t.Kernel().VFS().StatAt(t, t.Credentials(), &vfs.PathOperation{ - Root: root, - Start: start, - Path: path, - FollowFinalSymlink: flags&linux.AT_SYMLINK_NOFOLLOW == 0, - }, &opts) - if err != nil { - return err - } - var stat linux.Stat - convertStatxToUserStat(t, &statx, &stat) - _, err = stat.CopyOut(t, statAddr) - return err -} - -func timespecFromStatxTimestamp(sxts linux.StatxTimestamp) linux.Timespec { - return linux.Timespec{ - Sec: sxts.Sec, - Nsec: int64(sxts.Nsec), - } -} - -// Fstat implements Linux syscall fstat(2). -func Fstat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - statAddr := args[1].Pointer() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - statx, err := file.Stat(t, vfs.StatOptions{ - Mask: linux.STATX_BASIC_STATS, - }) - if err != nil { - return 0, nil, err - } - var stat linux.Stat - convertStatxToUserStat(t, &statx, &stat) - _, err = stat.CopyOut(t, statAddr) - return 0, nil, err -} - -// Statx implements Linux syscall statx(2). -func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - flags := args[2].Int() - mask := args[3].Uint() - statxAddr := args[4].Pointer() - - if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW|linux.AT_STATX_SYNC_TYPE) != 0 { - return 0, nil, linuxerr.EINVAL - } - // Make sure that only one sync type option is set. - syncType := uint32(flags & linux.AT_STATX_SYNC_TYPE) - if syncType != 0 && !bits.IsPowerOfTwo32(syncType) { - return 0, nil, linuxerr.EINVAL - } - if mask&linux.STATX__RESERVED != 0 { - return 0, nil, linuxerr.EINVAL - } - - opts := vfs.StatOptions{ - Mask: mask, - Sync: uint32(flags & linux.AT_STATX_SYNC_TYPE), - } - - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - - root := t.FSContext().RootDirectoryVFS2() - defer root.DecRef(t) - start := root - if !path.Absolute { - if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 { - return 0, nil, linuxerr.ENOENT - } - if dirfd == linux.AT_FDCWD { - start = t.FSContext().WorkingDirectoryVFS2() - defer start.DecRef(t) - } else { - dirfile := t.GetFileVFS2(dirfd) - if dirfile == nil { - return 0, nil, linuxerr.EBADF - } - if !path.HasComponents() { - // Use FileDescription.Stat() instead of - // VirtualFilesystem.StatAt() for statx(fd, ""), since the - // former may be able to use opened file state to expedite the - // Stat. - statx, err := dirfile.Stat(t, opts) - dirfile.DecRef(t) - if err != nil { - return 0, nil, err - } - userifyStatx(t, &statx) - _, err = statx.CopyOut(t, statxAddr) - return 0, nil, err - } - start = dirfile.VirtualDentry() - start.IncRef() - defer start.DecRef(t) - dirfile.DecRef(t) - } - } - - statx, err := t.Kernel().VFS().StatAt(t, t.Credentials(), &vfs.PathOperation{ - Root: root, - Start: start, - Path: path, - FollowFinalSymlink: flags&linux.AT_SYMLINK_NOFOLLOW == 0, - }, &opts) - if err != nil { - return 0, nil, err - } - userifyStatx(t, &statx) - _, err = statx.CopyOut(t, statxAddr) - return 0, nil, err -} - -func userifyStatx(t *kernel.Task, statx *linux.Statx) { - userns := t.UserNamespace() - statx.UID = uint32(auth.KUID(statx.UID).In(userns).OrOverflow()) - statx.GID = uint32(auth.KGID(statx.GID).In(userns).OrOverflow()) -} - -// Readlink implements Linux syscall readlink(2). -func Readlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - bufAddr := args[1].Pointer() - size := args[2].SizeT() - return readlinkat(t, linux.AT_FDCWD, pathAddr, bufAddr, size) -} - -// Access implements Linux syscall access(2). -func Access(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - addr := args[0].Pointer() - mode := args[1].ModeT() - - return 0, nil, accessAt(t, linux.AT_FDCWD, addr, mode, 0 /* flags */) -} - -// Faccessat implements Linux syscall faccessat(2). -func Faccessat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - addr := args[1].Pointer() - mode := args[2].ModeT() - - return 0, nil, accessAt(t, dirfd, addr, mode, 0 /* flags */) -} - -// Faccessat2 implements Linux syscall faccessat2(2). -func Faccessat2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - addr := args[1].Pointer() - mode := args[2].ModeT() - flags := args[3].Int() - - return 0, nil, accessAt(t, dirfd, addr, mode, flags) -} - -func accessAt(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint, flags int32) error { - const rOK = 4 - const wOK = 2 - const xOK = 1 - - // Sanity check the mode. - if mode&^(rOK|wOK|xOK) != 0 { - return linuxerr.EINVAL - } - - // faccessat2(2) isn't documented as supporting AT_EMPTY_PATH, but it does. - if flags&^(linux.AT_EACCESS|linux.AT_SYMLINK_NOFOLLOW|linux.AT_EMPTY_PATH) != 0 { - return linuxerr.EINVAL - } - - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, dirfd, path, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_NOFOLLOW == 0)) - if err != nil { - return err - } - defer tpop.Release(t) - - creds := t.Credentials() - if flags&linux.AT_EACCESS == 0 { - // access(2) and faccessat(2) check permissions using real - // UID/GID, not effective UID/GID. - // - // "access() needs to use the real uid/gid, not the effective - // uid/gid. We do this by temporarily clearing all FS-related - // capabilities and switching the fsuid/fsgid around to the - // real ones." -fs/open.c:faccessat - creds = creds.Fork() - creds.EffectiveKUID = creds.RealKUID - creds.EffectiveKGID = creds.RealKGID - if creds.EffectiveKUID.In(creds.UserNamespace) == auth.RootUID { - creds.EffectiveCaps = creds.PermittedCaps - } else { - creds.EffectiveCaps = 0 - } - } - - return t.Kernel().VFS().AccessAt(t, creds, vfs.AccessTypes(mode), &tpop.pop) -} - -// Readlinkat implements Linux syscall mknodat(2). -func Readlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - dirfd := args[0].Int() - pathAddr := args[1].Pointer() - bufAddr := args[2].Pointer() - size := args[3].SizeT() - return readlinkat(t, dirfd, pathAddr, bufAddr, size) -} - -func readlinkat(t *kernel.Task, dirfd int32, pathAddr, bufAddr hostarch.Addr, size uint) (uintptr, *kernel.SyscallControl, error) { - if int(size) <= 0 { - return 0, nil, linuxerr.EINVAL - } - - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - // "Since Linux 2.6.39, pathname can be an empty string, in which case the - // call operates on the symbolic link referred to by dirfd ..." - - // readlinkat(2) - tpop, err := getTaskPathOperation(t, dirfd, path, allowEmptyPath, nofollowFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - target, err := t.Kernel().VFS().ReadlinkAt(t, t.Credentials(), &tpop.pop) - if err != nil { - return 0, nil, err - } - - if len(target) > int(size) { - target = target[:size] - } - n, err := t.CopyOutBytes(bufAddr, gohacks.ImmutableBytesFromString(target)) - if n == 0 { - return 0, nil, err - } - return uintptr(n), nil, nil -} - -// Statfs implements Linux syscall statfs(2). -func Statfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - bufAddr := args[1].Pointer() - - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, followFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - statfs, err := t.Kernel().VFS().StatFSAt(t, t.Credentials(), &tpop.pop) - if err != nil { - return 0, nil, err - } - _, err = statfs.CopyOut(t, bufAddr) - return 0, nil, err -} - -// Fstatfs implements Linux syscall fstatfs(2). -func Fstatfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - bufAddr := args[1].Pointer() - - tpop, err := getTaskPathOperation(t, fd, fspath.Path{}, allowEmptyPath, nofollowFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - statfs, err := t.Kernel().VFS().StatFSAt(t, t.Credentials(), &tpop.pop) - if err != nil { - return 0, nil, err - } - _, err = statfs.CopyOut(t, bufAddr) - return 0, nil, err -} diff --git a/pkg/sentry/syscalls/linux/vfs2/stat_amd64.go b/pkg/sentry/syscalls/linux/vfs2/stat_amd64.go deleted file mode 100644 index 122921b52..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/stat_amd64.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build amd64 -// +build amd64 - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" -) - -// This takes both input and output as pointer arguments to avoid copying large -// structs. -func convertStatxToUserStat(t *kernel.Task, statx *linux.Statx, stat *linux.Stat) { - // Linux just copies fields from struct kstat without regard to struct - // kstat::result_mask (fs/stat.c:cp_new_stat()), so we do too. - userns := t.UserNamespace() - *stat = linux.Stat{ - Dev: uint64(linux.MakeDeviceID(uint16(statx.DevMajor), statx.DevMinor)), - Ino: statx.Ino, - Nlink: uint64(statx.Nlink), - Mode: uint32(statx.Mode), - UID: uint32(auth.KUID(statx.UID).In(userns).OrOverflow()), - GID: uint32(auth.KGID(statx.GID).In(userns).OrOverflow()), - Rdev: uint64(linux.MakeDeviceID(uint16(statx.RdevMajor), statx.RdevMinor)), - Size: int64(statx.Size), - Blksize: int64(statx.Blksize), - Blocks: int64(statx.Blocks), - ATime: timespecFromStatxTimestamp(statx.Atime), - MTime: timespecFromStatxTimestamp(statx.Mtime), - CTime: timespecFromStatxTimestamp(statx.Ctime), - } -} diff --git a/pkg/sentry/syscalls/linux/vfs2/stat_arm64.go b/pkg/sentry/syscalls/linux/vfs2/stat_arm64.go deleted file mode 100644 index d32031481..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/stat_arm64.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build arm64 -// +build arm64 - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" -) - -// This takes both input and output as pointer arguments to avoid copying large -// structs. -func convertStatxToUserStat(t *kernel.Task, statx *linux.Statx, stat *linux.Stat) { - // Linux just copies fields from struct kstat without regard to struct - // kstat::result_mask (fs/stat.c:cp_new_stat()), so we do too. - userns := t.UserNamespace() - *stat = linux.Stat{ - Dev: uint64(linux.MakeDeviceID(uint16(statx.DevMajor), statx.DevMinor)), - Ino: statx.Ino, - Nlink: uint32(statx.Nlink), - Mode: uint32(statx.Mode), - UID: uint32(auth.KUID(statx.UID).In(userns).OrOverflow()), - GID: uint32(auth.KGID(statx.GID).In(userns).OrOverflow()), - Rdev: uint64(linux.MakeDeviceID(uint16(statx.RdevMajor), statx.RdevMinor)), - Size: int64(statx.Size), - Blksize: int32(statx.Blksize), - Blocks: int64(statx.Blocks), - ATime: timespecFromStatxTimestamp(statx.Atime), - MTime: timespecFromStatxTimestamp(statx.Mtime), - CTime: timespecFromStatxTimestamp(statx.Ctime), - } -} diff --git a/pkg/sentry/syscalls/linux/vfs2/sync.go b/pkg/sentry/syscalls/linux/vfs2/sync.go deleted file mode 100644 index 8c5fb1e1f..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/sync.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// Sync implements Linux syscall sync(2). -func Sync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return 0, nil, t.Kernel().VFS().SyncAllFilesystems(t) -} - -// Syncfs implements Linux syscall syncfs(2). -func Syncfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - if file.StatusFlags()&linux.O_PATH != 0 { - return 0, nil, linuxerr.EBADF - } - - return 0, nil, file.SyncFS(t) -} - -// Fsync implements Linux syscall fsync(2). -func Fsync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - return 0, nil, file.Sync(t) -} - -// Fdatasync implements Linux syscall fdatasync(2). -func Fdatasync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - // TODO(gvisor.dev/issue/1897): Avoid writeback of unnecessary metadata. - return Fsync(t, args) -} - -// SyncFileRange implements Linux syscall sync_file_range(2). -func SyncFileRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - offset := args[1].Int64() - nbytes := args[2].Int64() - flags := args[3].Uint() - - // Check for negative values and overflow. - if offset < 0 || offset+nbytes < 0 { - return 0, nil, linuxerr.EINVAL - } - if flags&^(linux.SYNC_FILE_RANGE_WAIT_BEFORE|linux.SYNC_FILE_RANGE_WRITE|linux.SYNC_FILE_RANGE_WAIT_AFTER) != 0 { - return 0, nil, linuxerr.EINVAL - } - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - // TODO(gvisor.dev/issue/1897): Currently, the only file syncing we support - // is a full-file sync, i.e. fsync(2). As a result, there are severe - // limitations on how much we support sync_file_range: - // - In Linux, sync_file_range(2) doesn't write out the file's metadata, even - // if the file size is changed. We do. - // - We always sync the entire file instead of [offset, offset+nbytes). - // - We do not support the use of WAIT_BEFORE without WAIT_AFTER. For - // correctness, we would have to perform a write-out every time WAIT_BEFORE - // was used, but this would be much more expensive than expected if there - // were no write-out operations in progress. - // - Whenever WAIT_AFTER is used, we sync the file. - // - Ignore WRITE. If this flag is used with WAIT_AFTER, then the file will - // be synced anyway. If this flag is used without WAIT_AFTER, then it is - // safe (and less expensive) to do nothing, because the syscall will not - // wait for the write-out to complete--we only need to make sure that the - // next time WAIT_BEFORE or WAIT_AFTER are used, the write-out completes. - // - According to fs/sync.c, WAIT_BEFORE|WAIT_AFTER "will detect any I/O - // errors or ENOSPC conditions and will return those to the caller, after - // clearing the EIO and ENOSPC flags in the address_space." We don't do - // this. - - if flags&linux.SYNC_FILE_RANGE_WAIT_BEFORE != 0 && - flags&linux.SYNC_FILE_RANGE_WAIT_AFTER == 0 { - t.Kernel().EmitUnimplementedEvent(t) - return 0, nil, linuxerr.ENOSYS - } - - if flags&linux.SYNC_FILE_RANGE_WAIT_AFTER != 0 { - if err := file.Sync(t); err != nil { - return 0, nil, linuxerr.ConvertIntr(err, linuxerr.ERESTARTSYS) - } - } - return 0, nil, nil -} diff --git a/pkg/sentry/syscalls/linux/vfs2/timerfd.go b/pkg/sentry/syscalls/linux/vfs2/timerfd.go deleted file mode 100644 index b8f96a757..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/timerfd.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/timerfd" - "gvisor.dev/gvisor/pkg/sentry/kernel" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" -) - -// TimerfdCreate implements Linux syscall timerfd_create(2). -func TimerfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - clockID := args[0].Int() - flags := args[1].Int() - - if flags&^(linux.TFD_CLOEXEC|linux.TFD_NONBLOCK) != 0 { - return 0, nil, linuxerr.EINVAL - } - - // Timerfds aren't writable per se (their implementation of Write just - // returns EINVAL), but they are "opened for writing", which is necessary - // to actually reach said implementation of Write. - fileFlags := uint32(linux.O_RDWR) - if flags&linux.TFD_NONBLOCK != 0 { - fileFlags |= linux.O_NONBLOCK - } - - var clock ktime.Clock - switch clockID { - case linux.CLOCK_REALTIME: - clock = t.Kernel().RealtimeClock() - case linux.CLOCK_MONOTONIC, linux.CLOCK_BOOTTIME: - clock = t.Kernel().MonotonicClock() - default: - return 0, nil, linuxerr.EINVAL - } - vfsObj := t.Kernel().VFS() - file, err := timerfd.New(t, vfsObj, clock, fileFlags) - if err != nil { - return 0, nil, err - } - defer file.DecRef(t) - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ - CloseOnExec: flags&linux.TFD_CLOEXEC != 0, - }) - if err != nil { - return 0, nil, err - } - return uintptr(fd), nil, nil -} - -// TimerfdSettime implements Linux syscall timerfd_settime(2). -func TimerfdSettime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - flags := args[1].Int() - newValAddr := args[2].Pointer() - oldValAddr := args[3].Pointer() - - if flags&^(linux.TFD_TIMER_ABSTIME) != 0 { - return 0, nil, linuxerr.EINVAL - } - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - tfd, ok := file.Impl().(*timerfd.TimerFileDescription) - if !ok { - return 0, nil, linuxerr.EINVAL - } - - var newVal linux.Itimerspec - if _, err := newVal.CopyIn(t, newValAddr); err != nil { - return 0, nil, err - } - newS, err := ktime.SettingFromItimerspec(newVal, flags&linux.TFD_TIMER_ABSTIME != 0, tfd.Clock()) - if err != nil { - return 0, nil, err - } - tm, oldS := tfd.SetTime(newS) - if oldValAddr != 0 { - oldVal := ktime.ItimerspecFromSetting(tm, oldS) - if _, err := oldVal.CopyOut(t, oldValAddr); err != nil { - return 0, nil, err - } - } - return 0, nil, nil -} - -// TimerfdGettime implements Linux syscall timerfd_gettime(2). -func TimerfdGettime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - curValAddr := args[1].Pointer() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - tfd, ok := file.Impl().(*timerfd.TimerFileDescription) - if !ok { - return 0, nil, linuxerr.EINVAL - } - - tm, s := tfd.GetTime() - curVal := ktime.ItimerspecFromSetting(tm, s) - _, err := curVal.CopyOut(t, curValAddr) - return 0, nil, err -} diff --git a/pkg/sentry/syscalls/linux/vfs2/vfs2.go b/pkg/sentry/syscalls/linux/vfs2/vfs2.go deleted file mode 100644 index 3a9ac74b0..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/vfs2.go +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package vfs2 provides syscall implementations that use VFS2. -package vfs2 - -import ( - "gvisor.dev/gvisor/pkg/sentry/syscalls" - "gvisor.dev/gvisor/pkg/sentry/syscalls/linux" -) - -// Override syscall table to add syscalls implementations from this package. -func Override() { - // Override AMD64. - s := linux.AMD64 - s.Table[0] = syscalls.SupportedPoint("read", Read, linux.PointRead) - s.Table[1] = syscalls.Supported("write", Write) - s.Table[2] = syscalls.SupportedPoint("open", Open, linux.PointOpen) - s.Table[3] = syscalls.SupportedPoint("close", Close, linux.PointClose) - s.Table[4] = syscalls.Supported("stat", Stat) - s.Table[5] = syscalls.Supported("fstat", Fstat) - s.Table[6] = syscalls.Supported("lstat", Lstat) - s.Table[7] = syscalls.Supported("poll", Poll) - s.Table[8] = syscalls.Supported("lseek", Lseek) - s.Table[9] = syscalls.Supported("mmap", Mmap) - s.Table[16] = syscalls.Supported("ioctl", Ioctl) - s.Table[17] = syscalls.Supported("pread64", Pread64) - s.Table[18] = syscalls.Supported("pwrite64", Pwrite64) - s.Table[19] = syscalls.Supported("readv", Readv) - s.Table[20] = syscalls.Supported("writev", Writev) - s.Table[21] = syscalls.Supported("access", Access) - s.Table[22] = syscalls.SupportedPoint("pipe", Pipe, linux.PointPipe) - s.Table[23] = syscalls.Supported("select", Select) - s.Table[32] = syscalls.SupportedPoint("dup", Dup, linux.PointDup) - s.Table[33] = syscalls.SupportedPoint("dup2", Dup2, linux.PointDup2) - s.Table[40] = syscalls.Supported("sendfile", Sendfile) - s.Table[41] = syscalls.SupportedPoint("socket", Socket, linux.PointSocket) - s.Table[42] = syscalls.SupportedPoint("connect", Connect, linux.PointConnect) - s.Table[43] = syscalls.SupportedPoint("accept", Accept, linux.PointAccept) - s.Table[44] = syscalls.Supported("sendto", SendTo) - s.Table[45] = syscalls.Supported("recvfrom", RecvFrom) - s.Table[46] = syscalls.Supported("sendmsg", SendMsg) - s.Table[47] = syscalls.Supported("recvmsg", RecvMsg) - s.Table[48] = syscalls.Supported("shutdown", Shutdown) - s.Table[49] = syscalls.SupportedPoint("bind", Bind, linux.PointBind) - s.Table[50] = syscalls.Supported("listen", Listen) - s.Table[51] = syscalls.Supported("getsockname", GetSockName) - s.Table[52] = syscalls.Supported("getpeername", GetPeerName) - s.Table[53] = syscalls.SupportedPoint("socketpair", SocketPair, linux.PointSocketpair) - s.Table[54] = syscalls.Supported("setsockopt", SetSockOpt) - s.Table[55] = syscalls.Supported("getsockopt", GetSockOpt) - s.Table[59] = syscalls.SupportedPoint("execve", Execve, linux.PointExecve) - s.Table[72] = syscalls.SupportedPoint("fcntl", Fcntl, linux.PointFcntl) - s.Table[73] = syscalls.Supported("flock", Flock) - s.Table[74] = syscalls.Supported("fsync", Fsync) - s.Table[75] = syscalls.Supported("fdatasync", Fdatasync) - s.Table[76] = syscalls.Supported("truncate", Truncate) - s.Table[77] = syscalls.Supported("ftruncate", Ftruncate) - s.Table[78] = syscalls.Supported("getdents", Getdents) - s.Table[79] = syscalls.Supported("getcwd", Getcwd) - s.Table[80] = syscalls.SupportedPoint("chdir", Chdir, linux.PointChdir) - s.Table[81] = syscalls.SupportedPoint("fchdir", Fchdir, linux.PointFchdir) - s.Table[82] = syscalls.Supported("rename", Rename) - s.Table[83] = syscalls.Supported("mkdir", Mkdir) - s.Table[84] = syscalls.Supported("rmdir", Rmdir) - s.Table[85] = syscalls.SupportedPoint("creat", Creat, linux.PointCreat) - s.Table[86] = syscalls.Supported("link", Link) - s.Table[87] = syscalls.Supported("unlink", Unlink) - s.Table[88] = syscalls.Supported("symlink", Symlink) - s.Table[89] = syscalls.Supported("readlink", Readlink) - s.Table[90] = syscalls.Supported("chmod", Chmod) - s.Table[91] = syscalls.Supported("fchmod", Fchmod) - s.Table[92] = syscalls.Supported("chown", Chown) - s.Table[93] = syscalls.Supported("fchown", Fchown) - s.Table[94] = syscalls.Supported("lchown", Lchown) - s.Table[132] = syscalls.Supported("utime", Utime) - s.Table[133] = syscalls.Supported("mknod", Mknod) - s.Table[137] = syscalls.Supported("statfs", Statfs) - s.Table[138] = syscalls.Supported("fstatfs", Fstatfs) - s.Table[155] = syscalls.Supported("pivot_root", PivotRoot) - s.Table[161] = syscalls.SupportedPoint("chroot", Chroot, linux.PointChroot) - s.Table[162] = syscalls.Supported("sync", Sync) - s.Table[165] = syscalls.Supported("mount", Mount) - s.Table[166] = syscalls.Supported("umount2", Umount2) - s.Table[187] = syscalls.Supported("readahead", Readahead) - s.Table[188] = syscalls.Supported("setxattr", SetXattr) - s.Table[189] = syscalls.Supported("lsetxattr", Lsetxattr) - s.Table[190] = syscalls.Supported("fsetxattr", Fsetxattr) - s.Table[191] = syscalls.Supported("getxattr", GetXattr) - s.Table[192] = syscalls.Supported("lgetxattr", Lgetxattr) - s.Table[193] = syscalls.Supported("fgetxattr", Fgetxattr) - s.Table[194] = syscalls.Supported("listxattr", ListXattr) - s.Table[195] = syscalls.Supported("llistxattr", Llistxattr) - s.Table[196] = syscalls.Supported("flistxattr", Flistxattr) - s.Table[197] = syscalls.Supported("removexattr", RemoveXattr) - s.Table[198] = syscalls.Supported("lremovexattr", Lremovexattr) - s.Table[199] = syscalls.Supported("fremovexattr", Fremovexattr) - s.Table[209] = syscalls.PartiallySupported("io_submit", IoSubmit, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}) - s.Table[213] = syscalls.Supported("epoll_create", EpollCreate) - s.Table[217] = syscalls.Supported("getdents64", Getdents64) - s.Table[221] = syscalls.PartiallySupported("fadvise64", Fadvise64, "The syscall is 'supported', but ignores all provided advice.", nil) - s.Table[232] = syscalls.Supported("epoll_wait", EpollWait) - s.Table[233] = syscalls.Supported("epoll_ctl", EpollCtl) - s.Table[235] = syscalls.Supported("utimes", Utimes) - s.Table[240] = syscalls.Supported("mq_open", MqOpen) - s.Table[241] = syscalls.Supported("mq_unlink", MqUnlink) - s.Table[253] = syscalls.PartiallySupportedPoint("inotify_init", InotifyInit, linux.PointInotifyInit, "inotify events are only available inside the sandbox.", nil) - s.Table[254] = syscalls.PartiallySupportedPoint("inotify_add_watch", InotifyAddWatch, linux.PointInotifyAddWatch, "inotify events are only available inside the sandbox.", nil) - s.Table[255] = syscalls.PartiallySupportedPoint("inotify_rm_watch", InotifyRmWatch, linux.PointInotifyRmWatch, "inotify events are only available inside the sandbox.", nil) - s.Table[257] = syscalls.SupportedPoint("openat", Openat, linux.PointOpenat) - s.Table[258] = syscalls.Supported("mkdirat", Mkdirat) - s.Table[259] = syscalls.Supported("mknodat", Mknodat) - s.Table[260] = syscalls.Supported("fchownat", Fchownat) - s.Table[261] = syscalls.Supported("futimesat", Futimesat) - s.Table[262] = syscalls.Supported("newfstatat", Newfstatat) - s.Table[263] = syscalls.Supported("unlinkat", Unlinkat) - s.Table[264] = syscalls.Supported("renameat", Renameat) - s.Table[265] = syscalls.Supported("linkat", Linkat) - s.Table[266] = syscalls.Supported("symlinkat", Symlinkat) - s.Table[267] = syscalls.Supported("readlinkat", Readlinkat) - s.Table[268] = syscalls.Supported("fchmodat", Fchmodat) - s.Table[269] = syscalls.Supported("faccessat", Faccessat) - s.Table[270] = syscalls.Supported("pselect", Pselect) - s.Table[271] = syscalls.Supported("ppoll", Ppoll) - s.Table[275] = syscalls.Supported("splice", Splice) - s.Table[276] = syscalls.Supported("tee", Tee) - s.Table[277] = syscalls.Supported("sync_file_range", SyncFileRange) - s.Table[280] = syscalls.Supported("utimensat", Utimensat) - s.Table[281] = syscalls.Supported("epoll_pwait", EpollPwait) - s.Table[282] = syscalls.SupportedPoint("signalfd", Signalfd, linux.PointSignalfd) - s.Table[283] = syscalls.SupportedPoint("timerfd_create", TimerfdCreate, linux.PointTimerfdCreate) - s.Table[284] = syscalls.SupportedPoint("eventfd", Eventfd, linux.PointEventfd) - s.Table[285] = syscalls.PartiallySupported("fallocate", Fallocate, "Not all options are supported.", nil) - s.Table[286] = syscalls.SupportedPoint("timerfd_settime", TimerfdSettime, linux.PointTimerfdSettime) - s.Table[287] = syscalls.SupportedPoint("timerfd_gettime", TimerfdGettime, linux.PointTimerfdGettime) - s.Table[288] = syscalls.SupportedPoint("accept4", Accept4, linux.PointAccept4) - s.Table[289] = syscalls.SupportedPoint("signalfd4", Signalfd4, linux.PointSignalfd4) - s.Table[290] = syscalls.SupportedPoint("eventfd2", Eventfd2, linux.PointEventfd2) - s.Table[291] = syscalls.Supported("epoll_create1", EpollCreate1) - s.Table[292] = syscalls.SupportedPoint("dup3", Dup3, linux.PointDup3) - s.Table[293] = syscalls.SupportedPoint("pipe2", Pipe2, linux.PointPipe2) - s.Table[294] = syscalls.PartiallySupportedPoint("inotify_init1", InotifyInit1, linux.PointInotifyInit1, "inotify events are only available inside the sandbox.", nil) - s.Table[295] = syscalls.Supported("preadv", Preadv) - s.Table[296] = syscalls.Supported("pwritev", Pwritev) - s.Table[299] = syscalls.Supported("recvmmsg", RecvMMsg) - s.Table[306] = syscalls.Supported("syncfs", Syncfs) - s.Table[307] = syscalls.Supported("sendmmsg", SendMMsg) - // FIXME(zkoopmans): Re-enable calls for process_vm_(read/write)v. - s.Table[316] = syscalls.Supported("renameat2", Renameat2) - s.Table[319] = syscalls.Supported("memfd_create", MemfdCreate) - s.Table[322] = syscalls.SupportedPoint("execveat", Execveat, linux.PointExecveat) - s.Table[327] = syscalls.Supported("preadv2", Preadv2) - s.Table[328] = syscalls.Supported("pwritev2", Pwritev2) - s.Table[332] = syscalls.Supported("statx", Statx) - s.Table[425] = syscalls.PartiallySupported("io_uring_setup", IOUringSetup, "Not all flags and functionality supported.", nil) - s.Table[426] = syscalls.PartiallySupported("io_uring_enter", IOUringEnter, "Not all flags and functionality supported.", nil) - s.Table[436] = syscalls.Supported("close_range", CloseRange) - s.Table[439] = syscalls.Supported("faccessat2", Faccessat2) - s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2) - s.Init() - - // Override ARM64. - s = linux.ARM64 - s.Table[2] = syscalls.PartiallySupported("io_submit", IoSubmit, "Generally supported with exceptions. User ring optimizations are not implemented.", []string{"gvisor.dev/issue/204"}) - s.Table[5] = syscalls.Supported("setxattr", SetXattr) - s.Table[6] = syscalls.Supported("lsetxattr", Lsetxattr) - s.Table[7] = syscalls.Supported("fsetxattr", Fsetxattr) - s.Table[8] = syscalls.Supported("getxattr", GetXattr) - s.Table[9] = syscalls.Supported("lgetxattr", Lgetxattr) - s.Table[10] = syscalls.Supported("fgetxattr", Fgetxattr) - s.Table[11] = syscalls.Supported("listxattr", ListXattr) - s.Table[12] = syscalls.Supported("llistxattr", Llistxattr) - s.Table[13] = syscalls.Supported("flistxattr", Flistxattr) - s.Table[14] = syscalls.Supported("removexattr", RemoveXattr) - s.Table[15] = syscalls.Supported("lremovexattr", Lremovexattr) - s.Table[16] = syscalls.Supported("fremovexattr", Fremovexattr) - s.Table[17] = syscalls.Supported("getcwd", Getcwd) - s.Table[19] = syscalls.SupportedPoint("eventfd2", Eventfd2, linux.PointEventfd2) - s.Table[20] = syscalls.Supported("epoll_create1", EpollCreate1) - s.Table[21] = syscalls.Supported("epoll_ctl", EpollCtl) - s.Table[22] = syscalls.Supported("epoll_pwait", EpollPwait) - s.Table[23] = syscalls.SupportedPoint("dup", Dup, linux.PointDup) - s.Table[24] = syscalls.SupportedPoint("dup3", Dup3, linux.PointDup3) - s.Table[25] = syscalls.SupportedPoint("fcntl", Fcntl, linux.PointFcntl) - s.Table[26] = syscalls.PartiallySupportedPoint("inotify_init1", InotifyInit1, linux.PointInotifyInit1, "inotify events are only available inside the sandbox.", nil) - s.Table[27] = syscalls.PartiallySupportedPoint("inotify_add_watch", InotifyAddWatch, linux.PointInotifyAddWatch, "inotify events are only available inside the sandbox.", nil) - s.Table[28] = syscalls.PartiallySupportedPoint("inotify_rm_watch", InotifyRmWatch, linux.PointInotifyRmWatch, "inotify events are only available inside the sandbox.", nil) - s.Table[29] = syscalls.Supported("ioctl", Ioctl) - s.Table[32] = syscalls.Supported("flock", Flock) - s.Table[33] = syscalls.Supported("mknodat", Mknodat) - s.Table[34] = syscalls.Supported("mkdirat", Mkdirat) - s.Table[35] = syscalls.Supported("unlinkat", Unlinkat) - s.Table[36] = syscalls.Supported("symlinkat", Symlinkat) - s.Table[37] = syscalls.Supported("linkat", Linkat) - s.Table[38] = syscalls.Supported("renameat", Renameat) - s.Table[39] = syscalls.Supported("umount2", Umount2) - s.Table[40] = syscalls.Supported("mount", Mount) - s.Table[41] = syscalls.Supported("pivot_root", PivotRoot) - s.Table[43] = syscalls.Supported("statfs", Statfs) - s.Table[44] = syscalls.Supported("fstatfs", Fstatfs) - s.Table[45] = syscalls.Supported("truncate", Truncate) - s.Table[46] = syscalls.Supported("ftruncate", Ftruncate) - s.Table[47] = syscalls.PartiallySupported("fallocate", Fallocate, "Not all options are supported.", nil) - s.Table[48] = syscalls.Supported("faccessat", Faccessat) - s.Table[49] = syscalls.SupportedPoint("chdir", Chdir, linux.PointChdir) - s.Table[50] = syscalls.SupportedPoint("fchdir", Fchdir, linux.PointFchdir) - s.Table[51] = syscalls.SupportedPoint("chroot", Chroot, linux.PointChroot) - s.Table[52] = syscalls.Supported("fchmod", Fchmod) - s.Table[53] = syscalls.Supported("fchmodat", Fchmodat) - s.Table[54] = syscalls.Supported("fchownat", Fchownat) - s.Table[55] = syscalls.Supported("fchown", Fchown) - s.Table[56] = syscalls.SupportedPoint("openat", Openat, linux.PointOpenat) - s.Table[57] = syscalls.SupportedPoint("close", Close, linux.PointClose) - s.Table[59] = syscalls.SupportedPoint("pipe2", Pipe2, linux.PointPipe2) - s.Table[61] = syscalls.Supported("getdents64", Getdents64) - s.Table[62] = syscalls.Supported("lseek", Lseek) - s.Table[63] = syscalls.SupportedPoint("read", Read, linux.PointRead) - s.Table[64] = syscalls.Supported("write", Write) - s.Table[65] = syscalls.Supported("readv", Readv) - s.Table[66] = syscalls.Supported("writev", Writev) - s.Table[67] = syscalls.Supported("pread64", Pread64) - s.Table[68] = syscalls.Supported("pwrite64", Pwrite64) - s.Table[69] = syscalls.Supported("preadv", Preadv) - s.Table[70] = syscalls.Supported("pwritev", Pwritev) - s.Table[71] = syscalls.Supported("sendfile", Sendfile) - s.Table[72] = syscalls.Supported("pselect", Pselect) - s.Table[73] = syscalls.Supported("ppoll", Ppoll) - s.Table[74] = syscalls.SupportedPoint("signalfd4", Signalfd4, linux.PointSignalfd4) - s.Table[76] = syscalls.Supported("splice", Splice) - s.Table[77] = syscalls.Supported("tee", Tee) - s.Table[78] = syscalls.Supported("readlinkat", Readlinkat) - s.Table[79] = syscalls.Supported("newfstatat", Newfstatat) - s.Table[80] = syscalls.Supported("fstat", Fstat) - s.Table[81] = syscalls.Supported("sync", Sync) - s.Table[82] = syscalls.Supported("fsync", Fsync) - s.Table[83] = syscalls.Supported("fdatasync", Fdatasync) - s.Table[84] = syscalls.Supported("sync_file_range", SyncFileRange) - s.Table[85] = syscalls.SupportedPoint("timerfd_create", TimerfdCreate, linux.PointTimerfdCreate) - s.Table[86] = syscalls.SupportedPoint("timerfd_settime", TimerfdSettime, linux.PointTimerfdSettime) - s.Table[87] = syscalls.SupportedPoint("timerfd_gettime", TimerfdGettime, linux.PointTimerfdGettime) - s.Table[88] = syscalls.Supported("utimensat", Utimensat) - s.Table[180] = syscalls.Supported("mq_open", MqOpen) - s.Table[181] = syscalls.Supported("mq_unlink", MqUnlink) - s.Table[198] = syscalls.SupportedPoint("socket", Socket, linux.PointSocket) - s.Table[199] = syscalls.SupportedPoint("socketpair", SocketPair, linux.PointSocketpair) - s.Table[200] = syscalls.SupportedPoint("bind", Bind, linux.PointBind) - s.Table[201] = syscalls.Supported("listen", Listen) - s.Table[202] = syscalls.SupportedPoint("accept", Accept, linux.PointAccept) - s.Table[203] = syscalls.SupportedPoint("connect", Connect, linux.PointConnect) - s.Table[204] = syscalls.Supported("getsockname", GetSockName) - s.Table[205] = syscalls.Supported("getpeername", GetPeerName) - s.Table[206] = syscalls.Supported("sendto", SendTo) - s.Table[207] = syscalls.Supported("recvfrom", RecvFrom) - s.Table[208] = syscalls.Supported("setsockopt", SetSockOpt) - s.Table[209] = syscalls.Supported("getsockopt", GetSockOpt) - s.Table[210] = syscalls.Supported("shutdown", Shutdown) - s.Table[211] = syscalls.Supported("sendmsg", SendMsg) - s.Table[212] = syscalls.Supported("recvmsg", RecvMsg) - s.Table[213] = syscalls.Supported("readahead", Readahead) - s.Table[221] = syscalls.SupportedPoint("execve", Execve, linux.PointExecve) - s.Table[222] = syscalls.Supported("mmap", Mmap) - s.Table[223] = syscalls.PartiallySupported("fadvise64", Fadvise64, "Not all options are supported.", nil) - s.Table[242] = syscalls.SupportedPoint("accept4", Accept4, linux.PointAccept4) - s.Table[243] = syscalls.Supported("recvmmsg", RecvMMsg) - s.Table[267] = syscalls.Supported("syncfs", Syncfs) - s.Table[269] = syscalls.Supported("sendmmsg", SendMMsg) - s.Table[276] = syscalls.Supported("renameat2", Renameat2) - s.Table[279] = syscalls.Supported("memfd_create", MemfdCreate) - s.Table[281] = syscalls.SupportedPoint("execveat", Execveat, linux.PointExecveat) - s.Table[286] = syscalls.Supported("preadv2", Preadv2) - s.Table[287] = syscalls.Supported("pwritev2", Pwritev2) - s.Table[291] = syscalls.Supported("statx", Statx) - s.Table[425] = syscalls.PartiallySupported("io_uring_setup", IOUringSetup, "Not all flags and functionality supported.", nil) - s.Table[426] = syscalls.PartiallySupported("io_uring_enter", IOUringEnter, "Not all flags and functionality supported.", nil) - s.Table[436] = syscalls.Supported("close_range", CloseRange) - s.Table[439] = syscalls.Supported("faccessat2", Faccessat2) - s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2) - s.Init() -} diff --git a/pkg/sentry/syscalls/linux/vfs2/xattr.go b/pkg/sentry/syscalls/linux/vfs2/xattr.go deleted file mode 100644 index 7b2f69c45..000000000 --- a/pkg/sentry/syscalls/linux/vfs2/xattr.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vfs2 - -import ( - "bytes" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/gohacks" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// ListXattr implements Linux syscall listxattr(2). -func ListXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return listxattr(t, args, followFinalSymlink) -} - -// Llistxattr implements Linux syscall llistxattr(2). -func Llistxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return listxattr(t, args, nofollowFinalSymlink) -} - -func listxattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - listAddr := args[1].Pointer() - size := args[2].SizeT() - - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - names, err := t.Kernel().VFS().ListXattrAt(t, t.Credentials(), &tpop.pop, uint64(size)) - if err != nil { - return 0, nil, err - } - n, err := copyOutXattrNameList(t, listAddr, size, names) - if err != nil { - return 0, nil, err - } - return uintptr(n), nil, nil -} - -// Flistxattr implements Linux syscall flistxattr(2). -func Flistxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - listAddr := args[1].Pointer() - size := args[2].SizeT() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - names, err := file.ListXattr(t, uint64(size)) - if err != nil { - return 0, nil, err - } - n, err := copyOutXattrNameList(t, listAddr, size, names) - if err != nil { - return 0, nil, err - } - return uintptr(n), nil, nil -} - -// GetXattr implements Linux syscall getxattr(2). -func GetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return getxattr(t, args, followFinalSymlink) -} - -// Lgetxattr implements Linux syscall lgetxattr(2). -func Lgetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return getxattr(t, args, nofollowFinalSymlink) -} - -func getxattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) (uintptr, *kernel.SyscallControl, error) { - pathAddr := args[0].Pointer() - nameAddr := args[1].Pointer() - valueAddr := args[2].Pointer() - size := args[3].SizeT() - - path, err := copyInPath(t, pathAddr) - if err != nil { - return 0, nil, err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) - if err != nil { - return 0, nil, err - } - defer tpop.Release(t) - - name, err := copyInXattrName(t, nameAddr) - if err != nil { - return 0, nil, err - } - - value, err := t.Kernel().VFS().GetXattrAt(t, t.Credentials(), &tpop.pop, &vfs.GetXattrOptions{ - Name: name, - Size: uint64(size), - }) - if err != nil { - return 0, nil, err - } - n, err := copyOutXattrValue(t, valueAddr, size, value) - if err != nil { - return 0, nil, err - } - return uintptr(n), nil, nil -} - -// Fgetxattr implements Linux syscall fgetxattr(2). -func Fgetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - nameAddr := args[1].Pointer() - valueAddr := args[2].Pointer() - size := args[3].SizeT() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - name, err := copyInXattrName(t, nameAddr) - if err != nil { - return 0, nil, err - } - - value, err := file.GetXattr(t, &vfs.GetXattrOptions{Name: name, Size: uint64(size)}) - if err != nil { - return 0, nil, err - } - n, err := copyOutXattrValue(t, valueAddr, size, value) - if err != nil { - return 0, nil, err - } - return uintptr(n), nil, nil -} - -// SetXattr implements Linux syscall setxattr(2). -func SetXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return 0, nil, setxattr(t, args, followFinalSymlink) -} - -// Lsetxattr implements Linux syscall lsetxattr(2). -func Lsetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return 0, nil, setxattr(t, args, nofollowFinalSymlink) -} - -func setxattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) error { - pathAddr := args[0].Pointer() - nameAddr := args[1].Pointer() - valueAddr := args[2].Pointer() - size := args[3].SizeT() - flags := args[4].Int() - - if flags&^(linux.XATTR_CREATE|linux.XATTR_REPLACE) != 0 { - return linuxerr.EINVAL - } - - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) - if err != nil { - return err - } - defer tpop.Release(t) - - name, err := copyInXattrName(t, nameAddr) - if err != nil { - return err - } - value, err := copyInXattrValue(t, valueAddr, size) - if err != nil { - return err - } - - return t.Kernel().VFS().SetXattrAt(t, t.Credentials(), &tpop.pop, &vfs.SetXattrOptions{ - Name: name, - Value: value, - Flags: uint32(flags), - }) -} - -// Fsetxattr implements Linux syscall fsetxattr(2). -func Fsetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - nameAddr := args[1].Pointer() - valueAddr := args[2].Pointer() - size := args[3].SizeT() - flags := args[4].Int() - - if flags&^(linux.XATTR_CREATE|linux.XATTR_REPLACE) != 0 { - return 0, nil, linuxerr.EINVAL - } - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - name, err := copyInXattrName(t, nameAddr) - if err != nil { - return 0, nil, err - } - value, err := copyInXattrValue(t, valueAddr, size) - if err != nil { - return 0, nil, err - } - - return 0, nil, file.SetXattr(t, &vfs.SetXattrOptions{ - Name: name, - Value: value, - Flags: uint32(flags), - }) -} - -// RemoveXattr implements Linux syscall removexattr(2). -func RemoveXattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return 0, nil, removexattr(t, args, followFinalSymlink) -} - -// Lremovexattr implements Linux syscall lremovexattr(2). -func Lremovexattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - return 0, nil, removexattr(t, args, nofollowFinalSymlink) -} - -func removexattr(t *kernel.Task, args arch.SyscallArguments, shouldFollowFinalSymlink shouldFollowFinalSymlink) error { - pathAddr := args[0].Pointer() - nameAddr := args[1].Pointer() - - path, err := copyInPath(t, pathAddr) - if err != nil { - return err - } - tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, shouldFollowFinalSymlink) - if err != nil { - return err - } - defer tpop.Release(t) - - name, err := copyInXattrName(t, nameAddr) - if err != nil { - return err - } - - return t.Kernel().VFS().RemoveXattrAt(t, t.Credentials(), &tpop.pop, name) -} - -// Fremovexattr implements Linux syscall fremovexattr(2). -func Fremovexattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { - fd := args[0].Int() - nameAddr := args[1].Pointer() - - file := t.GetFileVFS2(fd) - if file == nil { - return 0, nil, linuxerr.EBADF - } - defer file.DecRef(t) - - name, err := copyInXattrName(t, nameAddr) - if err != nil { - return 0, nil, err - } - - return 0, nil, file.RemoveXattr(t, name) -} - -func copyInXattrName(t *kernel.Task, nameAddr hostarch.Addr) (string, error) { - name, err := t.CopyInString(nameAddr, linux.XATTR_NAME_MAX+1) - if err != nil { - if linuxerr.Equals(linuxerr.ENAMETOOLONG, err) { - return "", linuxerr.ERANGE - } - return "", err - } - if len(name) == 0 { - return "", linuxerr.ERANGE - } - return name, nil -} - -func copyOutXattrNameList(t *kernel.Task, listAddr hostarch.Addr, size uint, names []string) (int, error) { - if size > linux.XATTR_LIST_MAX { - size = linux.XATTR_LIST_MAX - } - var buf bytes.Buffer - for _, name := range names { - buf.WriteString(name) - buf.WriteByte(0) - } - if size == 0 { - // Return the size that would be required to accomodate the list. - return buf.Len(), nil - } - if buf.Len() > int(size) { - if size >= linux.XATTR_LIST_MAX { - return 0, linuxerr.E2BIG - } - return 0, linuxerr.ERANGE - } - return t.CopyOutBytes(listAddr, buf.Bytes()) -} - -func copyInXattrValue(t *kernel.Task, valueAddr hostarch.Addr, size uint) (string, error) { - if size > linux.XATTR_SIZE_MAX { - return "", linuxerr.E2BIG - } - buf := make([]byte, size) - if _, err := t.CopyInBytes(valueAddr, buf); err != nil { - return "", err - } - return gohacks.StringFromImmutableBytes(buf), nil -} - -func copyOutXattrValue(t *kernel.Task, valueAddr hostarch.Addr, size uint, value string) (int, error) { - if size > linux.XATTR_SIZE_MAX { - size = linux.XATTR_SIZE_MAX - } - if size == 0 { - // Return the size that would be required to accomodate the value. - return len(value), nil - } - if len(value) > int(size) { - if size >= linux.XATTR_SIZE_MAX { - return 0, linuxerr.E2BIG - } - return 0, linuxerr.ERANGE - } - return t.CopyOutBytes(valueAddr, gohacks.ImmutableBytesFromString(value)) -} diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index 82808e2c7..fa6d83dfe 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -84,7 +84,6 @@ go_library( "//pkg/sentry/socket/unix", "//pkg/sentry/state", "//pkg/sentry/strace", - "//pkg/sentry/syscalls/linux/vfs2", "//pkg/sentry/time", "//pkg/sentry/unimpl:unimplemented_syscall_go_proto", "//pkg/sentry/usage", diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 44896ea2a..91c8534d3 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -49,7 +49,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/seccheck" pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/sentry/socket/netfilter" - "gvisor.dev/gvisor/pkg/sentry/syscalls/linux/vfs2" "gvisor.dev/gvisor/pkg/sentry/time" "gvisor.dev/gvisor/pkg/sentry/usage" "gvisor.dev/gvisor/pkg/sentry/vfs" @@ -239,7 +238,6 @@ func New(args Args) (*Loader, error) { kernel.FUSEEnabled = args.Conf.FUSE kernel.LISAFSEnabled = args.Conf.Lisafs - vfs2.Override() // Make host FDs stable between invocations. Host FDs must map to the exact // same number when the sandbox is restored. Otherwise the wrong FD will be