// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kernel

import (
	"fmt"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/errors/linuxerr"
	"gvisor.dev/gvisor/pkg/hostarch"
	"gvisor.dev/gvisor/pkg/marshal/primitive"
	"gvisor.dev/gvisor/pkg/sentry/mm"
	"gvisor.dev/gvisor/pkg/usermem"
)

// ptraceOptions are the subset of options controlling a task's ptrace behavior
// that are set by ptrace(PTRACE_SETOPTIONS).
//
// +stateify savable
type ptraceOptions struct {
	// ExitKill is true if the tracee should be sent SIGKILL when the tracer
	// exits.
	ExitKill bool

	// If SysGood is true, set bit 7 in the signal number for
	// syscall-entry-stop and syscall-exit-stop traps delivered to this task's
	// tracer.
	SysGood bool

	// TraceClone is true if the tracer wants to receive PTRACE_EVENT_CLONE
	// events.
	TraceClone bool

	// TraceExec is true if the tracer wants to receive PTRACE_EVENT_EXEC
	// events.
	TraceExec bool

	// TraceExit is true if the tracer wants to receive PTRACE_EVENT_EXIT
	// events.
	TraceExit bool

	// TraceFork is true if the tracer wants to receive PTRACE_EVENT_FORK
	// events.
	TraceFork bool

	// TraceSeccomp is true if the tracer wants to receive PTRACE_EVENT_SECCOMP
	// events.
	TraceSeccomp bool

	// TraceVfork is true if the tracer wants to receive PTRACE_EVENT_VFORK
	// events.
	TraceVfork bool

	// TraceVforkDone is true if the tracer wants to receive
	// PTRACE_EVENT_VFORK_DONE events.
	TraceVforkDone bool
}

// ptraceSyscallMode controls the behavior of a ptraced task at syscall entry
// and exit.
type ptraceSyscallMode int

const (
	// ptraceSyscallNone indicates that the task has never ptrace-stopped, or
	// that it was resumed from its last ptrace-stop by PTRACE_CONT or
	// PTRACE_DETACH. The task's syscalls will not be intercepted.
	ptraceSyscallNone ptraceSyscallMode = iota

	// ptraceSyscallIntercept indicates that the task was resumed from its last
	// ptrace-stop by PTRACE_SYSCALL. The next time the task enters or exits a
	// syscall, a ptrace-stop will occur.
	ptraceSyscallIntercept

	// ptraceSyscallEmu indicates that the task was resumed from its last
	// ptrace-stop by PTRACE_SYSEMU or PTRACE_SYSEMU_SINGLESTEP. The next time
	// the task enters a syscall, the syscall will be skipped, and a
	// ptrace-stop will occur.
	ptraceSyscallEmu
)

// CanTrace checks that t is permitted to access target's state, as defined by
// ptrace(2), subsection "Ptrace access mode checking". If attach is true, it
// checks for access mode PTRACE_MODE_ATTACH; otherwise, it checks for access
// mode PTRACE_MODE_READ.
//
// In Linux, ptrace access restrictions may be configured by LSMs. While we do
// not support LSMs, we do add additional restrictions based on the commoncap
// and YAMA LSMs.
//
// TODO(gvisor.dev/issue/212): The result of CanTrace is immediately stale (e.g., a
// racing setuid(2) may change traceability). This may pose a risk when a task
// changes from traceable to not traceable. This is only problematic across
// execve, where privileges may increase.
//
// We currently do not implement privileged executables (set-user/group-ID bits
// and file capabilities), so that case is not reachable.
func (t *Task) CanTrace(target *Task, attach bool) bool {
	// "If the calling thread and the target thread are in the same thread
	// group, access is always allowed." - ptrace(2)
	//
	// Note: Strictly speaking, prior to 73af963f9f30 ("__ptrace_may_access()
	// should not deny sub-threads", first released in Linux 3.12), the rule
	// only applies if t and target are the same task. But, as that commit
	// message puts it, "[any] security check is pointless when the tasks share
	// the same ->mm."
	if t.tg == target.tg {
		return true
	}

	if !t.canTraceStandard(target, attach) {
		return false
	}

	if attach && t.k.YAMAPtraceScope.Load() == linux.YAMA_SCOPE_RELATIONAL {
		t.tg.pidns.owner.mu.RLock()
		defer t.tg.pidns.owner.mu.RUnlock()
		if !t.canTraceYAMALocked(target) {
			return false
		}
	}
	return true
}

// canTraceLocked is the same as CanTrace, except the caller must already hold
// the TaskSet mutex (for reading or writing).
func (t *Task) canTraceLocked(target *Task, attach bool) bool {
	if t.tg == target.tg {
		return true
	}

	if !t.canTraceStandard(target, attach) {
		return false
	}

	if attach && t.k.YAMAPtraceScope.Load() == linux.YAMA_SCOPE_RELATIONAL {
		if !t.canTraceYAMALocked(target) {
			return false
		}
	}
	return true
}

