diff --git a/pkg/sentry/kernel/task_usermem.go b/pkg/sentry/kernel/task_usermem.go index 4df472613..76a195ef1 100644 --- a/pkg/sentry/kernel/task_usermem.go +++ b/pkg/sentry/kernel/task_usermem.go @@ -21,10 +21,13 @@ import ( "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/sentry/mm" "gvisor.dev/gvisor/pkg/usermem" ) +const iovecLength = 16 + // MAX_RW_COUNT is the maximum size in bytes of a single read or write. // Reads and writes that exceed this size may be silently truncated. // (Linux: include/linux/fs.h:MAX_RW_COUNT) @@ -122,28 +125,33 @@ func (t *Task) CopyInVector(addr hostarch.Addr, maxElemSize, maxTotalSize int) ( } // CopyOutIovecs converts src to an array of struct iovecs and copies it to the -// memory mapped at addr. +// memory mapped at addr for Task. // // Preconditions: Same as usermem.IO.CopyOut, plus: // - The caller must be running on the task goroutine. // - t's AddressSpace must be active. func (t *Task) CopyOutIovecs(addr hostarch.Addr, src hostarch.AddrRangeSeq) error { + return copyOutIovecs(t, t, addr, src) +} + +// copyOutIovecs converts src to an array of struct iovecs and copies it to the +// memory mapped at addr. +func copyOutIovecs(ctx marshal.CopyContext, t *Task, addr hostarch.Addr, src hostarch.AddrRangeSeq) error { switch t.Arch().Width() { case 8: - const itemLen = 16 - if _, ok := addr.AddLength(uint64(src.NumRanges()) * itemLen); !ok { + if _, ok := addr.AddLength(uint64(src.NumRanges()) * iovecLength); !ok { return linuxerr.EFAULT } - b := t.CopyScratchBuffer(itemLen) + b := ctx.CopyScratchBuffer(iovecLength) for ; !src.IsEmpty(); src = src.Tail() { ar := src.Head() hostarch.ByteOrder.PutUint64(b[0:8], uint64(ar.Start)) hostarch.ByteOrder.PutUint64(b[8:16], uint64(ar.Length())) - if _, err := t.CopyOutBytes(addr, b); err != nil { + if _, err := ctx.CopyOutBytes(addr, b); err != nil { return err } - addr += itemLen + addr += iovecLength } default: @@ -153,32 +161,60 @@ func (t *Task) CopyOutIovecs(addr hostarch.Addr, src hostarch.AddrRangeSeq) erro return nil } -// CopyInIovecs copies an array of numIovecs struct iovecs from the memory +// CopyInIovecs copies in IoVecs for Task. +// +// Preconditions: Same as usermem.IO.CopyIn, plus: +// * The caller must be running on the task goroutine. +// * t's AddressSpace must be active. +func (t *Task) CopyInIovecs(addr hostarch.Addr, numIovecs int) (hostarch.AddrRangeSeq, error) { + // Special case to avoid allocating allocating a single hostaddr.AddrRange. + if numIovecs == 1 { + return copyInIovec(t, t, addr) + } + iovecs, err := copyInIovecs(t, t, addr, numIovecs) + if err != nil { + return hostarch.AddrRangeSeq{}, err + } + return hostarch.AddrRangeSeqFromSlice(iovecs), nil +} + +func copyInIovec(ctx marshal.CopyContext, t *Task, addr hostarch.Addr) (hostarch.AddrRangeSeq, error) { + if err := checkArch(t); err != nil { + return hostarch.AddrRangeSeq{}, err + } + b := ctx.CopyScratchBuffer(iovecLength) + ar, err := makeIovec(ctx, t, addr, b) + if err != nil { + return hostarch.AddrRangeSeq{}, err + } + return hostarch.AddrRangeSeqOf(ar).TakeFirst(MAX_RW_COUNT), nil +} + +// copyInIovecs copies an array of numIovecs struct iovecs from the memory // mapped at addr, converts them to hostarch.AddrRanges, and returns them as a // hostarch.AddrRangeSeq. // -// CopyInIovecs shares the following properties with Linux's +// copyInIovecs shares the following properties with Linux's // lib/iov_iter.c:import_iovec() => fs/read_write.c:rw_copy_check_uvector(): // -// - If the length of any AddrRange would exceed the range of an ssize_t, -// CopyInIovecs returns EINVAL. +// - If the length of any AddrRange would exceed the range of an ssize_t, +// copyInIovecs returns EINVAL. // -// - If the length of any AddrRange would cause its end to overflow, -// CopyInIovecs returns EFAULT. +// - If the length of any AddrRange would cause its end to overflow, +// copyInIovecs returns EFAULT. // -// - If any AddrRange would include addresses outside the application address -// range, CopyInIovecs returns EFAULT. +// - If any AddrRange would include addresses outside the application address +// range, copyInIovecs returns EFAULT. // // - The combined length of all AddrRanges is limited to MAX_RW_COUNT. If the // combined length of all AddrRanges would otherwise exceed this amount, ranges // beyond MAX_RW_COUNT are silently truncated. -// -// Preconditions: Same as usermem.IO.CopyIn, plus: -// - The caller must be running on the task goroutine. -// - t's AddressSpace must be active. -func (t *Task) CopyInIovecs(addr hostarch.Addr, numIovecs int) (hostarch.AddrRangeSeq, error) { +func copyInIovecs(ctx marshal.CopyContext, t *Task, addr hostarch.Addr, numIovecs int) ([]hostarch.AddrRange, error) { + if err := checkArch(t); err != nil { + return nil, err + } if numIovecs == 0 { - return hostarch.AddrRangeSeq{}, nil + return nil, nil } var dst []hostarch.AddrRange @@ -186,42 +222,20 @@ func (t *Task) CopyInIovecs(addr hostarch.Addr, numIovecs int) (hostarch.AddrRan dst = make([]hostarch.AddrRange, 0, numIovecs) } - switch t.Arch().Width() { - case 8: - const itemLen = 16 - if _, ok := addr.AddLength(uint64(numIovecs) * itemLen); !ok { - return hostarch.AddrRangeSeq{}, linuxerr.EFAULT - } - - b := t.CopyScratchBuffer(itemLen) - for i := 0; i < numIovecs; i++ { - if _, err := t.CopyInBytes(addr, b); err != nil { - return hostarch.AddrRangeSeq{}, err - } - - base := hostarch.Addr(hostarch.ByteOrder.Uint64(b[0:8])) - length := hostarch.ByteOrder.Uint64(b[8:16]) - if length > math.MaxInt64 { - return hostarch.AddrRangeSeq{}, linuxerr.EINVAL - } - ar, ok := t.MemoryManager().CheckIORange(base, int64(length)) - if !ok { - return hostarch.AddrRangeSeq{}, linuxerr.EFAULT - } - - if numIovecs == 1 { - // Special case to avoid allocating dst. - return hostarch.AddrRangeSeqOf(ar).TakeFirst(MAX_RW_COUNT), nil - } - dst = append(dst, ar) - - addr += itemLen - } - - default: - return hostarch.AddrRangeSeq{}, linuxerr.ENOSYS + if _, ok := addr.AddLength(uint64(numIovecs) * iovecLength); !ok { + return nil, linuxerr.EFAULT } + b := ctx.CopyScratchBuffer(iovecLength) + for i := 0; i < numIovecs; i++ { + ar, err := makeIovec(ctx, t, addr, b) + if err != nil { + return []hostarch.AddrRange{}, err + } + dst = append(dst, ar) + + addr += iovecLength + } // Truncate to MAX_RW_COUNT. var total uint64 for i := range dst { @@ -233,7 +247,31 @@ func (t *Task) CopyInIovecs(addr hostarch.Addr, numIovecs int) (hostarch.AddrRan total += dstlen } - return hostarch.AddrRangeSeqFromSlice(dst), nil + return dst, nil +} + +func checkArch(t *Task) error { + if t.Arch().Width() != 8 { + return linuxerr.ENOSYS + } + return nil +} + +func makeIovec(ctx marshal.CopyContext, t *Task, addr hostarch.Addr, b []byte) (hostarch.AddrRange, error) { + if _, err := ctx.CopyInBytes(addr, b); err != nil { + return hostarch.AddrRange{}, err + } + + base := hostarch.Addr(hostarch.ByteOrder.Uint64(b[0:8])) + length := hostarch.ByteOrder.Uint64(b[8:16]) + if length > math.MaxInt64 { + return hostarch.AddrRange{}, linuxerr.EINVAL + } + ar, ok := t.MemoryManager().CheckIORange(base, int64(length)) + if !ok { + return hostarch.AddrRange{}, linuxerr.EFAULT + } + return ar, nil } // SingleIOSequence returns a usermem.IOSequence representing [addr, @@ -284,9 +322,10 @@ func (t *Task) IovecsIOSequence(addr hostarch.Addr, iovcnt int, opts usermem.IOO } type taskCopyContext struct { - ctx context.Context - t *Task - opts usermem.IOOpts + ctx context.Context + t *Task + opts usermem.IOOpts + allocateNewBuffers bool } // CopyContext returns a marshal.CopyContext that copies to/from t's address @@ -301,7 +340,7 @@ func (t *Task) CopyContext(ctx context.Context, opts usermem.IOOpts) *taskCopyCo // CopyScratchBuffer implements marshal.CopyContext.CopyScratchBuffer. func (cc *taskCopyContext) CopyScratchBuffer(size int) []byte { - if ctxTask, ok := cc.ctx.(*Task); ok { + if ctxTask, ok := cc.ctx.(*Task); ok && !cc.allocateNewBuffers { return ctxTask.CopyScratchBuffer(size) } return make([]byte, size) @@ -337,6 +376,17 @@ func (cc *taskCopyContext) CopyOutBytes(addr hostarch.Addr, src []byte) (int, er return tmm.CopyOut(cc.ctx, addr, src, cc.opts) } +// CopyOutIovecs converts src to an array of struct iovecs and copies it to the +// memory mapped at addr for Task. +func (cc *taskCopyContext) CopyOutIovecs(addr hostarch.Addr, src hostarch.AddrRangeSeq) error { + return copyOutIovecs(cc, cc.t, addr, src) +} + +// CopyInIovecs copies in IoVecs for taskCopyContext. +func (cc *taskCopyContext) CopyInIovecs(addr hostarch.Addr, numIovecs int) ([]hostarch.AddrRange, error) { + return copyInIovecs(cc, cc.t, addr, numIovecs) +} + type ownTaskCopyContext struct { t *Task opts usermem.IOOpts diff --git a/pkg/sentry/syscalls/linux/vfs2/mmap.go b/pkg/sentry/syscalls/linux/vfs2/mmap.go index c804f9fd3..4f002f143 100644 --- a/pkg/sentry/syscalls/linux/vfs2/mmap.go +++ b/pkg/sentry/syscalls/linux/vfs2/mmap.go @@ -22,6 +22,7 @@ import ( "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). @@ -102,3 +103,122 @@ func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC 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*/) +} + +// ProcessVMWritev implements process_vm_writev(2). +func ProcessVMWritev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + return processVMRW(t, args, true /*isWrite*/) +} + +func processVMRW(t *kernel.Task, args arch.SyscallArguments, isWrite bool) (uintptr, *kernel.SyscallControl, error) { + pid := kernel.ThreadID(args[0].Int()) + lvec := hostarch.Addr(args[1].Pointer()) + liovcnt := int(args[2].Int64()) + rvec := hostarch.Addr(args[3].Pointer()) + riovcnt := int(args[4].Int64()) + flags := args[5].Int() + + switch { + case flags != 0: + return 0, nil, linuxerr.EINVAL + case liovcnt < 0 || liovcnt > linux.UIO_MAXIOV: + return 0, nil, linuxerr.EINVAL + case riovcnt < 0 || riovcnt > linux.UIO_MAXIOV: + return 0, nil, linuxerr.EFAULT + case lvec == 0 || rvec == 0: + return 0, nil, linuxerr.EFAULT + case riovcnt > linux.UIO_MAXIOV || liovcnt > linux.UIO_MAXIOV: + return 0, nil, linuxerr.EINVAL + case liovcnt == 0 || riovcnt == 0: + return 0, nil, nil + } + + localProcess := t.ThreadGroup().Leader() + if localProcess == nil { + return 0, nil, linuxerr.ESRCH + } + remoteThreadGroup := localProcess.PIDNamespace().ThreadGroupWithID(pid) + if remoteThreadGroup == nil { + return 0, nil, linuxerr.ESRCH + } + remoteProcess := remoteThreadGroup.Leader() + + // For the write case, we read from the local process and write to the remote process. + if isWrite { + return doProcessVMReadWrite(localProcess, remoteProcess, lvec, rvec, liovcnt, riovcnt) + } + // For the read case, we read from the remote process and write to the local process. + return doProcessVMReadWrite(remoteProcess, localProcess, rvec, lvec, riovcnt, liovcnt) +} + +func doProcessVMReadWrite(rProcess, wProcess *kernel.Task, rAddr, wAddr hostarch.Addr, rIovecCount, wIovecCount int) (uintptr, *kernel.SyscallControl, error) { + rCtx := rProcess.CopyContext(rProcess, usermem.IOOpts{}) + wCtx := wProcess.CopyContext(wProcess, usermem.IOOpts{}) + + rIovecs, err := rCtx.CopyInIovecs(rAddr, rIovecCount) + if err != nil { + return 0, nil, err + } + wIovecs, err := wCtx.CopyInIovecs(wAddr, wIovecCount) + if err != nil { + return 0, nil, err + } + + bufSize := 0 + for _, rIovec := range rIovecs { + if int(rIovec.Length()) > bufSize { + bufSize = int(rIovec.Length()) + } + } + + buf := rCtx.CopyScratchBuffer(bufSize) + wCount := 0 + for _, rIovec := range rIovecs { + if len(wIovecs) <= 0 { + break + } + + buf = buf[0:int(rIovec.Length())] + bytes, err := rCtx.CopyInBytes(rIovec.Start, buf) + if linuxerr.Equals(linuxerr.EFAULT, err) { + return uintptr(wCount), nil, nil + } + if err != nil { + return uintptr(wCount), nil, err + } + if bytes != int(rIovec.Length()) { + return uintptr(wCount), nil, nil + } + start := 0 + for bytes > start && 0 < len(wIovecs) { + writeLength := int(wIovecs[0].Length()) + if writeLength > (bytes - start) { + writeLength = bytes - start + } + out, err := wCtx.CopyOutBytes(wIovecs[0].Start, buf[start:writeLength+start]) + wCount += out + start += out + if linuxerr.Equals(linuxerr.EFAULT, err) { + return uintptr(wCount), nil, nil + } + if err != nil { + return uintptr(wCount), nil, err + } + if out != writeLength { + return uintptr(wCount), nil, nil + } + wIovecs[0].Start += hostarch.Addr(out) + if !wIovecs[0].WellFormed() { + return uintptr(wCount), nil, err + } + if wIovecs[0].Length() == 0 { + wIovecs = wIovecs[1:] + } + } + } + return uintptr(wCount), nil, nil +} diff --git a/pkg/sentry/syscalls/linux/vfs2/vfs2.go b/pkg/sentry/syscalls/linux/vfs2/vfs2.go index 115bed9bf..d85118562 100644 --- a/pkg/sentry/syscalls/linux/vfs2/vfs2.go +++ b/pkg/sentry/syscalls/linux/vfs2/vfs2.go @@ -156,6 +156,8 @@ func Override() { s.Table[299] = syscalls.Supported("recvmmsg", RecvMMsg) s.Table[306] = syscalls.Supported("syncfs", Syncfs) s.Table[307] = syscalls.Supported("sendmmsg", SendMMsg) + s.Table[310] = syscalls.Supported("process_vm_readv", ProcessVMReadv) + s.Table[311] = syscalls.Supported("process_vm_writev", ProcessVMWritev) s.Table[316] = syscalls.Supported("renameat2", Renameat2) s.Table[319] = syscalls.Supported("memfd_create", MemfdCreate) s.Table[322] = syscalls.SupportedPoint("execveat", Execveat, linux.PointExecveat) diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index b9fcf6f9d..3db2a8519 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -1063,3 +1063,8 @@ syscall_test( size = "small", test = "//test/syscalls/linux:close_range_test", ) + +syscall_test( + size = "small", + test = "//test/syscalls/linux:process_vm_read_write", +) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index f496864d5..95129b647 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -4474,3 +4474,20 @@ cc_binary( "//test/util:thread_util", ], ) + +cc_binary( + name = "process_vm_read_write", + testonly = 1, + srcs = ["process_vm_read_write.cc"], + linkstatic = 1, + deps = [ + gtest, + "//test/util:capability_util", + "//test/util:logging", + "//test/util:multiprocess_util", + "//test/util:posix_error", + "//test/util:test_main", + "//test/util:test_util", + "@com_google_absl//absl/strings", + ], +) diff --git a/test/syscalls/linux/process_vm_read_write.cc b/test/syscalls/linux/process_vm_read_write.cc new file mode 100644 index 000000000..dd6da4fca --- /dev/null +++ b/test/syscalls/linux/process_vm_read_write.cc @@ -0,0 +1,383 @@ +// Copyright 2022 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. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "test/util/linux_capability_util.h" +#include "test/util/logging.h" +#include "test/util/multiprocess_util.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +class TestIovecs { + public: + TestIovecs(std::vector& data) { + data_ = std::vector(data.size()); + initial_ = std::vector(data.size()); + for (int i = 0; i < data.size(); ++i) { + data_[i] = data[i]; + initial_[i] = data[i]; + struct iovec iov; + iov.iov_len = data_[i].size(); + iov.iov_base = data_[i].data(); + iovecs_.push_back(iov); + bytes_ += data[i].size(); + } + } + + bool compare(std::vector other) { + auto want = absl::StrJoin(other, ""); + auto got = absl::StrJoin(data_, ""); + // If the other buffer is smaller than this, make sure the remaining bytes + // haven't been overwritten. + if (want.size() < got.size()) { + auto initial = absl::StrJoin(initial_, ""); + want = absl::StrCat(want, initial.substr(want.size())); + } + // If the other buffer is smaller, truncate it so we can compare the two. + if (want.size() > got.size()) { + want = want.substr(0, got.size()); + } + if (want != got) { + std::cerr << "Mismatch buffers:\n want: " << want << "\n got: " << got + << std::endl; + return false; + } + + return true; + } + + std::vector marshal() { + std::vector ret(iovecs_.size()); + for (int i = 0; i < iovecs_.size(); ++i) { + ret[i] = &iovecs_[i]; + } + return ret; + } + + ssize_t total_bytes() { return bytes_; } + + private: + ssize_t bytes_ = 0; + std::vector data_; + std::vector initial_; + std::vector iovecs_; +}; + +struct ProcessVMTestCase { + std::string test_name; + std::vector local_data; + std::vector remote_data; +}; + +using ProcessVMTest = ::testing::TestWithParam; + +bool ProcessVMCallsNotSupported() { + struct iovec iov; + // Flags should be 0. + ssize_t ret = process_vm_readv(0, &iov, 1, &iov, 1, 10); + if (ret != 0 && errno == ENOSYS) return true; + + ret = process_vm_writev(0, &iov, 1, &iov, 1, 10); + return ret != 0 && errno == ENOSYS; +} + +// TestReadvSameProcess calls process_vm_readv in the same process with +// various local/remote buffers. +TEST_P(ProcessVMTest, TestReadvSameProcess) { + SKIP_IF(ProcessVMCallsNotSupported()); + auto local_data = GetParam().local_data; + auto remote_data = GetParam().remote_data; + TestIovecs local_iovecs(local_data); + TestIovecs remote_iovecs(remote_data); + + auto local = local_iovecs.marshal(); + auto remote = remote_iovecs.marshal(); + auto expected_bytes = + std::min(remote_iovecs.total_bytes(), local_iovecs.total_bytes()); + EXPECT_THAT(process_vm_readv(getpid(), *(local.data()), local.size(), + *(remote.data()), remote.size(), 0), + SyscallSucceedsWithValue(expected_bytes)); + EXPECT_TRUE(local_iovecs.compare(remote_data)); +} + +// TestReadvSubProcess repeats the previous test in a forked process. +TEST_P(ProcessVMTest, TestReadvSubProcess) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE((HaveCapability(CAP_SYS_PTRACE)))); + SKIP_IF(ProcessVMCallsNotSupported()); + auto local_data = GetParam().local_data; + auto remote_data = GetParam().remote_data; + + TestIovecs remote_iovecs(remote_data); + auto remote = remote_iovecs.marshal(); + auto remote_ptr = remote[0]; + auto remote_size = remote.size(); + auto remote_total_bytes = remote_iovecs.total_bytes(); + + const std::function fn = [local_data, remote_data, remote_ptr, + remote_size, remote_total_bytes] { + std::vector local_fn_data = local_data; + TestIovecs local_iovecs(local_fn_data); + auto local = local_iovecs.marshal(); + int ret = process_vm_readv(getppid(), local[0], local.size(), remote_ptr, + remote_size, 0); + auto expected_bytes = + std::min(remote_total_bytes, local_iovecs.total_bytes()); + TEST_CHECK_MSG( + ret == expected_bytes, + absl::StrCat("want: ", expected_bytes, " got: ", ret).c_str()); + TEST_CHECK(local_iovecs.compare(remote_data)); + }; + EXPECT_THAT(InForkedProcess(fn), IsPosixErrorOkAndHolds(0)); +} + +// TestWritevSameProcess calls process_vm_writev in the same process with +// various local/remote buffers. +TEST_P(ProcessVMTest, TestWritevSameProcess) { + SKIP_IF(ProcessVMCallsNotSupported()); + auto local_data = GetParam().local_data; + auto remote_data = GetParam().remote_data; + + TestIovecs local_iovecs(local_data); + TestIovecs remote_iovecs(remote_data); + + auto local = local_iovecs.marshal(); + auto remote = remote_iovecs.marshal(); + auto expected_bytes = + std::min(remote_iovecs.total_bytes(), local_iovecs.total_bytes()); + EXPECT_THAT(process_vm_writev(getpid(), remote[0], remote.size(), local[0], + local.size(), 0), + SyscallSucceedsWithValue(expected_bytes)); + EXPECT_TRUE(local_iovecs.compare(remote_data)); +} + +// TestWritevSubProcess repeats the previous test in a forked process. +TEST_P(ProcessVMTest, TestWritevSubProcess) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE((HaveCapability(CAP_SYS_PTRACE)))); + SKIP_IF(ProcessVMCallsNotSupported()); + auto local_data = GetParam().local_data; + auto remote_data = GetParam().remote_data; + TestIovecs remote_iovecs(remote_data); + auto remote = remote_iovecs.marshal(); + auto remote_ptr = remote[0]; + auto remote_size = remote.size(); + auto remote_total_bytes = remote_iovecs.total_bytes(); + + const std::function fn = [local_data, remote_ptr, remote_size, + remote_total_bytes] { + std::vector local_fn_data = local_data; + TestIovecs local_iovecs(local_fn_data); + auto local = local_iovecs.marshal(); + int ret = process_vm_writev(getppid(), local[0], local.size(), remote_ptr, + remote_size, 0); + auto expected_bytes = + std::min(remote_total_bytes, local_iovecs.total_bytes()); + TEST_CHECK_MSG( + ret == expected_bytes, + absl::StrCat("want: ", expected_bytes, " got: ", ret).c_str()); + }; + + EXPECT_THAT(InForkedProcess(fn), IsPosixErrorOkAndHolds(0)); + EXPECT_TRUE(remote_iovecs.compare(local_data)); +} + +INSTANTIATE_TEST_SUITE_P( + ProcessVMTests, ProcessVMTest, + ::testing::ValuesIn( + {{"BothEmpty" /*test name*/, + {""} /*local buffer*/, + {""} /*remote buffer*/}, + {"EmptyLocal", {""}, {"All too easy."}}, + {"EmptyRemote", {"Impressive. Most impressive."}, {""}}, + {"SingleChar", {"l"}, {"r"}}, + {"LargerRemoteBuffer", + {"OK, I'll try"}, + {"No!", "Try not", "Do...or do not", "There is no try."}}, + {"LargerLocalBuffer", + {"Look!", "The cave is collapsing!"}, + {"This is no cave."}}, + {"BothWithMultipleIovecs", + {"Obi-wan never told you what happened to your father.", + "He told me enough...he told me you killed him."}, + {"No...I am your father.", "No. No.", "That's not true.", + "That's impossible!"}}}), + [](const ::testing::TestParamInfo& info) { + return info.param.test_name; + }); + +TEST(ProcessVMInvalidTest, NonZeroFlags) { + SKIP_IF(ProcessVMCallsNotSupported()); + struct iovec iov; + // Flags should be 0. + EXPECT_THAT(process_vm_readv(0, &iov, 1, &iov, 1, 10), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(process_vm_writev(0, &iov, 1, &iov, 1, 10), + SyscallFailsWithErrno(EINVAL)); +} + +TEST(ProcessVMInvalidTest, NullLocalIovec) { + SKIP_IF(ProcessVMCallsNotSupported()); + struct iovec iov; + pid_t child = fork(); + if (child == 0) { + while (true) { + sleep(1); + } + } + + EXPECT_THAT(process_vm_readv(child, nullptr, 1, &iov, 1, 0), + SyscallFailsWithErrno(EFAULT)); + + EXPECT_THAT(process_vm_writev(child, nullptr, 1, &iov, 1, 0), + SyscallFailsWithErrno(EFAULT)); + EXPECT_THAT(kill(child, SIGKILL), SyscallSucceeds()); + EXPECT_THAT(waitpid(child, 0, 0), SyscallSucceeds()); +} + +TEST(ProcessVMInvalidTest, NULLRemoteIovec) { + SKIP_IF(ProcessVMCallsNotSupported()); + const std::function fn = [] { + std::string contents = "3263827"; + struct iovec child_iov; + child_iov.iov_base = contents.data(); + child_iov.iov_len = contents.size(); + + pid_t parent = getppid(); + int ret = + process_vm_readv(parent, &child_iov, contents.length(), nullptr, 1, 0); + TEST_CHECK(errno == EFAULT || errno == EINVAL); + + ret = + process_vm_writev(parent, &child_iov, contents.length(), nullptr, 1, 0); + TEST_CHECK(errno == EFAULT || errno == EINVAL); + }; + ASSERT_THAT(InForkedProcess(fn), IsPosixErrorOkAndHolds(0)); +} + +TEST(ProcessVMInvalidTest, ProcessNoExist) { + SKIP_IF(ProcessVMCallsNotSupported()); + struct iovec iov; + EXPECT_THAT(process_vm_readv(-1, &iov, 1, &iov, 1, 0), + SyscallFailsWithErrno(::testing::AnyOf(ESRCH, EFAULT))); + EXPECT_THAT(process_vm_writev(-1, &iov, 1, &iov, 1, 0), + SyscallFailsWithErrno(::testing::AnyOf(ESRCH, EFAULT))); +} + +TEST(ProcessVMInvalidTest, InvalidLength) { + SKIP_IF(ProcessVMCallsNotSupported()); + std::string contents = "3263827"; + struct iovec iov; + iov.iov_base = contents.data(); + auto iov_addr = &iov; + const std::function fn = [=] { + struct iovec child_iov; + std::string contents = "3263827"; + child_iov.iov_base = contents.data(); + child_iov.iov_len = contents.size(); + + pid_t parent = getppid(); + TEST_CHECK_ERRNO(process_vm_readv(parent, &child_iov, contents.length(), + iov_addr, -1, 0), + EFAULT); + TEST_CHECK_ERRNO(process_vm_writev(parent, &child_iov, contents.length(), + iov_addr, -1, 0), + EFAULT); + + TEST_CHECK_ERRNO(process_vm_readv(parent, &child_iov, -1, iov_addr, + contents.length(), 0), + EINVAL); + + TEST_CHECK_ERRNO(process_vm_writev(parent, &child_iov, -1, iov_addr, + contents.length(), 0), + EINVAL); + TEST_CHECK_ERRNO(process_vm_readv(parent, &child_iov, contents.length(), + iov_addr, IOV_MAX + 1, 0), + EFAULT); + + TEST_CHECK_ERRNO(process_vm_writev(parent, &child_iov, contents.length(), + iov_addr, IOV_MAX + 1, 0), + EFAULT); + + TEST_CHECK_ERRNO(process_vm_readv(parent, &child_iov, IOV_MAX + 2, iov_addr, + contents.length(), 0), + EINVAL); + + TEST_CHECK_ERRNO(process_vm_writev(parent, &child_iov, IOV_MAX + 8, + iov_addr, contents.length(), 0), + EINVAL); + }; + EXPECT_THAT(InForkedProcess(fn), IsPosixErrorOkAndHolds(0)); +} + +TEST(ProcessVMInvalidTest, PartialReadWrite) { + SKIP_IF(ProcessVMCallsNotSupported()); + std::string iov_content_1 = "1138"; + std::string iov_content_2 = "3720"; + struct iovec iov[2]; + iov[0].iov_base = iov_content_1.data(); + iov[0].iov_len = iov_content_1.size(); + iov[1].iov_base = iov_content_2.data(); + iov[1].iov_len = iov_content_2.size(); + + std::string iov_corrupted_content_1 = iov_content_1; + struct iovec corrupted_iov[2]; + corrupted_iov[0].iov_base = iov_corrupted_content_1.data(); + corrupted_iov[0].iov_len = iov_corrupted_content_1.size(); + corrupted_iov[1].iov_base = (void*)0xDEADBEEF; + corrupted_iov[1].iov_len = 42; + + EXPECT_THAT( + RetryEINTR(process_vm_writev)(getpid(), iov, 2, corrupted_iov, 2, 0), + SyscallSucceedsWithValue(iov_content_1.size())); + EXPECT_THAT( + RetryEINTR(process_vm_readv)(getpid(), corrupted_iov, 2, iov, 2, 0), + SyscallSucceedsWithValue(iov_content_1.size())); + EXPECT_THAT( + RetryEINTR(process_vm_writev)(getpid(), corrupted_iov, 2, iov, 2, 0), + SyscallSucceedsWithValue(iov_content_1.size())); + EXPECT_THAT( + RetryEINTR(process_vm_readv)(getpid(), iov, 2, corrupted_iov, 2, 0), + SyscallSucceedsWithValue(iov_content_1.size())); +} + +} // namespace + +} // namespace testing +} // namespace gvisor