Files
podman/pkg/pidhandle/pidhandle.go
Jan Kaluza f825639ebc Verify the ExecSession pid before killing it.
When container is being removed, podman iterates
through its exec sessions and checks whether exec
session pid is still alive.

The problem is that the pid can be reused for other processes,
so that it may not belong to exec session.
In this scenario podman may kill another process

This commit prevents it by doing following changes:

- Adds the PIDData string to ExecSession struct. This string
  is used to store additional context for a PID to later verify
  that the PID killed by the podman is really the one started by
  it.
- Adds new package called pidhandle which implements the methods
  generating the PIDData, and killing the PID with the PIDData
  ensuring the right PID is killed by verifying the metadata.

The new code uses pidfd_open and name_to_handle_at when available.
It fallbacks to process start-time get using the gopsutil package.

Fixes: #25104

Signed-off-by: Jan Kaluza <jkaluza@redhat.com>
2025-05-06 06:24:13 +02:00

141 lines
4.0 KiB
Go

//go:build !windows
// Package for handling processes and PIDs.
package pidhandle
import (
"strconv"
"strings"
"github.com/shirou/gopsutil/v4/process"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
// PIDHandle defines an interface for working with operating system processes
// in a reliable way. OS-specific implementations include additional logic
// to try to ensure that operations (e.g., sending signals) are performed
// on the exact same process that was originally referenced when the PIDHandle
// was created via NewPIDHandle or NewPIDHandleFromString.
//
// This prevents accidental interaction with a different process in scenarios
// where the original process has exited and its PID has been reused by
// the system for an unrelated process.
type PIDHandle interface {
// Returns the PID associated with this PIDHandle.
PID() int
// Releases the PIDHandle resources.
Close() error
// Sends the signal to process.
Kill(signal unix.Signal) error
// Returns true in case the process is still alive.
IsAlive() (bool, error)
// Returns a serialized representation of the PIDHandle.
// This string can be passed to NewPIDHandleFromString to recreate
// a PIDHandle that reliably refers to the same process as the original.
String() (string, error)
}
// The pidData value used when no process with this PID exists when creating
// the PIDHandle.
const noSuchProcessID = "no-proc"
// The pidData prefix used when only the process start time (creation time)
// is supported when creating the PIDHandle to uniquely identify the process.
const startTimePrefix = "start-time:"
type pidHandle struct {
pid int
pidData string
}
// Returns the PID.
func (h *pidHandle) PID() int {
return h.pid
}
// Close releases the PIDHandle resource.
func (h *pidHandle) Close() error {
// No resources for the default PIDHandle implementation.
return nil
}
// Sends the signal to process.
func (h *pidHandle) Kill(signal unix.Signal) error {
if h.pidData == noSuchProcessID {
// The process did not exist when we created the PIDHandle, so return
// ESRCH error.
return unix.ESRCH
}
// Get the start-time of the process and check if it is the same as
// the one we store in pidData. If it is not, we know that the PID
// has been recycled and return ESRCH error.
startTime, found := strings.CutPrefix(h.pidData, startTimePrefix)
if found {
p, err := process.NewProcess(int32(h.pid))
if err != nil {
if err == process.ErrorProcessNotRunning {
return unix.ESRCH
}
return err
}
ctime, err := p.CreateTime()
if err != nil {
return err
}
if strconv.FormatInt(ctime, 10) != startTime {
return unix.ESRCH
}
}
return unix.Kill(h.pid, signal)
}
// Returns true in case the process is still alive.
func (h *pidHandle) IsAlive() (bool, error) {
err := h.Kill(0)
if err != nil {
if err == unix.ESRCH {
return false, nil
}
return false, err
}
return true, nil
}
// Returns a serialized representation of the PIDHandle.
// This string can be passed to NewPIDHandleFromString to recreate
// a PIDHandle that reliably refers to the same process as the original.
func (h *pidHandle) String() (string, error) {
if len(h.pidData) != 0 {
return h.pidData, nil
}
// Get the start-time of the process and return it as string.
p, err := process.NewProcess(int32(h.pid))
if err != nil {
if err == process.ErrorProcessNotRunning {
return noSuchProcessID, nil
}
return "", err
}
ctime, err := p.CreateTime()
if err != nil {
// The process existed, but we cannot get its start-time. There is
// either an issue with getting it, or the process terminated in the
// mean-time. We have no way to find out what actually happened, so
// in this case, we just fallback to an empty string. This will mean
// that Kill or IsAlive might kill wrong process in rare situation
// when CreateTime() failed for different reason than the process
// terminated...
logrus.Debugf("Getting CreateTime for process (%d) failed: %v", h.pid, err)
return "", nil
}
return startTimePrefix + strconv.FormatInt(ctime, 10), nil
}