// canTraceStandard performs standard ptrace access checks as defined by
// kernel/ptrace.c:__ptrace_may_access as well as the commoncap LSM
// implementation of the security_ptrace_access_check() interface, which is
// always invoked.
func (t *Task) canTraceStandard(target *Task, attach bool) bool {
	// """
	// TODO(gvisor.dev/issue/260): 1. If the access mode specifies
	// PTRACE_MODE_FSCREDS (ED: snipped, doesn't exist until Linux 4.5).
	//
	// Otherwise, the access mode specifies PTRACE_MODE_REALCREDS, so use the
	// caller's real UID and GID for the checks in the next step. (Most APIs
	// that check the caller's UID and GID use the effective IDs. For
	// historical reasons, the PTRACE_MODE_REALCREDS check uses the real IDs
	// instead.)
	//
	// 2. Deny access if neither of the following is true:
	//
	//	- The real, effective, and saved-set user IDs of the target match the
	//		caller's user ID, *and* the real, effective, and saved-set group IDs of
	//		the target match the caller's group ID.
	//
	//	- The caller has the CAP_SYS_PTRACE capability in the user namespace of
	//		the target.
	//
	// 3. Deny access if the target process "dumpable" attribute has a value
	// other than 1 (SUID_DUMP_USER; see the discussion of PR_SET_DUMPABLE in
	// prctl(2)), and the caller does not have the CAP_SYS_PTRACE capability in
	// the user namespace of the target process.
	//
	// 4. The commoncap LSM performs the following steps:
	//
	// a) If the access mode includes PTRACE_MODE_FSCREDS, then use the
	// caller's effective capability set; otherwise (the access mode specifies
	// PTRACE_MODE_REALCREDS, so) use the caller's permitted capability set.
	//
	// b) Deny access if neither of the following is true:
	//
	//	- The caller and the target process are in the same user namespace, and
	//		the caller's capabilities are a proper superset of the target process's
	//		permitted capabilities.
	//
	//	- The caller has the CAP_SYS_PTRACE capability in the target process's
	//		user namespace.
	//
	// Note that the commoncap LSM does not distinguish between
	// PTRACE_MODE_READ and PTRACE_MODE_ATTACH. (ED: From earlier in this
	// section: "the commoncap LSM ... is always invoked".)
	// """
	callerCreds := t.Credentials()
	targetCreds := target.Credentials()
	if callerCreds.HasCapabilityIn(linux.CAP_SYS_PTRACE, targetCreds.UserNamespace) {
		return true
	}
	if cuid := callerCreds.RealKUID; cuid != targetCreds.RealKUID || cuid != targetCreds.EffectiveKUID || cuid != targetCreds.SavedKUID {
		return false
	}
	if cgid := callerCreds.RealKGID; cgid != targetCreds.RealKGID || cgid != targetCreds.EffectiveKGID || cgid != targetCreds.SavedKGID {
		return false
	}
	var targetMM *mm.MemoryManager
	target.WithMuLocked(func(t *Task) {
		targetMM = t.MemoryManager()
	})
	if targetMM != nil && targetMM.Dumpability() != mm.UserDumpable {
		return false
	}
	if callerCreds.UserNamespace != targetCreds.UserNamespace {
		return false
	}
	if targetCreds.PermittedCaps&^callerCreds.PermittedCaps != 0 {
		return false
	}
	return true
}

// canTraceYAMALocked performs ptrace access checks as defined by the YAMA LSM
// implementation of the security_ptrace_access_check() interface, with YAMA
// configured to mode 1. This is a common default among various Linux
// distributions.
//
// It only permits the tracer to proceed if one of the following conditions is
// met:
//
// a) The tracer is already attached to the tracee.
//
// b) The target is a descendant of the tracer.
//
// c) The target has explicitly given permission to the tracer through the
// PR_SET_PTRACER prctl.
//
// d) The tracer has CAP_SYS_PTRACE.
//
// See security/yama/yama_lsm.c:yama_ptrace_access_check.
//
// Precondition: the TaskSet mutex must be locked (for reading or writing).
func (t *Task) canTraceYAMALocked(target *Task) bool {
	if tracer := target.Tracer(); tracer != nil {
		if tracer.tg == t.tg {
			return true
		}
	}
	if target.isYAMADescendantOfLocked(t) {
		return true
	}
	if target.hasYAMAExceptionForLocked(t) {
		return true
	}
	if t.HasCapabilityIn(linux.CAP_SYS_PTRACE, target.UserNamespace()) {
		return true
	}
	return false
}

// Determines whether t is considered a descendant of ancestor for the purposes
// of YAMA permissions (specifically, whether t's thread group is descended from
// ancestor's).
//
// Precondition: the TaskSet mutex must be locked (for reading or writing).
func (t *Task) isYAMADescendantOfLocked(ancestor *Task) bool {
	walker := t
	for walker != nil {
		if walker.tg.leader == ancestor.tg.leader {
			return true
		}
		walker = walker.parent
	}
	return false
}

// Precondition: the TaskSet mutex must be locked (for reading or writing).
func (t *Task) hasYAMAExceptionForLocked(tracer *Task) bool {
	allowed, ok := t.k.ptraceExceptions[t.tg.leader]
	if !ok {
		return false
	}
	return allowed == nil || tracer.isYAMADescendantOfLocked(allowed)
}

// ClearYAMAException removes any YAMA exception with t as the tracee.
func (t *Task) ClearYAMAException() {
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	tracee := t.tg.leader
	delete(t.k.ptraceExceptions, tracee)
}

// SetYAMAException creates a YAMA exception allowing all descendants of tracer
// to trace t. If tracer is nil, then any task is allowed to trace t.
//
// If there was an existing exception, it is overwritten with the new one.
func (t *Task) SetYAMAException(tracer *Task) {
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()

	tracee := t.tg.leader
	tracee.ptraceYAMAExceptionAdded = true
	if tracer != nil {
		tracer.ptraceYAMAExceptionAdded = true
	}

	t.k.ptraceExceptions[tracee] = tracer
}

// Tracer returns t's ptrace Tracer.
func (t *Task) Tracer() *Task {
	return t.ptraceTracer.Load()
}

// hasTracer returns true if t has a ptrace tracer attached.
func (t *Task) hasTracer() bool {
	// This isn't just inlined into callers so that if Task.Tracer() turns out
	// to be too expensive because of e.g. interface conversion, we can switch
	// to having a separate atomic flag more easily.
	return t.Tracer() != nil
}

// ptraceStop is a TaskStop placed on tasks in a ptrace-stop.
//
// +stateify savable
type ptraceStop struct {
	// If frozen is true, the stopped task's tracer is currently operating on
	// it, so Task.Kill should not remove the stop.
	frozen bool

	// If listen is true, the stopped task's tracer invoked PTRACE_LISTEN, so
	// ptraceFreeze should fail.
	listen bool
}

// Killable implements TaskStop.Killable.
func (s *ptraceStop) Killable() bool {
	return !s.frozen
}

