Files
podman/pkg/pidhandle/pidhandle_linux_test.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

236 lines
6.3 KiB
Go

//go:build linux
package pidhandle
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/sys/unix"
)
func TestNewPIDHandle(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return 255, nil
}
// Mock the nameToHandleAt
original_nameToHandleAt := nameToHandleAt
defer func() { nameToHandleAt = original_nameToHandleAt }()
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
return newFileHandle(254, []byte("test")), 1, nil
}
h, err := NewPIDHandle(os.Getpid())
assert.NoError(t, err)
defer h.Close()
pidData, err := h.String()
assert.NoError(t, err)
assert.Equal(t, nameToHandlePrefix+"254 74657374", pidData)
assert.Equal(t, h.PID(), os.Getpid())
}
func TestNewPIDHandlepidfdOpenNotSupported(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return -1, unix.ENOSYS
}
h, err := NewPIDHandle(os.Getpid())
assert.NoError(t, err)
defer h.Close()
pidData, err := h.String()
assert.NoError(t, err)
assert.Contains(t, pidData, startTimePrefix)
assert.Equal(t, h.PID(), os.Getpid())
}
func TestPIDHandleStringnameToHandleAtNotSupported(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return 254, nil
}
// Mock the nameToHandleAt
original_nameToHandleAt := nameToHandleAt
defer func() { nameToHandleAt = original_nameToHandleAt }()
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
return newFileHandle(-1, []byte("")), 1, unix.ENOTSUP
}
h, err := NewPIDHandle(os.Getpid())
assert.NoError(t, err)
defer h.Close()
pidData, err := h.String()
assert.NoError(t, err)
assert.Contains(t, pidData, startTimePrefix)
}
func TestNewPIDHandleFromString(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return 254, nil
}
// Mock the newFileHandle
original_newFileHandle := newFileHandle
defer func() { newFileHandle = original_newFileHandle }()
newFileHandle = func(fhType int32, bytes []byte) unix.FileHandle {
return unix.NewFileHandle(254, []byte("test"))
}
// Mock the openByHandleAt
original_openByHandleAt := openByHandleAt
defer func() { openByHandleAt = original_openByHandleAt }()
openByHandleAt = func(mountFD int, handle unix.FileHandle, flags int) (fd int, err error) {
return 255, nil
}
// Mock the nameToHandleAt
original_nameToHandleAt := nameToHandleAt
defer func() { nameToHandleAt = original_nameToHandleAt }()
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
return newFileHandle(255, []byte("test")), 1, nil
}
h, err := NewPIDHandleFromString(os.Getpid(), nameToHandlePrefix+"254 74657374")
assert.NoError(t, err)
defer h.Close()
pidData, err := h.String()
assert.NoError(t, err)
assert.Equal(t, nameToHandlePrefix+"254 74657374", pidData)
assert.Equal(t, h.PID(), os.Getpid())
}
func TestNewPIDHandleFromStringWrongPidData(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return 254, nil
}
// Mock the newFileHandle
original_newFileHandle := newFileHandle
defer func() { newFileHandle = original_newFileHandle }()
newFileHandle = func(fhType int32, bytes []byte) unix.FileHandle {
return unix.NewFileHandle(254, []byte("test"))
}
// Mock the openByHandleAt
original_openByHandleAt := openByHandleAt
defer func() { openByHandleAt = original_openByHandleAt }()
openByHandleAt = func(mountFD int, handle unix.FileHandle, flags int) (fd int, err error) {
return 255, nil
}
values := []string{
nameToHandlePrefix + "foo",
nameToHandlePrefix + "254 foo",
nameToHandlePrefix + "254 foo bar",
nameToHandlePrefix + "foo 1245",
}
for _, s := range values {
_, err := NewPIDHandleFromString(os.Getpid(), s)
assert.Error(t, err)
}
}
func TestPIDHandlePidfdStartTime(t *testing.T) {
h, err := NewPIDHandleFromString(os.Getpid(), "start-time:1234567890")
assert.NoError(t, err)
defer h.Close()
}
func TestPIDHandleKill(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return 254, nil
}
// Mock the pidfdSendSignal
original_pidfdSendSignal := pidfdSendSignal
defer func() { pidfdSendSignal = original_pidfdSendSignal }()
pidfdSendSignal = func(pidfd int, sig unix.Signal, info *unix.Siginfo, flags int) (err error) {
return unix.ESRCH
}
// Mock the nameToHandleAt
original_nameToHandleAt := nameToHandleAt
defer func() { nameToHandleAt = original_nameToHandleAt }()
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
return newFileHandle(254, []byte("test")), 1, nil
}
h, err := NewPIDHandle(os.Getpid())
assert.NoError(t, err)
defer h.Close()
err = h.Kill(0)
assert.ErrorIs(t, err, unix.ESRCH)
}
func TestPIDHandleKillPidfdNotSupported(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return -1, unix.ENOSYS
}
h, err := NewPIDHandle(os.Getpid())
assert.NoError(t, err)
defer h.Close()
pidData, err := h.String()
assert.NoError(t, err)
h, err = NewPIDHandleFromString(os.Getpid(), pidData)
assert.NoError(t, err)
err = h.Kill(0)
assert.NoError(t, err)
isAlive, err := h.IsAlive()
assert.NoError(t, err)
assert.True(t, isAlive)
}
func TestPIDHandleKillPidfdNotSupportedStartTimeNotMatch(t *testing.T) {
// Mock the pidfdOpen
original := pidfdOpen
defer func() { pidfdOpen = original }()
pidfdOpen = func(pid int, flags int) (int, error) {
return -1, unix.ENOSYS
}
h, err := NewPIDHandle(os.Getpid())
assert.NoError(t, err)
defer h.Close()
h, err = NewPIDHandleFromString(os.Getpid(), "start-time:1234567890")
assert.NoError(t, err)
err = h.Kill(0)
assert.ErrorIs(t, err, unix.ESRCH)
isAlive, err := h.IsAlive()
assert.NoError(t, err)
assert.False(t, isAlive)
}