Support process_vm_read for same user only.

PiperOrigin-RevId: 466123035
This commit is contained in:
Zach Koopmans
2022-08-08 12:56:50 -07:00
committed by gVisor bot
parent e003828a6b
commit a963196f43
6 changed files with 636 additions and 59 deletions
+109 -59
View File
@@ -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
+120
View File
@@ -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
}
+2
View File
@@ -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)
+5
View File
@@ -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",
)
+17
View File
@@ -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",
],
)
@@ -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 <asm-generic/errno-base.h>
#include <bits/types/struct_iovec.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <climits>
#include <csignal>
#include <cstddef>
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#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<std::string>& data) {
data_ = std::vector<std::string>(data.size());
initial_ = std::vector<std::string>(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<std::string> 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<struct iovec*> marshal() {
std::vector<struct iovec*> 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<std::string> data_;
std::vector<std::string> initial_;
std::vector<struct iovec> iovecs_;
};
struct ProcessVMTestCase {
std::string test_name;
std::vector<std::string> local_data;
std::vector<std::string> remote_data;
};
using ProcessVMTest = ::testing::TestWithParam<ProcessVMTestCase>;
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<void()> fn = [local_data, remote_data, remote_ptr,
remote_size, remote_total_bytes] {
std::vector<std::string> 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<void()> fn = [local_data, remote_ptr, remote_size,
remote_total_bytes] {
std::vector<std::string> 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<ProcessVMTestCase>(
{{"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<ProcessVMTest::ParamType>& 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<void()> 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<void()> 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