// beginPtraceStopLocked initiates an unfrozen ptrace-stop on t. If t has been
// killed, the stop is skipped, and beginPtraceStopLocked returns false.
//
// beginPtraceStopLocked does not signal t's tracer or wake it if it is
// waiting.
//
// Preconditions:
//   - The TaskSet mutex must be locked.
//   - The caller must be running on the task goroutine.
func (t *Task) beginPtraceStopLocked() bool {
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	// This is analogous to Linux's kernel/signal.c:ptrace_stop() => ... =>
	// kernel/sched/core.c:__schedule() => signal_pending_state() check, which
	// is what prevents tasks from entering ptrace-stops after being killed.
	// Note that if t was SIGKILLed and beginPtraceStopLocked is being called
	// for PTRACE_EVENT_EXIT, the task will have dequeued the signal before
	// entering the exit path, so t.killedLocked() will no longer return true.
	// This is consistent with Linux: "Bugs: ... A SIGKILL signal may still
	// cause a PTRACE_EVENT_EXIT stop before actual signal death. This may be
	// changed in the future; SIGKILL is meant to always immediately kill tasks
	// even under ptrace. Last confirmed on Linux 3.13." - ptrace(2)
	if t.killedLocked() {
		return false
	}
	t.beginInternalStopLocked(&ptraceStop{})
	return true
}

// Preconditions: The TaskSet mutex must be locked.
func (t *Task) ptraceTrapLocked(code int32) {
	// This is unconditional in ptrace_stop().
	t.tg.signalHandlers.mu.Lock()
	t.trapStopPending = false
	t.tg.signalHandlers.mu.Unlock()
	t.ptraceCode = code
	t.ptraceSiginfo = &linux.SignalInfo{
		Signo: int32(linux.SIGTRAP),
		Code:  code,
	}
	t.ptraceSiginfo.SetPID(int32(t.tg.pidns.tids[t]))
	t.ptraceSiginfo.SetUID(int32(t.Credentials().RealKUID.In(t.UserNamespace()).OrOverflow()))
	if t.beginPtraceStopLocked() {
		tracer := t.Tracer()
		tracer.signalStop(t, linux.CLD_TRAPPED, int32(linux.SIGTRAP))
		tracer.tg.eventQueue.Notify(EventTraceeStop)
	}
}

// ptraceFreeze checks if t is in a ptraceStop. If so, it freezes the
// ptraceStop, temporarily preventing it from being removed by a concurrent
// Task.Kill, and returns true. Otherwise it returns false.
//
// Preconditions:
//   - The TaskSet mutex must be locked.
//   - The caller must be running on the task goroutine of t's tracer.
func (t *Task) ptraceFreeze() bool {
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	if t.stop == nil {
		return false
	}
	s, ok := t.stop.(*ptraceStop)
	if !ok {
		return false
	}
	if s.listen {
		return false
	}
	s.frozen = true
	return true
}

// ptraceUnfreeze ends the effect of a previous successful call to
// ptraceFreeze.
//
// Preconditions: t must be in a frozen ptraceStop.
func (t *Task) ptraceUnfreeze() {
	// t.tg.signalHandlers is stable because t is in a frozen ptrace-stop,
	// preventing its thread group from completing execve.
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	t.ptraceUnfreezeLocked()
}

// Preconditions:
//   - t must be in a frozen ptraceStop.
//   - t's signal mutex must be locked.
func (t *Task) ptraceUnfreezeLocked() {
	// Do this even if the task has been killed to ensure a panic if t.stop is
	// nil or not a ptraceStop.
	t.stop.(*ptraceStop).frozen = false
	if t.killedLocked() {
		t.endInternalStopLocked()
	}
}

// ptraceUnstop implements ptrace request PTRACE_CONT, PTRACE_SYSCALL,
// PTRACE_SINGLESTEP, PTRACE_SYSEMU, or PTRACE_SYSEMU_SINGLESTEP depending on
// mode and singlestep.
//
// Preconditions: t must be in a frozen ptrace stop.
//
// Postconditions: If ptraceUnstop returns nil, t will no longer be in a ptrace
// stop.
func (t *Task) ptraceUnstop(mode ptraceSyscallMode, singlestep bool, sig linux.Signal) error {
	if sig != 0 && !sig.IsValid() {
		return linuxerr.EIO
	}
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	t.ptraceCode = int32(sig)
	t.ptraceSyscallMode = mode
	t.ptraceSinglestep = singlestep
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	t.endInternalStopLocked()
	return nil
}

func (t *Task) ptraceTraceme() error {
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	if t.hasTracer() {
		return linuxerr.EPERM
	}
	if t.parent == nil {
		// In Linux, only init can not have a parent, and init is assumed never
		// to invoke PTRACE_TRACEME. In the sentry, TGID 1 is an arbitrary user
		// application that may invoke PTRACE_TRACEME; having no parent can
		// also occur if all tasks in the parent thread group have exited, and
		// failed to find a living thread group to reparent to. The former case
		// is treated as if TGID 1 has an exited parent in an invisible
		// ancestor PID namespace that is an owner of the root user namespace
		// (and consequently has CAP_SYS_PTRACE), and the latter case is a
		// special form of the exited parent case below. In either case,
		// returning nil here is correct.
		return nil
	}
	if !t.parent.canTraceLocked(t, true) {
		return linuxerr.EPERM
	}
	if t.parent.exitStateLocked() != TaskExitNone {
		// Fail silently, as if we were successfully attached but then
		// immediately detached. This is consistent with Linux.
		return nil
	}
	t.ptraceTracer.Store(t.parent)
	t.parent.ptraceTracees[t] = struct{}{}
	return nil
}

