Files
Ayush RanjanandgVisor bot 797b909368 Allow gVisor-specific mount options for bind mounts to be passed via OCI spec.
We have a few gVisor-specific mount options that can be set "externally" (see
pkg/fsimpl/gofer.SupportedMountOptions). Before this change, the meaning of
"externally" was that it could be set by runsc by appending the option to
spec.Mount.Options. This change expands the meaning of "externally" to the user
defined OCI spec.

It does so by making specutils.validateMountOption() allow "unknown" options.
(By unknown here I mean options not present in optionsMap and propOptionsMap.)
This only impacts gofer mounts. There are 2 places in which the mount options
are consumed, both of which don't break due to bogus options:
1. Some of the mount options are used to create the host bind mount in gofer's
   mount namespace. See runsc/cmd/gofer.go:setupMounts(); it only extracts
   options present in optionsMap and propOptionsMap. It will disregard any
   other user-defined options.
2. The mount options are passed to the fsimpl/gofer sentry-internal mount. See
   runsc/boot/vfs.go:getMountNameAndOptions(). It consumes what it understands
   and ignores the rest (logging a warning that it is doing so).

Fixes #10316

PiperOrigin-RevId: 628341966
2024-04-26 02:38:57 -07:00

182 lines
6.0 KiB
Go

// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package specutils
import (
"fmt"
"math/bits"
"path"
"slices"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
)
type mapping struct {
set bool
val uint32
}
// optionsMap maps mount propagation-related OCI filesystem options to mount(2)
// syscall flags.
var optionsMap = map[string]mapping{
"acl": {set: true, val: unix.MS_POSIXACL},
"async": {set: false, val: unix.MS_SYNCHRONOUS},
"atime": {set: false, val: unix.MS_NOATIME},
"bind": {set: true, val: unix.MS_BIND},
"defaults": {set: true, val: 0},
"dev": {set: false, val: unix.MS_NODEV},
"diratime": {set: false, val: unix.MS_NODIRATIME},
"dirsync": {set: true, val: unix.MS_DIRSYNC},
"exec": {set: false, val: unix.MS_NOEXEC},
"noexec": {set: true, val: unix.MS_NOEXEC},
"iversion": {set: true, val: unix.MS_I_VERSION},
"loud": {set: false, val: unix.MS_SILENT},
"mand": {set: true, val: unix.MS_MANDLOCK},
"noacl": {set: false, val: unix.MS_POSIXACL},
"noatime": {set: true, val: unix.MS_NOATIME},
"nodev": {set: true, val: unix.MS_NODEV},
"nodiratime": {set: true, val: unix.MS_NODIRATIME},
"noiversion": {set: false, val: unix.MS_I_VERSION},
"nomand": {set: false, val: unix.MS_MANDLOCK},
"norelatime": {set: false, val: unix.MS_RELATIME},
"nostrictatime": {set: false, val: unix.MS_STRICTATIME},
"nosuid": {set: true, val: unix.MS_NOSUID},
"rbind": {set: true, val: unix.MS_BIND | unix.MS_REC},
"relatime": {set: true, val: unix.MS_RELATIME},
"remount": {set: true, val: unix.MS_REMOUNT},
"ro": {set: true, val: unix.MS_RDONLY},
"rw": {set: false, val: unix.MS_RDONLY},
"silent": {set: true, val: unix.MS_SILENT},
"strictatime": {set: true, val: unix.MS_STRICTATIME},
"suid": {set: false, val: unix.MS_NOSUID},
"sync": {set: true, val: unix.MS_SYNCHRONOUS},
}
// propOptionsMap is similar to optionsMap, but it lists propagation options
// that cannot be used together with other flags.
var propOptionsMap = map[string]mapping{
"private": {set: true, val: unix.MS_PRIVATE},
"rprivate": {set: true, val: unix.MS_PRIVATE | unix.MS_REC},
"slave": {set: true, val: unix.MS_SLAVE},
"rslave": {set: true, val: unix.MS_SLAVE | unix.MS_REC},
"unbindable": {set: true, val: unix.MS_UNBINDABLE},
"runbindable": {set: true, val: unix.MS_UNBINDABLE | unix.MS_REC},
}
// invalidOptions list options not allowed.
// - shared: sandbox must be isolated from the host. Propagating mount changes
// from the sandbox to the host breaks the isolation. The sandbox's mount
// table is maintained in sentry memory. Mount operations from the application
// are not propagated to the host.
var invalidOptions = []string{"shared", "rshared"}
// OptionsToFlags converts mount options to syscall flags.
func OptionsToFlags(opts []string) uint32 {
return optionsToFlags(opts, optionsMap)
}
// PropOptionsToFlags converts propagation mount options to syscall flags.
// Propagation options cannot be set other with other options and must be
// handled separately.
func PropOptionsToFlags(opts []string) uint32 {
return optionsToFlags(opts, propOptionsMap)
}
func optionsToFlags(opts []string, source map[string]mapping) uint32 {
var rv uint32
for _, opt := range opts {
if m, ok := source[opt]; ok {
if m.set {
rv |= m.val
} else {
rv ^= m.val
}
}
}
return rv
}
// IsReadonlyMount returns true if the mount options has read only option.
func IsReadonlyMount(opts []string) bool {
for _, o := range opts {
if o == "ro" {
return true
}
}
return false
}
// validateMount validates that spec mounts are correct.
func validateMount(mnt *specs.Mount) error {
if !path.IsAbs(mnt.Destination) {
return fmt.Errorf("Mount.Destination must be an absolute path: %v", mnt)
}
if IsGoferMount(*mnt) {
return ValidateMountOptions(mnt.Options)
}
return nil
}
// FilterMountOptions filters out all invalid mount options.
func FilterMountOptions(opts []string) []string {
out := make([]string, 0, len(opts))
for _, o := range opts {
if err := validateMountOption(o); err == nil {
out = append(out, o)
} else {
log.Warningf("mount option skipped %q: %v", o, err)
}
}
return out
}
// ValidateMountOptions validates that mount options are correct.
func ValidateMountOptions(opts []string) error {
for _, o := range opts {
if err := validateMountOption(o); err != nil {
return err
}
}
return nil
}
func validateMountOption(o string) error {
if slices.Contains(invalidOptions, o) {
return fmt.Errorf("mount option %q is not supported", o)
}
return validatePropagation(o)
}
// ValidateRootfsPropagation validates that rootfs propagation options are
// correct.
func validateRootfsPropagation(opt string) error {
flags := PropOptionsToFlags([]string{opt})
if flags&(unix.MS_SLAVE|unix.MS_PRIVATE) == 0 {
return fmt.Errorf("root mount propagation option must specify private or slave: %q", opt)
}
return validatePropagation(opt)
}
func validatePropagation(opt string) error {
flags := PropOptionsToFlags([]string{opt})
exclusive := flags & (unix.MS_SLAVE | unix.MS_PRIVATE | unix.MS_SHARED | unix.MS_UNBINDABLE)
if bits.OnesCount32(exclusive) > 1 {
return fmt.Errorf("mount propagation options are mutually exclusive: %q", opt)
}
return nil
}