Files
podman/pkg/specgen/generate/config_linux.go
Daniel J Walsh 831d7fb0d7 Stop excessive wrapping of errors
Most of the builtin golang functions like os.Stat and
os.Open report errors including the file system object
path. We should not wrap these errors and put the file path
in a second time, causing stuttering of errors when they
get presented to the user.

This patch tries to cleanup a bunch of these errors.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2020-10-30 05:34:04 -04:00

353 lines
8.2 KiB
Go

package generate
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containers/podman/v2/pkg/rootless"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
var (
errNotADevice = errors.New("not a device node")
)
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
func addPrivilegedDevices(g *generate.Generator) 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: TypeBind,
Source: d.Path,
Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"},
}
if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") {
continue
}
if _, found := mounts[d.Path]; found {
continue
}
st, err := os.Stat(d.Path)
if err != nil {
if err == unix.EPERM {
continue
}
return err
}
// Skip devices that the user has not access to.
if st.Mode()&0007 == 0 {
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 {
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
}
// DevicesFromPath computes a list of devices
func DevicesFromPath(g *generate.Generator, devicePath string) error {
devs := strings.Split(devicePath, ":")
resolvedDevicePath := devs[0]
// check if it is a symbolic link
if src, err := os.Lstat(resolvedDevicePath); err == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
if linkedPathOnHost, err := filepath.EvalSymlinks(resolvedDevicePath); err == nil {
resolvedDevicePath = linkedPathOnHost
}
}
st, err := os.Stat(resolvedDevicePath)
if err != nil {
return err
}
if st.IsDir() {
found := false
src := resolvedDevicePath
dest := src
var devmode string
if len(devs) > 1 {
if len(devs[1]) > 0 && devs[1][0] == '/' {
dest = devs[1]
} else {
devmode = devs[1]
}
}
if len(devs) > 2 {
if devmode != "" {
return errors.Wrapf(unix.EINVAL, "invalid device specification %s", devicePath)
}
devmode = devs[2]
}
// mount the internal devices recursively
if err := filepath.Walk(resolvedDevicePath, func(dpath string, f os.FileInfo, e error) error {
if f.Mode()&os.ModeDevice == os.ModeDevice {
found = true
device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src)))
if devmode != "" {
device = fmt.Sprintf("%s:%s", device, devmode)
}
if err := addDevice(g, device); err != nil {
return errors.Wrapf(err, "failed to add %s device", dpath)
}
}
return nil
}); err != nil {
return err
}
if !found {
return errors.Wrapf(unix.EINVAL, "no devices found in %s", devicePath)
}
return nil
}
return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":"))
}
func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) {
if !privileged {
for _, mp := range []string{
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
"/sys/fs/selinux",
"/sys/dev",
} {
g.AddLinuxMaskedPaths(mp)
}
if pidModeIsHost && rootless.IsRootless() {
return
}
for _, rp := range []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
} {
g.AddLinuxReadonlyPaths(rp)
}
}
}
// based on getDevices from runc (libcontainer/devices/devices.go)
func getDevices(path string) ([]spec.LinuxDevice, error) {
files, err := ioutil.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.Mode()&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
}
func addDevice(g *generate.Generator, device string) error {
src, dst, permissions, err := ParseDevice(device)
if err != nil {
return err
}
dev, err := deviceFromPath(src)
if err != nil {
return errors.Wrapf(err, "%s is not a valid device", src)
}
if rootless.IsRootless() {
if _, err := os.Stat(src); err != nil {
return err
}
perm := "ro"
if strings.Contains(permissions, "w") {
perm = "rw"
}
devMnt := spec.Mount{
Destination: dst,
Type: TypeBind,
Source: src,
Options: []string{"slave", "nosuid", "noexec", perm, "rbind"},
}
g.Config.Mounts = append(g.Config.Mounts, devMnt)
return nil
} else if src == "/dev/fuse" {
// if the user is asking for fuse inside the container
// make sure the module is loaded.
f, err := unix.Open(src, unix.O_RDONLY|unix.O_NONBLOCK, 0)
if err == nil {
unix.Close(f)
}
}
dev.Path = dst
g.AddDevice(*dev)
g.AddLinuxResourcesDevice(true, dev.Type, &dev.Major, &dev.Minor, permissions)
return nil
}
// ParseDevice parses device mapping string to a src, dest & permissions string
func ParseDevice(device string) (string, string, string, error) { //nolint
src := ""
dst := ""
permissions := "rwm"
arr := strings.Split(device, ":")
switch len(arr) {
case 3:
if !IsValidDeviceMode(arr[2]) {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2])
}
permissions = arr[2]
fallthrough
case 2:
if IsValidDeviceMode(arr[1]) {
permissions = arr[1]
} else {
if arr[1][0] != '/' {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1])
}
dst = arr[1]
}
fallthrough
case 1:
src = arr[0]
default:
return "", "", "", fmt.Errorf("invalid device specification: %s", device)
}
if dst == "" {
dst = src
}
return src, dst, permissions, nil
}
// IsValidDeviceMode checks if the mode for device is valid or not.
// IsValid mode is a composition of r (read), w (write), and m (mknod).
func IsValidDeviceMode(mode string) bool {
var legalDeviceMode = map[rune]bool{
'r': true,
'w': true,
'm': true,
}
if mode == "" {
return false
}
for _, c := range mode {
if !legalDeviceMode[c] {
return false
}
legalDeviceMode[c] = false
}
return true
}
// 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)
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
}