// ptraceAttach implements ptrace(PTRACE_ATTACH, target) if seize is false, and
// ptrace(PTRACE_SEIZE, target, 0, opts) if seize is true. t is the caller.
func (t *Task) ptraceAttach(target *Task, seize bool, opts uintptr) error {
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	if !t.canTraceLocked(target, true) {
		return linuxerr.EPERM
	}
	if target.hasTracer() {
		return linuxerr.EPERM
	}
	// Attaching to zombies and dead tasks is not permitted; the exit
	// notification logic relies on this. Linux allows attaching to PF_EXITING
	// tasks, though.
	if target.exitStateLocked() >= TaskExitZombie {
		return linuxerr.EPERM
	}
	if seize {
		if err := target.ptraceSetOptionsLocked(opts); err != nil {
			return linuxerr.EIO
		}
	}
	target.ptraceTracer.Store(t)
	t.ptraceTracees[target] = struct{}{}
	target.tg.signalHandlers.mu.Lock()
	target.ptraceSeized = seize
	// "Unlike PTRACE_ATTACH, PTRACE_SEIZE does not stop the process." -
	// ptrace(2)
	if !seize {
		target.sendSignalLocked(&linux.SignalInfo{
			Signo: int32(linux.SIGSTOP),
			Code:  linux.SI_USER,
		}, false /* group */)
	}
	// Undocumented Linux feature: If the tracee is already group-stopped (and
	// consequently will not report the SIGSTOP just sent), force it to leave
	// and re-enter the stop so that it will switch to a ptrace-stop.
	if target.stop == (*groupStop)(nil) {
		target.trapStopPending = true
		target.endInternalStopLocked()
		// TODO(jamieliu): Linux blocks ptrace_attach() until the task has
		// entered the ptrace-stop (or exited) via JOBCTL_TRAPPING.
	}
	target.tg.signalHandlers.mu.Unlock()
	return nil
}

// ptraceDetach implements ptrace(PTRACE_DETACH, target, 0, sig). t is the
// caller.
//
// Preconditions: target must be a tracee of t in a frozen ptrace stop.
//
// Postconditions: If ptraceDetach returns nil, target will no longer be in a
// ptrace stop.
func (t *Task) ptraceDetach(target *Task, sig linux.Signal) error {
	if sig != 0 && !sig.IsValid() {
		return linuxerr.EIO
	}
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	target.ptraceCode = int32(sig)
	target.forgetTracerLocked()
	delete(t.ptraceTracees, target)
	return nil
}

// exitPtraceLocked is called in the exit path to detach all of t's tracees.
//
// Preconditions: The TaskSet mutex must be locked for writing.
func (t *Task) exitPtraceLocked() {
	for target := range t.ptraceTracees {
		if target.ptraceOpts.ExitKill {
			target.tg.signalHandlers.mu.Lock()
			target.sendSignalLocked(&linux.SignalInfo{
				Signo: int32(linux.SIGKILL),
			}, false /* group */)
			target.tg.signalHandlers.mu.Unlock()
		}
		// Leave ptraceCode unchanged so that if the task is ptrace-stopped, it
		// observes the ptraceCode it set before it entered the stop. I believe
		// this is consistent with Linux.
		target.forgetTracerLocked()
	}
	clear(t.ptraceTracees) // nil maps cannot be saved

	if t.ptraceYAMAExceptionAdded {
		delete(t.k.ptraceExceptions, t)
		for tracee, tracer := range t.k.ptraceExceptions {
			if tracer == t {
				delete(t.k.ptraceExceptions, tracee)
			}
		}
	}
}

// forgetTracerLocked detaches t's tracer and ensures that t is no longer
// ptrace-stopped.
//
// Preconditions: The TaskSet mutex must be locked for writing.
func (t *Task) forgetTracerLocked() {
	t.ptraceOpts = ptraceOptions{}
	t.ptraceSyscallMode = ptraceSyscallNone
	t.ptraceSinglestep = false
	t.ptraceTracer.Store(nil)
	if t.exitTracerNotified && !t.exitTracerAcked {
		t.exitTracerAcked = true
		// Don't hold signalHandlers.mu while calling exitNotifyLocked,
		// since it takes that mutex in some pathways.
		t.exitNotifyLocked(true)
	}
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	t.ptraceSeized = false
	// Unset t.trapStopPending, which might have been set by PTRACE_INTERRUPT. If
	// it wasn't, it will be reset via t.groupStopPending after the following.
	t.trapStopPending = false
	// If t's thread group is in a group stop and t is eligible to participate,
	// make it do so. This is essentially the reverse of the special case in
	// ptraceAttach, which converts a group stop to a ptrace stop. ("Handling
	// of restart from group-stop is currently buggy, but the "as planned"
	// behavior is to leave tracee stopped and waiting for SIGCONT." -
	// ptrace(2))
	if (t.tg.groupStopComplete || t.tg.groupStopPendingCount != 0) && !t.groupStopPending && t.exitStateLocked() < TaskExitInitiated {
		t.groupStopPending = true
		// t already participated in the group stop when it unset
		// groupStopPending.
		t.groupStopAcknowledged = true
		t.interrupt()
	}
	if _, ok := t.stop.(*ptraceStop); ok {
		t.endInternalStopLocked()
	}
}

// ptraceSignalLocked is called after signal dequeueing to check if t should
// enter ptrace signal-delivery-stop.
//
// Preconditions:
//   - The signal mutex must be locked.
//   - The caller must be running on the task goroutine.
//
// +checklocks:t.tg.signalHandlers.mu
func (t *Task) ptraceSignalLocked(info *linux.SignalInfo) bool {
	if linux.Signal(info.Signo) == linux.SIGKILL {
		return false
	}
	if !t.hasTracer() {
		return false
	}
	// The tracer might change this signal into a stop signal, in which case
	// any SIGCONT received after the signal was originally dequeued should
	// cancel it. This is consistent with Linux.
	t.tg.groupStopDequeued = true
	// This is unconditional in ptrace_stop().
	t.trapStopPending = false
	// Can't lock the TaskSet mutex while holding a signal mutex.
	t.tg.signalHandlers.mu.Unlock()
	defer t.tg.signalHandlers.mu.Lock()
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	tracer := t.Tracer()
	if tracer == nil {
		return false
	}
	t.ptraceCode = info.Signo
	t.ptraceSiginfo = info
	t.Debugf("Entering signal-delivery-stop for signal %d", info.Signo)
	if t.beginPtraceStopLocked() {
		tracer.signalStop(t, linux.CLD_TRAPPED, info.Signo)
		tracer.tg.eventQueue.Notify(EventTraceeStop)
	}
	return true
}

