github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/platform/systrap/systrap.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 systrap provides a seccomp-based implementation of the platform 16 // interface. 17 // 18 // In a nutshell, it works as follows: 19 // 20 // The creation of a new address space creates a new child processes. 21 // 22 // The creation of a new stub thread creates a new system thread with a 23 // specified address space. To initialize this thread, the following action 24 // will be done: 25 // - install a signal stack which is shared with the Sentry. 26 // - install a signal handler for SYS, BUS, FPE, CHLD, TRAP, SEGV signals. 27 // This signal handler is a key part of the systrap platform. Any stub event 28 // which has to be handled in a privilege mode (by the Sentry) triggers one of 29 // previous signals. The signal handler is running on the separate stack which 30 // is shared with the Sentry. There is the sysmsg structure to synchronize the 31 // Sentry and a stub thread. 32 // - install seccomp filters to trap user system calls. 33 // - send a fake SIGSEGV to stop the thread in the signal handler. 34 // 35 // A context is just a collection of temporary variables. Calling Switch on a 36 // context does the following: 37 // 38 // Set up proper registers and an FPU state on a stub signal frame. 39 // Wake up a stub thread by changing sysmsg->stage and calling FUTEX_WAKE. 40 // Wait for new stub event by polling sysmsg->stage. 41 // 42 // Lock order: 43 // 44 // subprocessPool.mu 45 // subprocess.mu 46 // context.mu 47 // 48 // +checkalignedignore 49 package systrap 50 51 import ( 52 "fmt" 53 "os" 54 "sync" 55 56 "github.com/nicocha30/gvisor-ligolo/pkg/abi/linux" 57 pkgcontext "github.com/nicocha30/gvisor-ligolo/pkg/context" 58 "github.com/nicocha30/gvisor-ligolo/pkg/hostarch" 59 "github.com/nicocha30/gvisor-ligolo/pkg/memutil" 60 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch" 61 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/pgalloc" 62 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform" 63 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform/interrupt" 64 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform/systrap/sysmsg" 65 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform/systrap/usertrap" 66 ) 67 68 var ( 69 // stubStart is the link address for our stub, and determines the 70 // maximum user address. This is valid only after a call to stubInit. 71 // 72 // We attempt to link the stub here, and adjust downward as needed. 73 stubStart uintptr = stubInitAddress 74 75 stubInitProcess uintptr 76 77 // Memory region to store thread specific stacks. 78 stubSysmsgStack uintptr 79 stubSysmsgStart uintptr 80 stubSysmsgEnd uintptr 81 // Memory region to store the contextQueue. 82 stubContextQueueRegion uintptr 83 stubContextQueueRegionLen uintptr 84 // Memory region to store instances of sysmsg.ThreadContext. 85 stubContextRegion uintptr 86 stubContextRegionLen uintptr 87 // The memory blob with precompiled seccomp rules. 88 stubSysmsgRules uintptr 89 stubSysmsgRulesLen uintptr 90 91 stubSpinningThreadQueueAddr uintptr 92 stubSpinningThreadQueueSize uintptr 93 94 // stubROMapEnd is the end address of the read-only stub region that 95 // contains the code and precompiled seccomp rules. 96 stubROMapEnd uintptr 97 98 // stubEnd is the first byte past the end of the stub, as with 99 // stubStart this is valid only after a call to stubInit. 100 stubEnd uintptr 101 102 // stubInitialized controls one-time stub initialization. 103 stubInitialized sync.Once 104 105 // archState stores architecture-specific details used in the platform. 106 archState sysmsg.ArchState 107 ) 108 109 // context is an implementation of the platform context. 110 type context struct { 111 // signalInfo is the signal info, if and when a signal is received. 112 signalInfo linux.SignalInfo 113 114 // interrupt is the interrupt context. 115 interrupt interrupt.Forwarder 116 117 // sharedContext is everything related to this context that is resident in 118 // shared memory with the stub thread. 119 // sharedContext is only accessed on the Task goroutine, therefore it is not 120 // mutex protected. 121 sharedContext *sharedContext 122 123 // mu protects the following fields. 124 mu sync.Mutex 125 126 // If lastFaultSP is non-nil, the last context switch was due to a fault 127 // received while executing lastFaultSP. Only context.Switch may set 128 // lastFaultSP to a non-nil value. 129 lastFaultSP *subprocess 130 131 // lastFaultAddr is the last faulting address; this is only meaningful if 132 // lastFaultSP is non-nil. 133 lastFaultAddr hostarch.Addr 134 135 // lastFaultIP is the address of the last faulting instruction; 136 // this is also only meaningful if lastFaultSP is non-nil. 137 lastFaultIP hostarch.Addr 138 139 // needRestoreFPState indicates that the FPU state has been changed by 140 // the Sentry and has to be updated on the stub thread. 141 needRestoreFPState bool 142 143 // needToPullFullState indicates that the Sentry doesn't have a full 144 // state of the thread. 145 needToPullFullState bool 146 } 147 148 // PullFullState implements platform.Context.PullFullState. 149 func (c *context) PullFullState(as platform.AddressSpace, ac *arch.Context64) error { 150 if !c.needToPullFullState { 151 return nil 152 } 153 s := as.(*subprocess) 154 if err := s.PullFullState(c, ac); err != nil { 155 return err 156 } 157 c.needToPullFullState = false 158 return nil 159 } 160 161 // FullStateChanged implements platform.Context.FullStateChanged. 162 func (c *context) FullStateChanged() { 163 c.needRestoreFPState = true 164 c.needToPullFullState = false 165 } 166 167 // Switch runs the provided context in the given address space. 168 func (c *context) Switch(ctx pkgcontext.Context, mm platform.MemoryManager, ac *arch.Context64, cpu int32) (*linux.SignalInfo, hostarch.AccessType, error) { 169 as := mm.AddressSpace() 170 s := as.(*subprocess) 171 if err := s.activateContext(c); err != nil { 172 return nil, hostarch.NoAccess, err 173 } 174 175 restart: 176 isSyscall, needPatch, err := s.switchToApp(c, ac) 177 if err != nil { 178 return nil, hostarch.NoAccess, err 179 } 180 if needPatch { 181 restart, _ := s.usertrap.PatchSyscall(ctx, ac, mm) 182 if restart { 183 goto restart 184 } 185 } 186 if !isSyscall && linux.Signal(c.signalInfo.Signo) == linux.SIGILL { 187 err := s.usertrap.HandleFault(ctx, ac, mm) 188 if err == usertrap.ErrFaultSyscall { 189 isSyscall = true 190 } else if err == usertrap.ErrFaultRestart { 191 goto restart 192 } else if err != nil { 193 ctx.Warningf("usertrap.HandleFault failed: %v", err) 194 } 195 } 196 var ( 197 faultSP *subprocess 198 faultAddr hostarch.Addr 199 faultIP hostarch.Addr 200 ) 201 if !isSyscall && linux.Signal(c.signalInfo.Signo) == linux.SIGSEGV { 202 faultSP = s 203 faultAddr = hostarch.Addr(c.signalInfo.Addr()) 204 faultIP = hostarch.Addr(ac.IP()) 205 } 206 207 // Update the context to reflect the outcome of this context switch. 208 c.mu.Lock() 209 lastFaultSP := c.lastFaultSP 210 lastFaultAddr := c.lastFaultAddr 211 lastFaultIP := c.lastFaultIP 212 // At this point, c may not yet be in s.faultedContexts, so c.lastFaultSP won't 213 // be updated by s.Unmap(). This is fine; we only need to synchronize with 214 // calls to s.Unmap() that occur after the handling of this fault. 215 c.lastFaultSP = faultSP 216 c.lastFaultAddr = faultAddr 217 c.lastFaultIP = faultIP 218 c.mu.Unlock() 219 220 // Update subprocesses to reflect the outcome of this context switch. 221 if lastFaultSP != faultSP { 222 if lastFaultSP != nil { 223 lastFaultSP.mu.Lock() 224 delete(lastFaultSP.faultedContexts, c) 225 lastFaultSP.mu.Unlock() 226 } 227 if faultSP != nil { 228 faultSP.mu.Lock() 229 faultSP.faultedContexts[c] = struct{}{} 230 faultSP.mu.Unlock() 231 } 232 } 233 234 if isSyscall { 235 return nil, hostarch.NoAccess, nil 236 } 237 238 si := c.signalInfo 239 if faultSP == nil { 240 // Non-fault signal. 241 return &si, hostarch.NoAccess, platform.ErrContextSignal 242 } 243 244 // See if this can be handled as a CPUID exception. 245 if linux.Signal(si.Signo) == linux.SIGSEGV && platform.TryCPUIDEmulate(ctx, mm, ac) { 246 goto restart 247 } 248 249 // Got a page fault. Ideally, we'd get real fault type here, but ptrace 250 // doesn't expose this information. Instead, we use a simple heuristic: 251 // 252 // It was an instruction fault iff the faulting addr == instruction 253 // pointer. 254 // 255 // It was a write fault if the fault is immediately repeated. 256 at := hostarch.Read 257 if faultAddr == faultIP { 258 at.Execute = true 259 } 260 if lastFaultSP == faultSP && 261 lastFaultAddr == faultAddr && 262 lastFaultIP == faultIP { 263 at.Write = true 264 } 265 266 // Handle as a signal. 267 return &si, at, platform.ErrContextSignal 268 } 269 270 // Interrupt interrupts the running guest application associated with this context. 271 func (c *context) Interrupt() { 272 c.interrupt.NotifyInterrupt() 273 } 274 275 // Release releases all platform resources used by the context. 276 func (c *context) Release() { 277 if c.sharedContext != nil { 278 c.sharedContext.release() 279 c.sharedContext = nil 280 } 281 } 282 283 // PrepareSleep implements platform.Context.platform.PrepareSleep. 284 func (c *context) PrepareSleep() { 285 ctx := c.sharedContext 286 if ctx == nil { 287 return 288 } 289 if !ctx.sleeping { 290 ctx.sleeping = true 291 ctx.subprocess.decAwakeContexts() 292 } 293 } 294 295 // Systrap represents a collection of seccomp subprocesses. 296 type Systrap struct { 297 platform.NoCPUPreemptionDetection 298 platform.UseHostGlobalMemoryBarrier 299 platform.DoesNotOwnPageTables 300 301 // memoryFile is used to create a stub sysmsg stack 302 // which is shared with the Sentry. 303 memoryFile *pgalloc.MemoryFile 304 } 305 306 // MinUserAddress implements platform.MinUserAddress. 307 func (*Systrap) MinUserAddress() hostarch.Addr { 308 return platform.SystemMMapMinAddr() 309 } 310 311 // New returns a new seccomp-based implementation of the platform interface. 312 func New() (*Systrap, error) { 313 // CPUID information has been initialized at this point. 314 archState.Init() 315 316 mf, err := createMemoryFile() 317 if err != nil { 318 return nil, err 319 } 320 321 stubInitialized.Do(func() { 322 // Initialize the stub. 323 stubInit() 324 325 // Create the source process for the global pool. This must be 326 // done before initializing any other processes. 327 source, err := newSubprocess(createStub, mf) 328 if err != nil { 329 // Should never happen. 330 panic("unable to initialize systrap source: " + err.Error()) 331 } 332 // The source subprocess is never released explicitly by a MM. 333 source.DecRef(nil) 334 335 globalPool.source = source 336 337 initSysmsgThreadPriority() 338 }) 339 340 return &Systrap{memoryFile: mf}, nil 341 } 342 343 // SupportsAddressSpaceIO implements platform.Platform.SupportsAddressSpaceIO. 344 func (*Systrap) SupportsAddressSpaceIO() bool { 345 return false 346 } 347 348 // CooperativelySchedulesAddressSpace implements platform.Platform.CooperativelySchedulesAddressSpace. 349 func (*Systrap) CooperativelySchedulesAddressSpace() bool { 350 return false 351 } 352 353 // MapUnit implements platform.Platform.MapUnit. 354 func (*Systrap) MapUnit() uint64 { 355 // The host kernel manages page tables and arbitrary-sized mappings 356 // have effectively the same cost. 357 return 0 358 } 359 360 // MaxUserAddress returns the first address that may not be used by user 361 // applications. 362 func (*Systrap) MaxUserAddress() hostarch.Addr { 363 return hostarch.Addr(maxStubUserAddress) 364 } 365 366 // NewAddressSpace returns a new subprocess. 367 func (p *Systrap) NewAddressSpace(any) (platform.AddressSpace, <-chan struct{}, error) { 368 as, err := newSubprocess(globalPool.source.createStub, p.memoryFile) 369 return as, nil, err 370 } 371 372 // NewContext returns an interruptible context. 373 func (*Systrap) NewContext(ctx pkgcontext.Context) platform.Context { 374 return &context{ 375 needRestoreFPState: true, 376 needToPullFullState: false, 377 } 378 } 379 380 type constructor struct{} 381 382 func (*constructor) New(_ *os.File) (platform.Platform, error) { 383 return New() 384 } 385 386 func (*constructor) OpenDevice(_ string) (*os.File, error) { 387 return nil, nil 388 } 389 390 // Requirements implements platform.Constructor.Requirements(). 391 func (*constructor) Requirements() platform.Requirements { 392 // TODO(b/75837838): Also set a new PID namespace so that we limit 393 // access to other host processes. 394 return platform.Requirements{ 395 RequiresCapSysPtrace: true, 396 RequiresCurrentPIDNS: true, 397 } 398 } 399 400 func init() { 401 platform.Register("systrap", &constructor{}) 402 } 403 404 func createMemoryFile() (*pgalloc.MemoryFile, error) { 405 const memfileName = "systrap-memory" 406 fd, err := memutil.CreateMemFD(memfileName, 0) 407 if err != nil { 408 return nil, fmt.Errorf("error creating memfd: %v", err) 409 } 410 memfile := os.NewFile(uintptr(fd), memfileName) 411 mf, err := pgalloc.NewMemoryFile(memfile, pgalloc.MemoryFileOpts{}) 412 if err != nil { 413 memfile.Close() 414 return nil, fmt.Errorf("error creating pgalloc.MemoryFile: %v", err) 415 } 416 return mf, nil 417 }