github.com/hpcng/singularity@v3.1.1+incompatible/internal/pkg/runtime/engines/config/starter/starter.go (about)

     1  // Copyright (c) 2018-2019, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package starter
     7  
     8  /*
     9  #include <stdlib.h>
    10  #include <string.h>
    11  #include <sys/mman.h>
    12  #include <sys/types.h>
    13  #include "starter.h"
    14  */
    15  // #cgo CFLAGS: -I../../../../../../cmd/starter/c/include
    16  import "C"
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"strings"
    21  	"syscall"
    22  	"unsafe"
    23  
    24  	"github.com/opencontainers/runtime-spec/specs-go"
    25  	"github.com/sylabs/singularity/internal/pkg/sylog"
    26  	"github.com/sylabs/singularity/pkg/util/capabilities"
    27  )
    28  
    29  // CConfig is the common type for C.struct_cConfig
    30  type CConfig *C.struct_cConfig
    31  
    32  // Config represents structure to manipulate C starter configuration
    33  type Config struct {
    34  	config CConfig
    35  	nsPath []byte
    36  }
    37  
    38  // NewConfig takes a pointer to C starter configuration and returns a
    39  // pointer to a Config
    40  func NewConfig(config CConfig) *Config {
    41  	return &Config{config: config, nsPath: make([]byte, 1)}
    42  }
    43  
    44  // GetIsSUID returns if SUID workflow is enabled or not
    45  func (c *Config) GetIsSUID() bool {
    46  	if c.config.container.isSuid == 1 {
    47  		return true
    48  	}
    49  	return false
    50  }
    51  
    52  // GetContainerPid returns container process ID
    53  func (c *Config) GetContainerPid() int {
    54  	return int(c.config.container.pid)
    55  }
    56  
    57  // SetInstance sets if starter should spawn instance or not
    58  func (c *Config) SetInstance(instance bool) {
    59  	if instance {
    60  		c.config.container.isInstance = C.uchar(1)
    61  	} else {
    62  		c.config.container.isInstance = C.uchar(0)
    63  	}
    64  }
    65  
    66  // GetInstance returns if container run as instance or not
    67  func (c *Config) GetInstance() bool {
    68  	if c.config.container.isInstance == 1 {
    69  		return true
    70  	}
    71  	return false
    72  }
    73  
    74  // SetNoNewPrivs sets NO_NEW_PRIVS flag
    75  func (c *Config) SetNoNewPrivs(noprivs bool) {
    76  	if noprivs {
    77  		c.config.container.noNewPrivs = C.uchar(1)
    78  	} else {
    79  		c.config.container.noNewPrivs = C.uchar(0)
    80  	}
    81  }
    82  
    83  // GetNoNewPrivs returns if NO_NEW_PRIVS flag is set or not
    84  func (c *Config) GetNoNewPrivs() bool {
    85  	if c.config.container.noNewPrivs == 1 {
    86  		return true
    87  	}
    88  	return false
    89  }
    90  
    91  // SetSharedMount sets if master/container shares mount point
    92  func (c *Config) SetSharedMount(shared bool) {
    93  	if shared {
    94  		c.config.container.sharedMount = C.uchar(1)
    95  	} else {
    96  		c.config.container.sharedMount = C.uchar(0)
    97  	}
    98  }
    99  
   100  // GetSharedMount returns if master/container shares mount point or not
   101  func (c *Config) GetSharedMount() bool {
   102  	if c.config.container.sharedMount == 1 {
   103  		return true
   104  	}
   105  	return false
   106  }
   107  
   108  // SetJoinMount sets if container process join a mount namespace
   109  func (c *Config) SetJoinMount(join bool) {
   110  	if join {
   111  		c.config.container.joinMount = C.uchar(1)
   112  	} else {
   113  		c.config.container.joinMount = C.uchar(0)
   114  	}
   115  }
   116  
   117  // GetJoinMount returns if container process join a mount namespace
   118  func (c *Config) GetJoinMount() bool {
   119  	if c.config.container.joinMount == 1 {
   120  		return true
   121  	}
   122  	return false
   123  }
   124  
   125  // SetBringLoopbackInterface sets if starter bring loopback network interface
   126  func (c *Config) SetBringLoopbackInterface(bring bool) {
   127  	if bring {
   128  		c.config.container.bringLoopbackInterface = C.uchar(1)
   129  	} else {
   130  		c.config.container.bringLoopbackInterface = C.uchar(0)
   131  	}
   132  }
   133  
   134  // GetBringLoopbackInterface returns if starter bring loopback network interface
   135  func (c *Config) GetBringLoopbackInterface() bool {
   136  	if c.config.container.bringLoopbackInterface == 1 {
   137  		return true
   138  	}
   139  	return false
   140  }
   141  
   142  // SetMountPropagation sets root filesystem mount propagation
   143  func (c *Config) SetMountPropagation(propagation string) {
   144  	var flags uintptr
   145  
   146  	switch propagation {
   147  	case "shared", "rshared":
   148  		flags = syscall.MS_SHARED
   149  	case "slave", "rslave":
   150  		flags = syscall.MS_SLAVE
   151  	case "private", "rprivate":
   152  		flags = syscall.MS_SHARED
   153  	case "unbindable", "runbindable":
   154  		flags = syscall.MS_UNBINDABLE
   155  	}
   156  
   157  	if strings.HasPrefix(propagation, "r") {
   158  		flags |= syscall.MS_REC
   159  	}
   160  	c.config.container.mountPropagation = C.ulong(flags)
   161  }
   162  
   163  // GetJSONConfig returns pointer to JSON configuration
   164  func (c *Config) GetJSONConfig() []byte {
   165  	return C.GoBytes(unsafe.Pointer(&c.config.json.config[0]), C.int(c.config.json.size))
   166  }
   167  
   168  // WriteConfig writes raw C configuration
   169  func (c *Config) Write(payload interface{}) error {
   170  	jsonConf, err := json.Marshal(payload)
   171  	if err != nil {
   172  		return fmt.Errorf("failed to marshal payload: %s", err)
   173  	}
   174  	size := len(jsonConf)
   175  	maxSize := C.MAX_JSON_SIZE - 1
   176  	c.config.json.size = C.size_t(size)
   177  
   178  	if size >= maxSize {
   179  		return fmt.Errorf("json configuration too big %d > %d", size, maxSize)
   180  	}
   181  
   182  	json := C.CBytes(jsonConf)
   183  
   184  	C.memcpy(unsafe.Pointer(&c.config.json.config[0]), json, c.config.json.size)
   185  	C.free(json)
   186  
   187  	return nil
   188  }
   189  
   190  // AddUIDMappings sets user namespace UID mapping.
   191  func (c *Config) AddUIDMappings(uids []specs.LinuxIDMapping) error {
   192  	uidMap := ""
   193  	for i, uid := range uids {
   194  		if i == 0 {
   195  			c.SetTargetUID(int(uid.ContainerID))
   196  		}
   197  		uidMap = uidMap + fmt.Sprintf("%d %d %d\n", uid.ContainerID, uid.HostID, uid.Size)
   198  	}
   199  
   200  	l := len(uidMap)
   201  	if l >= C.MAX_MAP_SIZE-1 {
   202  		return fmt.Errorf("UID map too big")
   203  	}
   204  
   205  	if l > 0 {
   206  		cpath := unsafe.Pointer(C.CString(uidMap))
   207  		size := C.size_t(l)
   208  
   209  		C.memcpy(unsafe.Pointer(&c.config.container.uidMap[0]), cpath, size)
   210  		C.free(cpath)
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  // AddGIDMappings sets user namespace GID mapping
   217  func (c *Config) AddGIDMappings(gids []specs.LinuxIDMapping) error {
   218  	var targetGids []int
   219  	gidMap := ""
   220  	for _, gid := range gids {
   221  		targetGids = append(targetGids, int(gid.ContainerID))
   222  		gidMap = gidMap + fmt.Sprintf("%d %d %d\n", gid.ContainerID, gid.HostID, gid.Size)
   223  	}
   224  
   225  	if len(targetGids) != 0 {
   226  		c.SetTargetGID(targetGids)
   227  	}
   228  
   229  	l := len(gidMap)
   230  	if l >= C.MAX_MAP_SIZE-1 {
   231  		return fmt.Errorf("GID map too big")
   232  	}
   233  
   234  	if l > 0 {
   235  		cpath := unsafe.Pointer(C.CString(gidMap))
   236  		size := C.size_t(l)
   237  
   238  		C.memcpy(unsafe.Pointer(&c.config.container.gidMap[0]), cpath, size)
   239  		C.free(cpath)
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  // SetNsFlags sets namespaces flag directly from flags argument
   246  func (c *Config) SetNsFlags(flags int) {
   247  	c.config.namespace.flags = C.uint(flags)
   248  }
   249  
   250  // SetNsFlagsFromSpec sets namespaces flag from OCI spec
   251  func (c *Config) SetNsFlagsFromSpec(namespaces []specs.LinuxNamespace) {
   252  	c.config.namespace.flags = 0
   253  	for _, namespace := range namespaces {
   254  		if namespace.Path == "" {
   255  			switch namespace.Type {
   256  			case specs.UserNamespace:
   257  				c.config.namespace.flags |= syscall.CLONE_NEWUSER
   258  			case specs.IPCNamespace:
   259  				c.config.namespace.flags |= syscall.CLONE_NEWIPC
   260  			case specs.UTSNamespace:
   261  				c.config.namespace.flags |= syscall.CLONE_NEWUTS
   262  			case specs.PIDNamespace:
   263  				c.config.namespace.flags |= syscall.CLONE_NEWPID
   264  			case specs.NetworkNamespace:
   265  				c.config.namespace.flags |= syscall.CLONE_NEWNET
   266  			case specs.MountNamespace:
   267  				c.config.namespace.flags |= syscall.CLONE_NEWNS
   268  			case specs.CgroupNamespace:
   269  				c.config.namespace.flags |= 0x2000000
   270  			}
   271  		}
   272  	}
   273  }
   274  
   275  // SetNsPath sets corresponding namespace to be joined
   276  func (c *Config) SetNsPath(nstype specs.LinuxNamespaceType, path string) error {
   277  	cpath := unsafe.Pointer(C.CString(path))
   278  	l := len(path)
   279  	size := C.size_t(l)
   280  
   281  	if l > C.MAX_NS_PATH_SIZE-1 {
   282  		return fmt.Errorf("%s namespace path too big", nstype)
   283  	}
   284  
   285  	switch nstype {
   286  	case specs.UserNamespace:
   287  		C.memcpy(unsafe.Pointer(&c.config.namespace.user[0]), cpath, size)
   288  	case specs.IPCNamespace:
   289  		C.memcpy(unsafe.Pointer(&c.config.namespace.ipc[0]), cpath, size)
   290  	case specs.UTSNamespace:
   291  		C.memcpy(unsafe.Pointer(&c.config.namespace.uts[0]), cpath, size)
   292  	case specs.PIDNamespace:
   293  		C.memcpy(unsafe.Pointer(&c.config.namespace.pid[0]), cpath, size)
   294  	case specs.NetworkNamespace:
   295  		C.memcpy(unsafe.Pointer(&c.config.namespace.network[0]), cpath, size)
   296  	case specs.MountNamespace:
   297  		C.memcpy(unsafe.Pointer(&c.config.namespace.mount[0]), cpath, size)
   298  	case specs.CgroupNamespace:
   299  		C.memcpy(unsafe.Pointer(&c.config.namespace.cgroup[0]), cpath, size)
   300  	}
   301  
   302  	C.free(cpath)
   303  
   304  	return nil
   305  }
   306  
   307  // SetNsPathFromSpec sets corresponding namespace to be joined from OCI spec
   308  func (c *Config) SetNsPathFromSpec(namespaces []specs.LinuxNamespace) error {
   309  	for _, namespace := range namespaces {
   310  		if namespace.Path != "" {
   311  			cpath := unsafe.Pointer(C.CString(namespace.Path))
   312  			l := len(namespace.Path)
   313  			size := C.size_t(l)
   314  
   315  			if l > C.MAX_NS_PATH_SIZE-1 {
   316  				return fmt.Errorf("%s namespace path too big", namespace.Type)
   317  			}
   318  
   319  			switch namespace.Type {
   320  			case specs.UserNamespace:
   321  				C.memcpy(unsafe.Pointer(&c.config.namespace.user[0]), cpath, size)
   322  			case specs.IPCNamespace:
   323  				C.memcpy(unsafe.Pointer(&c.config.namespace.ipc[0]), cpath, size)
   324  			case specs.UTSNamespace:
   325  				C.memcpy(unsafe.Pointer(&c.config.namespace.uts[0]), cpath, size)
   326  			case specs.PIDNamespace:
   327  				C.memcpy(unsafe.Pointer(&c.config.namespace.pid[0]), cpath, size)
   328  			case specs.NetworkNamespace:
   329  				C.memcpy(unsafe.Pointer(&c.config.namespace.network[0]), cpath, size)
   330  			case specs.MountNamespace:
   331  				C.memcpy(unsafe.Pointer(&c.config.namespace.mount[0]), cpath, size)
   332  			case specs.CgroupNamespace:
   333  				C.memcpy(unsafe.Pointer(&c.config.namespace.cgroup[0]), cpath, size)
   334  			}
   335  
   336  			C.free(cpath)
   337  		}
   338  	}
   339  
   340  	return nil
   341  }
   342  
   343  // SetCapabilities sets corresponding capability set identified by ctype
   344  // from a capability string list identified by ctype
   345  func (c *Config) SetCapabilities(ctype string, caps []string) {
   346  	switch ctype {
   347  	case capabilities.Permitted:
   348  		c.config.capabilities.permitted = 0
   349  		for _, v := range caps {
   350  			c.config.capabilities.permitted |= C.ulonglong(1 << capabilities.Map[v].Value)
   351  		}
   352  	case capabilities.Effective:
   353  		c.config.capabilities.effective = 0
   354  		for _, v := range caps {
   355  			c.config.capabilities.effective |= C.ulonglong(1 << capabilities.Map[v].Value)
   356  		}
   357  	case capabilities.Inheritable:
   358  		c.config.capabilities.inheritable = 0
   359  		for _, v := range caps {
   360  			c.config.capabilities.inheritable |= C.ulonglong(1 << capabilities.Map[v].Value)
   361  		}
   362  	case capabilities.Bounding:
   363  		c.config.capabilities.bounding = 0
   364  		for _, v := range caps {
   365  			c.config.capabilities.bounding |= C.ulonglong(1 << capabilities.Map[v].Value)
   366  		}
   367  	case capabilities.Ambient:
   368  		c.config.capabilities.ambient = 0
   369  		for _, v := range caps {
   370  			c.config.capabilities.ambient |= C.ulonglong(1 << capabilities.Map[v].Value)
   371  		}
   372  	}
   373  }
   374  
   375  // SetTargetUID sets target UID to execute the container process as user ID
   376  func (c *Config) SetTargetUID(uid int) {
   377  	c.config.container.targetUID = C.uid_t(uid)
   378  }
   379  
   380  // SetTargetGID sets target GIDs to execute container process as group IDs
   381  func (c *Config) SetTargetGID(gids []int) {
   382  	c.config.container.numGID = C.int(len(gids))
   383  
   384  	for i, gid := range gids {
   385  		if i > C.MAX_GID {
   386  			sylog.Warningf("you can't specify more than %d group IDs", C.MAX_GID)
   387  			break
   388  		}
   389  		c.config.container.targetGID[i] = C.gid_t(gid)
   390  	}
   391  }
   392  
   393  // Release performs a unmap on starter config and release mapped memory
   394  func (c *Config) Release() error {
   395  	if C.munmap(unsafe.Pointer(c.config), C.sizeof_struct_cConfig) != 0 {
   396  		return fmt.Errorf("failed to release starter memory")
   397  	}
   398  	return nil
   399  }