gvisor/pkg/sentry/safemem/block_unsafe.go

270 lines
7.5 KiB
Go

// 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 safemem
import (
"fmt"
"reflect"
"unsafe"
"gvisor.googlesource.com/gvisor/pkg/sentry/platform/safecopy"
)
// A Block is a range of contiguous bytes, similar to []byte but with the
// following differences:
//
// - The memory represented by a Block may require the use of safecopy to
// access.
//
// - Block does not carry a capacity and cannot be expanded.
//
// Blocks are immutable and may be copied by value. The zero value of Block
// represents an empty range, analogous to a nil []byte.
type Block struct {
// [start, start+length) is the represented memory.
//
// start is an unsafe.Pointer to ensure that Block prevents the represented
// memory from being garbage-collected.
start unsafe.Pointer
length int
// needSafecopy is true if accessing the represented memory requires the
// use of safecopy.
needSafecopy bool
}
// BlockFromSafeSlice returns a Block equivalent to slice, which is safe to
// access without safecopy.
func BlockFromSafeSlice(slice []byte) Block {
return blockFromSlice(slice, false)
}
// BlockFromUnsafeSlice returns a Block equivalent to bs, which is not safe to
// access without safecopy.
func BlockFromUnsafeSlice(slice []byte) Block {
return blockFromSlice(slice, true)
}
func blockFromSlice(slice []byte, needSafecopy bool) Block {
if len(slice) == 0 {
return Block{}
}
return Block{
start: unsafe.Pointer(&slice[0]),
length: len(slice),
needSafecopy: needSafecopy,
}
}
// BlockFromSafePointer returns a Block equivalent to [ptr, ptr+len), which is
// safe to access without safecopy.
//
// Preconditions: ptr+len does not overflow.
func BlockFromSafePointer(ptr unsafe.Pointer, len int) Block {
return blockFromPointer(ptr, len, false)
}
// BlockFromUnsafePointer returns a Block equivalent to [ptr, ptr+len), which
// is not safe to access without safecopy.
//
// Preconditions: ptr+len does not overflow.
func BlockFromUnsafePointer(ptr unsafe.Pointer, len int) Block {
return blockFromPointer(ptr, len, true)
}
func blockFromPointer(ptr unsafe.Pointer, len int, needSafecopy bool) Block {
if uptr := uintptr(ptr); uptr+uintptr(len) < uptr {
panic(fmt.Sprintf("ptr %#x + len %#x overflows", ptr, len))
}
return Block{
start: ptr,
length: len,
needSafecopy: needSafecopy,
}
}
// DropFirst returns a Block equivalent to b, but with the first n bytes
// omitted. It is analogous to the [n:] operation on a slice, except that if n
// > b.Len(), DropFirst returns an empty Block instead of panicking.
//
// Preconditions: n >= 0.
func (b Block) DropFirst(n int) Block {
if n < 0 {
panic(fmt.Sprintf("invalid n: %d", n))
}
return b.DropFirst64(uint64(n))
}
// DropFirst64 is equivalent to DropFirst but takes a uint64.
func (b Block) DropFirst64(n uint64) Block {
if n >= uint64(b.length) {
return Block{}
}
return Block{
start: unsafe.Pointer(uintptr(b.start) + uintptr(n)),
length: b.length - int(n),
needSafecopy: b.needSafecopy,
}
}
// TakeFirst returns a Block equivalent to the first n bytes of b. It is
// analogous to the [:n] operation on a slice, except that if n > b.Len(),
// TakeFirst returns a copy of b instead of panicking.
//
// Preconditions: n >= 0.
func (b Block) TakeFirst(n int) Block {
if n < 0 {
panic(fmt.Sprintf("invalid n: %d", n))
}
return b.TakeFirst64(uint64(n))
}
// TakeFirst64 is equivalent to TakeFirst but takes a uint64.
func (b Block) TakeFirst64(n uint64) Block {
if n == 0 {
return Block{}
}
if n >= uint64(b.length) {
return b
}
return Block{
start: b.start,
length: int(n),
needSafecopy: b.needSafecopy,
}
}
// ToSlice returns a []byte equivalent to b.
func (b Block) ToSlice() []byte {
var bs []byte
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
hdr.Data = uintptr(b.start)
hdr.Len = b.length
hdr.Cap = b.length
return bs
}
// Addr returns b's start address as a uintptr. It returns uintptr instead of
// unsafe.Pointer so that code using safemem cannot obtain unsafe.Pointers
// without importing the unsafe package explicitly.
//
// Note that a uintptr is not recognized as a pointer by the garbage collector,
// such that if there are no uses of b after a call to b.Addr() and the address
// is to Go-managed memory, the returned uintptr does not prevent garbage
// collection of the pointee.
func (b Block) Addr() uintptr {
return uintptr(b.start)
}
// Len returns b's length in bytes.
func (b Block) Len() int {
return b.length
}
// NeedSafecopy returns true if accessing b.ToSlice() requires the use of safecopy.
func (b Block) NeedSafecopy() bool {
return b.needSafecopy
}
// String implements fmt.Stringer.String.
func (b Block) String() string {
if uintptr(b.start) == 0 && b.length == 0 {
return "<nil>"
}
var suffix string
if b.needSafecopy {
suffix = "*"
}
return fmt.Sprintf("[%#x-%#x)%s", uintptr(b.start), uintptr(b.start)+uintptr(b.length), suffix)
}
// Copy copies src.Len() or dst.Len() bytes, whichever is less, from src
// to dst and returns the number of bytes copied.
//
// If src and dst overlap, the data stored in dst is unspecified.
func Copy(dst, src Block) (int, error) {
if !dst.needSafecopy && !src.needSafecopy {
return copy(dst.ToSlice(), src.ToSlice()), nil
}
n := dst.length
if n > src.length {
n = src.length
}
if n == 0 {
return 0, nil
}
switch {
case dst.needSafecopy && !src.needSafecopy:
return safecopy.CopyOut(dst.start, src.TakeFirst(n).ToSlice())
case !dst.needSafecopy && src.needSafecopy:
return safecopy.CopyIn(dst.TakeFirst(n).ToSlice(), src.start)
case dst.needSafecopy && src.needSafecopy:
n64, err := safecopy.Copy(dst.start, src.start, uintptr(n))
return int(n64), err
default:
panic("unreachable")
}
}
// Zero sets all bytes in dst to 0 and returns the number of bytes zeroed.
func Zero(dst Block) (int, error) {
if !dst.needSafecopy {
bs := dst.ToSlice()
for i := range bs {
bs[i] = 0
}
return len(bs), nil
}
n64, err := safecopy.ZeroOut(dst.start, uintptr(dst.length))
return int(n64), err
}
// Safecopy atomics are no slower than non-safecopy atomics, so use the former
// even when !b.needSafecopy to get consistent alignment checking.
// SwapUint32 invokes safecopy.SwapUint32 on the first 4 bytes of b.
//
// Preconditions: b.Len() >= 4.
func SwapUint32(b Block, new uint32) (uint32, error) {
if b.length < 4 {
panic(fmt.Sprintf("insufficient length: %d", b.length))
}
return safecopy.SwapUint32(b.start, new)
}
// SwapUint64 invokes safecopy.SwapUint64 on the first 8 bytes of b.
//
// Preconditions: b.Len() >= 8.
func SwapUint64(b Block, new uint64) (uint64, error) {
if b.length < 8 {
panic(fmt.Sprintf("insufficient length: %d", b.length))
}
return safecopy.SwapUint64(b.start, new)
}
// CompareAndSwapUint32 invokes safecopy.CompareAndSwapUint32 on the first 4
// bytes of b.
//
// Preconditions: b.Len() >= 4.
func CompareAndSwapUint32(b Block, old, new uint32) (uint32, error) {
if b.length < 4 {
panic(fmt.Sprintf("insufficient length: %d", b.length))
}
return safecopy.CompareAndSwapUint32(b.start, old, new)
}