Files
Shailend ChandandgVisor bot 329cdabbf5 Implement pidfds
This patch introduces pidfd support roughly up to Linux 6.8. Linux selftests
at this version pass, and are now run as an //image_test target. Some features,
like PIDFD_THREAD, that occur later than 6.8 in Linux, are included.

It includes:
- These new syscalls:
  - pidfd_open()
  - pidfd_getfd()
  - pidfd_send_signal()
- New aspects to these existing syscalls:
  - waitid() in its support of P_PIDFD
  - clone()/clone3() in their support of CLONE_PIDFD
  - poll()/epoll()/select() in their support for pidfds
  - setns() in its support for pidfds
  - pid introspection via /proc/[pid]/fdinfo/[pidfd]

It excludes:
- pidfdfs: pidfds are backed by the anon inode right now
- ioctls to retrieve namespace fds using a pidfd
- process_madvise()

Updates #5839.

PiperOrigin-RevId: 908341658
2026-04-30 13:22:58 -07:00

569 lines
17 KiB
Go

// 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 linux
import (
"math"
"time"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/signalfd"
"gvisor.dev/gvisor/pkg/sentry/kernel"
)
// Kill implements linux syscall kill(2).
func Kill(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
pid := kernel.ThreadID(args[0].Int())
sig := linux.Signal(args[1].Int())
switch {
case pid > 0:
// "If pid is positive, then signal sig is sent to the process with the
// ID specified by pid." - kill(2)
// This loops to handle races with execve where target dies between
// TaskWithID and SendGroupSignal. Compare Linux's
// kernel/signal.c:kill_pid_info().
for {
target := t.PIDNamespace().TaskWithID(pid)
if target == nil {
return 0, nil, linuxerr.ESRCH
}
if !t.MayKill(target, sig) {
return 0, nil, linuxerr.EPERM
}
info := &linux.SignalInfo{
Signo: int32(sig),
Code: linux.SI_USER,
}
info.SetPID(int32(target.PIDNamespace().IDOfTask(t)))
info.SetUID(int32(t.Credentials().RealKUID.In(target.UserNamespace()).OrOverflow()))
if err := target.SendGroupSignal(info); !linuxerr.Equals(linuxerr.ESRCH, err) {
return 0, nil, err
}
}
case pid == -1:
// "If pid equals -1, then sig is sent to every process for which the
// calling process has permission to send signals, except for process 1
// (init), but see below. ... POSIX.1-2001 requires that kill(-1,sig)
// send sig to all processes that the calling process may send signals
// to, except possibly for some implementation-defined system
// processes. Linux allows a process to signal itself, but on Linux the
// call kill(-1,sig) does not signal the calling process."
var (
lastErr error
delivered int
)
for _, tg := range t.PIDNamespace().ThreadGroups() {
if tg == t.ThreadGroup() {
continue
}
// Don't send the signal to the init process in t's PID namespace.
if tg.IsInitIn(t.PIDNamespace()) {
continue
}
// If pid == -1, the returned error is the last non-EPERM error
// from any call to group_send_sig_info.
if !t.MayKill(tg.Leader(), sig) {
continue
}
// Here and below, whether or not kill returns an error may
// depend on the iteration order. We at least implement the
// semantics documented by the man page: "On success (at least
// one signal was sent), zero is returned."
info := &linux.SignalInfo{
Signo: int32(sig),
Code: linux.SI_USER,
}
info.SetPID(int32(tg.PIDNamespace().IDOfTask(t)))
info.SetUID(int32(t.Credentials().RealKUID.In(tg.Leader().UserNamespace()).OrOverflow()))
err := tg.SendSignal(info)
if linuxerr.Equals(linuxerr.ESRCH, err) {
// ESRCH is ignored because it means the task
// exited while we were iterating. This is a
// race which would not normally exist on
// Linux, so we suppress it.
continue
}
delivered++
if err != nil {
lastErr = err
}
}
if delivered > 0 {
return 0, nil, lastErr
}
return 0, nil, linuxerr.ESRCH
default:
// "If pid equals 0, then sig is sent to every process in the process
// group of the calling process."
//
// "If pid is less than -1, then sig is sent to every process
// in the process group whose ID is -pid."
pgid := kernel.ProcessGroupID(-pid)
if pgid == 0 {
pgid = t.PIDNamespace().IDOfProcessGroup(t.ThreadGroup().ProcessGroup())
}
// If pid != -1 (i.e. signalling a process group), the returned error
// is the last error from any call to group_send_sig_info.
lastErr := error(linuxerr.ESRCH)
for _, tg := range t.PIDNamespace().ThreadGroups() {
if t.PIDNamespace().IDOfProcessGroup(tg.ProcessGroup()) == pgid {
if !t.MayKill(tg.Leader(), sig) {
lastErr = linuxerr.EPERM
continue
}
info := &linux.SignalInfo{
Signo: int32(sig),
Code: linux.SI_USER,
}
info.SetPID(int32(tg.PIDNamespace().IDOfTask(t)))
info.SetUID(int32(t.Credentials().RealKUID.In(tg.Leader().UserNamespace()).OrOverflow()))
// See note above regarding ESRCH race above.
if err := tg.SendSignal(info); !linuxerr.Equals(linuxerr.ESRCH, err) {
lastErr = err
}
}
}
return 0, nil, lastErr
}
}
func tkillSigInfo(sender, receiver *kernel.Task, sig linux.Signal) *linux.SignalInfo {
info := &linux.SignalInfo{
Signo: int32(sig),
Code: linux.SI_TKILL,
}
info.SetPID(int32(receiver.PIDNamespace().IDOfThreadGroup(sender.ThreadGroup())))
info.SetUID(int32(sender.Credentials().RealKUID.In(receiver.UserNamespace()).OrOverflow()))
return info
}
// Tkill implements linux syscall tkill(2).
func Tkill(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
tid := kernel.ThreadID(args[0].Int())
sig := linux.Signal(args[1].Int())
// N.B. Inconsistent with man page, linux actually rejects calls with
// tid <=0 by EINVAL. This isn't the same for all signal calls.
if tid <= 0 {
return 0, nil, linuxerr.EINVAL
}
target := t.PIDNamespace().TaskWithID(tid)
if target == nil {
return 0, nil, linuxerr.ESRCH
}
if !t.MayKill(target, sig) {
return 0, nil, linuxerr.EPERM
}
return 0, nil, target.SendSignal(tkillSigInfo(t, target, sig))
}
// Tgkill implements linux syscall tgkill(2).
func Tgkill(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
tgid := kernel.ThreadID(args[0].Int())
tid := kernel.ThreadID(args[1].Int())
sig := linux.Signal(args[2].Int())
// N.B. Inconsistent with man page, linux actually rejects calls with
// tgid/tid <=0 by EINVAL. This isn't the same for all signal calls.
if tgid <= 0 || tid <= 0 {
return 0, nil, linuxerr.EINVAL
}
targetTG := t.PIDNamespace().ThreadGroupWithID(tgid)
target := t.PIDNamespace().TaskWithID(tid)
if targetTG == nil || target == nil || target.ThreadGroup() != targetTG {
return 0, nil, linuxerr.ESRCH
}
if !t.MayKill(target, sig) {
return 0, nil, linuxerr.EPERM
}
return 0, nil, target.SendSignal(tkillSigInfo(t, target, sig))
}
// RtSigaction implements linux syscall rt_sigaction(2).
func RtSigaction(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
sig := linux.Signal(args[0].Int())
newactarg := args[1].Pointer()
oldactarg := args[2].Pointer()
sigsetsize := args[3].SizeT()
if sigsetsize != linux.SignalSetSize {
return 0, nil, linuxerr.EINVAL
}
var newactptr *linux.SigAction
if newactarg != 0 {
var newact linux.SigAction
if _, err := newact.CopyIn(t, newactarg); err != nil {
return 0, nil, err
}
newactptr = &newact
}
oldact, err := t.ThreadGroup().SetSigAction(sig, newactptr)
if err != nil {
return 0, nil, err
}
if oldactarg != 0 {
if _, err := oldact.CopyOut(t, oldactarg); err != nil {
return 0, nil, err
}
}
return 0, nil, nil
}
// Sigreturn implements linux syscall sigreturn(2).
func Sigreturn(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
ctrl, err := t.SignalReturn(false)
return 0, ctrl, err
}
// RtSigreturn implements linux syscall rt_sigreturn(2).
func RtSigreturn(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
ctrl, err := t.SignalReturn(true)
return 0, ctrl, err
}
// RtSigprocmask implements linux syscall rt_sigprocmask(2).
func RtSigprocmask(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
how := args[0].Int()
setaddr := args[1].Pointer()
oldaddr := args[2].Pointer()
sigsetsize := args[3].SizeT()
if sigsetsize != linux.SignalSetSize {
return 0, nil, linuxerr.EINVAL
}
oldmask := t.SignalMask()
if setaddr != 0 {
mask, err := copyInSigSet(t, setaddr, sigsetsize)
if err != nil {
return 0, nil, err
}
switch how {
case linux.SIG_BLOCK:
t.SetSignalMask(oldmask | mask)
case linux.SIG_UNBLOCK:
t.SetSignalMask(oldmask &^ mask)
case linux.SIG_SETMASK:
t.SetSignalMask(mask)
default:
return 0, nil, linuxerr.EINVAL
}
}
if oldaddr != 0 {
return 0, nil, copyOutSigSet(t, oldaddr, oldmask)
}
return 0, nil, nil
}
// Sigaltstack implements linux syscall sigaltstack(2).
func Sigaltstack(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
setaddr := args[0].Pointer()
oldaddr := args[1].Pointer()
ctrl, err := t.SigaltStack(setaddr, oldaddr)
return 0, ctrl, err
}
// Pause implements linux syscall pause(2).
func Pause(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
return 0, nil, linuxerr.ConvertIntr(t.Block(nil), linuxerr.ERESTARTNOHAND)
}
// RtSigpending implements linux syscall rt_sigpending(2).
func RtSigpending(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
addr := args[0].Pointer()
pending := t.PendingSignals()
_, err := pending.CopyOut(t, addr)
return 0, nil, err
}
// RtSigtimedwait implements linux syscall rt_sigtimedwait(2).
func RtSigtimedwait(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
sigset := args[0].Pointer()
siginfo := args[1].Pointer()
timespec := args[2].Pointer()
sigsetsize := args[3].SizeT()
mask, err := copyInSigSet(t, sigset, sigsetsize)
if err != nil {
return 0, nil, err
}
var timeout time.Duration
if timespec != 0 {
d, err := copyTimespecIn(t, timespec)
if err != nil {
return 0, nil, err
}
if !d.Valid() {
return 0, nil, linuxerr.EINVAL
}
timeout = time.Duration(d.ToNsecCapped())
} else {
timeout = time.Duration(math.MaxInt64)
}
si, err := t.Sigtimedwait(mask, timeout)
if err != nil {
return 0, nil, err
}
if siginfo != 0 {
si.FixSignalCodeForUser()
if _, err := si.CopyOut(t, siginfo); err != nil {
return 0, nil, err
}
}
return uintptr(si.Signo), nil, nil
}
// RtSigqueueinfo implements linux syscall rt_sigqueueinfo(2).
func RtSigqueueinfo(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
pid := kernel.ThreadID(args[0].Int())
sig := linux.Signal(args[1].Int())
infoAddr := args[2].Pointer()
// Copy in the info.
//
// We must ensure that the Signo is set (Linux overrides this in the
// same way), and that the code is in the allowed set. This same logic
// appears below in RtSigtgqueueinfo and should be kept in sync.
var info linux.SignalInfo
if _, err := info.CopyIn(t, infoAddr); err != nil {
return 0, nil, err
}
info.Signo = int32(sig)
// This must loop to handle the race with execve described in Kill.
for {
// Deliver to the given task's thread group.
target := t.PIDNamespace().TaskWithID(pid)
if target == nil {
return 0, nil, linuxerr.ESRCH
}
// If the sender is not the receiver, it can't use si_codes used by the
// kernel or SI_TKILL.
if (info.Code >= 0 || info.Code == linux.SI_TKILL) && target != t {
return 0, nil, linuxerr.EPERM
}
if !t.MayKill(target, sig) {
return 0, nil, linuxerr.EPERM
}
if err := target.SendGroupSignal(&info); !linuxerr.Equals(linuxerr.ESRCH, err) {
return 0, nil, err
}
}
}
// RtTgsigqueueinfo implements linux syscall rt_tgsigqueueinfo(2).
func RtTgsigqueueinfo(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
tgid := kernel.ThreadID(args[0].Int())
tid := kernel.ThreadID(args[1].Int())
sig := linux.Signal(args[2].Int())
infoAddr := args[3].Pointer()
// N.B. Inconsistent with man page, linux actually rejects calls with
// tgid/tid <=0 by EINVAL. This isn't the same for all signal calls.
if tgid <= 0 || tid <= 0 {
return 0, nil, linuxerr.EINVAL
}
// Copy in the info. See RtSigqueueinfo above.
var info linux.SignalInfo
if _, err := info.CopyIn(t, infoAddr); err != nil {
return 0, nil, err
}
info.Signo = int32(sig)
// Deliver to the given task.
targetTG := t.PIDNamespace().ThreadGroupWithID(tgid)
target := t.PIDNamespace().TaskWithID(tid)
if targetTG == nil || target == nil || target.ThreadGroup() != targetTG {
return 0, nil, linuxerr.ESRCH
}
// If the sender is not the receiver, it can't use si_codes used by the
// kernel or SI_TKILL.
if (info.Code >= 0 || info.Code == linux.SI_TKILL) && target != t {
return 0, nil, linuxerr.EPERM
}
if !t.MayKill(target, sig) {
return 0, nil, linuxerr.EPERM
}
return 0, nil, target.SendSignal(&info)
}
// RtSigsuspend implements linux syscall rt_sigsuspend(2).
func RtSigsuspend(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
sigset := args[0].Pointer()
// Copy in the signal mask.
var mask linux.SignalSet
if _, err := mask.CopyIn(t, sigset); err != nil {
return 0, nil, err
}
mask &^= kernel.UnblockableSignals
// Swap the mask.
oldmask := t.SignalMask()
t.SetSignalMask(mask)
t.SetSavedSignalMask(oldmask)
// Perform the wait.
return 0, nil, linuxerr.ConvertIntr(t.Block(nil), linuxerr.ERESTARTNOHAND)
}
// RestartSyscall implements the linux syscall restart_syscall(2).
func RestartSyscall(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
if r := t.SyscallRestartBlock(); r != nil {
n, err := r.Restart(t)
return n, nil, err
}
// The restart block should never be nil here, but it's possible
// ERESTART_RESTARTBLOCK was set by ptrace without the current syscall
// setting up a restart block. If ptrace didn't manipulate the return value,
// finding a nil restart block is a bug. Linux ensures that the restart
// function is never null by (re)initializing it with one that translates
// the restart into EINTR. We'll emulate that behaviour.
t.Debugf("Restart block missing in restart_syscall(2). Did ptrace inject a return value of ERESTART_RESTARTBLOCK?")
return 0, nil, linuxerr.EINTR
}
// sharedSignalfd is shared between the two calls.
func sharedSignalfd(t *kernel.Task, fd int32, sigset hostarch.Addr, sigsetsize uint, flags int32) (uintptr, *kernel.SyscallControl, error) {
// Copy in the signal mask.
mask, err := copyInSigSet(t, sigset, sigsetsize)
if err != nil {
return 0, nil, err
}
// Always check for valid flags, even if not creating.
if flags&^(linux.SFD_NONBLOCK|linux.SFD_CLOEXEC) != 0 {
return 0, nil, linuxerr.EINVAL
}
// Is this a change to an existing signalfd?
//
// The spec indicates that this should adjust the mask.
if fd != -1 {
file := t.GetFile(fd)
if file == nil {
return 0, nil, linuxerr.EBADF
}
defer file.DecRef(t)
// Is this a signalfd?
if sfd, ok := file.Impl().(*signalfd.SignalFileDescription); ok {
sfd.SetMask(mask)
return 0, nil, nil
}
// Not a signalfd.
return 0, nil, linuxerr.EINVAL
}
fileFlags := uint32(linux.O_RDWR)
if flags&linux.SFD_NONBLOCK != 0 {
fileFlags |= linux.O_NONBLOCK
}
// Create a new file.
vfsObj := t.Kernel().VFS()
file, err := signalfd.New(vfsObj, t, mask, fileFlags)
if err != nil {
return 0, nil, err
}
defer file.DecRef(t)
// Create a new descriptor.
fd, err = t.NewFDFrom(0, file, kernel.FDFlags{
CloseOnExec: flags&linux.SFD_CLOEXEC != 0,
})
if err != nil {
return 0, nil, err
}
// Done.
return uintptr(fd), nil, nil
}
// Signalfd implements the linux syscall signalfd(2).
func Signalfd(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
fd := args[0].Int()
sigset := args[1].Pointer()
sigsetsize := args[2].SizeT()
return sharedSignalfd(t, fd, sigset, sigsetsize, 0)
}
// Signalfd4 implements the linux syscall signalfd4(2).
func Signalfd4(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
fd := args[0].Int()
sigset := args[1].Pointer()
sigsetsize := args[2].SizeT()
flags := args[3].Int()
return sharedSignalfd(t, fd, sigset, sigsetsize, flags)
}
// PIDFDSendSignal implements linux system call pidfd_send_signal(2).
func PIDFDSendSignal(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
fd := args[0].Int()
sig := linux.Signal(args[1].Int())
infoAddr := args[2].Pointer()
flags := args[3].Uint()
switch flags {
case 0, linux.PIDFD_SIGNAL_THREAD, linux.PIDFD_SIGNAL_THREAD_GROUP, linux.PIDFD_SIGNAL_PROCESS_GROUP:
default:
return 0, nil, linuxerr.EINVAL
}
if !sig.IsValid() && sig != 0 {
return 0, nil, linuxerr.EINVAL
}
file := t.GetFile(fd)
if file == nil {
return 0, nil, linuxerr.EBADF
}
defer file.DecRef(t)
var info *linux.SignalInfo
if infoAddr != 0 {
info = &linux.SignalInfo{}
if _, err := info.CopyIn(t, infoAddr); err != nil {
return 0, nil, err
}
}
return 0, nil, t.PIDFDSendSignal(file, sig, info, flags)
}