github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/platform/systrap/subprocess.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 16 17 import ( 18 "fmt" 19 "os" 20 "runtime" 21 "sync" 22 "sync/atomic" 23 24 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 25 "github.com/ttpreport/gvisor-ligolo/pkg/hostarch" 26 "github.com/ttpreport/gvisor-ligolo/pkg/log" 27 "github.com/ttpreport/gvisor-ligolo/pkg/pool" 28 "github.com/ttpreport/gvisor-ligolo/pkg/seccomp" 29 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/arch" 30 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/memmap" 31 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/pgalloc" 32 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/platform" 33 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/platform/systrap/sysmsg" 34 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/platform/systrap/usertrap" 35 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/usage" 36 "golang.org/x/sys/unix" 37 ) 38 39 var ( 40 // globalPool tracks all subprocesses in various state: active or available for 41 // reuse. 42 globalPool = subprocessPool{} 43 44 // maximumUserAddress is the largest possible user address. 45 maximumUserAddress = linux.TaskSize 46 47 // stubInitAddress is the initial attempt link address for the stub. 48 stubInitAddress = linux.TaskSize 49 50 // maxRandomOffsetOfStubAddress is the maximum offset for randomizing a 51 // stub address. It is set to the default value of mm.mmap_rnd_bits. 52 // 53 // Note: Tools like ThreadSanitizer don't like when the memory layout 54 // is changed significantly. 55 maxRandomOffsetOfStubAddress = (linux.TaskSize >> 7) & ^(uintptr(hostarch.PageSize) - 1) 56 57 // maxStubUserAddress is the largest possible user address for 58 // processes running inside gVisor. It is fixed because 59 // * we don't want to reveal a stub address. 60 // * it has to be the same across checkpoint/restore. 61 maxStubUserAddress = maximumUserAddress - maxRandomOffsetOfStubAddress 62 ) 63 64 // Linux kernel errnos which "should never be seen by user programs", but will 65 // be revealed to ptrace syscall exit tracing. 66 // 67 // These constants are only used in subprocess.go. 68 const ( 69 ERESTARTSYS = unix.Errno(512) 70 ERESTARTNOINTR = unix.Errno(513) 71 ERESTARTNOHAND = unix.Errno(514) 72 ) 73 74 // thread is a traced thread; it is a thread identifier. 75 // 76 // This is a convenience type for defining ptrace operations. 77 type thread struct { 78 tgid int32 79 tid int32 80 81 // sysmsgStackID is a stack ID in subprocess.sysmsgStackPool. 82 sysmsgStackID uint64 83 84 // initRegs are the initial registers for the first thread. 85 // 86 // These are used for the register set for system calls. 87 initRegs arch.Registers 88 } 89 90 // requestThread is used to request a new sysmsg thread. A thread identifier will 91 // be sent into the thread channel. 92 type requestThread struct { 93 thread chan *thread 94 } 95 96 // requestStub is used to request a new stub process. 97 type requestStub struct { 98 done chan *thread 99 } 100 101 // maxSysmsgThreads specifies the maximum number of system threads that a 102 // subprocess can create in context decoupled mode. 103 // TODO(b/268366549): Replace maxSystemThreads below. 104 var maxSysmsgThreads = runtime.GOMAXPROCS(0) 105 106 const ( 107 // maxSystemThreads specifies the maximum number of system threads that a 108 // subprocess may create in order to process the contexts. 109 maxSystemThreads = 4096 110 // maxGuestContexts specifies the maximum number of task contexts that a 111 // subprocess can handle. 112 maxGuestContexts = 4095 113 // invalidContextID specifies an invalid ID. 114 invalidContextID uint32 = 0xfefefefe 115 // invalidThreadID is used to indicate that a context is not being worked on by 116 // any sysmsg thread. 117 invalidThreadID uint32 = 0xfefefefe 118 ) 119 120 // subprocess is a collection of threads being traced. 121 type subprocess struct { 122 platform.NoAddressSpaceIO 123 subprocessRefs 124 125 // requests is used to signal creation of new threads. 126 requests chan any 127 128 // sysmsgInitRegs is used to reset sysemu regs. 129 sysmsgInitRegs arch.Registers 130 131 // mu protects the following fields. 132 mu sync.Mutex 133 134 // faultedContexts is the set of contexts for which it's possible that 135 // context.lastFaultSP == this subprocess. 136 faultedContexts map[*context]struct{} 137 138 // sysmsgStackPool is a pool of available sysmsg stacks. 139 sysmsgStackPool pool.Pool 140 141 // threadContextPool is a pool of available sysmsg.ThreadContext IDs. 142 threadContextPool pool.Pool 143 144 // threadContextRegion defines the ThreadContext memory region start 145 // within the sentry address space. 146 threadContextRegion uintptr 147 148 // memoryFile is used to allocate a sysmsg stack which is shared 149 // between a stub process and the Sentry. 150 memoryFile *pgalloc.MemoryFile 151 152 // usertrap is the state of the usertrap table which contains syscall 153 // trampolines. 154 usertrap *usertrap.State 155 156 syscallThreadMu sync.Mutex 157 syscallThread *syscallThread 158 159 // sysmsgThreadsMu protects sysmsgThreads and numSysmsgThreads 160 sysmsgThreadsMu sync.Mutex 161 // sysmsgThreads is a collection of all active sysmsg threads in the 162 // subprocess. 163 sysmsgThreads map[uint32]*sysmsgThread 164 // numSysmsgThreads counts the number of active sysmsg threads; we use a 165 // counter instead of using len(sysmsgThreads) because we need to synchronize 166 // how many threads get created _before_ the creation happens. 167 numSysmsgThreads int 168 169 // contextQueue is a queue of all contexts that are ready to switch back to 170 // user mode. 171 contextQueue *contextQueue 172 } 173 174 func (s *subprocess) initSyscallThread(ptraceThread *thread) error { 175 s.syscallThreadMu.Lock() 176 defer s.syscallThreadMu.Unlock() 177 178 id, ok := s.sysmsgStackPool.Get() 179 if !ok { 180 panic("unable to allocate a sysmsg stub thread") 181 } 182 183 ptraceThread.sysmsgStackID = id 184 t := syscallThread{ 185 subproc: s, 186 thread: ptraceThread, 187 } 188 189 if err := t.init(); err != nil { 190 panic(fmt.Sprintf("failed to create a syscall thread")) 191 } 192 s.syscallThread = &t 193 194 s.syscallThread.detach() 195 196 return nil 197 } 198 199 // handlePtraceSyscallRequest executes system calls that can't be run via 200 // syscallThread without using ptrace. Look at the description of syscallThread 201 // to get more details about its limitations. 202 func (s *subprocess) handlePtraceSyscallRequest(req any) { 203 s.syscallThreadMu.Lock() 204 defer s.syscallThreadMu.Unlock() 205 runtime.LockOSThread() 206 defer runtime.UnlockOSThread() 207 s.syscallThread.attach() 208 defer s.syscallThread.detach() 209 210 ptraceThread := s.syscallThread.thread 211 212 switch req.(type) { 213 case requestThread: 214 r := req.(requestThread) 215 t, err := ptraceThread.clone() 216 if err != nil { 217 // Should not happen: not recoverable. 218 panic(fmt.Sprintf("error initializing first thread: %v", err)) 219 } 220 221 // Since the new thread was created with 222 // clone(CLONE_PTRACE), it will begin execution with 223 // SIGSTOP pending and with this thread as its tracer. 224 // (Hopefully nobody tgkilled it with a signal < 225 // SIGSTOP before the SIGSTOP was delivered, in which 226 // case that signal would be delivered before SIGSTOP.) 227 if sig := t.wait(stopped); sig != unix.SIGSTOP { 228 panic(fmt.Sprintf("error waiting for new clone: expected SIGSTOP, got %v", sig)) 229 } 230 231 id, ok := s.sysmsgStackPool.Get() 232 if !ok { 233 panic("unable to allocate a sysmsg stub thread") 234 } 235 t.sysmsgStackID = id 236 237 if _, _, e := unix.RawSyscall(unix.SYS_TGKILL, uintptr(t.tgid), uintptr(t.tid), uintptr(unix.SIGSTOP)); e != 0 { 238 panic(fmt.Sprintf("tkill failed: %v", e)) 239 } 240 241 // Detach the thread. 242 t.detach() 243 t.initRegs = ptraceThread.initRegs 244 245 // Return the thread. 246 r.thread <- t 247 case requestStub: 248 r := req.(requestStub) 249 t, err := ptraceThread.createStub() 250 if err != nil { 251 panic(fmt.Sprintf("unable to create a stub process: %s", err)) 252 } 253 r.done <- t 254 255 } 256 } 257 258 // newSubprocess returns a usable subprocess. 259 // 260 // This will either be a newly created subprocess, or one from the global pool. 261 // The create function will be called in the latter case, which is guaranteed 262 // to happen with the runtime thread locked. 263 func newSubprocess(create func() (*thread, error), memoryFile *pgalloc.MemoryFile) (*subprocess, error) { 264 if sp := globalPool.fetchAvailable(); sp != nil { 265 sp.subprocessRefs.InitRefs() 266 sp.usertrap = usertrap.New() 267 return sp, nil 268 } 269 270 // The following goroutine is responsible for creating the first traced 271 // thread, and responding to requests to make additional threads in the 272 // traced process. The process will be killed and reaped when the 273 // request channel is closed, which happens in Release below. 274 requests := make(chan any) 275 276 // Ready. 277 sp := &subprocess{ 278 requests: requests, 279 faultedContexts: make(map[*context]struct{}), 280 sysmsgStackPool: pool.Pool{Start: 0, Limit: maxSystemThreads}, 281 threadContextPool: pool.Pool{Start: 0, Limit: maxGuestContexts}, 282 memoryFile: memoryFile, 283 sysmsgThreads: make(map[uint32]*sysmsgThread), 284 } 285 sp.subprocessRefs.InitRefs() 286 runtime.LockOSThread() 287 defer runtime.UnlockOSThread() 288 289 // Initialize the syscall thread. 290 ptraceThread, err := create() 291 if err != nil { 292 return nil, err 293 } 294 sp.sysmsgInitRegs = ptraceThread.initRegs 295 296 if err := sp.initSyscallThread(ptraceThread); err != nil { 297 return nil, err 298 } 299 300 go func() { // S/R-SAFE: Platform-related. 301 302 // Wait for requests to create threads. 303 for req := range requests { 304 sp.handlePtraceSyscallRequest(req) 305 } 306 307 // Requests should never be closed. 308 panic("unreachable") 309 }() 310 311 sp.unmap() 312 sp.usertrap = usertrap.New() 313 sp.mapSharedRegions() 314 sp.mapPrivateRegions() 315 316 // Create the initial sysmsg thread. 317 atomic.AddUint32(&sp.contextQueue.numThreadsToWakeup, 1) 318 if err := sp.createSysmsgThread(); err != nil { 319 return nil, err 320 } 321 sp.numSysmsgThreads++ 322 323 return sp, nil 324 } 325 326 // mapSharedRegions maps the shared regions that are used between the subprocess 327 // and ALL of the subsequently created sysmsg threads into both the sentry and 328 // the syscall thread. 329 // 330 // Should be called before any sysmsg threads are created. 331 // Initializes s.contextQueue and s.threadContextRegion. 332 func (s *subprocess) mapSharedRegions() { 333 if s.contextQueue != nil || s.threadContextRegion != 0 { 334 panic("contextQueue or threadContextRegion was already initialized") 335 } 336 337 opts := pgalloc.AllocOpts{ 338 Kind: usage.System, 339 Dir: pgalloc.TopDown, 340 } 341 342 // Map shared regions into the sentry. 343 contextQueueFR, contextQueue := mmapContextQueueForSentry(s.memoryFile, opts) 344 contextQueue.init() 345 346 // Map thread context region into the syscall thread. 347 _, err := s.syscallThread.syscall( 348 unix.SYS_MMAP, 349 arch.SyscallArgument{Value: uintptr(stubContextQueueRegion)}, 350 arch.SyscallArgument{Value: uintptr(contextQueueFR.Length())}, 351 arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)}, 352 arch.SyscallArgument{Value: uintptr(unix.MAP_SHARED | unix.MAP_FILE | unix.MAP_FIXED)}, 353 arch.SyscallArgument{Value: uintptr(s.memoryFile.FD())}, 354 arch.SyscallArgument{Value: uintptr(contextQueueFR.Start)}) 355 if err != nil { 356 panic(fmt.Sprintf("failed to mmap context queue region into syscall thread: %v", err)) 357 } 358 359 s.contextQueue = contextQueue 360 361 // Map thread context region into the sentry. 362 threadContextFR, err := s.memoryFile.Allocate(uint64(stubContextRegionLen), opts) 363 if err != nil { 364 panic(fmt.Sprintf("failed to allocate a new subprocess context memory region")) 365 } 366 sentryThreadContextRegionAddr, _, errno := unix.RawSyscall6( 367 unix.SYS_MMAP, 368 0, 369 uintptr(threadContextFR.Length()), 370 unix.PROT_WRITE|unix.PROT_READ, 371 unix.MAP_SHARED|unix.MAP_FILE, 372 uintptr(s.memoryFile.FD()), uintptr(threadContextFR.Start)) 373 if errno != 0 { 374 panic(fmt.Sprintf("mmap failed for subprocess context memory region: %v", errno)) 375 } 376 377 // Map thread context region into the syscall thread. 378 if _, err := s.syscallThread.syscall( 379 unix.SYS_MMAP, 380 arch.SyscallArgument{Value: uintptr(stubContextRegion)}, 381 arch.SyscallArgument{Value: uintptr(threadContextFR.Length())}, 382 arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)}, 383 arch.SyscallArgument{Value: uintptr(unix.MAP_SHARED | unix.MAP_FILE | unix.MAP_FIXED)}, 384 arch.SyscallArgument{Value: uintptr(s.memoryFile.FD())}, 385 arch.SyscallArgument{Value: uintptr(threadContextFR.Start)}); err != nil { 386 panic(fmt.Sprintf("failed to mmap context queue region into syscall thread: %v", err)) 387 } 388 389 s.threadContextRegion = sentryThreadContextRegionAddr 390 } 391 392 func (s *subprocess) mapPrivateRegions() { 393 _, err := s.syscallThread.syscall( 394 unix.SYS_MMAP, 395 arch.SyscallArgument{Value: uintptr(stubSpinningThreadQueueAddr)}, 396 arch.SyscallArgument{Value: uintptr(sysmsg.SpinningQueueMemSize)}, 397 arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)}, 398 arch.SyscallArgument{Value: uintptr(unix.MAP_PRIVATE | unix.MAP_ANONYMOUS | unix.MAP_FIXED)}, 399 arch.SyscallArgument{Value: 0}, 400 arch.SyscallArgument{Value: 0}) 401 if err != nil { 402 panic(fmt.Sprintf("failed to mmap spinning queue region into syscall thread: %v", err)) 403 } 404 } 405 406 // unmap unmaps non-stub regions of the process. 407 // 408 // This will panic on failure (which should never happen). 409 func (s *subprocess) unmap() { 410 s.Unmap(0, uint64(stubStart)) 411 if maximumUserAddress != stubEnd { 412 s.Unmap(hostarch.Addr(stubEnd), uint64(maximumUserAddress-stubEnd)) 413 } 414 } 415 416 // Release kills the subprocess. 417 // 418 // Just kidding! We can't safely co-ordinate the detaching of all the 419 // tracees (since the tracers are random runtime threads, and the process 420 // won't exit until tracers have been notifier). 421 // 422 // Therefore we simply unmap everything in the subprocess and return it to the 423 // globalPool. This has the added benefit of reducing creation time for new 424 // subprocesses. 425 func (s *subprocess) Release() { 426 s.unmap() 427 s.DecRef(s.release) 428 } 429 430 // release returns the subprocess to the global pool. 431 func (s *subprocess) release() { 432 globalPool.markAvailable(s) 433 } 434 435 // newThread creates a new traced thread. 436 // 437 // Precondition: the OS thread must be locked. 438 func (s *subprocess) newThread() *thread { 439 // Ask the first thread to create a new one. 440 var r requestThread 441 r.thread = make(chan *thread) 442 s.requests <- r 443 t := <-r.thread 444 445 // Attach the subprocess to this one. 446 t.attach() 447 448 // Return the new thread, which is now bound. 449 return t 450 } 451 452 // attach attaches to the thread. 453 func (t *thread) attach() { 454 if _, _, errno := unix.RawSyscall6(unix.SYS_PTRACE, unix.PTRACE_ATTACH, uintptr(t.tid), 0, 0, 0, 0); errno != 0 { 455 panic(fmt.Sprintf("unable to attach: %v", errno)) 456 } 457 458 // PTRACE_ATTACH sends SIGSTOP, and wakes the tracee if it was already 459 // stopped from the SIGSTOP queued by CLONE_PTRACE (see inner loop of 460 // newSubprocess), so we always expect to see signal-delivery-stop with 461 // SIGSTOP. 462 if sig := t.wait(stopped); sig != unix.SIGSTOP { 463 panic(fmt.Sprintf("wait failed: expected SIGSTOP, got %v", sig)) 464 } 465 466 // Initialize options. 467 t.init() 468 } 469 470 func (t *thread) grabInitRegs() { 471 // Grab registers. 472 // 473 // Note that we adjust the current register RIP value to be just before 474 // the current system call executed. This depends on the definition of 475 // the stub itself. 476 if err := t.getRegs(&t.initRegs); err != nil { 477 panic(fmt.Sprintf("ptrace get regs failed: %v", err)) 478 } 479 t.adjustInitRegsRip() 480 t.initRegs.SetStackPointer(0) 481 } 482 483 // detach detaches from the thread. 484 // 485 // Because the SIGSTOP is not suppressed, the thread will enter group-stop. 486 func (t *thread) detach() { 487 if _, _, errno := unix.RawSyscall6(unix.SYS_PTRACE, unix.PTRACE_DETACH, uintptr(t.tid), 0, uintptr(unix.SIGSTOP), 0, 0); errno != 0 { 488 panic(fmt.Sprintf("can't detach new clone: %v", errno)) 489 } 490 } 491 492 // waitOutcome is used for wait below. 493 type waitOutcome int 494 495 const ( 496 // stopped indicates that the process was stopped. 497 stopped waitOutcome = iota 498 499 // killed indicates that the process was killed. 500 killed 501 ) 502 503 func (t *thread) Debugf(format string, v ...any) { 504 prefix := fmt.Sprintf("%8d:", t.tid) 505 log.DebugfAtDepth(1, prefix+format, v...) 506 } 507 508 func (t *thread) dumpAndPanic(message string) { 509 var regs arch.Registers 510 message += "\n" 511 if err := t.getRegs(®s); err == nil { 512 message += dumpRegs(®s) 513 } else { 514 log.Warningf("unable to get registers: %v", err) 515 } 516 message += fmt.Sprintf("stubStart\t = %016x\n", stubStart) 517 panic(message) 518 } 519 520 func (t *thread) dumpRegs(message string) { 521 var regs arch.Registers 522 message += "\n" 523 if err := t.getRegs(®s); err == nil { 524 message += dumpRegs(®s) 525 } else { 526 log.Warningf("unable to get registers: %v", err) 527 } 528 log.Infof("%s", message) 529 } 530 531 func (t *thread) unexpectedStubExit() { 532 msg, err := t.getEventMessage() 533 status := unix.WaitStatus(msg) 534 if status.Signaled() && status.Signal() == unix.SIGKILL { 535 // SIGKILL can be only sent by a user or OOM-killer. In both 536 // these cases, we don't need to panic. There is no reasons to 537 // think that something wrong in gVisor. 538 log.Warningf("The ptrace stub process %v has been killed by SIGKILL.", t.tgid) 539 pid := os.Getpid() 540 unix.Tgkill(pid, pid, unix.Signal(unix.SIGKILL)) 541 } 542 t.dumpAndPanic(fmt.Sprintf("wait failed: the process %d:%d exited: %x (err %v)", t.tgid, t.tid, msg, err)) 543 } 544 545 // wait waits for a stop event. 546 // 547 // Precondition: outcome is a valid waitOutcome. 548 func (t *thread) wait(outcome waitOutcome) unix.Signal { 549 var status unix.WaitStatus 550 551 for { 552 r, err := unix.Wait4(int(t.tid), &status, unix.WALL|unix.WUNTRACED, nil) 553 if err == unix.EINTR || err == unix.EAGAIN { 554 // Wait was interrupted; wait again. 555 continue 556 } else if err != nil { 557 panic(fmt.Sprintf("ptrace wait failed: %v", err)) 558 } 559 if int(r) != int(t.tid) { 560 panic(fmt.Sprintf("ptrace wait returned %v, expected %v", r, t.tid)) 561 } 562 switch outcome { 563 case stopped: 564 if !status.Stopped() { 565 t.dumpAndPanic(fmt.Sprintf("ptrace status unexpected: got %v, wanted stopped", status)) 566 } 567 stopSig := status.StopSignal() 568 if stopSig == 0 { 569 continue // Spurious stop. 570 } 571 if stopSig == unix.SIGTRAP { 572 if status.TrapCause() == unix.PTRACE_EVENT_EXIT { 573 t.unexpectedStubExit() 574 } 575 // Re-encode the trap cause the way it's expected. 576 return stopSig | unix.Signal(status.TrapCause()<<8) 577 } 578 // Not a trap signal. 579 return stopSig 580 case killed: 581 if !status.Exited() && !status.Signaled() { 582 t.dumpAndPanic(fmt.Sprintf("ptrace status unexpected: got %v, wanted exited", status)) 583 } 584 return unix.Signal(status.ExitStatus()) 585 default: 586 // Should not happen. 587 t.dumpAndPanic(fmt.Sprintf("unknown outcome: %v", outcome)) 588 } 589 } 590 } 591 592 // destroy kills the thread. 593 // 594 // Note that this should not be used in the general case; the death of threads 595 // will typically cause the death of the parent. This is a utility method for 596 // manually created threads. 597 func (t *thread) destroy() { 598 t.detach() 599 unix.Tgkill(int(t.tgid), int(t.tid), unix.Signal(unix.SIGKILL)) 600 t.wait(killed) 601 } 602 603 // init initializes trace options. 604 func (t *thread) init() { 605 // Set the TRACESYSGOOD option to differentiate real SIGTRAP. 606 // set PTRACE_O_EXITKILL to ensure that the unexpected exit of the 607 // sentry will immediately kill the associated stubs. 608 _, _, errno := unix.RawSyscall6( 609 unix.SYS_PTRACE, 610 unix.PTRACE_SETOPTIONS, 611 uintptr(t.tid), 612 0, 613 unix.PTRACE_O_TRACESYSGOOD|unix.PTRACE_O_TRACEEXIT|unix.PTRACE_O_EXITKILL, 614 0, 0) 615 if errno != 0 { 616 panic(fmt.Sprintf("ptrace set options failed: %v", errno)) 617 } 618 } 619 620 // syscall executes a system call cycle in the traced context. 621 // 622 // This is _not_ for use by application system calls, rather it is for use when 623 // a system call must be injected into the remote context (e.g. mmap, munmap). 624 // Note that clones are handled separately. 625 func (t *thread) syscall(regs *arch.Registers) (uintptr, error) { 626 // Set registers. 627 if err := t.setRegs(regs); err != nil { 628 panic(fmt.Sprintf("ptrace set regs failed: %v", err)) 629 } 630 631 for { 632 // Execute the syscall instruction. The task has to stop on the 633 // trap instruction which is right after the syscall 634 // instruction. 635 if _, _, errno := unix.RawSyscall6(unix.SYS_PTRACE, unix.PTRACE_CONT, uintptr(t.tid), 0, 0, 0, 0); errno != 0 { 636 panic(fmt.Sprintf("ptrace syscall-enter failed: %v", errno)) 637 } 638 639 sig := t.wait(stopped) 640 if sig == unix.SIGTRAP { 641 // Reached syscall-enter-stop. 642 break 643 } else { 644 // Some other signal caused a thread stop; ignore. 645 if sig != unix.SIGSTOP && sig != unix.SIGCHLD { 646 log.Warningf("The thread %d:%d has been interrupted by %d", t.tgid, t.tid, sig) 647 } 648 continue 649 } 650 } 651 652 // Grab registers. 653 if err := t.getRegs(regs); err != nil { 654 panic(fmt.Sprintf("ptrace get regs failed: %v", err)) 655 } 656 return syscallReturnValue(regs) 657 } 658 659 // syscallIgnoreInterrupt ignores interrupts on the system call thread and 660 // restarts the syscall if the kernel indicates that should happen. 661 func (t *thread) syscallIgnoreInterrupt( 662 initRegs *arch.Registers, 663 sysno uintptr, 664 args ...arch.SyscallArgument) (uintptr, error) { 665 for { 666 regs := createSyscallRegs(initRegs, sysno, args...) 667 rval, err := t.syscall(®s) 668 switch err { 669 case ERESTARTSYS: 670 continue 671 case ERESTARTNOINTR: 672 continue 673 case ERESTARTNOHAND: 674 continue 675 default: 676 return rval, err 677 } 678 } 679 } 680 681 // NotifyInterrupt implements interrupt.Receiver.NotifyInterrupt. 682 func (t *thread) NotifyInterrupt() { 683 unix.Tgkill(int(t.tgid), int(t.tid), unix.Signal(platform.SignalInterrupt)) 684 } 685 686 func (s *subprocess) incAwakeContexts() { 687 nr := atomic.AddUint32(&s.contextQueue.numAwakeContexts, 1) 688 if nr > uint32(maxSysmsgThreads) { 689 return 690 } 691 nr = nrMaxAwakeStubThreads.Add(1) 692 if nr > fastPathContextLimit { 693 dispatcher.disableStubFastPath() 694 } 695 } 696 697 func (s *subprocess) decAwakeContexts() { 698 nr := atomic.AddUint32(&s.contextQueue.numAwakeContexts, ^uint32(0)) 699 if nr >= uint32(maxSysmsgThreads) { 700 return 701 } 702 nrMaxAwakeStubThreads.Add(^uint32(0)) 703 } 704 705 // switchToApp is called from the main SwitchToApp entrypoint. 706 // 707 // This function returns true on a system call, false on a signal. 708 // The second return value is true if a syscall instruction can be replaced on 709 // a function call. 710 func (s *subprocess) switchToApp(c *context, ac *arch.Context64) (isSyscall bool, shouldPatchSyscall bool, err error) { 711 // Reset necessary registers. 712 regs := &ac.StateData().Regs 713 s.resetSysemuRegs(regs) 714 ctx := c.sharedContext 715 ctx.shared.Regs = regs.PtraceRegs 716 restoreArchSpecificState(ctx.shared, ac) 717 718 // Check for interrupts, and ensure that future interrupts signal the context. 719 if !c.interrupt.Enable(c.sharedContext) { 720 // Pending interrupt; simulate. 721 ctx.clearInterrupt() 722 c.signalInfo = linux.SignalInfo{Signo: int32(platform.SignalInterrupt)} 723 return false, false, nil 724 } 725 defer func() { 726 ctx.clearInterrupt() 727 c.interrupt.Disable() 728 }() 729 730 restoreFPState(ctx, c, ac) 731 732 // Place the context onto the context queue. 733 if ctx.sleeping { 734 ctx.sleeping = false 735 s.incAwakeContexts() 736 } 737 stubFastPathEnabled := dispatcher.stubFastPathEnabled() 738 ctx.setState(sysmsg.ContextStateNone) 739 s.contextQueue.add(ctx, stubFastPathEnabled) 740 s.waitOnState(ctx, stubFastPathEnabled) 741 742 // Check if there's been an error. 743 threadID := ctx.threadID() 744 if threadID != invalidThreadID { 745 if sysThread, ok := s.sysmsgThreads[threadID]; ok && sysThread.msg.Err != 0 { 746 msg := sysThread.msg 747 panic(fmt.Sprintf("stub thread %d failed: err 0x%x line %d: %s", sysThread.thread.tid, msg.Err, msg.Line, msg)) 748 } 749 log.Warningf("systrap: found unexpected ThreadContext.ThreadID field, expected %d found %d", invalidThreadID, threadID) 750 } 751 752 // Copy register state locally. 753 regs.PtraceRegs = ctx.shared.Regs 754 retrieveArchSpecificState(ctx.shared, ac) 755 c.needToPullFullState = true 756 // We have a signal. We verify however, that the signal was 757 // either delivered from the kernel or from this process. We 758 // don't respect other signals. 759 c.signalInfo = ctx.shared.SignalInfo 760 ctxState := ctx.state() 761 if ctxState == sysmsg.ContextStateSyscallCanBePatched { 762 ctxState = sysmsg.ContextStateSyscall 763 shouldPatchSyscall = true 764 } 765 766 if ctxState == sysmsg.ContextStateSyscall || ctxState == sysmsg.ContextStateSyscallTrap { 767 if maybePatchSignalInfo(regs, &c.signalInfo) { 768 return false, false, nil 769 } 770 updateSyscallRegs(regs) 771 return true, shouldPatchSyscall, nil 772 } else if ctxState != sysmsg.ContextStateFault { 773 panic(fmt.Sprintf("unknown context state: %v", ctxState)) 774 } 775 776 return false, false, nil 777 } 778 779 func (s *subprocess) waitOnState(ctx *sharedContext, stubFastPathEnabled bool) { 780 ctx.kicked = false 781 slowPath := false 782 start := cputicks() 783 ctx.startWaitingTS = start 784 if !stubFastPathEnabled || atomic.LoadUint32(&s.contextQueue.numActiveThreads) == 0 { 785 ctx.kicked = s.kickSysmsgThread() 786 } 787 for curState := ctx.state(); curState == sysmsg.ContextStateNone; curState = ctx.state() { 788 if !slowPath { 789 events := dispatcher.waitFor(ctx) 790 if events&sharedContextKicked != 0 { 791 if ctx.kicked { 792 continue 793 } 794 if ctx.isAcked() { 795 ctx.kicked = true 796 continue 797 } 798 s.kickSysmsgThread() 799 ctx.kicked = true 800 continue 801 } 802 if events&sharedContextSlowPath != 0 { 803 ctx.disableSentryFastPath() 804 slowPath = true 805 continue 806 } 807 } else { 808 // If the context already received a handshake then it knows it's being 809 // worked on. 810 if !ctx.kicked && !ctx.isAcked() { 811 ctx.kicked = s.kickSysmsgThread() 812 } 813 814 ctx.sleepOnState(curState) 815 } 816 } 817 818 ctx.resetAcked() 819 ctx.enableSentryFastPath() 820 } 821 822 // canKickSysmsgThread returns true if a new thread can be kicked. 823 // The second return value is the expected number of threads after kicking a 824 // new one. 825 func (s *subprocess) canKickSysmsgThread() (bool, uint32) { 826 // numActiveContexts and numActiveThreads can be changed from stub 827 // threads that handles the contextQueue without any locks. The idea 828 // here is that any stub thread that gets CPU time can make some 829 // progress. In stub threads, we can use only spinlock-like 830 // synchronizations, but they don't work well because a thread that 831 // holds a lock can be preempted by another thread that is waiting for 832 // the same lock. 833 nrActiveThreads := atomic.LoadUint32(&s.contextQueue.numActiveThreads) 834 nrThreadsToWakeup := atomic.LoadUint32(&s.contextQueue.numThreadsToWakeup) 835 nrActiveContexts := atomic.LoadUint32(&s.contextQueue.numActiveContexts) 836 837 nrActiveThreads += nrThreadsToWakeup + 1 838 if nrActiveThreads > nrActiveContexts { 839 // This can happen when one or more stub threads are 840 // waiting for cpu time. The host probably has more 841 // running tasks than a number of cpu-s. 842 return false, nrActiveThreads 843 } 844 return true, nrActiveThreads 845 } 846 847 func (s *subprocess) kickSysmsgThread() bool { 848 kick, _ := s.canKickSysmsgThread() 849 if !kick { 850 return false 851 } 852 853 s.sysmsgThreadsMu.Lock() 854 kick, nrThreads := s.canKickSysmsgThread() 855 if !kick { 856 s.sysmsgThreadsMu.Unlock() 857 return false 858 } 859 atomic.AddUint32(&s.contextQueue.numThreadsToWakeup, 1) 860 if s.numSysmsgThreads < maxSysmsgThreads && s.numSysmsgThreads < int(nrThreads) { 861 s.numSysmsgThreads++ 862 s.sysmsgThreadsMu.Unlock() 863 if err := s.createSysmsgThread(); err != nil { 864 log.Warningf("Unable to create a new stub thread: %s", err) 865 s.sysmsgThreadsMu.Lock() 866 s.numSysmsgThreads-- 867 s.sysmsgThreadsMu.Unlock() 868 } 869 } else { 870 s.sysmsgThreadsMu.Unlock() 871 } 872 s.contextQueue.wakeupSysmsgThread() 873 874 return false 875 } 876 877 // syscall executes the given system call without handling interruptions. 878 func (s *subprocess) syscall(sysno uintptr, args ...arch.SyscallArgument) (uintptr, error) { 879 s.syscallThreadMu.Lock() 880 defer s.syscallThreadMu.Unlock() 881 882 return s.syscallThread.syscall(sysno, args...) 883 } 884 885 // MapFile implements platform.AddressSpace.MapFile. 886 func (s *subprocess) MapFile(addr hostarch.Addr, f memmap.File, fr memmap.FileRange, at hostarch.AccessType, precommit bool) error { 887 var flags int 888 if precommit { 889 flags |= unix.MAP_POPULATE 890 } 891 _, err := s.syscall( 892 unix.SYS_MMAP, 893 arch.SyscallArgument{Value: uintptr(addr)}, 894 arch.SyscallArgument{Value: uintptr(fr.Length())}, 895 arch.SyscallArgument{Value: uintptr(at.Prot())}, 896 arch.SyscallArgument{Value: uintptr(flags | unix.MAP_SHARED | unix.MAP_FIXED)}, 897 arch.SyscallArgument{Value: uintptr(f.FD())}, 898 arch.SyscallArgument{Value: uintptr(fr.Start)}) 899 return err 900 } 901 902 // Unmap implements platform.AddressSpace.Unmap. 903 func (s *subprocess) Unmap(addr hostarch.Addr, length uint64) { 904 ar, ok := addr.ToRange(length) 905 if !ok { 906 panic(fmt.Sprintf("addr %#x + length %#x overflows", addr, length)) 907 } 908 s.mu.Lock() 909 for c := range s.faultedContexts { 910 c.mu.Lock() 911 if c.lastFaultSP == s && ar.Contains(c.lastFaultAddr) { 912 // Forget the last fault so that if c faults again, the fault isn't 913 // incorrectly reported as a write fault. If this is being called 914 // due to munmap() of the corresponding vma, handling of the second 915 // fault will fail anyway. 916 c.lastFaultSP = nil 917 delete(s.faultedContexts, c) 918 } 919 c.mu.Unlock() 920 } 921 s.mu.Unlock() 922 _, err := s.syscall( 923 unix.SYS_MUNMAP, 924 arch.SyscallArgument{Value: uintptr(addr)}, 925 arch.SyscallArgument{Value: uintptr(length)}) 926 if err != nil { 927 // We never expect this to happen. 928 panic(fmt.Sprintf("munmap(%x, %x)) failed: %v", addr, length, err)) 929 } 930 } 931 932 func (s *subprocess) PullFullState(c *context, ac *arch.Context64) error { 933 if !c.sharedContext.isActiveInSubprocess(s) { 934 panic("Attempted to PullFullState for context that is not used in subprocess") 935 } 936 saveFPState(c.sharedContext, ac) 937 return nil 938 } 939 940 var sysmsgThreadPriority int 941 942 func initSysmsgThreadPriority() { 943 prio, err := unix.Getpriority(unix.PRIO_PROCESS, 0) 944 if err != nil { 945 panic("unable to get current scheduling priority") 946 } 947 // Sysmsg threads are executed with a priority one lower than the Sentry. 948 sysmsgThreadPriority = 20 - prio + 1 949 } 950 951 // createSysmsgThread creates a new sysmsg thread. 952 // The thread starts processing any available context in the context queue. 953 func (s *subprocess) createSysmsgThread() error { 954 // Create a new seccomp process. 955 var r requestThread 956 r.thread = make(chan *thread) 957 s.requests <- r 958 p := <-r.thread 959 960 runtime.LockOSThread() 961 defer runtime.UnlockOSThread() 962 p.attach() 963 964 // Skip SIGSTOP. 965 if _, _, errno := unix.RawSyscall6(unix.SYS_PTRACE, unix.PTRACE_CONT, uintptr(p.tid), 0, 0, 0, 0); errno != 0 { 966 panic(fmt.Sprintf("ptrace cont failed: %v", errno)) 967 } 968 sig := p.wait(stopped) 969 if sig != unix.SIGSTOP { 970 panic(fmt.Sprintf("error waiting for new clone: expected SIGSTOP, got %v", sig)) 971 } 972 973 // Allocate a new stack for the BPF process. 974 opts := pgalloc.AllocOpts{ 975 Kind: usage.System, 976 Dir: pgalloc.TopDown, 977 } 978 fr, err := s.memoryFile.Allocate(uint64(sysmsg.PerThreadSharedStackSize), opts) 979 if err != nil { 980 // TODO(b/144063246): Need to fail the clone system call. 981 panic(fmt.Sprintf("failed to allocate a new stack: %v", err)) 982 } 983 sysThread := &sysmsgThread{ 984 thread: p, 985 subproc: s, 986 stackRange: fr, 987 } 988 // Use the sysmsgStackID as a handle on this thread instead of host tid in 989 // order to be able to reliably specify invalidThreadID. 990 threadID := uint32(p.sysmsgStackID) 991 992 // Map the stack into the sentry. 993 sentryStackAddr, _, errno := unix.RawSyscall6( 994 unix.SYS_MMAP, 995 0, 996 sysmsg.PerThreadSharedStackSize, 997 unix.PROT_WRITE|unix.PROT_READ, 998 unix.MAP_SHARED|unix.MAP_FILE, 999 uintptr(s.memoryFile.FD()), uintptr(fr.Start)) 1000 if errno != 0 { 1001 panic(fmt.Sprintf("mmap failed: %v", errno)) 1002 } 1003 1004 // Before installing the stub syscall filters, we need to call a few 1005 // system calls (e.g. sigaltstack, sigaction) which have in-memory 1006 // arguments. We need to prevent changing these parameters by other 1007 // stub threads, so lets map the future BPF stack as read-only and 1008 // fill syscall arguments from the Sentry. 1009 sysmsgStackAddr := sysThread.sysmsgPerThreadMemAddr() + sysmsg.PerThreadSharedStackOffset 1010 err = sysThread.mapStack(sysmsgStackAddr, true) 1011 if err != nil { 1012 panic(fmt.Sprintf("mmap failed: %v", err)) 1013 } 1014 1015 sysThread.init(sentryStackAddr, sysmsgStackAddr) 1016 1017 // Map the stack into the BPF process. 1018 err = sysThread.mapStack(sysmsgStackAddr, false) 1019 if err != nil { 1020 s.memoryFile.DecRef(fr) 1021 panic(fmt.Sprintf("mmap failed: %v", err)) 1022 } 1023 1024 // Map the stack into the BPF process. 1025 privateStackAddr := sysThread.sysmsgPerThreadMemAddr() + sysmsg.PerThreadPrivateStackOffset 1026 err = sysThread.mapPrivateStack(privateStackAddr, sysmsg.PerThreadPrivateStackSize) 1027 if err != nil { 1028 s.memoryFile.DecRef(fr) 1029 panic(fmt.Sprintf("mmap failed: %v", err)) 1030 } 1031 1032 sysThread.setMsg(sysmsg.StackAddrToMsg(sentryStackAddr)) 1033 sysThread.msg.Init(threadID) 1034 sysThread.msg.Self = uint64(sysmsgStackAddr + sysmsg.MsgOffsetFromSharedStack) 1035 sysThread.msg.SyshandlerStack = uint64(sysmsg.StackAddrToSyshandlerStack(sysThread.sysmsgPerThreadMemAddr())) 1036 sysThread.msg.Syshandler = uint64(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_syshandler)) 1037 1038 sysThread.msg.State.Set(sysmsg.ThreadStateInitializing) 1039 1040 if err := unix.Setpriority(unix.PRIO_PROCESS, int(p.tid), sysmsgThreadPriority); err != nil { 1041 log.Warningf("Unable to change priority of a stub thread: %s", err) 1042 } 1043 1044 // Install a pre-compiled seccomp rules for the BPF process. 1045 _, err = p.syscallIgnoreInterrupt(&p.initRegs, unix.SYS_PRCTL, 1046 arch.SyscallArgument{Value: uintptr(linux.PR_SET_NO_NEW_PRIVS)}, 1047 arch.SyscallArgument{Value: uintptr(1)}, 1048 arch.SyscallArgument{Value: uintptr(0)}, 1049 arch.SyscallArgument{Value: uintptr(0)}, 1050 arch.SyscallArgument{Value: uintptr(0)}, 1051 arch.SyscallArgument{Value: uintptr(0)}) 1052 if err != nil { 1053 panic(fmt.Sprintf("prctl(PR_SET_NO_NEW_PRIVS) failed: %v", err)) 1054 } 1055 1056 _, err = p.syscallIgnoreInterrupt(&p.initRegs, seccomp.SYS_SECCOMP, 1057 arch.SyscallArgument{Value: uintptr(linux.SECCOMP_SET_MODE_FILTER)}, 1058 arch.SyscallArgument{Value: uintptr(0)}, 1059 arch.SyscallArgument{Value: stubSysmsgRules}) 1060 if err != nil { 1061 panic(fmt.Sprintf("seccomp failed: %v", err)) 1062 } 1063 1064 // Prepare to start the BPF process. 1065 tregs := &arch.Registers{} 1066 s.resetSysemuRegs(tregs) 1067 setArchSpecificRegs(sysThread, tregs) 1068 if err := p.setRegs(tregs); err != nil { 1069 panic(fmt.Sprintf("ptrace set regs failed: %v", err)) 1070 } 1071 archSpecificSysmsgThreadInit(sysThread) 1072 // Skip SIGSTOP. 1073 if _, _, e := unix.RawSyscall(unix.SYS_TGKILL, uintptr(p.tgid), uintptr(p.tid), uintptr(unix.SIGCONT)); e != 0 { 1074 panic(fmt.Sprintf("tkill failed: %v", e)) 1075 } 1076 // Resume the BPF process. 1077 if _, _, errno := unix.RawSyscall6(unix.SYS_PTRACE, unix.PTRACE_DETACH, uintptr(p.tid), 0, 0, 0, 0); errno != 0 { 1078 panic(fmt.Sprintf("can't detach new clone: %v", errno)) 1079 } 1080 1081 s.sysmsgThreadsMu.Lock() 1082 s.sysmsgThreads[threadID] = sysThread 1083 s.sysmsgThreadsMu.Unlock() 1084 1085 return nil 1086 } 1087 1088 // PreFork implements platform.AddressSpace.PreFork. 1089 // We need to take the usertrap lock to be sure that fork() will not be in the 1090 // middle of applying a binary patch. 1091 func (s *subprocess) PreFork() { 1092 s.usertrap.PreFork() 1093 } 1094 1095 // PostFork implements platform.AddressSpace.PostFork. 1096 func (s *subprocess) PostFork() { 1097 s.usertrap.PostFork() // +checklocksforce: PreFork acquires, above. 1098 } 1099 1100 // activateContext activates the context in this subprocess. 1101 // No-op if the context is already active within the subprocess; if not, 1102 // deactivates it from its last subprocess. 1103 func (s *subprocess) activateContext(c *context) error { 1104 if !c.sharedContext.isActiveInSubprocess(s) { 1105 c.sharedContext.release() 1106 c.sharedContext = nil 1107 1108 shared, err := s.getSharedContext() 1109 if err != nil { 1110 return err 1111 } 1112 c.sharedContext = shared 1113 } 1114 return nil 1115 }