github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/runsc/specutils/fs.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package specutils
    16  
    17  import (
    18  	"fmt"
    19  	"math/bits"
    20  	"path"
    21  	"strings"
    22  
    23  	specs "github.com/opencontainers/runtime-spec/specs-go"
    24  	"golang.org/x/sys/unix"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    26  )
    27  
    28  type mapping struct {
    29  	set bool
    30  	val uint32
    31  }
    32  
    33  // optionsMap maps mount propagation-related OCI filesystem options to mount(2)
    34  // syscall flags.
    35  var optionsMap = map[string]mapping{
    36  	"acl":           {set: true, val: unix.MS_POSIXACL},
    37  	"async":         {set: false, val: unix.MS_SYNCHRONOUS},
    38  	"atime":         {set: false, val: unix.MS_NOATIME},
    39  	"bind":          {set: true, val: unix.MS_BIND},
    40  	"defaults":      {set: true, val: 0},
    41  	"dev":           {set: false, val: unix.MS_NODEV},
    42  	"diratime":      {set: false, val: unix.MS_NODIRATIME},
    43  	"dirsync":       {set: true, val: unix.MS_DIRSYNC},
    44  	"exec":          {set: false, val: unix.MS_NOEXEC},
    45  	"noexec":        {set: true, val: unix.MS_NOEXEC},
    46  	"iversion":      {set: true, val: unix.MS_I_VERSION},
    47  	"loud":          {set: false, val: unix.MS_SILENT},
    48  	"mand":          {set: true, val: unix.MS_MANDLOCK},
    49  	"noacl":         {set: false, val: unix.MS_POSIXACL},
    50  	"noatime":       {set: true, val: unix.MS_NOATIME},
    51  	"nodev":         {set: true, val: unix.MS_NODEV},
    52  	"nodiratime":    {set: true, val: unix.MS_NODIRATIME},
    53  	"noiversion":    {set: false, val: unix.MS_I_VERSION},
    54  	"nomand":        {set: false, val: unix.MS_MANDLOCK},
    55  	"norelatime":    {set: false, val: unix.MS_RELATIME},
    56  	"nostrictatime": {set: false, val: unix.MS_STRICTATIME},
    57  	"nosuid":        {set: true, val: unix.MS_NOSUID},
    58  	"rbind":         {set: true, val: unix.MS_BIND | unix.MS_REC},
    59  	"relatime":      {set: true, val: unix.MS_RELATIME},
    60  	"remount":       {set: true, val: unix.MS_REMOUNT},
    61  	"ro":            {set: true, val: unix.MS_RDONLY},
    62  	"rw":            {set: false, val: unix.MS_RDONLY},
    63  	"silent":        {set: true, val: unix.MS_SILENT},
    64  	"strictatime":   {set: true, val: unix.MS_STRICTATIME},
    65  	"suid":          {set: false, val: unix.MS_NOSUID},
    66  	"sync":          {set: true, val: unix.MS_SYNCHRONOUS},
    67  }
    68  
    69  // propOptionsMap is similar to optionsMap, but it lists propagation options
    70  // that cannot be used together with other flags.
    71  var propOptionsMap = map[string]mapping{
    72  	"private":     {set: true, val: unix.MS_PRIVATE},
    73  	"rprivate":    {set: true, val: unix.MS_PRIVATE | unix.MS_REC},
    74  	"slave":       {set: true, val: unix.MS_SLAVE},
    75  	"rslave":      {set: true, val: unix.MS_SLAVE | unix.MS_REC},
    76  	"unbindable":  {set: true, val: unix.MS_UNBINDABLE},
    77  	"runbindable": {set: true, val: unix.MS_UNBINDABLE | unix.MS_REC},
    78  }
    79  
    80  // invalidOptions list options not allowed.
    81  //   - shared: sandbox must be isolated from the host. Propagating mount changes
    82  //     from the sandbox to the host breaks the isolation.
    83  var invalidOptions = []string{"shared", "rshared"}
    84  
    85  // OptionsToFlags converts mount options to syscall flags.
    86  func OptionsToFlags(opts []string) uint32 {
    87  	return optionsToFlags(opts, optionsMap)
    88  }
    89  
    90  // PropOptionsToFlags converts propagation mount options to syscall flags.
    91  // Propagation options cannot be set other with other options and must be
    92  // handled separately.
    93  func PropOptionsToFlags(opts []string) uint32 {
    94  	return optionsToFlags(opts, propOptionsMap)
    95  }
    96  
    97  func optionsToFlags(opts []string, source map[string]mapping) uint32 {
    98  	var rv uint32
    99  	for _, opt := range opts {
   100  		if m, ok := source[opt]; ok {
   101  			if m.set {
   102  				rv |= m.val
   103  			} else {
   104  				rv ^= m.val
   105  			}
   106  		}
   107  	}
   108  	return rv
   109  }
   110  
   111  // IsReadonlyMount returns true if the mount options has read only option.
   112  func IsReadonlyMount(opts []string) bool {
   113  	for _, o := range opts {
   114  		if o == "ro" {
   115  			return true
   116  		}
   117  	}
   118  	return false
   119  }
   120  
   121  // validateMount validates that spec mounts are correct.
   122  func validateMount(mnt *specs.Mount) error {
   123  	if !path.IsAbs(mnt.Destination) {
   124  		return fmt.Errorf("Mount.Destination must be an absolute path: %v", mnt)
   125  	}
   126  	if mnt.Type == "bind" {
   127  		return ValidateMountOptions(mnt.Options)
   128  	}
   129  	return nil
   130  }
   131  
   132  func moptKey(opt string) string {
   133  	if len(opt) == 0 {
   134  		return opt
   135  	}
   136  	// Guaranteed to have at least one token, since opt is not empty.
   137  	return strings.SplitN(opt, "=", 2)[0]
   138  }
   139  
   140  // FilterMountOptions filters out all invalid mount options.
   141  func FilterMountOptions(opts []string) []string {
   142  	out := make([]string, 0, len(opts))
   143  	for _, o := range opts {
   144  		if err := validateMountOption(o); err == nil {
   145  			out = append(out, o)
   146  		} else {
   147  			log.Warningf("mount option skipped %q: %v", o, err)
   148  		}
   149  	}
   150  	return out
   151  }
   152  
   153  // ValidateMountOptions validates that mount options are correct.
   154  func ValidateMountOptions(opts []string) error {
   155  	for _, o := range opts {
   156  		if err := validateMountOption(o); err != nil {
   157  			return err
   158  		}
   159  	}
   160  	return nil
   161  }
   162  
   163  func validateMountOption(o string) error {
   164  	if ContainsStr(invalidOptions, o) {
   165  		return fmt.Errorf("mount option %q is not supported", o)
   166  	}
   167  	_, ok1 := optionsMap[o]
   168  	_, ok2 := propOptionsMap[o]
   169  	if !ok1 && !ok2 {
   170  		return fmt.Errorf("unknown mount option %q", o)
   171  	}
   172  	return validatePropagation(o)
   173  }
   174  
   175  // ValidateRootfsPropagation validates that rootfs propagation options are
   176  // correct.
   177  func validateRootfsPropagation(opt string) error {
   178  	flags := PropOptionsToFlags([]string{opt})
   179  	if flags&(unix.MS_SLAVE|unix.MS_PRIVATE) == 0 {
   180  		return fmt.Errorf("root mount propagation option must specify private or slave: %q", opt)
   181  	}
   182  	return validatePropagation(opt)
   183  }
   184  
   185  func validatePropagation(opt string) error {
   186  	flags := PropOptionsToFlags([]string{opt})
   187  	exclusive := flags & (unix.MS_SLAVE | unix.MS_PRIVATE | unix.MS_SHARED | unix.MS_UNBINDABLE)
   188  	if bits.OnesCount32(exclusive) > 1 {
   189  		return fmt.Errorf("mount propagation options are mutually exclusive: %q", opt)
   190  	}
   191  	return nil
   192  }