// ptraceSeccomp is called when a seccomp-bpf filter returns action
// SECCOMP_RET_TRACE to check if t should enter PTRACE_EVENT_SECCOMP stop. data
// is the lower 16 bits of the filter's return value.
func (t *Task) ptraceSeccomp(data uint16) bool {
	if !t.hasTracer() {
		return false
	}
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	if !t.ptraceOpts.TraceSeccomp {
		return false
	}
	t.Debugf("Entering PTRACE_EVENT_SECCOMP stop")
	t.ptraceEventLocked(linux.PTRACE_EVENT_SECCOMP, uint64(data))
	return true
}

// ptraceSyscallEnter is called immediately before entering a syscall to check
// if t should enter ptrace syscall-enter-stop.
func (t *Task) ptraceSyscallEnter() (taskRunState, bool) {
	if !t.hasTracer() {
		return nil, false
	}
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	switch t.ptraceSyscallMode {
	case ptraceSyscallNone:
		return nil, false
	case ptraceSyscallIntercept:
		t.Debugf("Entering syscall-enter-stop from PTRACE_SYSCALL")
		t.ptraceSyscallStopLocked()
		return (*runSyscallAfterSyscallEnterStop)(nil), true
	case ptraceSyscallEmu:
		t.Debugf("Entering syscall-enter-stop from PTRACE_SYSEMU")
		t.ptraceSyscallStopLocked()
		return (*runSyscallAfterSysemuStop)(nil), true
	}
	panic(fmt.Sprintf("Unknown ptraceSyscallMode: %v", t.ptraceSyscallMode))
}

// ptraceSyscallExit is called immediately after leaving a syscall to check if
// t should enter ptrace syscall-exit-stop.
func (t *Task) ptraceSyscallExit() {
	if !t.hasTracer() {
		return
	}
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	if t.ptraceSyscallMode != ptraceSyscallIntercept {
		return
	}
	t.Debugf("Entering syscall-exit-stop")
	t.ptraceSyscallStopLocked()
}

// Preconditions: The TaskSet mutex must be locked.
func (t *Task) ptraceSyscallStopLocked() {
	code := int32(linux.SIGTRAP)
	if t.ptraceOpts.SysGood {
		code |= 0x80
	}
	t.ptraceTrapLocked(code)
}

type ptraceCloneKind int32

const (
	// ptraceCloneKindClone represents a call to Task.Clone where
	// TerminationSignal is not SIGCHLD and Vfork is false.
	ptraceCloneKindClone ptraceCloneKind = iota

	// ptraceCloneKindFork represents a call to Task.Clone where
	// TerminationSignal is SIGCHLD and Vfork is false.
	ptraceCloneKindFork

	// ptraceCloneKindVfork represents a call to Task.Clone where Vfork is
	// true.
	ptraceCloneKindVfork
)

// ptraceClone is called at the end of a clone or fork syscall to check if t
// should enter PTRACE_EVENT_CLONE, PTRACE_EVENT_FORK, or PTRACE_EVENT_VFORK
// stop. child is the new task.
func (t *Task) ptraceClone(kind ptraceCloneKind, child *Task, args *linux.CloneArgs) bool {
	if !t.hasTracer() {
		return false
	}
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	event := false
	if args.Flags&linux.CLONE_UNTRACED == 0 {
		switch kind {
		case ptraceCloneKindClone:
			if t.ptraceOpts.TraceClone {
				t.Debugf("Entering PTRACE_EVENT_CLONE stop")
				t.ptraceEventLocked(linux.PTRACE_EVENT_CLONE, uint64(t.tg.pidns.tids[child]))
				event = true
			}
		case ptraceCloneKindFork:
			if t.ptraceOpts.TraceFork {
				t.Debugf("Entering PTRACE_EVENT_FORK stop")
				t.ptraceEventLocked(linux.PTRACE_EVENT_FORK, uint64(t.tg.pidns.tids[child]))
				event = true
			}
		case ptraceCloneKindVfork:
			if t.ptraceOpts.TraceVfork {
				t.Debugf("Entering PTRACE_EVENT_VFORK stop")
				t.ptraceEventLocked(linux.PTRACE_EVENT_VFORK, uint64(t.tg.pidns.tids[child]))
				event = true
			}
		default:
			panic(fmt.Sprintf("Unknown ptraceCloneKind: %v", kind))
		}
	}
	// "If the PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, or PTRACE_O_TRACECLONE
	// options are in effect, then children created by, respectively, vfork(2)
	// or clone(2) with the CLONE_VFORK flag, fork(2) or clone(2) with the exit
	// signal set to SIGCHLD, and other kinds of clone(2), are automatically
	// attached to the same tracer which traced their parent. SIGSTOP is
	// delivered to the children, causing them to enter signal-delivery-stop
	// after they exit the system call which created them." - ptrace(2)
	//
	// clone(2)'s documentation of CLONE_UNTRACED and CLONE_PTRACE is
	// confusingly wrong; see kernel/fork.c:_do_fork() => copy_process() =>
	// include/linux/ptrace.h:ptrace_init_task().
	if event || args.Flags&linux.CLONE_PTRACE != 0 {
		tracer := t.Tracer()
		if tracer != nil {
			child.ptraceTracer.Store(tracer)
			tracer.ptraceTracees[child] = struct{}{}
			child.tg.signalHandlers.mu.Lock()
			// "The "seized" behavior ... is inherited by children that are
			// automatically attached using PTRACE_O_TRACEFORK,
			// PTRACE_O_TRACEVFORK, and PTRACE_O_TRACECLONE." - ptrace(2)
			child.ptraceSeized = t.ptraceSeized
			// "Flags are inherited by new tracees created and "auto-attached"
			// via active PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, or
			// PTRACE_O_TRACECLONE options." - ptrace(2)
			child.ptraceOpts = t.ptraceOpts
			// "PTRACE_SEIZE: ... Automatically attached children stop with
			// PTRACE_EVENT_STOP and WSTOPSIG(status) returns SIGTRAP instead
			// of having SIGSTOP signal delivered to them." - ptrace(2)
			if child.ptraceSeized {
				child.trapStopPending = true
			} else {
				child.pendingSignals.enqueue(&linux.SignalInfo{
					Signo: int32(linux.SIGSTOP),
				}, nil)
			}
			// The child will self-interrupt() when its task goroutine starts
			// running, so we don't have to.
			child.tg.signalHandlers.mu.Unlock()
		}
	}
	return event
}

