231 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // +build linux
 | |
| 
 | |
| package validate
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 
 | |
| 	"github.com/syndtr/gocapability/capability"
 | |
| 
 | |
| 	multierror "github.com/hashicorp/go-multierror"
 | |
| 	rspec "github.com/opencontainers/runtime-spec/specs-go"
 | |
| 	osFilepath "github.com/opencontainers/runtime-tools/filepath"
 | |
| 	"github.com/opencontainers/runtime-tools/specerror"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // LastCap return last cap of system
 | |
| func LastCap() capability.Cap {
 | |
| 	last := capability.CAP_LAST_CAP
 | |
| 	// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
 | |
| 	if last == capability.Cap(63) {
 | |
| 		last = capability.CAP_BLOCK_SUSPEND
 | |
| 	}
 | |
| 
 | |
| 	return last
 | |
| }
 | |
| 
 | |
| func deviceValid(d rspec.LinuxDevice) bool {
 | |
| 	switch d.Type {
 | |
| 	case "b", "c", "u":
 | |
| 		if d.Major <= 0 || d.Minor <= 0 {
 | |
| 			return false
 | |
| 		}
 | |
| 	case "p":
 | |
| 		if d.Major != 0 || d.Minor != 0 {
 | |
| 			return false
 | |
| 		}
 | |
| 	default:
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // CheckLinux checks v.spec.Linux
 | |
| func (v *Validator) CheckLinux() (errs error) {
 | |
| 	logrus.Debugf("check linux")
 | |
| 
 | |
| 	if v.spec.Linux == nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var nsTypeList = map[rspec.LinuxNamespaceType]struct {
 | |
| 		num      int
 | |
| 		newExist bool
 | |
| 	}{
 | |
| 		rspec.PIDNamespace:     {0, false},
 | |
| 		rspec.NetworkNamespace: {0, false},
 | |
| 		rspec.MountNamespace:   {0, false},
 | |
| 		rspec.IPCNamespace:     {0, false},
 | |
| 		rspec.UTSNamespace:     {0, false},
 | |
| 		rspec.UserNamespace:    {0, false},
 | |
| 		rspec.CgroupNamespace:  {0, false},
 | |
| 	}
 | |
| 
 | |
| 	for index := 0; index < len(v.spec.Linux.Namespaces); index++ {
 | |
| 		ns := v.spec.Linux.Namespaces[index]
 | |
| 		if ns.Path != "" && !osFilepath.IsAbs(v.platform, ns.Path) {
 | |
| 			errs = multierror.Append(errs, specerror.NewError(specerror.NSPathAbs, fmt.Errorf("namespace.path %q is not an absolute path", ns.Path), rspec.Version))
 | |
| 		}
 | |
| 
 | |
| 		tmpItem := nsTypeList[ns.Type]
 | |
| 		tmpItem.num = tmpItem.num + 1
 | |
| 		if tmpItem.num > 1 {
 | |
| 			errs = multierror.Append(errs, specerror.NewError(specerror.NSErrorOnDup, fmt.Errorf("duplicated namespace %q", ns.Type), rspec.Version))
 | |
| 		}
 | |
| 
 | |
| 		if len(ns.Path) == 0 {
 | |
| 			tmpItem.newExist = true
 | |
| 		}
 | |
| 		nsTypeList[ns.Type] = tmpItem
 | |
| 	}
 | |
| 
 | |
| 	if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !nsTypeList[rspec.UserNamespace].newExist {
 | |
| 		errs = multierror.Append(errs, errors.New("the UID/GID mappings requires a new User namespace to be specified as well"))
 | |
| 	}
 | |
| 
 | |
| 	for k := range v.spec.Linux.Sysctl {
 | |
| 		if strings.HasPrefix(k, "net.") && !nsTypeList[rspec.NetworkNamespace].newExist {
 | |
| 			errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new Network namespace to be specified as well", k))
 | |
| 		}
 | |
| 		if strings.HasPrefix(k, "fs.mqueue.") {
 | |
| 			if !nsTypeList[rspec.MountNamespace].newExist || !nsTypeList[rspec.IPCNamespace].newExist {
 | |
| 				errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if v.platform == "linux" && !nsTypeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" {
 | |
| 		errs = multierror.Append(errs, fmt.Errorf("on Linux, hostname requires a new UTS namespace to be specified as well"))
 | |
| 	}
 | |
| 
 | |
| 	// Linux devices validation
 | |
| 	devList := make(map[string]bool)
 | |
| 	devTypeList := make(map[string]bool)
 | |
| 	for index := 0; index < len(v.spec.Linux.Devices); index++ {
 | |
| 		device := v.spec.Linux.Devices[index]
 | |
| 		if !deviceValid(device) {
 | |
| 			errs = multierror.Append(errs, fmt.Errorf("device %v is invalid", device))
 | |
| 		}
 | |
| 
 | |
| 		if _, exists := devList[device.Path]; exists {
 | |
| 			errs = multierror.Append(errs, fmt.Errorf("device %s is duplicated", device.Path))
 | |
| 		} else {
 | |
| 			var rootfsPath string
 | |
| 			if filepath.IsAbs(v.spec.Root.Path) {
 | |
| 				rootfsPath = v.spec.Root.Path
 | |
| 			} else {
 | |
| 				rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
 | |
| 			}
 | |
| 			absPath := filepath.Join(rootfsPath, device.Path)
 | |
| 			fi, err := os.Stat(absPath)
 | |
| 			if os.IsNotExist(err) {
 | |
| 				devList[device.Path] = true
 | |
| 			} else if err != nil {
 | |
| 				errs = multierror.Append(errs, err)
 | |
| 			} else {
 | |
| 				fStat, ok := fi.Sys().(*syscall.Stat_t)
 | |
| 				if !ok {
 | |
| 					errs = multierror.Append(errs, specerror.NewError(specerror.DevicesAvailable,
 | |
| 						fmt.Errorf("cannot determine state for device %s", device.Path), rspec.Version))
 | |
| 					continue
 | |
| 				}
 | |
| 				var devType string
 | |
| 				switch fStat.Mode & syscall.S_IFMT {
 | |
| 				case syscall.S_IFCHR:
 | |
| 					devType = "c"
 | |
| 				case syscall.S_IFBLK:
 | |
| 					devType = "b"
 | |
| 				case syscall.S_IFIFO:
 | |
| 					devType = "p"
 | |
| 				default:
 | |
| 					devType = "unmatched"
 | |
| 				}
 | |
| 				if devType != device.Type || (devType == "c" && device.Type == "u") {
 | |
| 					errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
 | |
| 						fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
 | |
| 					continue
 | |
| 				}
 | |
| 				if devType != "p" {
 | |
| 					dev := fStat.Rdev
 | |
| 					major := (dev >> 8) & 0xfff
 | |
| 					minor := (dev & 0xff) | ((dev >> 12) & 0xfff00)
 | |
| 					if int64(major) != device.Major || int64(minor) != device.Minor {
 | |
| 						errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
 | |
| 							fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
 | |
| 						continue
 | |
| 					}
 | |
| 				}
 | |
| 				if device.FileMode != nil {
 | |
| 					expectedPerm := *device.FileMode & os.ModePerm
 | |
| 					actualPerm := fi.Mode() & os.ModePerm
 | |
| 					if expectedPerm != actualPerm {
 | |
| 						errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
 | |
| 							fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
 | |
| 						continue
 | |
| 					}
 | |
| 				}
 | |
| 				if device.UID != nil {
 | |
| 					if *device.UID != fStat.Uid {
 | |
| 						errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
 | |
| 							fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
 | |
| 						continue
 | |
| 					}
 | |
| 				}
 | |
| 				if device.GID != nil {
 | |
| 					if *device.GID != fStat.Gid {
 | |
| 						errs = multierror.Append(errs, specerror.NewError(specerror.DevicesFileNotMatch,
 | |
| 							fmt.Errorf("unmatched %s already exists in filesystem", device.Path), rspec.Version))
 | |
| 						continue
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// unify u->c when comparing, they are synonyms
 | |
| 		var devID string
 | |
| 		if device.Type == "u" {
 | |
| 			devID = fmt.Sprintf("%s:%d:%d", "c", device.Major, device.Minor)
 | |
| 		} else {
 | |
| 			devID = fmt.Sprintf("%s:%d:%d", device.Type, device.Major, device.Minor)
 | |
| 		}
 | |
| 
 | |
| 		if _, exists := devTypeList[devID]; exists {
 | |
| 			logrus.Warnf("%v", specerror.NewError(specerror.DevicesErrorOnDup, fmt.Errorf("type:%s, major:%d and minor:%d for linux devices is duplicated", device.Type, device.Major, device.Minor), rspec.Version))
 | |
| 		} else {
 | |
| 			devTypeList[devID] = true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if v.spec.Linux.Resources != nil {
 | |
| 		errs = multierror.Append(errs, v.CheckLinuxResources())
 | |
| 	}
 | |
| 
 | |
| 	for _, maskedPath := range v.spec.Linux.MaskedPaths {
 | |
| 		if !strings.HasPrefix(maskedPath, "/") {
 | |
| 			errs = multierror.Append(errs,
 | |
| 				specerror.NewError(
 | |
| 					specerror.MaskedPathsAbs,
 | |
| 					fmt.Errorf("maskedPath %v is not an absolute path", maskedPath),
 | |
| 					rspec.Version))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, readonlyPath := range v.spec.Linux.ReadonlyPaths {
 | |
| 		if !strings.HasPrefix(readonlyPath, "/") {
 | |
| 			errs = multierror.Append(errs,
 | |
| 				specerror.NewError(
 | |
| 					specerror.ReadonlyPathsAbs,
 | |
| 					fmt.Errorf("readonlyPath %v is not an absolute path", readonlyPath),
 | |
| 					rspec.Version))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | 
