mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00

When we walk the /dev tree we need to lookup all device paths. Now in order to get the major and minor version we have to actually stat each device. This can again fail of course. There is at least a race between the readdir at stat call so it must ignore ENOENT errors to avoid the race condition as this is not a user problem. Second, we should also not return other errors and just log them instead, returning an error means stopping the walk and returning early which means inspect fails with an error which would be bad. Also there seems to be cases were ENOENT will be returned all the time, e.g. when a device is forcefully removed. In the reported bug this is triggered with iSCSI devices. Because the caller does already lookup the device from the created map it reports a warning there if the device is missing on the host so it is not a problem to ignore a error during lookup here. [NO NEW TESTS NEEDED] Requires special device setup to trigger consistentlyand we cannot do that in CI. Original Fixed https://issues.redhat.com/browse/RHEL-11158 This fixes: https://issues.redhat.com/browse/RHEL-20491 Signed-off-by: Paul Holzinger <pholzing@redhat.com> Signed-off-by: tomsweeneyredhat <tsweeney@redhat.com>
257 lines
7.3 KiB
Go
257 lines
7.3 KiB
Go
package util
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/containers/podman/v4/pkg/rootless"
|
|
"github.com/containers/psgo"
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/opencontainers/runtime-tools/generate"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
var (
|
|
errNotADevice = errors.New("not a device node")
|
|
)
|
|
|
|
// GetContainerPidInformationDescriptors returns a string slice of all supported
|
|
// format descriptors of GetContainerPidInformation.
|
|
func GetContainerPidInformationDescriptors() ([]string, error) {
|
|
return psgo.ListDescriptors(), nil
|
|
}
|
|
|
|
// FindDeviceNodes parses /dev/ into a set of major:minor -> path, where
|
|
// [major:minor] is the device's major and minor numbers formatted as, for
|
|
// example, 2:0 and path is the path to the device node.
|
|
// Symlinks to nodes are ignored.
|
|
func FindDeviceNodes() (map[string]string, error) {
|
|
nodes := make(map[string]string)
|
|
err := filepath.WalkDir("/dev", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
logrus.Warnf("Error descending into path %s: %v", path, err)
|
|
return filepath.SkipDir
|
|
}
|
|
|
|
// If we aren't a device node, do nothing.
|
|
if d.Type()&(os.ModeDevice|os.ModeCharDevice) == 0 {
|
|
return nil
|
|
}
|
|
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
// Info() can return ErrNotExist if the file was deleted between the readdir and stat call.
|
|
// This race can happen and is no reason to log an ugly error. If this is a container device
|
|
// that is used the code later will print a proper error in such case.
|
|
// There also seem to be cases were ErrNotExist is always returned likely due a weird device
|
|
// state, e.g. removing a device forcefully. This can happen with iSCSI devices.
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
logrus.Errorf("Failed to get device information for %s: %v", path, err)
|
|
}
|
|
// return nil here as we want to continue looking for more device and not stop the WalkDir()
|
|
return nil
|
|
}
|
|
// We are a device node. Get major/minor.
|
|
sysstat, ok := info.Sys().(*syscall.Stat_t)
|
|
if !ok {
|
|
return errors.New("could not convert stat output for use")
|
|
}
|
|
// We must typeconvert sysstat.Rdev from uint64->int to avoid constant overflow
|
|
rdev := int(sysstat.Rdev)
|
|
major := ((rdev >> 8) & 0xfff) | ((rdev >> 32) & ^0xfff)
|
|
minor := (rdev & 0xff) | ((rdev >> 12) & ^0xff)
|
|
|
|
nodes[fmt.Sprintf("%d:%d", major, minor)] = path
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
// isVirtualConsoleDevice returns true if path is a virtual console device
|
|
// (/dev/tty\d+).
|
|
// The passed path must be clean (filepath.Clean).
|
|
func isVirtualConsoleDevice(path string) bool {
|
|
/*
|
|
Virtual consoles are of the form `/dev/tty\d+`, any other device such as
|
|
/dev/tty, ttyUSB0, or ttyACM0 should not be matched.
|
|
See `man 4 console` for more information.
|
|
*/
|
|
suffix := strings.TrimPrefix(path, "/dev/tty")
|
|
if suffix == path || suffix == "" {
|
|
return false
|
|
}
|
|
|
|
// 16bit because, max. supported TTY devices is 512 in Linux 6.1.5.
|
|
_, err := strconv.ParseUint(suffix, 10, 16)
|
|
return err == nil
|
|
}
|
|
|
|
func AddPrivilegedDevices(g *generate.Generator, systemdMode bool) error {
|
|
hostDevices, err := getDevices("/dev")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.ClearLinuxDevices()
|
|
|
|
if rootless.IsRootless() {
|
|
mounts := make(map[string]interface{})
|
|
for _, m := range g.Mounts() {
|
|
mounts[m.Destination] = true
|
|
}
|
|
newMounts := []spec.Mount{}
|
|
for _, d := range hostDevices {
|
|
devMnt := spec.Mount{
|
|
Destination: d.Path,
|
|
Type: define.TypeBind,
|
|
Source: d.Path,
|
|
Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"},
|
|
}
|
|
|
|
/* The following devices should not be mounted in rootless containers:
|
|
*
|
|
* /dev/ptmx: The host-provided /dev/ptmx should not be shared to
|
|
* the rootless containers for security reasons, and
|
|
* the container runtime will create it for us
|
|
* anyway (ln -s /dev/pts/ptmx /dev/ptmx);
|
|
* /dev/tty and
|
|
* /dev/tty[0-9]+: Prevent the container from taking over the host's
|
|
* virtual consoles, even when not in systemd mode
|
|
* for backwards compatibility.
|
|
*/
|
|
if d.Path == "/dev/ptmx" || d.Path == "/dev/tty" || isVirtualConsoleDevice(d.Path) {
|
|
continue
|
|
}
|
|
if _, found := mounts[d.Path]; found {
|
|
continue
|
|
}
|
|
newMounts = append(newMounts, devMnt)
|
|
}
|
|
g.Config.Mounts = append(newMounts, g.Config.Mounts...)
|
|
if g.Config.Linux.Resources != nil {
|
|
g.Config.Linux.Resources.Devices = nil
|
|
}
|
|
} else {
|
|
for _, d := range hostDevices {
|
|
/* Restrict access to the virtual consoles *only* when running
|
|
* in systemd mode to improve backwards compatibility. See
|
|
* https://github.com/containers/podman/issues/15878.
|
|
*
|
|
* NOTE: May need revisiting in the future to drop the systemd
|
|
* condition if more use cases end up breaking the virtual terminals
|
|
* of people who specifically disable the systemd mode. It would
|
|
* also provide a more consistent behaviour between rootless and
|
|
* rootfull containers.
|
|
*/
|
|
if systemdMode && isVirtualConsoleDevice(d.Path) {
|
|
continue
|
|
}
|
|
g.AddDevice(d)
|
|
}
|
|
// Add resources device - need to clear the existing one first.
|
|
if g.Config.Linux.Resources != nil {
|
|
g.Config.Linux.Resources.Devices = nil
|
|
}
|
|
g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// based on getDevices from runc (libcontainer/devices/devices.go)
|
|
func getDevices(path string) ([]spec.LinuxDevice, error) {
|
|
files, err := os.ReadDir(path)
|
|
if err != nil {
|
|
if rootless.IsRootless() && os.IsPermission(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
out := []spec.LinuxDevice{}
|
|
for _, f := range files {
|
|
switch {
|
|
case f.IsDir():
|
|
switch f.Name() {
|
|
// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
|
|
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts":
|
|
continue
|
|
default:
|
|
sub, err := getDevices(filepath.Join(path, f.Name()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if sub != nil {
|
|
out = append(out, sub...)
|
|
}
|
|
continue
|
|
}
|
|
case f.Name() == "console":
|
|
continue
|
|
case f.Type()&os.ModeSymlink != 0:
|
|
continue
|
|
}
|
|
|
|
device, err := DeviceFromPath(filepath.Join(path, f.Name()))
|
|
if err != nil {
|
|
if err == errNotADevice {
|
|
continue
|
|
}
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
out = append(out, *device)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Copied from github.com/opencontainers/runc/libcontainer/devices
|
|
// Given the path to a device look up the information about a linux device
|
|
func DeviceFromPath(path string) (*spec.LinuxDevice, error) {
|
|
var stat unix.Stat_t
|
|
err := unix.Lstat(path, &stat)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var (
|
|
devType string
|
|
mode = stat.Mode
|
|
devNumber = uint64(stat.Rdev) //nolint: unconvert
|
|
m = os.FileMode(mode)
|
|
)
|
|
|
|
switch {
|
|
case mode&unix.S_IFBLK == unix.S_IFBLK:
|
|
devType = "b"
|
|
case mode&unix.S_IFCHR == unix.S_IFCHR:
|
|
devType = "c"
|
|
case mode&unix.S_IFIFO == unix.S_IFIFO:
|
|
devType = "p"
|
|
default:
|
|
return nil, errNotADevice
|
|
}
|
|
|
|
return &spec.LinuxDevice{
|
|
Type: devType,
|
|
Path: path,
|
|
FileMode: &m,
|
|
UID: &stat.Uid,
|
|
GID: &stat.Gid,
|
|
Major: int64(unix.Major(devNumber)),
|
|
Minor: int64(unix.Minor(devNumber)),
|
|
}, nil
|
|
}
|