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 }