280 lines
7.8 KiB
Go
280 lines
7.8 KiB
Go
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// 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.dev/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)
|
|
}
|
|
|
|
// LoadUint32 invokes safecopy.LoadUint32 on the first 4 bytes of b.
|
|
//
|
|
// Preconditions: b.Len() >= 4.
|
|
func LoadUint32(b Block) (uint32, error) {
|
|
if b.length < 4 {
|
|
panic(fmt.Sprintf("insufficient length: %d", b.length))
|
|
}
|
|
return safecopy.LoadUint32(b.start)
|
|
}
|