github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/platform/ptrace/ptrace.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 ptrace provides a ptrace-based implementation of the platform 16 // interface. This is useful for development and testing purposes primarily, 17 // and runs on stock kernels without special permissions. 18 // 19 // In a nutshell, it works as follows: 20 // 21 // The creation of a new address space creates a new child process with a single 22 // thread which is traced by a single goroutine. 23 // 24 // A context is just a collection of temporary variables. Calling Switch on a 25 // context does the following: 26 // 27 // Locks the runtime thread. 28 // 29 // Looks up a traced subprocess thread for the current runtime thread. If 30 // none exists, the dedicated goroutine is asked to create a new stopped 31 // thread in the subprocess. This stopped subprocess thread is then traced 32 // by the current thread and this information is stored for subsequent 33 // switches. 34 // 35 // The context is then bound with information about the subprocess thread 36 // so that the context may be appropriately interrupted via a signal. 37 // 38 // The requested operation is performed in the traced subprocess thread 39 // (e.g. set registers, execute, return). 40 // 41 // Lock order: 42 // 43 // subprocess.mu 44 // context.mu 45 package ptrace 46 47 import ( 48 "os" 49 50 "github.com/nicocha30/gvisor-ligolo/pkg/abi/linux" 51 pkgcontext "github.com/nicocha30/gvisor-ligolo/pkg/context" 52 "github.com/nicocha30/gvisor-ligolo/pkg/hostarch" 53 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch" 54 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform" 55 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform/interrupt" 56 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 57 ) 58 59 var ( 60 // stubStart is the link address for our stub, and determines the 61 // maximum user address. This is valid only after a call to stubInit. 62 // 63 // We attempt to link the stub here, and adjust downward as needed. 64 stubStart uintptr = stubInitAddress 65 66 // stubEnd is the first byte past the end of the stub, as with 67 // stubStart this is valid only after a call to stubInit. 68 stubEnd uintptr 69 70 // stubInitialized controls one-time stub initialization. 71 stubInitialized sync.Once 72 ) 73 74 type context struct { 75 archContext 76 77 // signalInfo is the signal info, if and when a signal is received. 78 signalInfo linux.SignalInfo 79 80 // interrupt is the interrupt context. 81 interrupt interrupt.Forwarder 82 83 // mu protects the following fields. 84 mu sync.Mutex 85 86 // If lastFaultSP is non-nil, the last context switch was due to a fault 87 // received while executing lastFaultSP. Only context.Switch may set 88 // lastFaultSP to a non-nil value. 89 lastFaultSP *subprocess 90 91 // lastFaultAddr is the last faulting address; this is only meaningful if 92 // lastFaultSP is non-nil. 93 lastFaultAddr hostarch.Addr 94 95 // lastFaultIP is the address of the last faulting instruction; 96 // this is also only meaningful if lastFaultSP is non-nil. 97 lastFaultIP hostarch.Addr 98 } 99 100 // NewContext implements platform.Platform.NewContext. 101 func (*PTrace) NewContext(ctx pkgcontext.Context) platform.Context { 102 c := new(context) 103 c.archContext.init(ctx) 104 return c 105 } 106 107 // Switch runs the provided context in the given address space. 108 func (c *context) Switch(ctx pkgcontext.Context, mm platform.MemoryManager, ac *arch.Context64, cpu int32) (*linux.SignalInfo, hostarch.AccessType, error) { 109 as := mm.AddressSpace() 110 s := as.(*subprocess) 111 restart: 112 isSyscall := s.switchToApp(c, ac) 113 114 var ( 115 faultSP *subprocess 116 faultAddr hostarch.Addr 117 faultIP hostarch.Addr 118 ) 119 if !isSyscall && linux.Signal(c.signalInfo.Signo) == linux.SIGSEGV { 120 faultSP = s 121 faultAddr = hostarch.Addr(c.signalInfo.Addr()) 122 faultIP = hostarch.Addr(ac.IP()) 123 } 124 125 // Update the context to reflect the outcome of this context switch. 126 c.mu.Lock() 127 lastFaultSP := c.lastFaultSP 128 lastFaultAddr := c.lastFaultAddr 129 lastFaultIP := c.lastFaultIP 130 // At this point, c may not yet be in s.contexts, so c.lastFaultSP won't be 131 // updated by s.Unmap(). This is fine; we only need to synchronize with 132 // calls to s.Unmap() that occur after the handling of this fault. 133 c.lastFaultSP = faultSP 134 c.lastFaultAddr = faultAddr 135 c.lastFaultIP = faultIP 136 c.mu.Unlock() 137 138 // Update subprocesses to reflect the outcome of this context switch. 139 if lastFaultSP != faultSP { 140 if lastFaultSP != nil { 141 lastFaultSP.mu.Lock() 142 delete(lastFaultSP.contexts, c) 143 lastFaultSP.mu.Unlock() 144 } 145 if faultSP != nil { 146 faultSP.mu.Lock() 147 faultSP.contexts[c] = struct{}{} 148 faultSP.mu.Unlock() 149 } 150 } 151 152 if isSyscall { 153 return nil, hostarch.NoAccess, nil 154 } 155 156 si := c.signalInfo 157 if faultSP == nil { 158 // Non-fault signal. 159 return &si, hostarch.NoAccess, platform.ErrContextSignal 160 } 161 162 // See if this can be handled as a CPUID instruction. 163 if linux.Signal(si.Signo) == linux.SIGSEGV && platform.TryCPUIDEmulate(ctx, mm, ac) { 164 goto restart 165 } 166 167 // Got a page fault. Ideally, we'd get real fault type here, but ptrace 168 // doesn't expose this information. Instead, we use a simple heuristic: 169 // 170 // It was an instruction fault iff the faulting addr == instruction 171 // pointer. 172 // 173 // It was a write fault if the fault is immediately repeated. 174 at := hostarch.Read 175 if faultAddr == faultIP { 176 at.Execute = true 177 } 178 if lastFaultSP == faultSP && 179 lastFaultAddr == faultAddr && 180 lastFaultIP == faultIP { 181 at.Write = true 182 } 183 184 // Handle as a signal. 185 return &si, at, platform.ErrContextSignal 186 } 187 188 // Interrupt interrupts the running guest application associated with this context. 189 func (c *context) Interrupt() { 190 c.interrupt.NotifyInterrupt() 191 } 192 193 // Release implements platform.Context.Release(). 194 func (c *context) Release() {} 195 196 // FullStateChanged implements platform.Context.FullStateChanged. 197 func (c *context) FullStateChanged() {} 198 199 // PullFullState implements platform.Context.PullFullState. 200 func (c *context) PullFullState(as platform.AddressSpace, ac *arch.Context64) error { return nil } 201 202 // PrepareSleep implements platform.Context.platform.PrepareSleep. 203 func (*context) PrepareSleep() {} 204 205 // PTrace represents a collection of ptrace subprocesses. 206 type PTrace struct { 207 platform.MMapMinAddr 208 platform.NoCPUPreemptionDetection 209 platform.UseHostGlobalMemoryBarrier 210 platform.DoesNotOwnPageTables 211 } 212 213 // New returns a new ptrace-based implementation of the platform interface. 214 func New() (*PTrace, error) { 215 stubInitialized.Do(func() { 216 // Initialize the stub. 217 stubInit() 218 219 // Create the master process for the global pool. This must be 220 // done before initializing any other processes. 221 master, err := newSubprocess(createStub) 222 if err != nil { 223 // Should never happen. 224 panic("unable to initialize ptrace master: " + err.Error()) 225 } 226 227 // Set the master on the globalPool. 228 globalPool.master = master 229 }) 230 231 return &PTrace{}, nil 232 } 233 234 // SupportsAddressSpaceIO implements platform.Platform.SupportsAddressSpaceIO. 235 func (*PTrace) SupportsAddressSpaceIO() bool { 236 return false 237 } 238 239 // CooperativelySchedulesAddressSpace implements platform.Platform.CooperativelySchedulesAddressSpace. 240 func (*PTrace) CooperativelySchedulesAddressSpace() bool { 241 return false 242 } 243 244 // MapUnit implements platform.Platform.MapUnit. 245 func (*PTrace) MapUnit() uint64 { 246 // The host kernel manages page tables and arbitrary-sized mappings 247 // have effectively the same cost. 248 return 0 249 } 250 251 // MaxUserAddress returns the first address that may not be used by user 252 // applications. 253 func (*PTrace) MaxUserAddress() hostarch.Addr { 254 return hostarch.Addr(stubStart) 255 } 256 257 // NewAddressSpace returns a new subprocess. 258 func (p *PTrace) NewAddressSpace(any) (platform.AddressSpace, <-chan struct{}, error) { 259 as, err := newSubprocess(globalPool.master.createStub) 260 return as, nil, err 261 } 262 263 type constructor struct{} 264 265 func (*constructor) New(*os.File) (platform.Platform, error) { 266 return New() 267 } 268 269 func (*constructor) OpenDevice(_ string) (*os.File, error) { 270 return nil, nil 271 } 272 273 // Flags implements platform.Constructor.Flags(). 274 func (*constructor) Requirements() platform.Requirements { 275 // TODO(b/75837838): Also set a new PID namespace so that we limit 276 // access to other host processes. 277 return platform.Requirements{ 278 RequiresCapSysPtrace: true, 279 RequiresCurrentPIDNS: true, 280 } 281 } 282 283 func init() { 284 platform.Register("ptrace", &constructor{}) 285 }