mirror of
				https://github.com/containers/podman.git
				synced 2025-11-01 02:42:11 +08:00 
			
		
		
		
	 498445871c
			
		
	
	498445871c
	
	
	
		
			
			This is racy by design, if you walk a tree and the directory was removed between listing and then opening we get an ENOENT error. Simply ignore that case and do not log it. Fixes #21782 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
		
			
				
	
	
		
			259 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			259 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package util
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/fs"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 
 | |
| 	"github.com/containers/podman/v5/libpod/define"
 | |
| 	"github.com/containers/podman/v5/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 {
 | |
| 			if !errors.Is(err, fs.ErrNotExist) {
 | |
| 				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
 | |
| }
 |