diff options
author | Googler <noreply@google.com> | 2018-04-27 10:37:02 -0700 |
---|---|---|
committer | Adin Scannell <ascannell@google.com> | 2018-04-28 01:44:26 -0400 |
commit | d02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 (patch) | |
tree | 54f95eef73aee6bacbfc736fffc631be2605ed53 /pkg/sentry/fs/ashmem | |
parent | f70210e742919f40aa2f0934a22f1c9ba6dada62 (diff) |
Check in gVisor.
PiperOrigin-RevId: 194583126
Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
Diffstat (limited to 'pkg/sentry/fs/ashmem')
-rw-r--r-- | pkg/sentry/fs/ashmem/BUILD | 83 | ||||
-rw-r--r-- | pkg/sentry/fs/ashmem/area.go | 313 | ||||
-rw-r--r-- | pkg/sentry/fs/ashmem/device.go | 169 | ||||
-rw-r--r-- | pkg/sentry/fs/ashmem/pin_board.go | 125 | ||||
-rw-r--r-- | pkg/sentry/fs/ashmem/pin_board_test.go | 130 |
5 files changed, 820 insertions, 0 deletions
diff --git a/pkg/sentry/fs/ashmem/BUILD b/pkg/sentry/fs/ashmem/BUILD new file mode 100644 index 000000000..e20e22a0f --- /dev/null +++ b/pkg/sentry/fs/ashmem/BUILD @@ -0,0 +1,83 @@ +package(licenses = ["notice"]) # Apache 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("//tools/go_stateify:defs.bzl", "go_stateify") +load("//tools/go_generics:defs.bzl", "go_template_instance") + +go_stateify( + name = "ashmem_state", + srcs = [ + "area.go", + "device.go", + "pin_board.go", + "uint64_range.go", + "uint64_set.go", + ], + out = "ashmem_state.go", + package = "ashmem", +) + +go_library( + name = "ashmem", + srcs = [ + "area.go", + "ashmem_state.go", + "device.go", + "pin_board.go", + "uint64_range.go", + "uint64_set.go", + ], + importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ashmem", + visibility = ["//pkg/sentry:internal"], + deps = [ + "//pkg/abi/linux", + "//pkg/sentry/arch", + "//pkg/sentry/context", + "//pkg/sentry/fs", + "//pkg/sentry/fs/fsutil", + "//pkg/sentry/fs/tmpfs", + "//pkg/sentry/kernel", + "//pkg/sentry/kernel/time", + "//pkg/sentry/memmap", + "//pkg/sentry/platform", + "//pkg/sentry/usage", + "//pkg/sentry/usermem", + "//pkg/state", + "//pkg/syserror", + "//pkg/tcpip/transport/unix", + ], +) + +go_test( + name = "ashmem_test", + size = "small", + srcs = ["pin_board_test.go"], + embed = [":ashmem"], + deps = [ + "//pkg/abi/linux", + "//pkg/sentry/usermem", + ], +) + +go_template_instance( + name = "uint64_range", + out = "uint64_range.go", + package = "ashmem", + template = "//pkg/segment:generic_range", + types = { + "T": "uint64", + }, +) + +go_template_instance( + name = "uint64_set", + out = "uint64_set.go", + package = "ashmem", + template = "//pkg/segment:generic_set", + types = { + "Key": "uint64", + "Range": "Range", + "Value": "noValue", + "Functions": "setFunctions", + }, +) diff --git a/pkg/sentry/fs/ashmem/area.go b/pkg/sentry/fs/ashmem/area.go new file mode 100644 index 000000000..e4f76f0d0 --- /dev/null +++ b/pkg/sentry/fs/ashmem/area.go @@ -0,0 +1,313 @@ +// Copyright 2018 Google Inc. +// +// 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. + +package ashmem + +import ( + "sync" + + "gvisor.googlesource.com/gvisor/pkg/abi/linux" + "gvisor.googlesource.com/gvisor/pkg/sentry/arch" + "gvisor.googlesource.com/gvisor/pkg/sentry/context" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs/tmpfs" + "gvisor.googlesource.com/gvisor/pkg/sentry/memmap" + "gvisor.googlesource.com/gvisor/pkg/sentry/platform" + "gvisor.googlesource.com/gvisor/pkg/sentry/usage" + "gvisor.googlesource.com/gvisor/pkg/sentry/usermem" + "gvisor.googlesource.com/gvisor/pkg/syserror" +) + +const ( + // namePrefix is the name prefix assumed and forced by the Linux implementation. + namePrefix = "dev/ashmem" + + // nameLen is the maximum name length. + nameLen = 256 +) + +// Area implements fs.FileOperations. +type Area struct { + fsutil.NoFsync + fsutil.DeprecatedFileOperations + fsutil.NotDirReaddir + + ad *Device + + // mu protects fields below. + mu sync.Mutex `state:"nosave"` + tmpfsFile *fs.File + name string + size uint64 + perms usermem.AccessType + pb *PinBoard +} + +// Release implements fs.FileOperations.Release. +func (a *Area) Release() { + a.mu.Lock() + defer a.mu.Unlock() + if a.tmpfsFile != nil { + a.tmpfsFile.DecRef() + a.tmpfsFile = nil + } +} + +// Seek implements fs.FileOperations.Seek. +func (a *Area) Seek(ctx context.Context, file *fs.File, whence fs.SeekWhence, offset int64) (int64, error) { + a.mu.Lock() + defer a.mu.Unlock() + if a.size == 0 { + return 0, syserror.EINVAL + } + if a.tmpfsFile == nil { + return 0, syserror.EBADF + } + return a.tmpfsFile.FileOperations.Seek(ctx, file, whence, offset) +} + +// Read implements fs.FileOperations.Read. +func (a *Area) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { + a.mu.Lock() + defer a.mu.Unlock() + if a.size == 0 { + return 0, nil + } + if a.tmpfsFile == nil { + return 0, syserror.EBADF + } + return a.tmpfsFile.FileOperations.Read(ctx, file, dst, offset) +} + +// Write implements fs.FileOperations.Write. +func (a *Area) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { + return 0, syserror.ENOSYS +} + +// Flush implements fs.FileOperations.Flush. +func (a *Area) Flush(ctx context.Context, file *fs.File) error { + return nil +} + +// ConfigureMMap implements fs.FileOperations.ConfigureMMap. +func (a *Area) ConfigureMMap(ctx context.Context, file *fs.File, opts *memmap.MMapOpts) error { + a.mu.Lock() + defer a.mu.Unlock() + if a.size == 0 { + return syserror.EINVAL + } + + if !a.perms.SupersetOf(opts.Perms) { + return syserror.EPERM + } + opts.MaxPerms = opts.MaxPerms.Intersect(a.perms) + + if a.tmpfsFile == nil { + p := platform.FromContext(ctx) + if p == nil { + return syserror.ENOMEM + } + tmpfsInodeOps := tmpfs.NewInMemoryFile(ctx, usage.Tmpfs, fs.UnstableAttr{}, p) + // This is not backed by a real filesystem, so we pass in nil. + tmpfsInode := fs.NewInode(tmpfsInodeOps, fs.NewNonCachingMountSource(nil, fs.MountSourceFlags{}), fs.StableAttr{}) + dirent := fs.NewDirent(tmpfsInode, namePrefix+"/"+a.name) + tmpfsFile, err := tmpfsInode.GetFile(ctx, dirent, fs.FileFlags{Read: true, Write: true}) + // Drop the extra reference on the Dirent. + dirent.DecRef() + + if err != nil { + return err + } + + // Truncate to the size set by ASHMEM_SET_SIZE ioctl. + err = tmpfsInodeOps.Truncate(ctx, tmpfsInode, int64(a.size)) + if err != nil { + return err + } + a.tmpfsFile = tmpfsFile + a.pb = NewPinBoard() + } + + return a.tmpfsFile.ConfigureMMap(ctx, opts) +} + +// Ioctl implements fs.FileOperations.Ioctl. +func (a *Area) Ioctl(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { + // Switch on ioctl request. + switch args[1].Uint() { + case linux.AshmemSetNameIoctl: + name, err := usermem.CopyStringIn(ctx, io, args[2].Pointer(), nameLen-1, usermem.IOOpts{ + AddressSpaceActive: true, + }) + if err != nil { + return 0, err + } + + a.mu.Lock() + defer a.mu.Unlock() + + // Cannot set name for already mapped ashmem. + if a.tmpfsFile != nil { + return 0, syserror.EINVAL + } + a.name = name + return 0, nil + + case linux.AshmemGetNameIoctl: + a.mu.Lock() + var local []byte + if a.name != "" { + nameLen := len([]byte(a.name)) + local = make([]byte, nameLen, nameLen+1) + copy(local, []byte(a.name)) + local = append(local, 0) + } else { + nameLen := len([]byte(namePrefix)) + local = make([]byte, nameLen, nameLen+1) + copy(local, []byte(namePrefix)) + local = append(local, 0) + } + a.mu.Unlock() + + if _, err := io.CopyOut(ctx, args[2].Pointer(), local, usermem.IOOpts{ + AddressSpaceActive: true, + }); err != nil { + return 0, syserror.EFAULT + } + return 0, nil + + case linux.AshmemSetSizeIoctl: + a.mu.Lock() + defer a.mu.Unlock() + + // Cannot set size for already mapped ashmem. + if a.tmpfsFile != nil { + return 0, syserror.EINVAL + } + a.size = uint64(args[2].SizeT()) + return 0, nil + + case linux.AshmemGetSizeIoctl: + return uintptr(a.size), nil + + case linux.AshmemPinIoctl, linux.AshmemUnpinIoctl, linux.AshmemGetPinStatusIoctl: + // Locking and unlocking is ok since once tmpfsFile is set, it won't be nil again + // even after unmapping! Unlocking is needed in order to avoid a deadlock on + // usermem.CopyObjectIn. + + // Cannot execute pin-related ioctls before mapping. + a.mu.Lock() + if a.tmpfsFile == nil { + a.mu.Unlock() + return 0, syserror.EINVAL + } + a.mu.Unlock() + + var pin linux.AshmemPin + _, err := usermem.CopyObjectIn(ctx, io, args[2].Pointer(), &pin, usermem.IOOpts{ + AddressSpaceActive: true, + }) + if err != nil { + return 0, syserror.EFAULT + } + + a.mu.Lock() + defer a.mu.Unlock() + return a.pinOperation(pin, args[1].Uint()) + + case linux.AshmemPurgeAllCachesIoctl: + return 0, nil + + case linux.AshmemSetProtMaskIoctl: + prot := uint64(args[2].ModeT()) + perms := usermem.AccessType{ + Read: prot&linux.PROT_READ != 0, + Write: prot&linux.PROT_WRITE != 0, + Execute: prot&linux.PROT_EXEC != 0, + } + + a.mu.Lock() + defer a.mu.Unlock() + + // Can only narrow prot mask. + if !a.perms.SupersetOf(perms) { + return 0, syserror.EINVAL + } + + // TODO: If personality flag + // READ_IMPLIES_EXEC is set, set PROT_EXEC if PORT_READ is set. + + a.perms = perms + return 0, nil + + case linux.AshmemGetProtMaskIoctl: + return uintptr(a.perms.Prot()), nil + default: + // Ioctls irrelevant to Ashmem. + return 0, syserror.EINVAL + } +} + +// pinOperation should only be called while holding a.mu. +func (a *Area) pinOperation(pin linux.AshmemPin, op uint32) (uintptr, error) { + // Page-align a.size for checks. + pageAlignedSize, ok := usermem.Addr(a.size).RoundUp() + if !ok { + return 0, syserror.EINVAL + } + // Len 0 means everything onward. + if pin.Len == 0 { + pin.Len = uint32(pageAlignedSize) - pin.Offset + } + // Both Offset and Len have to be page-aligned. + if pin.Offset%uint32(usermem.PageSize) != 0 { + return 0, syserror.EINVAL + } + if pin.Len%uint32(usermem.PageSize) != 0 { + return 0, syserror.EINVAL + } + // Adding Offset and Len must not cause an uint32 overflow. + if end := pin.Offset + pin.Len; end < pin.Offset { + return 0, syserror.EINVAL + } + // Pin range must not exceed a's size. + if uint32(pageAlignedSize) < pin.Offset+pin.Len { + return 0, syserror.EINVAL + } + // Handle each operation. + r := RangeFromAshmemPin(pin) + switch op { + case linux.AshmemPinIoctl: + if a.pb.PinRange(r) { + return linux.AshmemWasPurged, nil + } + return linux.AshmemNotPurged, nil + + case linux.AshmemUnpinIoctl: + // TODO: Implement purge on unpin. + a.pb.UnpinRange(r) + return 0, nil + + case linux.AshmemGetPinStatusIoctl: + if a.pb.RangePinnedStatus(r) { + return linux.AshmemIsPinned, nil + } + return linux.AshmemIsUnpinned, nil + + default: + panic("unreachable") + } + +} diff --git a/pkg/sentry/fs/ashmem/device.go b/pkg/sentry/fs/ashmem/device.go new file mode 100644 index 000000000..c5b51d4a7 --- /dev/null +++ b/pkg/sentry/fs/ashmem/device.go @@ -0,0 +1,169 @@ +// Copyright 2018 Google Inc. +// +// 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. + +// Package ashmem implements Android ashmem module (Anonymus Shared Memory). +package ashmem + +import ( + "sync" + + "gvisor.googlesource.com/gvisor/pkg/sentry/context" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil" + "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time" + "gvisor.googlesource.com/gvisor/pkg/sentry/usermem" + "gvisor.googlesource.com/gvisor/pkg/syserror" +) + +// Device implements fs.InodeOperations. +type Device struct { + fsutil.DeprecatedFileOperations + fsutil.InodeNoExtendedAttributes + fsutil.InodeNotDirectory + fsutil.InodeNotRenameable + fsutil.InodeNotSocket + fsutil.InodeNotSymlink + fsutil.NoFsync + fsutil.NoMappable + fsutil.NoopWriteOut + fsutil.NotDirReaddir + + mu sync.Mutex `state:"nosave"` + unstable fs.UnstableAttr +} + +// NewDevice creates and intializes a Device structure. +func NewDevice(ctx context.Context, owner fs.FileOwner, fp fs.FilePermissions) *Device { + return &Device{ + unstable: fs.WithCurrentTime(ctx, fs.UnstableAttr{ + Owner: owner, + Perms: fp, + Links: 1, + }), + } +} + +// Release implements fs.InodeOperations.Release. +func (ad *Device) Release(context.Context) {} + +// GetFile implements fs.InodeOperations.GetFile. +func (ad *Device) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { + return fs.NewFile(ctx, d, flags, &Area{ + ad: ad, + tmpfsFile: nil, + perms: usermem.AnyAccess, + }), nil +} + +// UnstableAttr implements fs.InodeOperations.UnstableAttr. +func (ad *Device) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { + ad.mu.Lock() + defer ad.mu.Unlock() + return ad.unstable, nil +} + +// Check implements fs.InodeOperations.Check. +func (ad *Device) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { + return fs.ContextCanAccessFile(ctx, inode, p) +} + +// SetPermissions implements fs.InodeOperations.SetPermissions. +func (ad *Device) SetPermissions(ctx context.Context, inode *fs.Inode, fp fs.FilePermissions) bool { + ad.mu.Lock() + defer ad.mu.Unlock() + ad.unstable.Perms = fp + ad.unstable.StatusChangeTime = time.NowFromContext(ctx) + return true +} + +// SetOwner implements fs.InodeOperations.SetOwner. +func (ad *Device) SetOwner(ctx context.Context, inode *fs.Inode, owner fs.FileOwner) error { + ad.mu.Lock() + defer ad.mu.Unlock() + if owner.UID.Ok() { + ad.unstable.Owner.UID = owner.UID + } + if owner.GID.Ok() { + ad.unstable.Owner.GID = owner.GID + } + return nil +} + +// SetTimestamps implements fs.InodeOperations.SetTimestamps. +func (ad *Device) SetTimestamps(ctx context.Context, inode *fs.Inode, ts fs.TimeSpec) error { + if ts.ATimeOmit && ts.MTimeOmit { + return nil + } + + ad.mu.Lock() + defer ad.mu.Unlock() + + now := time.NowFromContext(ctx) + if !ts.ATimeOmit { + if ts.ATimeSetSystemTime { + ad.unstable.AccessTime = now + } else { + ad.unstable.AccessTime = ts.ATime + } + } + if !ts.MTimeOmit { + if ts.MTimeSetSystemTime { + ad.unstable.ModificationTime = now + } else { + ad.unstable.ModificationTime = ts.MTime + } + } + ad.unstable.StatusChangeTime = now + return nil +} + +// Truncate implements fs.InodeOperations.WriteOut. +// +// Ignored by ashmem. +func (ad *Device) Truncate(ctx context.Context, inode *fs.Inode, size int64) error { + return nil +} + +// AddLink implements fs.InodeOperations.AddLink. +// +// Ashmem doesn't support links, no-op. +func (ad *Device) AddLink() {} + +// DropLink implements fs.InodeOperations.DropLink. +// +// Ashmem doesn't support links, no-op. +func (ad *Device) DropLink() {} + +// NotifyStatusChange implements fs.InodeOperations.NotifyStatusChange. +func (ad *Device) NotifyStatusChange(ctx context.Context) { + ad.mu.Lock() + defer ad.mu.Unlock() + now := time.NowFromContext(ctx) + ad.unstable.ModificationTime = now + ad.unstable.StatusChangeTime = now +} + +// IsVirtual implements fs.InodeOperations.IsVirtual. +// +// Ashmem is virtual. +func (ad *Device) IsVirtual() bool { + return true +} + +// StatFS implements fs.InodeOperations.StatFS. +// +// Ashmem doesn't support querying for filesystem info. +func (ad *Device) StatFS(context.Context) (fs.Info, error) { + return fs.Info{}, syserror.ENOSYS +} diff --git a/pkg/sentry/fs/ashmem/pin_board.go b/pkg/sentry/fs/ashmem/pin_board.go new file mode 100644 index 000000000..c7fb3822c --- /dev/null +++ b/pkg/sentry/fs/ashmem/pin_board.go @@ -0,0 +1,125 @@ +// Copyright 2018 Google Inc. +// +// 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. + +package ashmem + +import "gvisor.googlesource.com/gvisor/pkg/abi/linux" + +const maxUint64 = ^uint64(0) + +// setFunctions implements segment.Functions generated from segment.Functions for +// uint64 Key and noValue Value. For more information, see the build file and +// segment set implementation at pkg/segment/set.go. +type setFunctions struct{} + +// noValue is a type of range attached value, which is irrelevant here. +type noValue struct{} + +// MinKey implements segment.Functions.MinKey. +func (setFunctions) MinKey() uint64 { + return 0 +} + +// MaxKey implements segment.Functions.MaxKey. +func (setFunctions) MaxKey() uint64 { + return maxUint64 +} + +// ClearValue implements segment.Functions.ClearValue. +func (setFunctions) ClearValue(*noValue) { + return +} + +// Merge implements segment.Functions.Merge. +func (setFunctions) Merge(Range, noValue, Range, noValue) (noValue, bool) { + return noValue{}, true +} + +// Split implements segment.Functions.Split. +func (setFunctions) Split(Range, noValue, uint64) (noValue, noValue) { + return noValue{}, noValue{} +} + +// PinBoard represents a set of pinned ranges in ashmem. +// +// segment.Set is used for implementation where segments represent +// ranges of pinned bytes, while gaps represent ranges of unpinned +// bytes. All ranges are page-aligned. +type PinBoard struct { + Set +} + +// NewPinBoard creates a new pin board with all pages pinned. +func NewPinBoard() *PinBoard { + var pb PinBoard + pb.PinRange(Range{0, maxUint64}) + return &pb +} + +// PinRange pins all pages in the specified range and returns true +// if there are any newly pinned pages. +func (pb *PinBoard) PinRange(r Range) bool { + pinnedPages := false + for gap := pb.LowerBoundGap(r.Start); gap.Ok() && gap.Start() < r.End; { + common := gap.Range().Intersect(r) + if common.Length() == 0 { + gap = gap.NextGap() + continue + } + pinnedPages = true + gap = pb.Insert(gap, common, noValue{}).NextGap() + } + return pinnedPages +} + +// UnpinRange unpins all pages in the specified range. +func (pb *PinBoard) UnpinRange(r Range) { + for seg := pb.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; { + common := seg.Range().Intersect(r) + if common.Length() == 0 { + seg = seg.NextSegment() + continue + } + seg = pb.RemoveRange(common).NextSegment() + } +} + +// RangePinnedStatus returns false if there's at least one unpinned page in the +// specified range. +func (pb *PinBoard) RangePinnedStatus(r Range) bool { + for gap := pb.LowerBoundGap(r.Start); gap.Ok() && gap.Start() < r.End; { + common := gap.Range().Intersect(r) + if common.Length() == 0 { + gap = gap.NextGap() + continue + } + return false + } + return true +} + +// RangeFromAshmemPin converts ashmem's original pin structure +// to Range. +func RangeFromAshmemPin(ap linux.AshmemPin) Range { + if ap.Len == 0 { + return Range{ + uint64(ap.Offset), + maxUint64, + } + } + return Range{ + uint64(ap.Offset), + uint64(ap.Offset) + uint64(ap.Len), + } +} diff --git a/pkg/sentry/fs/ashmem/pin_board_test.go b/pkg/sentry/fs/ashmem/pin_board_test.go new file mode 100644 index 000000000..f4ea5de6d --- /dev/null +++ b/pkg/sentry/fs/ashmem/pin_board_test.go @@ -0,0 +1,130 @@ +// Copyright 2018 Google Inc. +// +// 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. + +package ashmem + +import ( + "testing" + + "gvisor.googlesource.com/gvisor/pkg/abi/linux" + "gvisor.googlesource.com/gvisor/pkg/sentry/usermem" +) + +func TestPinBoard(t *testing.T) { + pb := NewPinBoard() + + // Confirm that all pages are pinned. + if !pb.RangePinnedStatus(RangeFromAshmemPin(linux.AshmemPin{0, 0})) { + t.Errorf("RangePinnedStatus(all pages) returned false (unpinned) at start.") + } + + // Unpin pages [1, 11) (counting from 0) + pb.UnpinRange(RangeFromAshmemPin(linux.AshmemPin{ + usermem.PageSize, + usermem.PageSize * 10, + })) + + // Confirm that pages [1, 11) are unpinned and that page 0 and pages + // larger than 10 are pinned. + pinned := []linux.AshmemPin{ + { + 0, + usermem.PageSize, + }, { + usermem.PageSize * 11, + 0, + }, + } + + for _, pin := range pinned { + if !pb.RangePinnedStatus(RangeFromAshmemPin(pin)) { + t.Errorf("RangePinnedStatus(AshmemPin{offset (pages): %v, len (pages): %v}) returned false (unpinned).", + pin.Offset, pin.Len) + } + } + + unpinned := []linux.AshmemPin{ + { + usermem.PageSize, + usermem.PageSize * 10, + }, + } + + for _, pin := range unpinned { + if pb.RangePinnedStatus(RangeFromAshmemPin(pin)) { + t.Errorf("RangePinnedStatus(AshmemPin{offset (pages): %v, len (pages): %v}) returned true (pinned).", + pin.Offset, pin.Len) + } + } + + // Pin pages [2, 6). + pb.PinRange(RangeFromAshmemPin(linux.AshmemPin{ + usermem.PageSize * 2, + usermem.PageSize * 4, + })) + + // Confirm that pages 0, [2, 6) and pages larger than 10 are pinned + // while others remain unpinned. + pinned = []linux.AshmemPin{ + { + 0, + usermem.PageSize, + }, + { + usermem.PageSize * 2, + usermem.PageSize * 4, + }, + { + usermem.PageSize * 11, + 0, + }, + } + + for _, pin := range pinned { + if !pb.RangePinnedStatus(RangeFromAshmemPin(pin)) { + t.Errorf("RangePinnedStatus(AshmemPin{offset (pages): %v, len (pages): %v}) returned false (unpinned).", + pin.Offset, pin.Len) + } + } + + unpinned = []linux.AshmemPin{ + { + usermem.PageSize, + usermem.PageSize, + }, { + usermem.PageSize * 6, + usermem.PageSize * 5, + }, + } + + for _, pin := range unpinned { + if pb.RangePinnedStatus(RangeFromAshmemPin(pin)) { + t.Errorf("RangePinnedStatus(AshmemPin{offset (pages): %v, len (pages): %v}) returned true (pinned).", + pin.Offset, pin.Len) + } + } + + // Status of a partially pinned range is unpinned. + if pb.RangePinnedStatus(RangeFromAshmemPin(linux.AshmemPin{0, 0})) { + t.Errorf("RangePinnedStatus(all pages) returned true (pinned).") + } + + // Pin the whole range again. + pb.PinRange(RangeFromAshmemPin(linux.AshmemPin{0, 0})) + + // Confirm that all pages are pinned. + if !pb.RangePinnedStatus(RangeFromAshmemPin(linux.AshmemPin{0, 0})) { + t.Errorf("RangePinnedStatus(all pages) returned false (unpinned) at start.") + } +} |