gvisor/pkg/sentry/fs/ashmem/area.go

316 lines
8.2 KiB
Go

// Copyright 2018 Google LLC
//
// 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.
//
// +stateify savable
type Area struct {
fsutil.NoFsync `state:"nosave"`
fsutil.DeprecatedFileOperations `state:"nosave"`
fsutil.NotDirReaddir `state:"nosave"`
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")
}
}