github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/safemem/block_unsafe.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package safemem
    16  
    17  import (
    18  	"fmt"
    19  	"unsafe"
    20  
    21  	"github.com/nicocha30/gvisor-ligolo/pkg/gohacks"
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/safecopy"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    24  )
    25  
    26  // A Block is a range of contiguous bytes, similar to []byte but with the
    27  // following differences:
    28  //
    29  //   - The memory represented by a Block may require the use of safecopy to
    30  //     access.
    31  //
    32  //   - Block does not carry a capacity and cannot be expanded.
    33  //
    34  // Blocks are immutable and may be copied by value. The zero value of Block
    35  // represents an empty range, analogous to a nil []byte.
    36  type Block struct {
    37  	// [start, start+length) is the represented memory.
    38  	//
    39  	// start is an unsafe.Pointer to ensure that Block prevents the represented
    40  	// memory from being garbage-collected.
    41  	start  unsafe.Pointer
    42  	length int
    43  
    44  	// needSafecopy is true if accessing the represented memory requires the
    45  	// use of safecopy.
    46  	needSafecopy bool
    47  }
    48  
    49  // BlockFromSafeSlice returns a Block equivalent to slice, which is safe to
    50  // access without safecopy.
    51  func BlockFromSafeSlice(slice []byte) Block {
    52  	return blockFromSlice(slice, false)
    53  }
    54  
    55  // BlockFromUnsafeSlice returns a Block equivalent to bs, which is not safe to
    56  // access without safecopy.
    57  func BlockFromUnsafeSlice(slice []byte) Block {
    58  	return blockFromSlice(slice, true)
    59  }
    60  
    61  func blockFromSlice(slice []byte, needSafecopy bool) Block {
    62  	if len(slice) == 0 {
    63  		return Block{}
    64  	}
    65  	return Block{
    66  		start:        unsafe.Pointer(&slice[0]),
    67  		length:       len(slice),
    68  		needSafecopy: needSafecopy,
    69  	}
    70  }
    71  
    72  // BlockFromSafePointer returns a Block equivalent to [ptr, ptr+length), which is
    73  // safe to access without safecopy.
    74  //
    75  // Preconditions: ptr+length does not overflow.
    76  func BlockFromSafePointer(ptr unsafe.Pointer, length int) Block {
    77  	return blockFromPointer(ptr, length, false)
    78  }
    79  
    80  // BlockFromUnsafePointer returns a Block equivalent to [ptr, ptr+len), which
    81  // is not safe to access without safecopy.
    82  //
    83  // Preconditions: ptr+len does not overflow.
    84  func BlockFromUnsafePointer(ptr unsafe.Pointer, length int) Block {
    85  	return blockFromPointer(ptr, length, true)
    86  }
    87  
    88  func blockFromPointer(ptr unsafe.Pointer, length int, needSafecopy bool) Block {
    89  	if uptr := uintptr(ptr); uptr+uintptr(length) < uptr {
    90  		panic(fmt.Sprintf("ptr %#x + len %#x overflows", uptr, length))
    91  	}
    92  	return Block{
    93  		start:        ptr,
    94  		length:       length,
    95  		needSafecopy: needSafecopy,
    96  	}
    97  }
    98  
    99  // DropFirst returns a Block equivalent to b, but with the first n bytes
   100  // omitted. It is analogous to the [n:] operation on a slice, except that if n
   101  // > b.Len(), DropFirst returns an empty Block instead of panicking.
   102  //
   103  // Preconditions: n >= 0.
   104  func (b Block) DropFirst(n int) Block {
   105  	if n < 0 {
   106  		panic(fmt.Sprintf("invalid n: %d", n))
   107  	}
   108  	return b.DropFirst64(uint64(n))
   109  }
   110  
   111  // DropFirst64 is equivalent to DropFirst but takes a uint64.
   112  func (b Block) DropFirst64(n uint64) Block {
   113  	if n >= uint64(b.length) {
   114  		return Block{}
   115  	}
   116  	return Block{
   117  		start:        unsafe.Pointer(uintptr(b.start) + uintptr(n)),
   118  		length:       b.length - int(n),
   119  		needSafecopy: b.needSafecopy,
   120  	}
   121  }
   122  
   123  // TakeFirst returns a Block equivalent to the first n bytes of b. It is
   124  // analogous to the [:n] operation on a slice, except that if n > b.Len(),
   125  // TakeFirst returns a copy of b instead of panicking.
   126  //
   127  // Preconditions: n >= 0.
   128  func (b Block) TakeFirst(n int) Block {
   129  	if n < 0 {
   130  		panic(fmt.Sprintf("invalid n: %d", n))
   131  	}
   132  	return b.TakeFirst64(uint64(n))
   133  }
   134  
   135  // TakeFirst64 is equivalent to TakeFirst but takes a uint64.
   136  func (b Block) TakeFirst64(n uint64) Block {
   137  	if n == 0 {
   138  		return Block{}
   139  	}
   140  	if n >= uint64(b.length) {
   141  		return b
   142  	}
   143  	return Block{
   144  		start:        b.start,
   145  		length:       int(n),
   146  		needSafecopy: b.needSafecopy,
   147  	}
   148  }
   149  
   150  // ToSlice returns a []byte equivalent to b.
   151  func (b Block) ToSlice() []byte {
   152  	return gohacks.Slice((*byte)(b.start), b.length)
   153  }
   154  
   155  // Addr returns b's start address as a uintptr. It returns uintptr instead of
   156  // unsafe.Pointer so that code using safemem cannot obtain unsafe.Pointers
   157  // without importing the unsafe package explicitly.
   158  //
   159  // Note that a uintptr is not recognized as a pointer by the garbage collector,
   160  // such that if there are no uses of b after a call to b.Addr() and the address
   161  // is to Go-managed memory, the returned uintptr does not prevent garbage
   162  // collection of the pointee.
   163  func (b Block) Addr() uintptr {
   164  	return uintptr(b.start)
   165  }
   166  
   167  // Len returns b's length in bytes.
   168  func (b Block) Len() int {
   169  	return b.length
   170  }
   171  
   172  // NeedSafecopy returns true if accessing b.ToSlice() requires the use of safecopy.
   173  func (b Block) NeedSafecopy() bool {
   174  	return b.needSafecopy
   175  }
   176  
   177  // String implements fmt.Stringer.String.
   178  func (b Block) String() string {
   179  	if uintptr(b.start) == 0 && b.length == 0 {
   180  		return "<nil>"
   181  	}
   182  	var suffix string
   183  	if b.needSafecopy {
   184  		suffix = "*"
   185  	}
   186  	return fmt.Sprintf("[%#x-%#x)%s", uintptr(b.start), uintptr(b.start)+uintptr(b.length), suffix)
   187  }
   188  
   189  // Copy copies src.Len() or dst.Len() bytes, whichever is less, from src
   190  // to dst and returns the number of bytes copied.
   191  //
   192  // If src and dst overlap, the data stored in dst is unspecified.
   193  func Copy(dst, src Block) (int, error) {
   194  	if !dst.needSafecopy && !src.needSafecopy {
   195  		return copy(dst.ToSlice(), src.ToSlice()), nil
   196  	}
   197  
   198  	n := dst.length
   199  	if n > src.length {
   200  		n = src.length
   201  	}
   202  	if n == 0 {
   203  		return 0, nil
   204  	}
   205  
   206  	switch {
   207  	case dst.needSafecopy && !src.needSafecopy:
   208  		return safecopy.CopyOut(dst.start, src.TakeFirst(n).ToSlice())
   209  	case !dst.needSafecopy && src.needSafecopy:
   210  		return safecopy.CopyIn(dst.TakeFirst(n).ToSlice(), src.start)
   211  	case dst.needSafecopy && src.needSafecopy:
   212  		n64, err := safecopy.Copy(dst.start, src.start, uintptr(n))
   213  		return int(n64), err
   214  	default:
   215  		panic("unreachable")
   216  	}
   217  }
   218  
   219  // Zero sets all bytes in dst to 0 and returns the number of bytes zeroed.
   220  func Zero(dst Block) (int, error) {
   221  	if !dst.needSafecopy {
   222  		bs := dst.ToSlice()
   223  		if !sync.RaceEnabled {
   224  			// If the race detector isn't enabled, the golang
   225  			// compiler replaces the next loop with memclr
   226  			// (https://github.com/golang/go/issues/5373).
   227  			for i := range bs {
   228  				bs[i] = 0
   229  			}
   230  		} else {
   231  			bsLen := len(bs)
   232  			if bsLen == 0 {
   233  				return 0, nil
   234  			}
   235  			bs[0] = 0
   236  			for i := 1; i < bsLen; i *= 2 {
   237  				copy(bs[i:], bs[:i])
   238  			}
   239  		}
   240  		return len(bs), nil
   241  	}
   242  
   243  	n64, err := safecopy.ZeroOut(dst.start, uintptr(dst.length))
   244  	return int(n64), err
   245  }
   246  
   247  // Safecopy atomics are no slower than non-safecopy atomics, so use the former
   248  // even when !b.needSafecopy to get consistent alignment checking.
   249  
   250  // SwapUint32 invokes safecopy.SwapUint32 on the first 4 bytes of b.
   251  //
   252  // Preconditions: b.Len() >= 4.
   253  func SwapUint32(b Block, new uint32) (uint32, error) {
   254  	if b.length < 4 {
   255  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   256  	}
   257  	return safecopy.SwapUint32(b.start, new)
   258  }
   259  
   260  // SwapUint64 invokes safecopy.SwapUint64 on the first 8 bytes of b.
   261  //
   262  // Preconditions: b.Len() >= 8.
   263  func SwapUint64(b Block, new uint64) (uint64, error) {
   264  	if b.length < 8 {
   265  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   266  	}
   267  	return safecopy.SwapUint64(b.start, new)
   268  }
   269  
   270  // CompareAndSwapUint32 invokes safecopy.CompareAndSwapUint32 on the first 4
   271  // bytes of b.
   272  //
   273  // Preconditions: b.Len() >= 4.
   274  func CompareAndSwapUint32(b Block, old, new uint32) (uint32, error) {
   275  	if b.length < 4 {
   276  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   277  	}
   278  	return safecopy.CompareAndSwapUint32(b.start, old, new)
   279  }
   280  
   281  // LoadUint32 invokes safecopy.LoadUint32 on the first 4 bytes of b.
   282  //
   283  // Preconditions: b.Len() >= 4.
   284  func LoadUint32(b Block) (uint32, error) {
   285  	if b.length < 4 {
   286  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   287  	}
   288  	return safecopy.LoadUint32(b.start)
   289  }