diff --git a/pkg/sentry/syscalls/linux/sys_process_vm.go b/pkg/sentry/syscalls/linux/sys_process_vm.go index 92242fe9d..f92c215d1 100644 --- a/pkg/sentry/syscalls/linux/sys_process_vm.go +++ b/pkg/sentry/syscalls/linux/sys_process_vm.go @@ -141,6 +141,10 @@ type processVMOpArgs struct { writeIovecCount int } +// maxScratchBufferSize is the maximum size of a scratch buffer. It should be +// sufficiently large to minimizing the number of trips through MM. +const maxScratchBufferSize = 1 << 20 + func doProcessVMOpMaybeLocked(t *kernel.Task, args processVMOpArgs) (int, error) { // Copy IOVecs in to kernel. readIovecs, err := t.CopyInIovecsAsSlice(args.readAddr, args.readIovecCount) @@ -160,6 +164,9 @@ func doProcessVMOpMaybeLocked(t *kernel.Task, args processVMOpArgs) (int, error) bufSize = int(readIovec.Length()) } } + if bufSize > maxScratchBufferSize { + bufSize = maxScratchBufferSize + } buf := t.CopyScratchBuffer(bufSize) // Number of bytes written. @@ -169,42 +176,52 @@ func doProcessVMOpMaybeLocked(t *kernel.Task, args processVMOpArgs) (int, error) break } - buf = buf[0:int(readIovec.Length())] - bytes, err := args.readCtx.CopyInBytes(readIovec.Start, buf) - if linuxerr.Equals(linuxerr.EFAULT, err) { - return n, nil - } - if err != nil { - return n, err - } - if bytes != int(readIovec.Length()) { - return n, nil - } - - start := 0 - for bytes > start && 0 < len(writeIovecs) { - writeLength := int(writeIovecs[0].Length()) - if writeLength > (bytes - start) { - writeLength = bytes - start + for readIovec.Length() != 0 { + length := readIovec.Length() + if length > maxScratchBufferSize { + length = maxScratchBufferSize } - out, err := args.writeCtx.CopyOutBytes(writeIovecs[0].Start, buf[start:writeLength+start]) - n += out - start += out + buf = buf[0:int(length)] + bytes, err := args.readCtx.CopyInBytes(readIovec.Start, buf) if linuxerr.Equals(linuxerr.EFAULT, err) { return n, nil } - if err != nil { + if bytes == 0 && err != nil { return n, err } - if out != writeLength { - return n, nil - } - writeIovecs[0].Start += hostarch.Addr(out) - if !writeIovecs[0].WellFormed() { - return n, err - } - if writeIovecs[0].Length() == 0 { - writeIovecs = writeIovecs[1:] + readIovec.Start += hostarch.Addr(bytes) + + start := 0 + for bytes > start && 0 < len(writeIovecs) { + if t.Interrupted() { + if n == 0 { + return 0, linuxerr.EINTR + } + return n, nil + } + writeLength := int(writeIovecs[0].Length()) + if writeLength > (bytes - start) { + writeLength = bytes - start + } + out, err := args.writeCtx.CopyOutBytes(writeIovecs[0].Start, buf[start:writeLength+start]) + n += out + start += out + if linuxerr.Equals(linuxerr.EFAULT, err) { + return n, nil + } + if err != nil { + return n, err + } + if out != writeLength { + return n, nil + } + writeIovecs[0].Start += hostarch.Addr(out) + if !writeIovecs[0].WellFormed() { + return n, err + } + if writeIovecs[0].Length() == 0 { + writeIovecs = writeIovecs[1:] + } } } } diff --git a/test/syscalls/linux/process_vm_read_write.cc b/test/syscalls/linux/process_vm_read_write.cc index d3e9b6346..01ca0c9ff 100644 --- a/test/syscalls/linux/process_vm_read_write.cc +++ b/test/syscalls/linux/process_vm_read_write.cc @@ -113,6 +113,18 @@ struct ProcessVMTestCase { using ProcessVMTest = ::testing::TestWithParam; +std::string getTestBuffer(std::string pattern, size_t size) { + std::string s; + + auto pattern_length = pattern.length(); + s.reserve(size); + while (s.length() + pattern_length < size) { + s += pattern; + } + s += pattern.substr(0, size - s.length()); + return s; +} + INSTANTIATE_TEST_SUITE_P( ProcessVMTests, ProcessVMTest, ::testing::ValuesIn( @@ -132,7 +144,20 @@ INSTANTIATE_TEST_SUITE_P( {"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!"}}}), + "That's impossible!"}}, + { + "LargeBuffer", + { + getTestBuffer( + "Train yourself to let go of everything you fear to lose.", + 32 << 20), + "Hello there!", + }, + { + "Do. Or do not. There is no try.", + getTestBuffer("The greatest teacher, failure is.", 32 << 20), + }, + }}), [](const ::testing::TestParamInfo& info) { return info.param.test_name; });