mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support process_vm_read for same user only.
PiperOrigin-RevId: 466123035
This commit is contained in:
committed by
gVisor bot
parent
e003828a6b
commit
a963196f43
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user