...

Source file src/github.com/Microsoft/hcsshim/internal/guest/runtime/hcsv2/spec.go

Documentation: github.com/Microsoft/hcsshim/internal/guest/runtime/hcsv2

     1  //go:build linux
     2  // +build linux
     3  
     4  package hcsv2
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  
    13  	int_oci "github.com/Microsoft/hcsshim/internal/oci"
    14  	"github.com/opencontainers/runc/libcontainer/devices"
    15  	"github.com/opencontainers/runc/libcontainer/user"
    16  	oci "github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  
    19  	"github.com/Microsoft/hcsshim/internal/log"
    20  	"github.com/Microsoft/hcsshim/pkg/annotations"
    21  )
    22  
    23  const (
    24  	devShmPath = "/dev/shm"
    25  )
    26  
    27  // getNetworkNamespaceID returns the `ToLower` of
    28  // `spec.Windows.Network.NetworkNamespace` or `""`.
    29  func getNetworkNamespaceID(spec *oci.Spec) string {
    30  	if spec.Windows != nil &&
    31  		spec.Windows.Network != nil {
    32  		return strings.ToLower(spec.Windows.Network.NetworkNamespace)
    33  	}
    34  	return ""
    35  }
    36  
    37  // isRootReadonly returns `true` if the spec specifies the rootfs is readonly.
    38  func isRootReadonly(spec *oci.Spec) bool {
    39  	if spec.Root != nil {
    40  		return spec.Root.Readonly
    41  	}
    42  	return false
    43  }
    44  
    45  // removeMount removes mount from the array if `target` matches `Destination`
    46  func removeMount(target string, mounts []oci.Mount) []oci.Mount {
    47  	var result []oci.Mount
    48  	for _, m := range mounts {
    49  		if m.Destination == target {
    50  			continue
    51  		}
    52  		result = append(result, m)
    53  	}
    54  	return result
    55  }
    56  
    57  func setProcess(spec *oci.Spec) {
    58  	if spec.Process == nil {
    59  		spec.Process = &oci.Process{}
    60  	}
    61  }
    62  
    63  func setCoreRLimit(spec *oci.Spec, value string) error {
    64  	setProcess(spec)
    65  
    66  	vals := strings.Split(value, ";")
    67  	if len(vals) != 2 {
    68  		return errors.New("wrong number of values supplied for rlimit core")
    69  	}
    70  
    71  	soft, err := strconv.ParseUint(vals[0], 10, 64)
    72  	if err != nil {
    73  		return errors.Wrap(err, "failed to parse soft core rlimit")
    74  	}
    75  	hard, err := strconv.ParseUint(vals[1], 10, 64)
    76  	if err != nil {
    77  		return errors.Wrap(err, "failed to parse hard core rlimit")
    78  	}
    79  
    80  	spec.Process.Rlimits = append(spec.Process.Rlimits, oci.POSIXRlimit{
    81  		Type: "RLIMIT_CORE",
    82  		Soft: soft,
    83  		Hard: hard,
    84  	})
    85  
    86  	return nil
    87  }
    88  
    89  // setUserStr sets `spec.Process` to the valid `userstr` based on the OCI Image Spec
    90  // v1.0.0 `userstr`.
    91  //
    92  // Valid values are: user, uid, user:group, uid:gid, uid:group, user:gid.
    93  // If uid is provided instead of the username then that value is not checked against the
    94  // /etc/passwd file to verify if the user with given uid actually exists.
    95  func setUserStr(spec *oci.Spec, userstr string) error {
    96  	setProcess(spec)
    97  
    98  	parts := strings.Split(userstr, ":")
    99  	switch len(parts) {
   100  	case 1:
   101  		v, err := strconv.Atoi(parts[0])
   102  		if err != nil {
   103  			// evaluate username to uid/gid
   104  			return setUsername(spec, userstr)
   105  		}
   106  		return setUserID(spec, int(v))
   107  	case 2:
   108  		var (
   109  			username, groupname string
   110  			uid, gid            int
   111  		)
   112  		v, err := strconv.Atoi(parts[0])
   113  		if err != nil {
   114  			username = parts[0]
   115  		} else {
   116  			uid = int(v)
   117  		}
   118  		v, err = strconv.Atoi(parts[1])
   119  		if err != nil {
   120  			groupname = parts[1]
   121  		} else {
   122  			gid = int(v)
   123  		}
   124  		if username != "" {
   125  			u, err := getUser(spec, func(u user.User) bool {
   126  				return u.Name == username
   127  			})
   128  			if err != nil {
   129  				return errors.Wrapf(err, "failed to find user by username: %s", username)
   130  			}
   131  			uid = u.Uid
   132  		}
   133  		if groupname != "" {
   134  			g, err := getGroup(spec, func(g user.Group) bool {
   135  				return g.Name == groupname
   136  			})
   137  			if err != nil {
   138  				return errors.Wrapf(err, "failed to find group by groupname: %s", groupname)
   139  			}
   140  			gid = g.Gid
   141  		}
   142  		spec.Process.User.UID, spec.Process.User.GID = uint32(uid), uint32(gid)
   143  		return nil
   144  	default:
   145  		return fmt.Errorf("invalid userstr: '%s'", userstr)
   146  	}
   147  }
   148  
   149  func setUsername(spec *oci.Spec, username string) error {
   150  	u, err := getUser(spec, func(u user.User) bool {
   151  		return u.Name == username
   152  	})
   153  	if err != nil {
   154  		return errors.Wrapf(err, "failed to find user by username: %s", username)
   155  	}
   156  	spec.Process.User.UID, spec.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
   157  	return nil
   158  }
   159  
   160  func setUserID(spec *oci.Spec, uid int) error {
   161  	u, err := getUser(spec, func(u user.User) bool {
   162  		return u.Uid == uid
   163  	})
   164  	if err != nil {
   165  		spec.Process.User.UID, spec.Process.User.GID = uint32(uid), 0
   166  		return nil
   167  	}
   168  	spec.Process.User.UID, spec.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
   169  	return nil
   170  }
   171  
   172  func getUser(spec *oci.Spec, filter func(user.User) bool) (user.User, error) {
   173  	users, err := user.ParsePasswdFileFilter(filepath.Join(spec.Root.Path, "/etc/passwd"), filter)
   174  	if err != nil {
   175  		return user.User{}, err
   176  	}
   177  	if len(users) != 1 {
   178  		return user.User{}, errors.Errorf("expected exactly 1 user matched '%d'", len(users))
   179  	}
   180  	return users[0], nil
   181  }
   182  
   183  func getGroup(spec *oci.Spec, filter func(user.Group) bool) (user.Group, error) {
   184  	groups, err := user.ParseGroupFileFilter(filepath.Join(spec.Root.Path, "/etc/group"), filter)
   185  	if err != nil {
   186  		return user.Group{}, err
   187  	}
   188  	if len(groups) != 1 {
   189  		return user.Group{}, errors.Errorf("expected exactly 1 group matched '%d'", len(groups))
   190  	}
   191  	return groups[0], nil
   192  }
   193  
   194  // applyAnnotationsToSpec modifies the spec based on additional information from annotations
   195  func applyAnnotationsToSpec(ctx context.Context, spec *oci.Spec) error {
   196  	// Check if we need to override container's /dev/shm
   197  	if val, ok := spec.Annotations[annotations.LCOWDevShmSizeInKb]; ok {
   198  		mt, err := devShmMountWithSize(val)
   199  		if err != nil {
   200  			return err
   201  		}
   202  		spec.Mounts = removeMount(devShmPath, spec.Mounts)
   203  		spec.Mounts = append(spec.Mounts, *mt)
   204  		log.G(ctx).WithField("sizeKB", val).Debug("set custom /dev/shm size")
   205  	}
   206  
   207  	// Check if we need to do any capability/device mappings
   208  	if int_oci.ParseAnnotationsBool(ctx, spec.Annotations, annotations.LCOWPrivileged, false) {
   209  		log.G(ctx).Debugf("'%s' set for privileged container", annotations.LCOWPrivileged)
   210  
   211  		// Add all host devices
   212  		hostDevices, err := devices.HostDevices()
   213  		if err != nil {
   214  			return err
   215  		}
   216  		for _, hostDevice := range hostDevices {
   217  			addLinuxDeviceToSpec(ctx, hostDevice, spec, false)
   218  		}
   219  
   220  		// Set the cgroup access
   221  		spec.Linux.Resources.Devices = []oci.LinuxDeviceCgroup{
   222  			{
   223  				Allow:  true,
   224  				Access: "rwm",
   225  			},
   226  		}
   227  	} else {
   228  		tempLinuxDevices := spec.Linux.Devices
   229  		spec.Linux.Devices = []oci.LinuxDevice{}
   230  		for _, ld := range tempLinuxDevices {
   231  			hostDevice, err := devices.DeviceFromPath(ld.Path, "rwm")
   232  			if err != nil {
   233  				return err
   234  			}
   235  			addLinuxDeviceToSpec(ctx, hostDevice, spec, true)
   236  		}
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  // addDevSev adds SEV device to container spec. On 5.x kernel the device is /dev/sev,
   243  // however this changed in 6.x where the device is /dev/sev-guest.
   244  func addDevSev(ctx context.Context, spec *oci.Spec) error {
   245  	// try adding /dev/sev, which should be present for 5.x kernel
   246  	devSev, err := devices.DeviceFromPath("/dev/sev", "rwm")
   247  	if err != nil {
   248  		// try adding /dev/guest-sev, which should be present for 6.x kernel
   249  		sevErr := fmt.Errorf("failed to add SEV device to spec: %w", err)
   250  		var errSevGuest error
   251  		devSev, errSevGuest = devices.DeviceFromPath("/dev/sev-guest", "rwm")
   252  		if errSevGuest != nil {
   253  			return fmt.Errorf("%s: %w", sevErr, errSevGuest)
   254  		}
   255  	}
   256  	addLinuxDeviceToSpec(ctx, devSev, spec, true)
   257  	return nil
   258  }
   259  
   260  // devShmMountWithSize returns a /dev/shm device mount with size set to
   261  // `sizeString` if it represents a valid size in KB, returns error otherwise.
   262  func devShmMountWithSize(sizeString string) (*oci.Mount, error) {
   263  	size, err := strconv.ParseUint(sizeString, 10, 64)
   264  	if err != nil {
   265  		return nil, fmt.Errorf("/dev/shm size must be a valid integer: %w", err)
   266  	}
   267  	if size == 0 {
   268  		return nil, errors.New("/dev/shm size must be non-zero")
   269  	}
   270  
   271  	// Use the same options as in upstream https://github.com/containerd/containerd/blob/0def98e462706286e6eaeff4a90be22fda75e761/oci/mounts.go#L49
   272  	sizeKB := fmt.Sprintf("size=%sk", sizeString)
   273  	return &oci.Mount{
   274  		Source:      "shm",
   275  		Destination: devShmPath,
   276  		Type:        "tmpfs",
   277  		Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", sizeKB},
   278  	}, nil
   279  }
   280  

View as plain text