diff options
author | Nicolas Lacasse <nlacasse@google.com> | 2018-06-27 14:30:45 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-06-27 14:31:35 -0700 |
commit | 99afc982f1f0e40059e1446ea6f3cb725b1fbde7 (patch) | |
tree | eb34c666d0c1a736af382095379304c15c604680 | |
parent | 4215e059e24c5ed6298060769444b0eeaa03da8a (diff) |
Call mm.CheckIORange() when copying in IOVecs.
CheckIORange is analagous to Linux's access_ok() method, which is checked when
copying in IOVecs in both lib/iov_iter.c:import_single_range() and
lib/iov_iter.c:import_iovec() => fs/read_write.c:rw_copy_check_uvector().
gVisor copies in IOVecs via Task.SingleIOSequence() and Task.CopyInIovecs().
We were checking the address range bounds, but not whether the address is
valid. To conform with linux, we should also check that the address is valid.
For usual preadv/pwritev syscalls, the effect of this change is not noticeable,
since we find out that the address is invalid before the syscall completes.
For vectorized async-IO operations, however, this change is necessary because
Linux returns EFAULT when the operation is submitted, but before it executes.
Thus, we must validate the iovecs when copying them in.
PiperOrigin-RevId: 202370092
Change-Id: I8759a63ccf7e6b90d90d30f78ab8935a0fcf4936
-rw-r--r-- | pkg/sentry/kernel/task_usermem.go | 17 | ||||
-rw-r--r-- | pkg/sentry/mm/io.go | 18 |
2 files changed, 20 insertions, 15 deletions
diff --git a/pkg/sentry/kernel/task_usermem.go b/pkg/sentry/kernel/task_usermem.go index 54964dd0d..2b4954869 100644 --- a/pkg/sentry/kernel/task_usermem.go +++ b/pkg/sentry/kernel/task_usermem.go @@ -184,6 +184,9 @@ func (t *Task) CopyOutIovecs(addr usermem.Addr, src usermem.AddrRangeSeq) error // - 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. +// // - 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. @@ -218,7 +221,7 @@ func (t *Task) CopyInIovecs(addr usermem.Addr, numIovecs int) (usermem.AddrRange if length > math.MaxInt64 { return usermem.AddrRangeSeq{}, syserror.EINVAL } - ar, ok := base.ToRange(length) + ar, ok := t.MemoryManager().CheckIORange(base, int64(length)) if !ok { return usermem.AddrRangeSeq{}, syserror.EFAULT } @@ -251,18 +254,20 @@ func (t *Task) CopyInIovecs(addr usermem.Addr, numIovecs int) (usermem.AddrRange } // SingleIOSequence returns a usermem.IOSequence representing [addr, -// addr+length) in t's address space. If length exceeds _MAX_RW_COUNT, it is -// silently truncated. +// addr+length) in t's address space. If this contains addresses outside the +// application address range, it returns EFAULT. If length exceeds +// _MAX_RW_COUNT, the range is silently truncated. // // SingleIOSequence is analogous to Linux's // lib/iov_iter.c:import_single_range(). (Note that the non-vectorized read and -// write syscalls in Linux do not use import_single_range(), but are still -// truncated to _MAX_RW_COUNT by fs/read_write.c:rw_verify_area().) +// write syscalls in Linux do not use import_single_range(). However they check +// access_ok() in fs/read_write.c:vfs_read/vfs_write, and overflowing address +// ranges are truncated to _MAX_RW_COUNT by fs/read_write.c:rw_verify_area().) func (t *Task) SingleIOSequence(addr usermem.Addr, length int, opts usermem.IOOpts) (usermem.IOSequence, error) { if length > _MAX_RW_COUNT { length = _MAX_RW_COUNT } - ar, ok := addr.ToRange(uint64(length)) + ar, ok := t.MemoryManager().CheckIORange(addr, int64(length)) if !ok { return usermem.IOSequence{}, syserror.EFAULT } diff --git a/pkg/sentry/mm/io.go b/pkg/sentry/mm/io.go index cac81a59d..6741db594 100644 --- a/pkg/sentry/mm/io.go +++ b/pkg/sentry/mm/io.go @@ -60,11 +60,11 @@ const ( rwMapMinBytes = 512 ) -// checkIORange is similar to usermem.Addr.ToRange, but applies bounds checks +// CheckIORange is similar to usermem.Addr.ToRange, but applies bounds checks // consistent with Linux's arch/x86/include/asm/uaccess.h:access_ok(). // // Preconditions: length >= 0. -func (mm *MemoryManager) checkIORange(addr usermem.Addr, length int64) (usermem.AddrRange, bool) { +func (mm *MemoryManager) CheckIORange(addr usermem.Addr, length int64) (usermem.AddrRange, bool) { // Note that access_ok() constrains end even if length == 0. ar, ok := addr.ToRange(uint64(length)) return ar, (ok && ar.End <= mm.layout.MaxAddr) @@ -75,7 +75,7 @@ func (mm *MemoryManager) checkIORange(addr usermem.Addr, length int64) (usermem. func (mm *MemoryManager) checkIOVec(ars usermem.AddrRangeSeq) bool { for !ars.IsEmpty() { ar := ars.Head() - if _, ok := mm.checkIORange(ar.Start, int64(ar.Length())); !ok { + if _, ok := mm.CheckIORange(ar.Start, int64(ar.Length())); !ok { return false } ars = ars.Tail() @@ -101,7 +101,7 @@ func translateIOError(ctx context.Context, err error) error { // CopyOut implements usermem.IO.CopyOut. func (mm *MemoryManager) CopyOut(ctx context.Context, addr usermem.Addr, src []byte, opts usermem.IOOpts) (int, error) { - ar, ok := mm.checkIORange(addr, int64(len(src))) + ar, ok := mm.CheckIORange(addr, int64(len(src))) if !ok { return 0, syserror.EFAULT } @@ -144,7 +144,7 @@ func (mm *MemoryManager) asCopyOut(ctx context.Context, addr usermem.Addr, src [ // CopyIn implements usermem.IO.CopyIn. func (mm *MemoryManager) CopyIn(ctx context.Context, addr usermem.Addr, dst []byte, opts usermem.IOOpts) (int, error) { - ar, ok := mm.checkIORange(addr, int64(len(dst))) + ar, ok := mm.CheckIORange(addr, int64(len(dst))) if !ok { return 0, syserror.EFAULT } @@ -187,7 +187,7 @@ func (mm *MemoryManager) asCopyIn(ctx context.Context, addr usermem.Addr, dst [] // ZeroOut implements usermem.IO.ZeroOut. func (mm *MemoryManager) ZeroOut(ctx context.Context, addr usermem.Addr, toZero int64, opts usermem.IOOpts) (int64, error) { - ar, ok := mm.checkIORange(addr, toZero) + ar, ok := mm.CheckIORange(addr, toZero) if !ok { return 0, syserror.EFAULT } @@ -311,7 +311,7 @@ func (mm *MemoryManager) CopyInTo(ctx context.Context, ars usermem.AddrRangeSeq, // SwapUint32 implements usermem.IO.SwapUint32. func (mm *MemoryManager) SwapUint32(ctx context.Context, addr usermem.Addr, new uint32, opts usermem.IOOpts) (uint32, error) { - ar, ok := mm.checkIORange(addr, 4) + ar, ok := mm.CheckIORange(addr, 4) if !ok { return 0, syserror.EFAULT } @@ -353,7 +353,7 @@ func (mm *MemoryManager) SwapUint32(ctx context.Context, addr usermem.Addr, new // CompareAndSwapUint32 implements usermem.IO.CompareAndSwapUint32. func (mm *MemoryManager) CompareAndSwapUint32(ctx context.Context, addr usermem.Addr, old, new uint32, opts usermem.IOOpts) (uint32, error) { - ar, ok := mm.checkIORange(addr, 4) + ar, ok := mm.CheckIORange(addr, 4) if !ok { return 0, syserror.EFAULT } @@ -399,7 +399,7 @@ func (mm *MemoryManager) CompareAndSwapUint32(ctx context.Context, addr usermem. // Preconditions: mm.as != nil. ioar.Length() != 0. ioar.Contains(addr). func (mm *MemoryManager) handleASIOFault(ctx context.Context, addr usermem.Addr, ioar usermem.AddrRange, at usermem.AccessType) error { // Try to map all remaining pages in the I/O operation. This RoundUp can't - // overflow because otherwise it would have been caught by checkIORange. + // overflow because otherwise it would have been caught by CheckIORange. end, _ := ioar.End.RoundUp() ar := usermem.AddrRange{addr.RoundDown(), end} |