Files
Shailend ChandandgVisor bot d5ef80320d pidfds: Return EINVAL for fasync
Matches latest Linux behavior.

The native pidfd test has also been deflaked.

PiperOrigin-RevId: 911141375
2026-05-05 23:59:41 -07:00

450 lines
13 KiB
Go

// Copyright 2026 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 (
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/waiter"
)
// pidFD implements vfs.FileDescriptionImpl for pidfds.
//
// +stateify savable
type pidFD struct {
vfsFD vfs.FileDescription
vfs.FileDescriptionDefaultImpl
vfs.DentryMetadataFileDescriptionImpl
vfs.NoLockFD
// isThread is true if the pidfd represents a task.
// It is set during creation and is immutable thereafter.
isThread bool
// All pidfds pointing to the same task point to the one pid struct.
// It is set during creation and is immutable thereafter.
pid *pid
}
// Release implements vfs.FileDescriptionImpl.Release.
func (f *pidFD) Release(ctx context.Context) {
}
// Readiness implements waiter.Waitable.Readiness.
func (f *pidFD) Readiness(mask waiter.EventMask) waiter.EventMask {
// man pidfd_open(2) says:
// When the task that it refers to terminates and becomes a
// zombie, these interfaces indicate the file descriptor as
// readable (EPOLLIN). When the task is reaped, these interfaces
// produce a hangup event (EPOLLHUP).
// ...
// • With PIDFD_THREAD, the file descriptor becomes readable when
// the task exits and becomes a zombie, even if the thread-
// group is not empty.
// • Without PIDFD_THREAD, the file descriptor becomes readable
// only when the last thread in the thread group exits.
if f.pid.t.Load() == nil {
return waiter.EventHUp | waiter.ReadableEvents
}
if f.pid.ready.Load() {
return waiter.ReadableEvents
}
return 0
}
// EventRegister implements waiter.Waitable.EventRegister.
func (f *pidFD) EventRegister(e *waiter.Entry) error {
f.pid.notifier.EventRegister(e)
return nil
}
// EventUnregister implements waiter.Waitable.EventUnregister.
func (f *pidFD) EventUnregister(e *waiter.Entry) {
f.pid.notifier.EventUnregister(e)
}
// Epollable implements vfs.FileDescriptionImpl.Epollable.
func (f *pidFD) Epollable() bool {
return true
}
// RegisterFileAsyncHandler implements vfs.FileDescriptionImpl.RegisterFileAsyncHandler.
func (f *pidFD) RegisterFileAsyncHandler(fd *vfs.FileDescription) error {
return linuxerr.EINVAL
}
// UnregisterFileAsyncHandler implements vfs.FileDescriptionImpl.UnregisterFileAsyncHandler.
func (f *pidFD) UnregisterFileAsyncHandler(fd *vfs.FileDescription) {}
// PIDFDOpen helps implement the linux syscall pidfd_open(2).
// It returns an fd with an extra reference.
func (t *Task) PIDFDOpen(tid ThreadID, isThread bool, nonBlock bool) (*vfs.FileDescription, error) {
t.tg.pidns.owner.mu.Lock()
pid, err := t.pidStructFromTIDLocked(tid, isThread)
t.tg.pidns.owner.mu.Unlock()
if err != nil {
return nil, err
}
return t.pidFDOpen(pid, isThread, nonBlock)
}
func (t *Task) pidFDOpen(pid *pid, isThread bool, nonBlock bool) (*vfs.FileDescription, error) {
f := &pidFD{
isThread: isThread,
pid: pid,
}
vd := t.Kernel().VFS().NewAnonVirtualDentry("[pidfd]")
defer vd.DecRef(t)
var flags uint32
if nonBlock {
flags |= linux.O_NONBLOCK
}
err := f.vfsFD.Init(f, flags, t.Credentials(), vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{
UseDentryMetadata: true,
})
if err != nil {
return nil, err
}
return &f.vfsFD, nil
}
// PIDFDGetFD helps implement the Linux syscall pidfd_getfd(2).
func (t *Task) PIDFDGetFD(pidfd int32, targetfd int32, flags uint32) (uintptr, error) {
file := t.GetFile(pidfd)
if file == nil {
return 0, linuxerr.EBADF
}
defer file.DecRef(t)
pfd, ok := file.Impl().(*pidFD)
if !ok {
return 0, linuxerr.EBADF
}
target, err := pfd.liveTask()
if err != nil {
return 0, err
}
if !t.CanTrace(target, true) {
return 0, linuxerr.EPERM
}
targetFile := target.GetFile(targetfd)
if targetFile == nil {
return 0, linuxerr.EBADF
}
defer targetFile.DecRef(t)
// man pidfd_getfd(2) says:
// "The close-on-exec flag (FD_CLOEXEC; see fcntl(2)) is set on the
// file descriptor returned by pidfd_getfd()."
fd, err := t.NewFDFrom(0, targetFile, FDFlags{
CloseOnExec: true,
})
if err != nil {
return 0, err
}
return uintptr(fd), nil
}
func (f *pidFD) liveTask() (*Task, error) {
if f.pid.ready.Load() {
return nil, linuxerr.ESRCH
}
t := f.pid.t.Load()
if t == nil {
return nil, linuxerr.ESRCH
}
// Not necessary on account of the "ready" check above, but extra defense feels good.
if t.ExitState() >= TaskExitZombie {
return nil, linuxerr.ESRCH
}
return t, nil
}
// ThreadGroupFromPIDFD returns the thread group associated with the integral pidfd
// and a bool indicating if the pidfd has O_NONBLOCK set.
// Note: Returns EINVAL if the pidfd is a PIDFD_THREAD pidfd.
func (t *Task) ThreadGroupFromPIDFD(pfdNum int32) (*ThreadGroup, bool, error) {
pfd, err := t.pidFDFromFDNum(pfdNum)
if err != nil {
return nil, false, err
}
if pfd.isThread {
return nil, false, linuxerr.EINVAL
}
target := pfd.pid.t.Load()
if target == nil {
return nil, false, linuxerr.ESRCH
}
tg := target.ThreadGroup()
nonBlock := pfd.vfsFD.StatusFlags()&linux.O_NONBLOCK != 0
return tg, nonBlock, nil
}
func (t *Task) pidFDFromFDNum(pfdNum int32) (*pidFD, error) {
file := t.GetFile(pfdNum)
if file == nil {
return nil, linuxerr.EBADF
}
defer file.DecRef(t)
pfd, ok := file.Impl().(*pidFD)
if !ok {
return nil, linuxerr.EBADF
}
return pfd, nil
}
// pid represents a task for the purposes of pidfds.
// There is at most one pid struct per task which all pidfds refer to.
//
// +stateify savable
type pid struct {
// The task that this pid struct refers to.
// It is set during creation and is not mutated until it's nilled when the task is reaped.
t atomic.Pointer[Task] `state:".(*Task)"`
// For a non-leader subthread, a set ready means that it has become a zombie.
// For a leader thread, a set ready means that it has become a zombie and that the
// thread group has no other tasks.
ready atomicbitops.Bool
notifier waiter.Queue
}
func (t *Task) pidStructFromTIDLocked(tid ThreadID, isThread bool) (*pid, error) {
t2 := t.tg.pidns.tasks[tid]
if t2 == nil {
return nil, linuxerr.ESRCH
}
isLeader := t2 == t2.tg.leader
if !isThread && !isLeader {
return nil, linuxerr.ENOENT
}
return t2.pidStructLocked()
}
func (t *Task) pidStructLocked() (*pid, error) {
if t.ExitState() == TaskExitDead {
return nil, linuxerr.ESRCH
}
if t.pid != nil {
return t.pid, nil
}
t.pid = &pid{}
t.pid.t.Store(t)
isLeader := t == t.tg.leader
isZombie := t.exitStateLocked() == TaskExitZombie
if isLeader {
if isZombie && t.tg.tasksCount == 1 {
t.pid.ready.Store(true)
}
} else if isZombie {
t.pid.ready.Store(true)
}
return t.pid, nil
}
// notifyPIDFDsOnDeathLocked() is invoked by Task.exitNotifyLocked() when a task is dying.
// Preconditions: The TaskSet mutex must be held.
func (t *Task) notifyPIDFDsOnDeathLocked() {
if pid := t.pid; pid != nil {
// Stop referring to t from the pid struct, so that the presence of pidfds does not
// prevent the GC from reclaiming (the sizeable) Task t.
if pid.t.Swap(nil) == nil {
return
}
pid.notifier.Notify(waiter.ReadableEvents | waiter.EventHUp)
t.pid = nil // Inconsequential but hygienic.
}
}
// notifyPIDFDsLocked() is invoked by runExitNotify.execute() in the following cases:
// - If t is a non leader, as soon as it becomes a zombie.
// - If t is a leader, when it becomes a zombie and it is the last task in the thread group.
// Preconditions: The TaskSet mutex must be held.
func (t *Task) notifyPIDFDsLocked() {
if pid := t.pid; pid != nil {
if pid.ready.Swap(true) {
return
}
pid.notifier.Notify(waiter.ReadableEvents)
}
}
// TaskOwnedInode is an interface for /proc/$pid inodes so that pidfd syscalls can
// also work on "classic" pidfds from procfs.
type TaskOwnedInode interface {
TaskFromProcPIDInode() *Task
}
// pidfd syscalls work on not just pidfds, but also /proc/$pid procfs fds.
// Man pidfd_send_signal(2) says:
//
// "The pidfd argument is a PID file descriptor, a file descriptor
// that refers to process. Such a file descriptor can be obtained in
// any of the following ways:
// - by opening a /proc/pid directory;
// - using pidfd_open(2); or
// - via the PID file descriptor that is returned by a call to
// clone(2) or clone3(2) that specifies the CLONE_PIDFD flag."
//
// taskFromProcPIDFD returns the task associated with the /proc/$pid node represented by vfsfd.
// Returns an error if the fd is not a /proc/$pid node.
func taskFromProcPIDFD(vfsfd *vfs.FileDescription) (*Task, error) {
d, ok := vfsfd.Dentry().Impl().(*kernfs.Dentry)
if !ok {
return nil, linuxerr.EBADF
}
i, ok := d.Inode().(TaskOwnedInode)
if !ok {
return nil, linuxerr.EBADF
}
t := i.TaskFromProcPIDInode()
if t == nil {
return nil, linuxerr.ESRCH
}
return t, nil
}
// PIDFDSendSignal sends a signal to the task or thread group associated with the pidfd represented
// by vfsfd. It could either be a true pidfd or a /proc/pid node.
func (t *Task) PIDFDSendSignal(vfsfd *vfs.FileDescription, sig linux.Signal, info *linux.SignalInfo, flags uint32) error {
pfd, isPidFD := vfsfd.Impl().(*pidFD)
var target *Task
var err error
if isPidFD {
target = pfd.pid.t.Load()
if target == nil {
err = linuxerr.ESRCH
}
} else {
target, err = taskFromProcPIDFD(vfsfd)
}
if err != nil {
return err
}
effectiveFlags := flags
if isPidFD && effectiveFlags == 0 {
if pfd.isThread {
effectiveFlags = linux.PIDFD_SIGNAL_THREAD
} else {
effectiveFlags = linux.PIDFD_SIGNAL_THREAD_GROUP
}
}
if effectiveFlags == 0 {
effectiveFlags = linux.PIDFD_SIGNAL_THREAD_GROUP
}
if info == nil {
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()))
} else {
if info.Signo != int32(sig) {
return linuxerr.EINVAL
}
// Do not allow spoofing unless to self.
if info.Code >= 0 || info.Code == linux.SI_TKILL {
if target != t || effectiveFlags == linux.PIDFD_SIGNAL_PROCESS_GROUP {
return linuxerr.EPERM
}
}
}
if !t.MayKill(target, sig) {
return linuxerr.EPERM
}
switch effectiveFlags {
case linux.PIDFD_SIGNAL_THREAD:
return target.SendSignal(info)
case linux.PIDFD_SIGNAL_THREAD_GROUP:
return target.SendGroupSignal(info)
case linux.PIDFD_SIGNAL_PROCESS_GROUP:
tg := target.ThreadGroup()
if tg.ProcessGroup().Originator() != tg {
return linuxerr.EINVAL
}
return tg.ProcessGroup().SendSignal(info)
default: // Unreachable.
t.Warningf("Unexpected error in PIDFDSendSignal with flags %d", flags)
return linuxerr.EINVAL
}
}
// PIDFDTask returns the task associated with the pidfd represented by vfsfd.
// If the vfsfd is neither a true pidfd nor a /proc/pid node, an error is returned.
func PIDFDTask(vfsfd *vfs.FileDescription) (*Task, error) {
pfd, ok := vfsfd.Impl().(*pidFD)
if ok { // vfsfd is a true pidfd (a product of pidfd_open() or clone3(CLONE_PIDFD)).
return pfd.liveTask()
}
t, err := taskFromProcPIDFD(vfsfd) // Maybe it's a classic pidfd from /proc/$pid.
if err != nil {
return nil, err
}
if t.ExitState() >= TaskExitZombie {
return nil, linuxerr.ESRCH
}
return t, nil
}
// ObservedTIDsForPIDFD returns a slice of Thread IDs of the target task represented by
// vfsfd in all its ancestral pidns's ending at the pidns of the observing task t, with
// deeper pidns tids earlier in the slice.
// Returns an error if vfsfd is not a true pidfd.
func ObservedTIDsForPIDFD(vfsfd *vfs.FileDescription, t *Task) ([]int32, error) {
pfd, ok := vfsfd.Impl().(*pidFD)
if !ok {
return nil, linuxerr.EBADF
}
target := pfd.pid.t.Load()
if target == nil {
return []int32{-1}, nil // target is reaped.
}
tNS := t.PIDNamespace()
targetNS := target.PIDNamespace()
tidAtT := tNS.IDOfTask(target)
if tidAtT == 0 {
return []int32{0}, nil
}
var nspids []int32
for cur := targetNS; cur != nil; cur = cur.parent {
nspids = append(nspids, int32(cur.IDOfTask(target)))
if cur == tNS {
break // Stop at the observing task's pidns.
}
}
return nspids, nil
}