// ptraceVforkDone is called after the end of a vfork stop to check if t should
// enter PTRACE_EVENT_VFORK_DONE stop. child is the new task's thread ID in t's
// PID namespace.
func (t *Task) ptraceVforkDone(child ThreadID) bool {
	if !t.hasTracer() {
		return false
	}
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	if !t.ptraceOpts.TraceVforkDone {
		return false
	}
	t.Debugf("Entering PTRACE_EVENT_VFORK_DONE stop")
	t.ptraceEventLocked(linux.PTRACE_EVENT_VFORK_DONE, uint64(child))
	return true
}

// ptraceExec is called at the end of an execve syscall to check if t should
// enter PTRACE_EVENT_EXEC stop. oldTID is t's thread ID, in its *tracer's* PID
// namespace, prior to the execve. (If t did not have a tracer at the time
// oldTID was read, oldTID may be 0. This is consistent with Linux.)
func (t *Task) ptraceExec(oldTID ThreadID) {
	if !t.hasTracer() {
		return
	}
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	// Recheck with the TaskSet mutex locked. Most ptrace points don't need to
	// do this because detaching resets ptrace options, but PTRACE_EVENT_EXEC
	// is special because both TraceExec and !TraceExec do something if a
	// tracer is attached.
	if !t.hasTracer() {
		return
	}
	if t.ptraceOpts.TraceExec {
		t.Debugf("Entering PTRACE_EVENT_EXEC stop")
		t.ptraceEventLocked(linux.PTRACE_EVENT_EXEC, uint64(oldTID))
		return
	}
	// "If the PTRACE_O_TRACEEXEC option is not in effect for the execing
	// tracee, and if the tracee was PTRACE_ATTACHed rather that [sic]
	// PTRACE_SEIZEd, the kernel delivers an extra SIGTRAP to the tracee after
	// execve(2) returns. This is an ordinary signal (similar to one which can
	// be generated by `kill -TRAP`, not a special kind of ptrace-stop.
	// Employing PTRACE_GETSIGINFO for this signal returns si_code set to 0
	// (SI_USER). This signal may be blocked by signal mask, and thus may be
	// delivered (much) later." - ptrace(2)
	if t.ptraceSeized {
		return
	}
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	t.sendSignalLocked(&linux.SignalInfo{
		Signo: int32(linux.SIGTRAP),
		Code:  linux.SI_USER,
	}, false /* group */)
}

// ptraceExit is called early in the task exit path to check if t should enter
// PTRACE_EVENT_EXIT stop.
func (t *Task) ptraceExit() {
	if !t.hasTracer() {
		return
	}
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	if !t.ptraceOpts.TraceExit {
		return
	}
	t.tg.signalHandlers.mu.Lock()
	status := t.exitStatus
	t.tg.signalHandlers.mu.Unlock()
	t.Debugf("Entering PTRACE_EVENT_EXIT stop")
	t.ptraceEventLocked(linux.PTRACE_EVENT_EXIT, uint64(status))
}

// Preconditions: The TaskSet mutex must be locked.
func (t *Task) ptraceEventLocked(event int32, msg uint64) {
	t.ptraceEventMsg = msg
	// """
	// PTRACE_EVENT stops are observed by the tracer as waitpid(2) returning
	// with WIFSTOPPED(status), and WSTOPSIG(status) returns SIGTRAP. An
	// additional bit is set in the higher byte of the status word: the value
	// status>>8 will be
	//
	//   (SIGTRAP | PTRACE_EVENT_foo << 8).
	//
	// ...
	//
	// """ - ptrace(2)
	t.ptraceTrapLocked(int32(linux.SIGTRAP) | (event << 8))
}

// ptraceKill implements ptrace(PTRACE_KILL, target). t is the caller.
func (t *Task) ptraceKill(target *Task) error {
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	if target.Tracer() != t {
		return linuxerr.ESRCH
	}
	target.tg.signalHandlers.mu.Lock()
	defer target.tg.signalHandlers.mu.Unlock()
	// "This operation is deprecated; do not use it! Instead, send a SIGKILL
	// directly using kill(2) or tgkill(2). The problem with PTRACE_KILL is
	// that it requires the tracee to be in signal-delivery-stop, otherwise it
	// may not work (i.e., may complete successfully but won't kill the
	// tracee)." - ptrace(2)
	if target.stop == nil {
		return nil
	}
	if _, ok := target.stop.(*ptraceStop); !ok {
		return nil
	}
	target.ptraceCode = int32(linux.SIGKILL)
	target.endInternalStopLocked()
	return nil
}

func (t *Task) ptraceInterrupt(target *Task) error {
	t.tg.pidns.owner.mu.Lock()
	defer t.tg.pidns.owner.mu.Unlock()
	if target.Tracer() != t {
		return linuxerr.ESRCH
	}
	if !target.ptraceSeized {
		return linuxerr.EIO
	}
	target.tg.signalHandlers.mu.Lock()
	defer target.tg.signalHandlers.mu.Unlock()
	if target.killedLocked() || target.exitStateLocked() >= TaskExitInitiated {
		return nil
	}
	target.trapStopPending = true
	if s, ok := target.stop.(*ptraceStop); ok && s.listen {
		target.endInternalStopLocked()
	}
	target.interrupt()
	return nil
}

// Preconditions:
//   - The TaskSet mutex must be locked for writing.
//   - t must have a tracer.
func (t *Task) ptraceSetOptionsLocked(opts uintptr) error {
	const valid = uintptr(linux.PTRACE_O_EXITKILL |
		linux.PTRACE_O_TRACESYSGOOD |
		linux.PTRACE_O_TRACECLONE |
		linux.PTRACE_O_TRACEEXEC |
		linux.PTRACE_O_TRACEEXIT |
		linux.PTRACE_O_TRACEFORK |
		linux.PTRACE_O_TRACESECCOMP |
		linux.PTRACE_O_TRACEVFORK |
		linux.PTRACE_O_TRACEVFORKDONE)
	if opts&^valid != 0 {
		return linuxerr.EINVAL
	}
	t.ptraceOpts = ptraceOptions{
		ExitKill:       opts&linux.PTRACE_O_EXITKILL != 0,
		SysGood:        opts&linux.PTRACE_O_TRACESYSGOOD != 0,
		TraceClone:     opts&linux.PTRACE_O_TRACECLONE != 0,
		TraceExec:      opts&linux.PTRACE_O_TRACEEXEC != 0,
		TraceExit:      opts&linux.PTRACE_O_TRACEEXIT != 0,
		TraceFork:      opts&linux.PTRACE_O_TRACEFORK != 0,
		TraceSeccomp:   opts&linux.PTRACE_O_TRACESECCOMP != 0,
		TraceVfork:     opts&linux.PTRACE_O_TRACEVFORK != 0,
		TraceVforkDone: opts&linux.PTRACE_O_TRACEVFORKDONE != 0,
	}
	return nil
}

