github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/arch/arch_amd64.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 //go:build amd64 16 // +build amd64 17 18 package arch 19 20 import ( 21 "bytes" 22 "fmt" 23 "math/rand" 24 25 "golang.org/x/sys/unix" 26 "github.com/nicocha30/gvisor-ligolo/pkg/hostarch" 27 "github.com/nicocha30/gvisor-ligolo/pkg/marshal" 28 "github.com/nicocha30/gvisor-ligolo/pkg/marshal/primitive" 29 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch/fpu" 30 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/limits" 31 ) 32 33 // Host specifies the host architecture. 34 const Host = AMD64 35 36 // These constants come directly from Linux. 37 const ( 38 // maxAddr64 is the maximum userspace address. It is TASK_SIZE in Linux 39 // for a 64-bit process. 40 maxAddr64 hostarch.Addr = (1 << 47) - hostarch.PageSize 41 42 // maxStackRand64 is the maximum randomization to apply to the stack. 43 // It is defined by arch/x86/mm/mmap.c:stack_maxrandom_size in Linux. 44 maxStackRand64 = 16 << 30 // 16 GB 45 46 // maxMmapRand64 is the maximum randomization to apply to the mmap 47 // layout. It is defined by arch/x86/mm/mmap.c:arch_mmap_rnd in Linux. 48 maxMmapRand64 = (1 << 28) * hostarch.PageSize 49 50 // minGap64 is the minimum gap to leave at the top of the address space 51 // for the stack. It is defined by arch/x86/mm/mmap.c:MIN_GAP in Linux. 52 minGap64 = (128 << 20) + maxStackRand64 53 54 // preferredPIELoadAddr is the standard Linux position-independent 55 // executable base load address. It is ELF_ET_DYN_BASE in Linux. 56 // 57 // The Platform {Min,Max}UserAddress() may preclude loading at this 58 // address. See other preferredFoo comments below. 59 preferredPIELoadAddr hostarch.Addr = maxAddr64 / 3 * 2 60 ) 61 62 // These constants are selected as heuristics to help make the Platform's 63 // potentially limited address space conform as closely to Linux as possible. 64 const ( 65 // Select a preferred minimum TopDownBase address. 66 // 67 // Some applications (TSAN and other *SANs) are very particular about 68 // the way the Linux mmap allocator layouts out the address space. 69 // 70 // TSAN in particular expects top down allocations to be made in the 71 // range [0x7e8000000000, 0x800000000000). 72 // 73 // The minimum TopDownBase on Linux would be: 74 // 0x800000000000 - minGap64 - maxMmapRand64 = 0x7efbf8000000. 75 // 76 // (minGap64 because TSAN uses a small RLIMIT_STACK.) 77 // 78 // 0x7e8000000000 is selected arbitrarily by TSAN to leave room for 79 // allocations below TopDownBase. 80 // 81 // N.B. ASAN and MSAN are more forgiving; ASAN allows allocations all 82 // the way down to 0x10007fff8000, and MSAN down to 0x700000000000. 83 // 84 // Of course, there is no hard minimum to allocation; an allocator can 85 // search all the way from TopDownBase to Min. However, TSAN declared 86 // their range "good enough". 87 // 88 // We would like to pick a TopDownBase such that it is unlikely that an 89 // allocator will select an address below TSAN's minimum. We achieve 90 // this by trying to leave a sizable gap below TopDownBase. 91 // 92 // This is all "preferred" because the layout min/max address may not 93 // allow us to select such a TopDownBase, in which case we have to fall 94 // back to a layout that TSAN may not be happy with. 95 preferredTopDownAllocMin hostarch.Addr = 0x7e8000000000 96 preferredAllocationGap = 128 << 30 // 128 GB 97 preferredTopDownBaseMin = preferredTopDownAllocMin + preferredAllocationGap 98 99 // minMmapRand64 is the smallest we are willing to make the 100 // randomization to stay above preferredTopDownBaseMin. 101 minMmapRand64 = (1 << 26) * hostarch.PageSize 102 ) 103 104 // Context64 represents an AMD64 context. 105 // 106 // +stateify savable 107 type Context64 struct { 108 State 109 } 110 111 // Arch implements Context.Arch. 112 func (c *Context64) Arch() Arch { 113 return AMD64 114 } 115 116 // FloatingPointData returns the state of the floating-point unit. 117 func (c *Context64) FloatingPointData() *fpu.State { 118 return &c.State.fpState 119 } 120 121 // Fork returns an exact copy of this context. 122 func (c *Context64) Fork() *Context64 { 123 return &Context64{ 124 State: c.State.Fork(), 125 } 126 } 127 128 // Return returns the current syscall return value. 129 func (c *Context64) Return() uintptr { 130 return uintptr(c.Regs.Rax) 131 } 132 133 // SetReturn sets the syscall return value. 134 func (c *Context64) SetReturn(value uintptr) { 135 c.Regs.Rax = uint64(value) 136 } 137 138 // IP returns the current instruction pointer. 139 func (c *Context64) IP() uintptr { 140 return uintptr(c.Regs.Rip) 141 } 142 143 // SetIP sets the current instruction pointer. 144 func (c *Context64) SetIP(value uintptr) { 145 c.Regs.Rip = uint64(value) 146 } 147 148 // Stack returns the current stack pointer. 149 func (c *Context64) Stack() uintptr { 150 return uintptr(c.Regs.Rsp) 151 } 152 153 // SetStack sets the current stack pointer. 154 func (c *Context64) SetStack(value uintptr) { 155 c.Regs.Rsp = uint64(value) 156 } 157 158 // TLS returns the current TLS pointer. 159 func (c *Context64) TLS() uintptr { 160 return uintptr(c.Regs.Fs_base) 161 } 162 163 // SetTLS sets the current TLS pointer. Returns false if value is invalid. 164 func (c *Context64) SetTLS(value uintptr) bool { 165 if !isValidSegmentBase(uint64(value)) { 166 return false 167 } 168 169 c.Regs.Fs = 0 170 c.Regs.Fs_base = uint64(value) 171 return true 172 } 173 174 // SetOldRSeqInterruptedIP implements Context.SetOldRSeqInterruptedIP. 175 func (c *Context64) SetOldRSeqInterruptedIP(value uintptr) { 176 c.Regs.R10 = uint64(value) 177 } 178 179 // Native returns the native type for the given val. 180 func (c *Context64) Native(val uintptr) marshal.Marshallable { 181 v := primitive.Uint64(val) 182 return &v 183 } 184 185 // Value returns the generic val for the given native type. 186 func (c *Context64) Value(val marshal.Marshallable) uintptr { 187 return uintptr(*val.(*primitive.Uint64)) 188 } 189 190 // Width returns the byte width of this architecture. 191 func (c *Context64) Width() uint { 192 return 8 193 } 194 195 // mmapRand returns a random adjustment for randomizing an mmap layout. 196 func mmapRand(max uint64) hostarch.Addr { 197 return hostarch.Addr(rand.Int63n(int64(max))).RoundDown() 198 } 199 200 // NewMmapLayout implements Context.NewMmapLayout consistently with Linux. 201 func (c *Context64) NewMmapLayout(min, max hostarch.Addr, r *limits.LimitSet) (MmapLayout, error) { 202 min, ok := min.RoundUp() 203 if !ok { 204 return MmapLayout{}, unix.EINVAL 205 } 206 if max > maxAddr64 { 207 max = maxAddr64 208 } 209 max = max.RoundDown() 210 211 if min > max { 212 return MmapLayout{}, unix.EINVAL 213 } 214 215 stackSize := r.Get(limits.Stack) 216 217 // MAX_GAP in Linux. 218 maxGap := (max / 6) * 5 219 gap := hostarch.Addr(stackSize.Cur) 220 if gap < minGap64 { 221 gap = minGap64 222 } 223 if gap > maxGap { 224 gap = maxGap 225 } 226 defaultDir := MmapTopDown 227 if stackSize.Cur == limits.Infinity { 228 defaultDir = MmapBottomUp 229 } 230 231 topDownMin := max - gap - maxMmapRand64 232 maxRand := hostarch.Addr(maxMmapRand64) 233 if topDownMin < preferredTopDownBaseMin { 234 // Try to keep TopDownBase above preferredTopDownBaseMin by 235 // shrinking maxRand. 236 maxAdjust := maxRand - minMmapRand64 237 needAdjust := preferredTopDownBaseMin - topDownMin 238 if needAdjust <= maxAdjust { 239 maxRand -= needAdjust 240 } 241 } 242 243 rnd := mmapRand(uint64(maxRand)) 244 l := MmapLayout{ 245 MinAddr: min, 246 MaxAddr: max, 247 // TASK_UNMAPPED_BASE in Linux. 248 BottomUpBase: (max/3 + rnd).RoundDown(), 249 TopDownBase: (max - gap - rnd).RoundDown(), 250 DefaultDirection: defaultDir, 251 // We may have reduced the maximum randomization to keep 252 // TopDownBase above preferredTopDownBaseMin while maintaining 253 // our stack gap. Stack allocations must use that max 254 // randomization to avoiding eating into the gap. 255 MaxStackRand: uint64(maxRand), 256 } 257 258 // Final sanity check on the layout. 259 if !l.Valid() { 260 panic(fmt.Sprintf("Invalid MmapLayout: %+v", l)) 261 } 262 263 return l, nil 264 } 265 266 // PIELoadAddress implements Context.PIELoadAddress. 267 func (c *Context64) PIELoadAddress(l MmapLayout) hostarch.Addr { 268 base := preferredPIELoadAddr 269 max, ok := base.AddLength(maxMmapRand64) 270 if !ok { 271 panic(fmt.Sprintf("preferredPIELoadAddr %#x too large", base)) 272 } 273 274 if max > l.MaxAddr { 275 // preferredPIELoadAddr won't fit; fall back to the standard 276 // Linux behavior of 2/3 of TopDownBase. TSAN won't like this. 277 // 278 // Don't bother trying to shrink the randomization for now. 279 base = l.TopDownBase / 3 * 2 280 } 281 282 return base + mmapRand(maxMmapRand64) 283 } 284 285 // userStructSize is the size in bytes of Linux's struct user on amd64. 286 const userStructSize = 928 287 288 // PtracePeekUser implements Context.PtracePeekUser. 289 func (c *Context64) PtracePeekUser(addr uintptr) (marshal.Marshallable, error) { 290 if addr&7 != 0 || addr >= userStructSize { 291 return nil, unix.EIO 292 } 293 // PTRACE_PEEKUSER and PTRACE_POKEUSER are only effective on regs and 294 // u_debugreg, returning 0 or silently no-oping for other fields 295 // respectively. 296 if addr < uintptr(ptraceRegistersSize) { 297 regs := c.ptraceGetRegs() 298 buf := make([]byte, regs.SizeBytes()) 299 regs.MarshalUnsafe(buf) 300 return c.Native(uintptr(hostarch.ByteOrder.Uint64(buf[addr:]))), nil 301 } 302 // Note: x86 debug registers are missing. 303 return c.Native(0), nil 304 } 305 306 // PtracePokeUser implements Context.PtracePokeUser. 307 func (c *Context64) PtracePokeUser(addr, data uintptr) error { 308 if addr&7 != 0 || addr >= userStructSize { 309 return unix.EIO 310 } 311 if addr < uintptr(ptraceRegistersSize) { 312 regs := c.ptraceGetRegs() 313 buf := make([]byte, regs.SizeBytes()) 314 regs.MarshalUnsafe(buf) 315 hostarch.ByteOrder.PutUint64(buf[addr:], uint64(data)) 316 _, err := c.PtraceSetRegs(bytes.NewBuffer(buf)) 317 return err 318 } 319 // Note: x86 debug registers are missing. 320 return nil 321 }