github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/kernel/task_clone.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 kernel 16 17 import ( 18 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 19 "github.com/ttpreport/gvisor-ligolo/pkg/atomicbitops" 20 "github.com/ttpreport/gvisor-ligolo/pkg/bpf" 21 "github.com/ttpreport/gvisor-ligolo/pkg/cleanup" 22 "github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr" 23 "github.com/ttpreport/gvisor-ligolo/pkg/hostarch" 24 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/fsimpl/kernfs" 25 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/fsimpl/nsfs" 26 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/inet" 27 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/seccheck" 28 pb "github.com/ttpreport/gvisor-ligolo/pkg/sentry/seccheck/points/points_go_proto" 29 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs" 30 "github.com/ttpreport/gvisor-ligolo/pkg/usermem" 31 ) 32 33 // Clone implements the clone(2) syscall and returns the thread ID of the new 34 // task in t's PID namespace. Clone may return both a non-zero thread ID and a 35 // non-nil error. 36 // 37 // Preconditions: The caller must be running Task.doSyscallInvoke on the task 38 // goroutine. 39 func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { 40 // Since signal actions may refer to application signal handlers by virtual 41 // address, any set of signal handlers must refer to the same address 42 // space. 43 if args.Flags&(linux.CLONE_SIGHAND|linux.CLONE_VM) == linux.CLONE_SIGHAND { 44 return 0, nil, linuxerr.EINVAL 45 } 46 // In order for the behavior of thread-group-directed signals to be sane, 47 // all tasks in a thread group must share signal handlers. 48 if args.Flags&(linux.CLONE_THREAD|linux.CLONE_SIGHAND) == linux.CLONE_THREAD { 49 return 0, nil, linuxerr.EINVAL 50 } 51 // All tasks in a thread group must be in the same PID namespace. 52 if (args.Flags&linux.CLONE_THREAD != 0) && (args.Flags&linux.CLONE_NEWPID != 0 || t.childPIDNamespace != nil) { 53 return 0, nil, linuxerr.EINVAL 54 } 55 // The two different ways of specifying a new PID namespace are 56 // incompatible. 57 if args.Flags&linux.CLONE_NEWPID != 0 && t.childPIDNamespace != nil { 58 return 0, nil, linuxerr.EINVAL 59 } 60 // Thread groups and FS contexts cannot span user namespaces. 61 if args.Flags&linux.CLONE_NEWUSER != 0 && args.Flags&(linux.CLONE_THREAD|linux.CLONE_FS) != 0 { 62 return 0, nil, linuxerr.EINVAL 63 } 64 // args.ExitSignal must be a valid signal. 65 if args.ExitSignal != 0 && !linux.Signal(args.ExitSignal).IsValid() { 66 return 0, nil, linuxerr.EINVAL 67 } 68 69 // Pull task registers and FPU state, a cloned task will inherit the 70 // state of the current task. 71 if err := t.p.PullFullState(t.MemoryManager().AddressSpace(), t.Arch()); err != nil { 72 t.Warningf("Unable to pull a full state: %v", err) 73 t.forceSignal(linux.SIGILL, true /* unconditional */) 74 t.SendSignal(SignalInfoPriv(linux.SIGILL)) 75 return 0, nil, linuxerr.EFAULT 76 } 77 78 // "If CLONE_NEWUSER is specified along with other CLONE_NEW* flags in a 79 // single clone(2) or unshare(2) call, the user namespace is guaranteed to 80 // be created first, giving the child (clone(2)) or caller (unshare(2)) 81 // privileges over the remaining namespaces created by the call." - 82 // user_namespaces(7) 83 creds := t.Credentials() 84 userns := creds.UserNamespace 85 if args.Flags&linux.CLONE_NEWUSER != 0 { 86 var err error 87 // "EPERM (since Linux 3.9): CLONE_NEWUSER was specified in flags and 88 // the caller is in a chroot environment (i.e., the caller's root 89 // directory does not match the root directory of the mount namespace 90 // in which it resides)." - clone(2). Neither chroot(2) nor 91 // user_namespaces(7) document this. 92 if t.IsChrooted() { 93 return 0, nil, linuxerr.EPERM 94 } 95 userns, err = creds.NewChildUserNamespace() 96 if err != nil { 97 return 0, nil, err 98 } 99 } 100 if args.Flags&(linux.CLONE_NEWPID|linux.CLONE_NEWNET|linux.CLONE_NEWUTS|linux.CLONE_NEWIPC) != 0 && !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, userns) { 101 return 0, nil, linuxerr.EPERM 102 } 103 104 utsns := t.UTSNamespace() 105 if args.Flags&linux.CLONE_NEWUTS != 0 { 106 // Note that this must happen after NewUserNamespace so we get 107 // the new userns if there is one. 108 utsns = t.UTSNamespace().Clone(userns) 109 } 110 111 ipcns := t.IPCNamespace() 112 if args.Flags&linux.CLONE_NEWIPC != 0 { 113 ipcns = NewIPCNamespace(userns) 114 ipcns.InitPosixQueues(t, t.k.VFS(), creds) 115 } else { 116 ipcns.IncRef() 117 } 118 cu := cleanup.Make(func() { 119 ipcns.DecRef(t) 120 }) 121 defer cu.Clean() 122 123 netns := t.netns.Load() 124 if args.Flags&linux.CLONE_NEWNET != 0 { 125 netns = inet.NewNamespace(netns, userns) 126 inode := nsfs.NewInode(t, t.k.nsfsMount, netns) 127 netns.SetInode(inode) 128 } else { 129 netns.IncRef() 130 } 131 cu.Add(func() { 132 netns.DecRef(t) 133 }) 134 135 // TODO(b/63601033): Implement CLONE_NEWNS. 136 mntns := t.mountNamespace 137 if mntns != nil { 138 mntns.IncRef() 139 cu.Add(func() { 140 mntns.DecRef(t) 141 }) 142 } 143 144 // We must hold t.mu to access t.image, but we can't hold it during Fork(), 145 // since TaskImage.Fork()=>mm.Fork() takes mm.addressSpaceMu, which is ordered 146 // above Task.mu. So we copy t.image with t.mu held and call Fork() on the copy. 147 t.mu.Lock() 148 curImage := t.image 149 t.mu.Unlock() 150 image, err := curImage.Fork(t, t.k, args.Flags&linux.CLONE_VM != 0) 151 if err != nil { 152 return 0, nil, err 153 } 154 cu.Add(func() { 155 image.release(t) 156 }) 157 // clone() returns 0 in the child. 158 image.Arch.SetReturn(0) 159 if args.Stack != 0 { 160 image.Arch.SetStack(uintptr(args.Stack)) 161 } 162 if args.Flags&linux.CLONE_SETTLS != 0 { 163 if !image.Arch.SetTLS(uintptr(args.TLS)) { 164 return 0, nil, linuxerr.EPERM 165 } 166 } 167 168 var fsContext *FSContext 169 if args.Flags&linux.CLONE_FS == 0 { 170 fsContext = t.fsContext.Fork() 171 } else { 172 fsContext = t.fsContext 173 fsContext.IncRef() 174 } 175 176 var fdTable *FDTable 177 if args.Flags&linux.CLONE_FILES == 0 { 178 fdTable = t.fdTable.Fork(t, MaxFdLimit) 179 } else { 180 fdTable = t.fdTable 181 fdTable.IncRef() 182 } 183 184 pidns := t.tg.pidns 185 if t.childPIDNamespace != nil { 186 pidns = t.childPIDNamespace 187 } else if args.Flags&linux.CLONE_NEWPID != 0 { 188 pidns = pidns.NewChild(userns) 189 } 190 191 tg := t.tg 192 rseqAddr := hostarch.Addr(0) 193 rseqSignature := uint32(0) 194 if args.Flags&linux.CLONE_THREAD == 0 { 195 sh := t.tg.signalHandlers 196 if args.Flags&linux.CLONE_SIGHAND == 0 { 197 sh = sh.Fork() 198 } 199 tg = t.k.NewThreadGroup(pidns, sh, linux.Signal(args.ExitSignal), tg.limits.GetCopy()) 200 tg.oomScoreAdj = atomicbitops.FromInt32(t.tg.oomScoreAdj.Load()) 201 rseqAddr = t.rseqAddr 202 rseqSignature = t.rseqSignature 203 } 204 205 uc := t.userCounters 206 if uc.uid != creds.RealKUID { 207 uc = t.k.GetUserCounters(creds.RealKUID) 208 } 209 210 cfg := &TaskConfig{ 211 Kernel: t.k, 212 ThreadGroup: tg, 213 SignalMask: t.SignalMask(), 214 TaskImage: image, 215 FSContext: fsContext, 216 FDTable: fdTable, 217 Credentials: creds, 218 Niceness: t.Niceness(), 219 NetworkNamespace: netns, 220 AllowedCPUMask: t.CPUMask(), 221 UTSNamespace: utsns, 222 IPCNamespace: ipcns, 223 AbstractSocketNamespace: t.abstractSockets, 224 MountNamespace: mntns, 225 RSeqAddr: rseqAddr, 226 RSeqSignature: rseqSignature, 227 ContainerID: t.ContainerID(), 228 UserCounters: uc, 229 } 230 if args.Flags&linux.CLONE_THREAD == 0 { 231 cfg.Parent = t 232 } else { 233 cfg.InheritParent = t 234 } 235 nt, err := t.tg.pidns.owner.NewTask(t, cfg) 236 // If NewTask succeeds, we transfer references to nt. If NewTask fails, it does 237 // the cleanup for us. 238 cu.Release() 239 if err != nil { 240 return 0, nil, err 241 } 242 243 // "A child process created via fork(2) inherits a copy of its parent's 244 // alternate signal stack settings" - sigaltstack(2). 245 // 246 // However kernel/fork.c:copy_process() adds a limitation to this: 247 // "sigaltstack should be cleared when sharing the same VM". 248 if args.Flags&linux.CLONE_VM == 0 || args.Flags&linux.CLONE_VFORK != 0 { 249 nt.SetSignalStack(t.SignalStack()) 250 } 251 252 if userns != creds.UserNamespace { 253 if err := nt.SetUserNamespace(userns); err != nil { 254 // This shouldn't be possible: userns was created from nt.creds, so 255 // nt should have CAP_SYS_ADMIN in userns. 256 panic("Task.Clone: SetUserNamespace failed: " + err.Error()) 257 } 258 } 259 260 // This has to happen last, because e.g. ptraceClone may send a SIGSTOP to 261 // nt that it must receive before its task goroutine starts running. 262 tid := nt.k.tasks.Root.IDOfTask(nt) 263 defer nt.Start(tid) 264 265 if seccheck.Global.Enabled(seccheck.PointClone) { 266 mask, info := getCloneSeccheckInfo(t, nt, args.Flags) 267 if err := seccheck.Global.SentToSinks(func(c seccheck.Sink) error { 268 return c.Clone(t, mask, info) 269 }); err != nil { 270 // nt has been visible to the rest of the system since NewTask, so 271 // it may be blocking execve or a group stop, have been notified 272 // for group signal delivery, had children reparented to it, etc. 273 // Thus we can't just drop it on the floor. Instead, instruct the 274 // task goroutine to exit immediately, as quietly as possible. 275 nt.exitTracerNotified = true 276 nt.exitTracerAcked = true 277 nt.exitParentNotified = true 278 nt.exitParentAcked = true 279 nt.runState = (*runExitMain)(nil) 280 return 0, nil, err 281 } 282 } 283 284 // "If fork/clone and execve are allowed by @prog, any child processes will 285 // be constrained to the same filters and system call ABI as the parent." - 286 // Documentation/prctl/seccomp_filter.txt 287 if f := t.syscallFilters.Load(); f != nil { 288 copiedFilters := append([]bpf.Program(nil), f.([]bpf.Program)...) 289 nt.syscallFilters.Store(copiedFilters) 290 } 291 if args.Flags&linux.CLONE_VFORK != 0 { 292 nt.vforkParent = t 293 } 294 295 if args.Flags&linux.CLONE_CHILD_CLEARTID != 0 { 296 nt.SetClearTID(hostarch.Addr(args.ChildTID)) 297 } 298 if args.Flags&linux.CLONE_CHILD_SETTID != 0 { 299 ctid := nt.ThreadID() 300 ctid.CopyOut(nt.CopyContext(t, usermem.IOOpts{AddressSpaceActive: false}), hostarch.Addr(args.ChildTID)) 301 } 302 ntid := t.tg.pidns.IDOfTask(nt) 303 if args.Flags&linux.CLONE_PARENT_SETTID != 0 { 304 ntid.CopyOut(t, hostarch.Addr(args.ParentTID)) 305 } 306 307 t.traceCloneEvent(tid) 308 kind := ptraceCloneKindClone 309 if args.Flags&linux.CLONE_VFORK != 0 { 310 kind = ptraceCloneKindVfork 311 } else if linux.Signal(args.ExitSignal) == linux.SIGCHLD { 312 kind = ptraceCloneKindFork 313 } 314 if t.ptraceClone(kind, nt, args) { 315 if args.Flags&linux.CLONE_VFORK != 0 { 316 return ntid, &SyscallControl{next: &runSyscallAfterPtraceEventClone{vforkChild: nt, vforkChildTID: ntid}}, nil 317 } 318 return ntid, &SyscallControl{next: &runSyscallAfterPtraceEventClone{}}, nil 319 } 320 if args.Flags&linux.CLONE_VFORK != 0 { 321 t.maybeBeginVforkStop(nt) 322 return ntid, &SyscallControl{next: &runSyscallAfterVforkStop{childTID: ntid}}, nil 323 } 324 return ntid, nil, nil 325 } 326 327 func getCloneSeccheckInfo(t, nt *Task, flags uint64) (seccheck.FieldSet, *pb.CloneInfo) { 328 fields := seccheck.Global.GetFieldSet(seccheck.PointClone) 329 var cwd string 330 if fields.Context.Contains(seccheck.FieldCtxtCwd) { 331 cwd = getTaskCurrentWorkingDirectory(t) 332 } 333 t.k.tasks.mu.RLock() 334 defer t.k.tasks.mu.RUnlock() 335 info := &pb.CloneInfo{ 336 CreatedThreadId: int32(nt.k.tasks.Root.tids[nt]), 337 CreatedThreadGroupId: int32(nt.k.tasks.Root.tgids[nt.tg]), 338 CreatedThreadStartTimeNs: nt.startTime.Nanoseconds(), 339 Flags: flags, 340 } 341 342 if !fields.Context.Empty() { 343 info.ContextData = &pb.ContextData{} 344 LoadSeccheckDataLocked(t, fields.Context, info.ContextData, cwd) 345 } 346 347 return fields, info 348 } 349 350 // maybeBeginVforkStop checks if a previously-started vfork child is still 351 // running and has not yet released its MM, such that its parent t should enter 352 // a vforkStop. 353 // 354 // Preconditions: The caller must be running on t's task goroutine. 355 func (t *Task) maybeBeginVforkStop(child *Task) { 356 t.tg.pidns.owner.mu.RLock() 357 defer t.tg.pidns.owner.mu.RUnlock() 358 t.tg.signalHandlers.mu.Lock() 359 defer t.tg.signalHandlers.mu.Unlock() 360 if t.killedLocked() { 361 child.vforkParent = nil 362 return 363 } 364 if child.vforkParent == t { 365 t.beginInternalStopLocked((*vforkStop)(nil)) 366 } 367 } 368 369 func (t *Task) unstopVforkParent() { 370 t.tg.pidns.owner.mu.RLock() 371 defer t.tg.pidns.owner.mu.RUnlock() 372 if p := t.vforkParent; p != nil { 373 p.tg.signalHandlers.mu.Lock() 374 defer p.tg.signalHandlers.mu.Unlock() 375 if _, ok := p.stop.(*vforkStop); ok { 376 p.endInternalStopLocked() 377 } 378 // Parent no longer needs to be unstopped. 379 t.vforkParent = nil 380 } 381 } 382 383 // +stateify savable 384 type runSyscallAfterPtraceEventClone struct { 385 vforkChild *Task 386 387 // If vforkChild is not nil, vforkChildTID is its thread ID in the parent's 388 // PID namespace. vforkChildTID must be stored since the child may exit and 389 // release its TID before the PTRACE_EVENT stop ends. 390 vforkChildTID ThreadID 391 } 392 393 func (r *runSyscallAfterPtraceEventClone) execute(t *Task) taskRunState { 394 if r.vforkChild != nil { 395 t.maybeBeginVforkStop(r.vforkChild) 396 return &runSyscallAfterVforkStop{r.vforkChildTID} 397 } 398 return (*runSyscallExit)(nil) 399 } 400 401 // +stateify savable 402 type runSyscallAfterVforkStop struct { 403 // childTID has the same meaning as 404 // runSyscallAfterPtraceEventClone.vforkChildTID. 405 childTID ThreadID 406 } 407 408 func (r *runSyscallAfterVforkStop) execute(t *Task) taskRunState { 409 t.ptraceVforkDone(r.childTID) 410 return (*runSyscallExit)(nil) 411 } 412 413 // Setns reassociates thread with the specified namespace. 414 func (t *Task) Setns(fd *vfs.FileDescription, flags int32) error { 415 d, ok := fd.Dentry().Impl().(*kernfs.Dentry) 416 if !ok { 417 return linuxerr.EINVAL 418 } 419 i, ok := d.Inode().(*nsfs.Inode) 420 if !ok { 421 return linuxerr.EINVAL 422 } 423 424 switch ns := i.Namespace().(type) { 425 case *inet.Namespace: 426 if flags != 0 && flags != linux.CLONE_NEWNET { 427 return linuxerr.EINVAL 428 } 429 if !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, ns.UserNamespace()) || 430 !t.Credentials().HasCapability(linux.CAP_SYS_ADMIN) { 431 return linuxerr.EPERM 432 } 433 oldNS := t.NetworkNamespace() 434 ns.IncRef() 435 t.mu.Lock() 436 t.netns.Store(ns) 437 t.mu.Unlock() 438 oldNS.DecRef(t) 439 return nil 440 default: 441 return linuxerr.EINVAL 442 } 443 } 444 445 // Unshare changes the set of resources t shares with other tasks, as specified 446 // by flags. 447 // 448 // Preconditions: The caller must be running on the task goroutine. 449 func (t *Task) Unshare(flags int32) error { 450 // "CLONE_THREAD, CLONE_SIGHAND, and CLONE_VM can be specified in flags if 451 // the caller is single threaded (i.e., it is not sharing its address space 452 // with another process or thread). In this case, these flags have no 453 // effect. (Note also that specifying CLONE_THREAD automatically implies 454 // CLONE_VM, and specifying CLONE_VM automatically implies CLONE_SIGHAND.) 455 // If the process is multithreaded, then the use of these flags results in 456 // an error." - unshare(2). This is incorrect (cf. 457 // kernel/fork.c:ksys_unshare()): 458 // 459 // - CLONE_THREAD does not imply CLONE_VM. 460 // 461 // - CLONE_SIGHAND implies CLONE_THREAD. 462 // 463 // - Only CLONE_VM requires that the caller is not sharing its address 464 // space with another thread. CLONE_SIGHAND requires that the caller is not 465 // sharing its signal handlers, and CLONE_THREAD requires that the caller 466 // is the only thread in its thread group. 467 // 468 // Since we don't count the number of tasks using each address space or set 469 // of signal handlers, we reject CLONE_VM and CLONE_SIGHAND altogether. 470 if flags&(linux.CLONE_VM|linux.CLONE_SIGHAND) != 0 { 471 return linuxerr.EINVAL 472 } 473 creds := t.Credentials() 474 if flags&linux.CLONE_THREAD != 0 { 475 t.tg.signalHandlers.mu.Lock() 476 if t.tg.tasksCount != 1 { 477 t.tg.signalHandlers.mu.Unlock() 478 return linuxerr.EINVAL 479 } 480 t.tg.signalHandlers.mu.Unlock() 481 // This isn't racy because we're the only living task, and therefore 482 // the only task capable of creating new ones, in our thread group. 483 } 484 if flags&linux.CLONE_NEWUSER != 0 { 485 if t.IsChrooted() { 486 return linuxerr.EPERM 487 } 488 newUserNS, err := creds.NewChildUserNamespace() 489 if err != nil { 490 return err 491 } 492 err = t.SetUserNamespace(newUserNS) 493 if err != nil { 494 return err 495 } 496 // Need to reload creds, because t.SetUserNamespace() changed task credentials. 497 creds = t.Credentials() 498 } 499 haveCapSysAdmin := t.HasCapability(linux.CAP_SYS_ADMIN) 500 if flags&linux.CLONE_NEWPID != 0 { 501 if !haveCapSysAdmin { 502 return linuxerr.EPERM 503 } 504 t.childPIDNamespace = t.tg.pidns.NewChild(t.UserNamespace()) 505 } 506 if flags&linux.CLONE_NEWNET != 0 { 507 if !haveCapSysAdmin { 508 return linuxerr.EPERM 509 } 510 netns := t.NetworkNamespace() 511 netns = inet.NewNamespace(netns, t.UserNamespace()) 512 netnsInode := nsfs.NewInode(t, t.k.nsfsMount, netns) 513 netns.SetInode(netnsInode) 514 t.mu.Lock() 515 netns = t.netns.Swap(netns) 516 t.mu.Unlock() 517 netns.DecRef(t) 518 } 519 t.mu.Lock() 520 // Can't defer unlock: DecRefs must occur without holding t.mu. 521 if flags&linux.CLONE_NEWUTS != 0 { 522 if !haveCapSysAdmin { 523 t.mu.Unlock() 524 return linuxerr.EPERM 525 } 526 // Note that this must happen after NewUserNamespace, so the 527 // new user namespace is used if there is one. 528 t.utsns = t.utsns.Clone(creds.UserNamespace) 529 } 530 var oldIPCNS *IPCNamespace 531 if flags&linux.CLONE_NEWIPC != 0 { 532 if !haveCapSysAdmin { 533 t.mu.Unlock() 534 return linuxerr.EPERM 535 } 536 // Note that "If CLONE_NEWIPC is set, then create the process in a new IPC 537 // namespace" 538 oldIPCNS = t.ipcns 539 t.ipcns = NewIPCNamespace(creds.UserNamespace) 540 t.ipcns.InitPosixQueues(t, t.k.VFS(), creds) 541 } 542 var oldFDTable *FDTable 543 if flags&linux.CLONE_FILES != 0 { 544 oldFDTable = t.fdTable 545 t.fdTable = oldFDTable.Fork(t, MaxFdLimit) 546 } 547 var oldFSContext *FSContext 548 if flags&linux.CLONE_FS != 0 { 549 oldFSContext = t.fsContext 550 t.fsContext = oldFSContext.Fork() 551 } 552 t.mu.Unlock() 553 if oldIPCNS != nil { 554 oldIPCNS.DecRef(t) 555 } 556 if oldFDTable != nil { 557 oldFDTable.DecRef(t) 558 } 559 if oldFSContext != nil { 560 oldFSContext.DecRef(t) 561 } 562 return nil 563 } 564 565 // UnshareFdTable unshares the FdTable that task t shares with other tasks, upto 566 // the maxFd. 567 // 568 // Preconditions: The caller must be running on the task goroutine. 569 func (t *Task) UnshareFdTable(maxFd int32) { 570 t.mu.Lock() 571 oldFDTable := t.fdTable 572 t.fdTable = oldFDTable.Fork(t, maxFd) 573 t.mu.Unlock() 574 575 oldFDTable.DecRef(t) 576 } 577 578 // vforkStop is a TaskStop imposed on a task that creates a child with 579 // CLONE_VFORK or vfork(2), that ends when the child task ceases to use its 580 // current MM. (Normally, CLONE_VFORK is used in conjunction with CLONE_VM, so 581 // that the child and parent share mappings until the child execve()s into a 582 // new process image or exits.) 583 // 584 // +stateify savable 585 type vforkStop struct{} 586 587 // StopIgnoresKill implements TaskStop.Killable. 588 func (*vforkStop) Killable() bool { return true }