// PtraceAttach implements PTRACE_ATTACH and PTRACE_SEIZE.
func (t *Task) PtraceAttach(req int64, pid ThreadID, addr, data hostarch.Addr) (*SyscallControl, error) {
	target := t.tg.pidns.TaskWithID(pid)
	if target == nil {
		return nil, linuxerr.ESRCH
	}
	seize := req == linux.PTRACE_SEIZE
	if seize && addr != 0 {
		return nil, linuxerr.EIO
	}
	if t.tg == target.tg {
		return nil, linuxerr.EPERM
	}

	// Serialize with execve(2) to ensure correct creds computation in execve(2),
	// see Task.shouldStopPrivGainDueToPtracerLocked().
	t.execveCredsMutexStartLock(target.tg)
	r := runPtraceAfterExecveCredsLock{
		target: target,
		seize:  seize,
		data:   data,
	}
	return &SyscallControl{next: &r, ignoreReturn: false}, nil
}

// The runPtraceAfterExecveCredsLock state continues PTRACE_ATTACH and PTRACE_SEIZE after
// the lock that serializes them with execve has been acquired.
//
// +stateify savable
type runPtraceAfterExecveCredsLock struct {
	target *Task
	seize  bool
	data   hostarch.Addr
}

func (r *runPtraceAfterExecveCredsLock) execute(t *Task) taskRunState {
	if t.killed() {
		// execveCredsMutexUnlock() will not pass us the lock after we were killed, so there is no
		// race in loading execveCredsMutexOwner without a lock.
		if t.execveCredsMutexOwner != nil {
			t.execveCredsMutexUnlock()
		}
		return (*runInterrupt)(nil)
	}

	defer t.execveCredsMutexUnlock()
	err := t.ptraceAttach(r.target, r.seize, uintptr(r.data))
	if err != nil {
		return t.doSyscallError(err)
	}
	return (*runSyscallExit)(nil)
}

