podman
1package util2
3import (4"errors"5"fmt"6"io/fs"7"os"8"path/filepath"9"strconv"10"strings"11"syscall"12
13"github.com/containers/podman/v5/libpod/define"14"github.com/containers/podman/v5/pkg/rootless"15"github.com/containers/psgo"16spec "github.com/opencontainers/runtime-spec/specs-go"17"github.com/opencontainers/runtime-tools/generate"18"github.com/sirupsen/logrus"19"golang.org/x/sys/unix"20)
21
22var (23errNotADevice = errors.New("not a device node")24)
25
26// GetContainerPidInformationDescriptors returns a string slice of all supported
27// format descriptors of GetContainerPidInformation.
28func GetContainerPidInformationDescriptors() ([]string, error) {29return psgo.ListDescriptors(), nil30}
31
32// FindDeviceNodes parses /dev/ into a set of major:minor -> path, where
33// [major:minor] is the device's major and minor numbers formatted as, for
34// example, 2:0 and path is the path to the device node.
35// Symlinks to nodes are ignored.
36func FindDeviceNodes() (map[string]string, error) {37nodes := make(map[string]string)38err := filepath.WalkDir("/dev", func(path string, d fs.DirEntry, err error) error {39if err != nil {40if !errors.Is(err, fs.ErrNotExist) {41logrus.Warnf("Error descending into path %s: %v", path, err)42}43return filepath.SkipDir44}45
46// If we aren't a device node, do nothing.47if d.Type()&(os.ModeDevice|os.ModeCharDevice) == 0 {48return nil49}50
51info, err := d.Info()52if err != nil {53// Info() can return ErrNotExist if the file was deleted between the readdir and stat call.54// This race can happen and is no reason to log an ugly error. If this is a container device55// that is used the code later will print a proper error in such case.56// There also seem to be cases were ErrNotExist is always returned likely due a weird device57// state, e.g. removing a device forcefully. This can happen with iSCSI devices.58if !errors.Is(err, fs.ErrNotExist) {59logrus.Errorf("Failed to get device information for %s: %v", path, err)60}61// return nil here as we want to continue looking for more device and not stop the WalkDir()62return nil63}64// We are a device node. Get major/minor.65sysstat, ok := info.Sys().(*syscall.Stat_t)66if !ok {67return errors.New("could not convert stat output for use")68}69// We must typeconvert sysstat.Rdev from uint64->int to avoid constant overflow70rdev := int(sysstat.Rdev)71major := ((rdev >> 8) & 0xfff) | ((rdev >> 32) & ^0xfff)72minor := (rdev & 0xff) | ((rdev >> 12) & ^0xff)73
74nodes[fmt.Sprintf("%d:%d", major, minor)] = path75
76return nil77})78if err != nil {79return nil, err80}81
82return nodes, nil83}
84
85// isVirtualConsoleDevice returns true if path is a virtual console device
86// (/dev/tty\d+).
87// The passed path must be clean (filepath.Clean).
88func isVirtualConsoleDevice(path string) bool {89/*90Virtual consoles are of the form `/dev/tty\d+`, any other device such as
91/dev/tty, ttyUSB0, or ttyACM0 should not be matched.
92See `man 4 console` for more information.
93*/
94suffix := strings.TrimPrefix(path, "/dev/tty")95if suffix == path || suffix == "" {96return false97}98
99// 16bit because, max. supported TTY devices is 512 in Linux 6.1.5.100_, err := strconv.ParseUint(suffix, 10, 16)101return err == nil102}
103
104func AddPrivilegedDevices(g *generate.Generator, systemdMode bool) error {105hostDevices, err := getDevices("/dev")106if err != nil {107return err108}109g.ClearLinuxDevices()110
111if rootless.IsRootless() {112mounts := make(map[string]interface{})113for _, m := range g.Mounts() {114mounts[m.Destination] = true115}116newMounts := []spec.Mount{}117for _, d := range hostDevices {118devMnt := spec.Mount{119Destination: d.Path,120Type: define.TypeBind,121Source: d.Path,122Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"},123}124
125/* The following devices should not be mounted in rootless containers:126*
127* /dev/ptmx: The host-provided /dev/ptmx should not be shared to
128* the rootless containers for security reasons, and
129* the container runtime will create it for us
130* anyway (ln -s /dev/pts/ptmx /dev/ptmx);
131* /dev/tty and
132* /dev/tty[0-9]+: Prevent the container from taking over the host's
133* virtual consoles, even when not in systemd mode
134* for backwards compatibility.
135*/
136if d.Path == "/dev/ptmx" || d.Path == "/dev/tty" || isVirtualConsoleDevice(d.Path) {137continue138}139if _, found := mounts[d.Path]; found {140continue141}142newMounts = append(newMounts, devMnt)143}144g.Config.Mounts = append(newMounts, g.Config.Mounts...)145if g.Config.Linux.Resources != nil {146g.Config.Linux.Resources.Devices = nil147}148} else {149for _, d := range hostDevices {150/* Restrict access to the virtual consoles *only* when running151* in systemd mode to improve backwards compatibility. See
152* https://github.com/containers/podman/issues/15878.
153*
154* NOTE: May need revisiting in the future to drop the systemd
155* condition if more use cases end up breaking the virtual terminals
156* of people who specifically disable the systemd mode. It would
157* also provide a more consistent behaviour between rootless and
158* rootfull containers.
159*/
160if systemdMode && isVirtualConsoleDevice(d.Path) {161continue162}163g.AddDevice(d)164}165// Add resources device - need to clear the existing one first.166if g.Config.Linux.Resources != nil {167g.Config.Linux.Resources.Devices = nil168}169g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")170}171
172return nil173}
174
175// based on getDevices from runc (libcontainer/devices/devices.go)
176func getDevices(path string) ([]spec.LinuxDevice, error) {177files, err := os.ReadDir(path)178if err != nil {179if rootless.IsRootless() && os.IsPermission(err) {180return nil, nil181}182return nil, err183}184out := []spec.LinuxDevice{}185for _, f := range files {186switch {187case f.IsDir():188switch f.Name() {189// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825190case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts":191continue192default:193sub, err := getDevices(filepath.Join(path, f.Name()))194if err != nil {195return nil, err196}197if sub != nil {198out = append(out, sub...)199}200continue201}202case f.Name() == "console":203continue204case f.Type()&os.ModeSymlink != 0:205continue206}207
208device, err := DeviceFromPath(filepath.Join(path, f.Name()))209if err != nil {210if err == errNotADevice {211continue212}213if os.IsNotExist(err) {214continue215}216return nil, err217}218out = append(out, *device)219}220return out, nil221}
222
223// Copied from github.com/opencontainers/runc/libcontainer/devices
224// Given the path to a device look up the information about a linux device
225func DeviceFromPath(path string) (*spec.LinuxDevice, error) {226var stat unix.Stat_t227err := unix.Lstat(path, &stat)228if err != nil {229return nil, err230}231var (232devType string233mode = stat.Mode234devNumber = uint64(stat.Rdev) //nolint: unconvert235m = os.FileMode(mode)236)237
238switch {239case mode&unix.S_IFBLK == unix.S_IFBLK:240devType = "b"241case mode&unix.S_IFCHR == unix.S_IFCHR:242devType = "c"243case mode&unix.S_IFIFO == unix.S_IFIFO:244devType = "p"245default:246return nil, errNotADevice247}248
249return &spec.LinuxDevice{250Type: devType,251Path: path,252FileMode: &m,253UID: &stat.Uid,254GID: &stat.Gid,255Major: int64(unix.Major(devNumber)),256Minor: int64(unix.Minor(devNumber)),257}, nil258}
259