github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/specgen/generate/config_linux.go (about)

     1  package generate
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/containers/podman/v2/pkg/rootless"
    11  	spec "github.com/opencontainers/runtime-spec/specs-go"
    12  	"github.com/opencontainers/runtime-tools/generate"
    13  	"github.com/pkg/errors"
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  var (
    18  	errNotADevice = errors.New("not a device node")
    19  )
    20  
    21  func u32Ptr(i int64) *uint32     { u := uint32(i); return &u }
    22  func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
    23  
    24  func addPrivilegedDevices(g *generate.Generator) error {
    25  	hostDevices, err := getDevices("/dev")
    26  	if err != nil {
    27  		return err
    28  	}
    29  	g.ClearLinuxDevices()
    30  
    31  	if rootless.IsRootless() {
    32  		mounts := make(map[string]interface{})
    33  		for _, m := range g.Mounts() {
    34  			mounts[m.Destination] = true
    35  		}
    36  		newMounts := []spec.Mount{}
    37  		for _, d := range hostDevices {
    38  			devMnt := spec.Mount{
    39  				Destination: d.Path,
    40  				Type:        TypeBind,
    41  				Source:      d.Path,
    42  				Options:     []string{"slave", "nosuid", "noexec", "rw", "rbind"},
    43  			}
    44  			if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") {
    45  				continue
    46  			}
    47  			if _, found := mounts[d.Path]; found {
    48  				continue
    49  			}
    50  			st, err := os.Stat(d.Path)
    51  			if err != nil {
    52  				if err == unix.EPERM {
    53  					continue
    54  				}
    55  				return err
    56  			}
    57  			// Skip devices that the user has not access to.
    58  			if st.Mode()&0007 == 0 {
    59  				continue
    60  			}
    61  			newMounts = append(newMounts, devMnt)
    62  		}
    63  		g.Config.Mounts = append(newMounts, g.Config.Mounts...)
    64  		if g.Config.Linux.Resources != nil {
    65  			g.Config.Linux.Resources.Devices = nil
    66  		}
    67  	} else {
    68  		for _, d := range hostDevices {
    69  			g.AddDevice(d)
    70  		}
    71  		// Add resources device - need to clear the existing one first.
    72  		if g.Config.Linux.Resources != nil {
    73  			g.Config.Linux.Resources.Devices = nil
    74  		}
    75  		g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  // DevicesFromPath computes a list of devices
    82  func DevicesFromPath(g *generate.Generator, devicePath string) error {
    83  	devs := strings.Split(devicePath, ":")
    84  	resolvedDevicePath := devs[0]
    85  	// check if it is a symbolic link
    86  	if src, err := os.Lstat(resolvedDevicePath); err == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
    87  		if linkedPathOnHost, err := filepath.EvalSymlinks(resolvedDevicePath); err == nil {
    88  			resolvedDevicePath = linkedPathOnHost
    89  		}
    90  	}
    91  	st, err := os.Stat(resolvedDevicePath)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	if st.IsDir() {
    96  		found := false
    97  		src := resolvedDevicePath
    98  		dest := src
    99  		var devmode string
   100  		if len(devs) > 1 {
   101  			if len(devs[1]) > 0 && devs[1][0] == '/' {
   102  				dest = devs[1]
   103  			} else {
   104  				devmode = devs[1]
   105  			}
   106  		}
   107  		if len(devs) > 2 {
   108  			if devmode != "" {
   109  				return errors.Wrapf(unix.EINVAL, "invalid device specification %s", devicePath)
   110  			}
   111  			devmode = devs[2]
   112  		}
   113  
   114  		// mount the internal devices recursively
   115  		if err := filepath.Walk(resolvedDevicePath, func(dpath string, f os.FileInfo, e error) error {
   116  
   117  			if f.Mode()&os.ModeDevice == os.ModeDevice {
   118  				found = true
   119  				device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src)))
   120  				if devmode != "" {
   121  					device = fmt.Sprintf("%s:%s", device, devmode)
   122  				}
   123  				if err := addDevice(g, device); err != nil {
   124  					return errors.Wrapf(err, "failed to add %s device", dpath)
   125  				}
   126  			}
   127  			return nil
   128  		}); err != nil {
   129  			return err
   130  		}
   131  		if !found {
   132  			return errors.Wrapf(unix.EINVAL, "no devices found in %s", devicePath)
   133  		}
   134  		return nil
   135  	}
   136  
   137  	return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":"))
   138  }
   139  
   140  func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) {
   141  	if !privileged {
   142  		for _, mp := range []string{
   143  			"/proc/acpi",
   144  			"/proc/kcore",
   145  			"/proc/keys",
   146  			"/proc/latency_stats",
   147  			"/proc/timer_list",
   148  			"/proc/timer_stats",
   149  			"/proc/sched_debug",
   150  			"/proc/scsi",
   151  			"/sys/firmware",
   152  			"/sys/fs/selinux",
   153  			"/sys/dev",
   154  		} {
   155  			g.AddLinuxMaskedPaths(mp)
   156  		}
   157  
   158  		if pidModeIsHost && rootless.IsRootless() {
   159  			return
   160  		}
   161  
   162  		for _, rp := range []string{
   163  			"/proc/asound",
   164  			"/proc/bus",
   165  			"/proc/fs",
   166  			"/proc/irq",
   167  			"/proc/sys",
   168  			"/proc/sysrq-trigger",
   169  		} {
   170  			g.AddLinuxReadonlyPaths(rp)
   171  		}
   172  	}
   173  }
   174  
   175  // based on getDevices from runc (libcontainer/devices/devices.go)
   176  func getDevices(path string) ([]spec.LinuxDevice, error) {
   177  	files, err := ioutil.ReadDir(path)
   178  	if err != nil {
   179  		if rootless.IsRootless() && os.IsPermission(err) {
   180  			return nil, nil
   181  		}
   182  		return nil, err
   183  	}
   184  	out := []spec.LinuxDevice{}
   185  	for _, f := range files {
   186  		switch {
   187  		case f.IsDir():
   188  			switch f.Name() {
   189  			// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
   190  			case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts":
   191  				continue
   192  			default:
   193  				sub, err := getDevices(filepath.Join(path, f.Name()))
   194  				if err != nil {
   195  					return nil, err
   196  				}
   197  				if sub != nil {
   198  					out = append(out, sub...)
   199  				}
   200  				continue
   201  			}
   202  		case f.Name() == "console":
   203  			continue
   204  		case f.Mode()&os.ModeSymlink != 0:
   205  			continue
   206  		}
   207  
   208  		device, err := deviceFromPath(filepath.Join(path, f.Name()))
   209  		if err != nil {
   210  			if err == errNotADevice {
   211  				continue
   212  			}
   213  			if os.IsNotExist(err) {
   214  				continue
   215  			}
   216  			return nil, err
   217  		}
   218  		out = append(out, *device)
   219  	}
   220  	return out, nil
   221  }
   222  
   223  func addDevice(g *generate.Generator, device string) error {
   224  	src, dst, permissions, err := ParseDevice(device)
   225  	if err != nil {
   226  		return err
   227  	}
   228  	dev, err := deviceFromPath(src)
   229  	if err != nil {
   230  		return errors.Wrapf(err, "%s is not a valid device", src)
   231  	}
   232  	if rootless.IsRootless() {
   233  		if _, err := os.Stat(src); err != nil {
   234  			return err
   235  		}
   236  		perm := "ro"
   237  		if strings.Contains(permissions, "w") {
   238  			perm = "rw"
   239  		}
   240  		devMnt := spec.Mount{
   241  			Destination: dst,
   242  			Type:        TypeBind,
   243  			Source:      src,
   244  			Options:     []string{"slave", "nosuid", "noexec", perm, "rbind"},
   245  		}
   246  		g.Config.Mounts = append(g.Config.Mounts, devMnt)
   247  		return nil
   248  	} else if src == "/dev/fuse" {
   249  		// if the user is asking for fuse inside the container
   250  		// make sure the module is loaded.
   251  		f, err := unix.Open(src, unix.O_RDONLY|unix.O_NONBLOCK, 0)
   252  		if err == nil {
   253  			unix.Close(f)
   254  		}
   255  	}
   256  	dev.Path = dst
   257  	g.AddDevice(*dev)
   258  	g.AddLinuxResourcesDevice(true, dev.Type, &dev.Major, &dev.Minor, permissions)
   259  	return nil
   260  }
   261  
   262  // ParseDevice parses device mapping string to a src, dest & permissions string
   263  func ParseDevice(device string) (string, string, string, error) { //nolint
   264  	src := ""
   265  	dst := ""
   266  	permissions := "rwm"
   267  	arr := strings.Split(device, ":")
   268  	switch len(arr) {
   269  	case 3:
   270  		if !IsValidDeviceMode(arr[2]) {
   271  			return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2])
   272  		}
   273  		permissions = arr[2]
   274  		fallthrough
   275  	case 2:
   276  		if IsValidDeviceMode(arr[1]) {
   277  			permissions = arr[1]
   278  		} else {
   279  			if arr[1][0] != '/' {
   280  				return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1])
   281  			}
   282  			dst = arr[1]
   283  		}
   284  		fallthrough
   285  	case 1:
   286  		src = arr[0]
   287  	default:
   288  		return "", "", "", fmt.Errorf("invalid device specification: %s", device)
   289  	}
   290  
   291  	if dst == "" {
   292  		dst = src
   293  	}
   294  	return src, dst, permissions, nil
   295  }
   296  
   297  // IsValidDeviceMode checks if the mode for device is valid or not.
   298  // IsValid mode is a composition of r (read), w (write), and m (mknod).
   299  func IsValidDeviceMode(mode string) bool {
   300  	var legalDeviceMode = map[rune]bool{
   301  		'r': true,
   302  		'w': true,
   303  		'm': true,
   304  	}
   305  	if mode == "" {
   306  		return false
   307  	}
   308  	for _, c := range mode {
   309  		if !legalDeviceMode[c] {
   310  			return false
   311  		}
   312  		legalDeviceMode[c] = false
   313  	}
   314  	return true
   315  }
   316  
   317  // Copied from github.com/opencontainers/runc/libcontainer/devices
   318  // Given the path to a device look up the information about a linux device
   319  func deviceFromPath(path string) (*spec.LinuxDevice, error) {
   320  	var stat unix.Stat_t
   321  	err := unix.Lstat(path, &stat)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	var (
   326  		devType   string
   327  		mode      = stat.Mode
   328  		devNumber = uint64(stat.Rdev)
   329  		m         = os.FileMode(mode)
   330  	)
   331  
   332  	switch {
   333  	case mode&unix.S_IFBLK == unix.S_IFBLK:
   334  		devType = "b"
   335  	case mode&unix.S_IFCHR == unix.S_IFCHR:
   336  		devType = "c"
   337  	case mode&unix.S_IFIFO == unix.S_IFIFO:
   338  		devType = "p"
   339  	default:
   340  		return nil, errNotADevice
   341  	}
   342  
   343  	return &spec.LinuxDevice{
   344  		Type:     devType,
   345  		Path:     path,
   346  		FileMode: &m,
   347  		UID:      &stat.Uid,
   348  		GID:      &stat.Gid,
   349  		Major:    int64(unix.Major(devNumber)),
   350  		Minor:    int64(unix.Minor(devNumber)),
   351  	}, nil
   352  }
   353  
   354  func supportAmbientCapabilities() bool {
   355  	err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0)
   356  	return err == nil
   357  }