// Ptrace implements ptrace(2) excluding PTRACE_ATTACH and PTRACE_SEIZE.
func (t *Task) Ptrace(req int64, pid ThreadID, addr, data hostarch.Addr) error {
	// PTRACE_TRACEME ignores all other arguments.
	if req == linux.PTRACE_TRACEME {
		return t.ptraceTraceme()
	}
	// All other ptrace requests operate on a current or future tracee
	// specified by pid.
	target := t.tg.pidns.TaskWithID(pid)
	if target == nil {
		return linuxerr.ESRCH
	}
	// PTRACE_KILL and PTRACE_INTERRUPT require that the target is a tracee,
	// but does not require that it is ptrace-stopped.
	if req == linux.PTRACE_KILL {
		return t.ptraceKill(target)
	}
	if req == linux.PTRACE_INTERRUPT {
		return t.ptraceInterrupt(target)
	}
	// All other ptrace requests require that the target is a ptrace-stopped
	// tracee, and freeze the ptrace-stop so the tracee can be operated on.
	t.tg.pidns.owner.mu.RLock()
	if target.Tracer() != t {
		t.tg.pidns.owner.mu.RUnlock()
		return linuxerr.ESRCH
	}
	if !target.ptraceFreeze() {
		t.tg.pidns.owner.mu.RUnlock()
		// "Most ptrace commands (all except PTRACE_ATTACH, PTRACE_SEIZE,
		// PTRACE_TRACEME, PTRACE_INTERRUPT, and PTRACE_KILL) require the
		// tracee to be in a ptrace-stop, otherwise they fail with ESRCH." -
		// ptrace(2)
		return linuxerr.ESRCH
	}
	t.tg.pidns.owner.mu.RUnlock()
	// Even if the target has a ptrace-stop active, the tracee's task goroutine
	// may not yet have reached Task.doStop; wait for it to do so. This is safe
	// because there's no way for target to initiate a ptrace-stop and then
	// block (by calling Task.block) before entering it.
	//
	// Caveat: If tasks were just restored, the tracee's first call to
	// Task.Activate (in Task.run) occurs before its first call to Task.doStop,
	// which may block if the tracer's address space is active.
	t.UninterruptibleSleepStart(true)
	target.waitGoroutineStoppedOrExited()
	t.UninterruptibleSleepFinish(true)

	// Resuming commands end the ptrace stop, but only if successful.
	// PTRACE_LISTEN ends the ptrace stop if trapNotifyPending is already set on the
	// target.
	switch req {
	case linux.PTRACE_DETACH:
		if err := t.ptraceDetach(target, linux.Signal(data)); err != nil {
			target.ptraceUnfreeze()
			return err
		}
		return nil

	case linux.PTRACE_CONT:
		if err := target.ptraceUnstop(ptraceSyscallNone, false, linux.Signal(data)); err != nil {
			target.ptraceUnfreeze()
			return err
		}
		return nil

	case linux.PTRACE_SYSCALL:
		if err := target.ptraceUnstop(ptraceSyscallIntercept, false, linux.Signal(data)); err != nil {
			target.ptraceUnfreeze()
			return err
		}
		return nil

	case linux.PTRACE_SINGLESTEP:
		if err := target.ptraceUnstop(ptraceSyscallNone, true, linux.Signal(data)); err != nil {
			target.ptraceUnfreeze()
			return err
		}
		return nil

	case linux.PTRACE_SYSEMU:
		if err := target.ptraceUnstop(ptraceSyscallEmu, false, linux.Signal(data)); err != nil {
			target.ptraceUnfreeze()
			return err
		}
		return nil

	case linux.PTRACE_SYSEMU_SINGLESTEP:
		if err := target.ptraceUnstop(ptraceSyscallEmu, true, linux.Signal(data)); err != nil {
			target.ptraceUnfreeze()
			return err
		}
		return nil

	case linux.PTRACE_LISTEN:
		t.tg.pidns.owner.mu.RLock()
		defer t.tg.pidns.owner.mu.RUnlock()
		if !target.ptraceSeized {
			return linuxerr.EIO
		}
		if target.ptraceSiginfo == nil {
			return linuxerr.EIO
		}
		if target.ptraceSiginfo.Code>>8 != linux.PTRACE_EVENT_STOP {
			return linuxerr.EIO
		}
		target.tg.signalHandlers.mu.Lock()
		defer target.tg.signalHandlers.mu.Unlock()
		if target.trapNotifyPending {
			target.endInternalStopLocked()
		} else {
			target.stop.(*ptraceStop).listen = true
			target.ptraceUnfreezeLocked()
		}
		return nil
	}

	// All other ptrace requests expect us to unfreeze the stop.
	defer target.ptraceUnfreeze()

	switch req {
	case linux.PTRACE_PEEKTEXT, linux.PTRACE_PEEKDATA:
		// "At the system call level, the PTRACE_PEEKTEXT, PTRACE_PEEKDATA, and
		// PTRACE_PEEKUSER requests have a different API: they store the result
		// at the address specified by the data parameter, and the return value
		// is the error flag." - ptrace(2)
		word := t.Arch().Native(0)
		if _, err := word.CopyIn(target.CopyContext(t, usermem.IOOpts{IgnorePermissions: true}), addr); err != nil {
			return err
		}
		_, err := word.CopyOut(t, data)
		return err

	case linux.PTRACE_POKETEXT, linux.PTRACE_POKEDATA:
		word := t.Arch().Native(uintptr(data))
		_, err := word.CopyOut(target.CopyContext(t, usermem.IOOpts{IgnorePermissions: true}), addr)
		return err

	case linux.PTRACE_GETREGSET:
		// "Read the tracee's registers. addr specifies, in an
		// architecture-dependent way, the type of registers to be read. ...
		// data points to a struct iovec, which describes the destination
		// buffer's location and length. On return, the kernel modifies iov.len
		// to indicate the actual number of bytes returned." - ptrace(2)
		ars, err := t.CopyInIovecs(data, 1)
		if err != nil {
			return err
		}

		ar := ars.Head()
		n, err := target.Arch().PtraceGetRegSet(uintptr(addr), &usermem.IOReadWriter{
			Ctx:  t,
			IO:   t.MemoryManager(),
			Addr: ar.Start,
			Opts: usermem.IOOpts{
				AddressSpaceActive: true,
			},
		}, int(ar.Length()), target.Kernel().FeatureSet())
		if err != nil {
			return err
		}

		// Update iovecs to represent the range of the written register set.
		end, ok := ar.Start.AddLength(uint64(n))
		if !ok {
			panic(fmt.Sprintf("%#x + %#x overflows. Invalid reg size > %#x", ar.Start, n, ar.Length()))
		}
		ar.End = end
		return t.CopyOutIovecs(data, hostarch.AddrRangeSeqOf(ar))

	case linux.PTRACE_SETREGSET:
		ars, err := t.CopyInIovecs(data, 1)
		if err != nil {
			return err
		}

		ar := ars.Head()
		n, err := target.Arch().PtraceSetRegSet(uintptr(addr), &usermem.IOReadWriter{
			Ctx:  t,
			IO:   t.MemoryManager(),
			Addr: ar.Start,
			Opts: usermem.IOOpts{
				AddressSpaceActive: true,
			},
		}, int(ar.Length()), target.Kernel().FeatureSet())
		if err != nil {
			return err
		}
		target.p.FullStateChanged()
		ar.End -= hostarch.Addr(n)
		return t.CopyOutIovecs(data, hostarch.AddrRangeSeqOf(ar))

	case linux.PTRACE_GETSIGINFO:
		t.tg.pidns.owner.mu.RLock()
		defer t.tg.pidns.owner.mu.RUnlock()
		if target.ptraceSiginfo == nil {
			return linuxerr.EINVAL
		}
		_, err := target.ptraceSiginfo.CopyOut(t, data)
		return err

	case linux.PTRACE_SETSIGINFO:
		var info linux.SignalInfo
		if _, err := info.CopyIn(t, data); err != nil {
			return err
		}
		t.tg.pidns.owner.mu.RLock()
		defer t.tg.pidns.owner.mu.RUnlock()
		if target.ptraceSiginfo == nil {
			return linuxerr.EINVAL
		}
		target.ptraceSiginfo = &info
		return nil

	case linux.PTRACE_GETSIGMASK:
		if addr != linux.SignalSetSize {
			return linuxerr.EINVAL
		}
		mask := target.SignalMask()
		_, err := mask.CopyOut(t, data)
		return err

	case linux.PTRACE_SETSIGMASK:
		if addr != linux.SignalSetSize {
			return linuxerr.EINVAL
		}
		var mask linux.SignalSet
		if _, err := mask.CopyIn(t, data); err != nil {
			return err
		}
		// The target's task goroutine is stopped, so this is safe:
		target.SetSignalMask(mask &^ UnblockableSignals)
		return nil

	case linux.PTRACE_SETOPTIONS:
		t.tg.pidns.owner.mu.Lock()
		defer t.tg.pidns.owner.mu.Unlock()
		return target.ptraceSetOptionsLocked(uintptr(data))

	case linux.PTRACE_GETEVENTMSG:
		t.tg.pidns.owner.mu.RLock()
		defer t.tg.pidns.owner.mu.RUnlock()
		_, err := primitive.CopyUint64Out(t, hostarch.Addr(data), target.ptraceEventMsg)
		return err

	// PEEKSIGINFO is unimplemented but seems to have no users anywhere.

	default:
		return t.ptraceArch(target, req, addr, data)
	}
}
