From 79cf0321dda10585d07ae6ea067678379e46cde2 Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Fri, 7 Mar 2025 17:09:40 +0100 Subject: [PATCH] util: do not use `mount-utils.IsLikelyNotMountPoint` anymore `IsLikelyNotMountPoint()` is an optimized version for `IsMountPoint()` which can not detect all type of mounts (anymore). The slower `IsMountPoint()` is more safe to use. This can cause a slight performance regression in the case there are many mountpoints on the system, but correctness is more important than speed while mounting. Fixes: #4633 Signed-off-by: Niels de Vos --- e2e/go.mod | 26 +-- e2e/go.sum | 4 +- .../k8s.io/mount-utils/mount_helper_unix.go | 8 +- e2e/vendor/k8s.io/mount-utils/mount_linux.go | 145 +++++++++++-- .../k8s.io/mount-utils/mount_windows.go | 6 +- e2e/vendor/modules.txt | 5 +- go.mod | 7 +- go.sum | 13 +- internal/cephfs/fuserecovery.go | 2 +- internal/cephfs/nodeserver.go | 10 +- internal/csi-common/utils.go | 3 +- internal/nfs/nodeserver/nodeserver.go | 6 +- internal/rbd/nodeserver.go | 14 +- internal/util/util.go | 10 - vendor/github.com/moby/sys/userns/LICENSE | 202 ++++++++++++++++++ vendor/github.com/moby/sys/userns/userns.go | 16 ++ .../moby/sys/userns/userns_linux.go | 53 +++++ .../moby/sys/userns/userns_linux_fuzzer.go | 8 + .../moby/sys/userns/userns_unsupported.go | 6 + .../k8s.io/mount-utils/mount_helper_unix.go | 8 +- vendor/k8s.io/mount-utils/mount_linux.go | 145 +++++++++++-- vendor/k8s.io/mount-utils/mount_windows.go | 6 +- vendor/modules.txt | 8 +- 23 files changed, 599 insertions(+), 112 deletions(-) create mode 100644 vendor/github.com/moby/sys/userns/LICENSE create mode 100644 vendor/github.com/moby/sys/userns/userns.go create mode 100644 vendor/github.com/moby/sys/userns/userns_linux.go create mode 100644 vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go create mode 100644 vendor/github.com/moby/sys/userns/userns_unsupported.go diff --git a/e2e/go.mod b/e2e/go.mod index 92a96c56b..001711228 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -16,6 +16,7 @@ require ( github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0 github.com/onsi/ginkgo/v2 v2.23.0 github.com/onsi/gomega v1.36.2 + // when updating k8s.io modules, update the 'replace' section below too k8s.io/api v0.32.2 k8s.io/apimachinery v0.32.2 k8s.io/client-go v12.0.0+incompatible @@ -24,6 +25,18 @@ require ( k8s.io/pod-security-admission v0.32.2 ) +replace ( + k8s.io/api => k8s.io/api v0.32.2 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.2 + k8s.io/client-go => k8s.io/client-go v0.32.2 + k8s.io/cri-client => k8s.io/cri-client v0.32.2 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.2 + k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.32.2 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2 + k8s.io/kubectl => k8s.io/kubectl v0.32.2 + k8s.io/kubelet => k8s.io/kubelet v0.32.2 +) + require ( cel.dev/expr v0.19.1 // indirect github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect @@ -155,18 +168,5 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace ( - k8s.io/api => k8s.io/api v0.32.2 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.2 - k8s.io/client-go => k8s.io/client-go v0.32.2 - k8s.io/cri-client => k8s.io/cri-client v0.32.2 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.32.2 - k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.32.2 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2 - k8s.io/kubectl => k8s.io/kubectl v0.32.2 - k8s.io/kubelet => k8s.io/kubelet v0.32.2 - k8s.io/mount-utils => k8s.io/mount-utils v0.29.3 -) - // version 3.9 is really old, don't use that! exclude github.com/openshift/api v3.9.0+incompatible diff --git a/e2e/go.sum b/e2e/go.sum index 533f033ea..e2a0c7e00 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -368,8 +368,8 @@ k8s.io/kubelet v0.32.2 h1:WFTSYdt3BB1aTApDuKNI16x/4MYqqX8WBBBBh3KupDg= k8s.io/kubelet v0.32.2/go.mod h1:cC1ms5RS+lu0ckVr6AviCQXHLSPKEBC3D5oaCBdTGkI= k8s.io/kubernetes v1.32.2 h1:mShetlA102UpjRVSGzB+5vjJwy8oPy8FMWrkTH5f37o= k8s.io/kubernetes v1.32.2/go.mod h1:tiIKO63GcdPRBHW2WiUFm3C0eoLczl3f7qi56Dm1W8I= -k8s.io/mount-utils v0.29.3 h1:iEcqPP7Vv8UClH8nnMfovtmy/04fIloRW9JuSXykoZ0= -k8s.io/mount-utils v0.29.3/go.mod h1:9IWJTMe8tG0MYMLEp60xK9GYVeCdA3g4LowmnVi+t9Y= +k8s.io/mount-utils v0.32.2 h1:aDwp+ucWiVnDr/LpRg88/dsXf/vm6gI1VZkYH3+3+Vw= +k8s.io/mount-utils v0.32.2/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0= k8s.io/pod-security-admission v0.32.2 h1:zDfAb/t0LbNU3z0ZMHtCb1zp8x05gWCGhmBYpUptm9A= k8s.io/pod-security-admission v0.32.2/go.mod h1:yxMPB3i1pGMLfxbe4BiWMuowMD7cdHR32y4nCj4wH+s= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= diff --git a/e2e/vendor/k8s.io/mount-utils/mount_helper_unix.go b/e2e/vendor/k8s.io/mount-utils/mount_helper_unix.go index 9193e7c8d..1c603dca7 100644 --- a/e2e/vendor/k8s.io/mount-utils/mount_helper_unix.go +++ b/e2e/vendor/k8s.io/mount-utils/mount_helper_unix.go @@ -61,7 +61,13 @@ func IsCorruptedMnt(err error) bool { underlyingError = err } - return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES || underlyingError == syscall.EHOSTDOWN || underlyingError == syscall.EWOULDBLOCK + return errors.Is(underlyingError, syscall.ENOTCONN) || + errors.Is(underlyingError, syscall.ESTALE) || + errors.Is(underlyingError, syscall.EIO) || + errors.Is(underlyingError, syscall.EACCES) || + errors.Is(underlyingError, syscall.EHOSTDOWN) || + errors.Is(underlyingError, syscall.EWOULDBLOCK) || + errors.Is(underlyingError, syscall.ENODEV) } // MountInfo represents a single line in /proc//mountinfo. diff --git a/e2e/vendor/k8s.io/mount-utils/mount_linux.go b/e2e/vendor/k8s.io/mount-utils/mount_linux.go index 7d1807230..439d50589 100644 --- a/e2e/vendor/k8s.io/mount-utils/mount_linux.go +++ b/e2e/vendor/k8s.io/mount-utils/mount_linux.go @@ -33,7 +33,9 @@ import ( "time" "github.com/moby/sys/mountinfo" + "golang.org/x/sys/unix" + inuserns "github.com/moby/sys/userns" "k8s.io/klog/v2" utilexec "k8s.io/utils/exec" ) @@ -55,6 +57,11 @@ const ( errNotMounted = "not mounted" ) +var ( + // Error statx support since Linux 4.11, https://man7.org/linux/man-pages/man2/statx.2.html + errStatxNotSupport = errors.New("the statx syscall is not supported. At least Linux kernel 4.11 is needed") +) + // Mounter provides the default implementation of mount.Interface // for the linux platform. This implementation assumes that the // kubelet is running in the host's root mount namespace. @@ -105,6 +112,59 @@ func (mounter *Mounter) hasSystemd() bool { return *mounter.withSystemd } +// Map unix.Statfs mount flags ro, nodev, noexec, nosuid, noatime, relatime, +// nodiratime to mount option flag strings. +func getUserNSBindMountOptions(path string, statfs func(path string, buf *unix.Statfs_t) (err error)) ([]string, error) { + var s unix.Statfs_t + var mountOpts []string + if err := statfs(path, &s); err != nil { + return nil, &os.PathError{Op: "statfs", Path: path, Err: err} + } + flagMapping := map[int]string{ + unix.MS_RDONLY: "ro", + unix.MS_NODEV: "nodev", + unix.MS_NOEXEC: "noexec", + unix.MS_NOSUID: "nosuid", + unix.MS_NOATIME: "noatime", + unix.MS_RELATIME: "relatime", + unix.MS_NODIRATIME: "nodiratime", + } + for k, v := range flagMapping { + if int(s.Flags)&k == k { + mountOpts = append(mountOpts, v) + } + } + return mountOpts, nil +} + +// Do a bind mount including the needed remount for applying the bind opts. +// If the remount fails and we are running in a user namespace +// figure out if the source filesystem has the ro, nodev, noexec, nosuid, +// noatime, relatime or nodiratime flag set and try another remount with the found flags. +func (mounter *Mounter) bindMountSensitive(mounterPath string, mountCmd string, source string, target string, fstype string, bindOpts []string, bindRemountOpts []string, bindRemountOptsSensitive []string, mountFlags []string, systemdMountRequired bool) error { + err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired) + if err != nil { + return err + } + err = mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired) + if inuserns.RunningInUserNS() { + if err == nil { + return nil + } + // Check if the source has ro, nodev, noexec, nosuid, noatime, relatime, + // nodiratime flag... + fixMountOpts, err := getUserNSBindMountOptions(source, unix.Statfs) + if err != nil { + return &os.PathError{Op: "statfs", Path: source, Err: err} + } + // ... and retry the mount with flags found above. + bindRemountOpts = append(bindRemountOpts, fixMountOpts...) + return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired) + } else { + return err + } +} + // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must // be an empty string in case it's not required, e.g. for remount, or for auto filesystem // type, where kernel handles fstype for you. The mount 'options' is a list of options, @@ -125,11 +185,7 @@ func (mounter *Mounter) MountSensitive(source string, target string, fstype stri mounterPath := "" bind, bindOpts, bindRemountOpts, bindRemountOptsSensitive := MakeBindOptsSensitive(options, sensitiveOptions) if bind { - err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, nil /* mountFlags */, mounter.trySystemd) - if err != nil { - return err - } - return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, nil /* mountFlags */, mounter.trySystemd) + return mounter.bindMountSensitive(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOpts, bindRemountOptsSensitive, nil /* mountFlags */, mounter.trySystemd) } // The list of filesystems that require containerized mounter on GCI image cluster fsTypesNeedMounter := map[string]struct{}{ @@ -154,11 +210,7 @@ func (mounter *Mounter) MountSensitiveWithoutSystemdWithMountFlags(source string mounterPath := "" bind, bindOpts, bindRemountOpts, bindRemountOptsSensitive := MakeBindOptsSensitive(options, sensitiveOptions) if bind { - err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, mountFlags, false) - if err != nil { - return err - } - return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, false) + return mounter.bindMountSensitive(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOpts, bindRemountOptsSensitive, mountFlags, false) } // The list of filesystems that require containerized mounter on GCI image cluster fsTypesNeedMounter := map[string]struct{}{ @@ -385,14 +437,20 @@ func (*Mounter) List() ([]MountPoint, error) { return ListProcMounts(procMountsPath) } -// IsLikelyNotMountPoint determines if a directory is not a mountpoint. -// It is fast but not necessarily ALWAYS correct. If the path is in fact -// a bind mount from one part of a mount to another it will not be detected. -// It also can not distinguish between mountpoints and symbolic links. -// mkdir /tmp/a /tmp/b; mount --bind /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b") -// will return true. When in fact /tmp/b is a mount point. If this situation -// is of interest to you, don't use this function... -func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { +func statx(file string) (unix.Statx_t, error) { + var stat unix.Statx_t + if err := unix.Statx(unix.AT_FDCWD, file, unix.AT_STATX_DONT_SYNC, 0, &stat); err != nil { + if err == unix.ENOSYS { + return stat, errStatxNotSupport + } + + return stat, err + } + + return stat, nil +} + +func (mounter *Mounter) isLikelyNotMountPointStat(file string) (bool, error) { stat, err := os.Stat(file) if err != nil { return true, err @@ -409,6 +467,51 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, nil } +func (mounter *Mounter) isLikelyNotMountPointStatx(file string) (bool, error) { + var stat, rootStat unix.Statx_t + var err error + + if stat, err = statx(file); err != nil { + return true, err + } + + if stat.Attributes_mask != 0 { + if stat.Attributes_mask&unix.STATX_ATTR_MOUNT_ROOT != 0 { + if stat.Attributes&unix.STATX_ATTR_MOUNT_ROOT != 0 { + // file is a mountpoint + return false, nil + } else { + // no need to check rootStat if unix.STATX_ATTR_MOUNT_ROOT supported + return true, nil + } + } + } + + root := filepath.Dir(strings.TrimSuffix(file, "/")) + if rootStat, err = statx(root); err != nil { + return true, err + } + + return (stat.Dev_major == rootStat.Dev_major && stat.Dev_minor == rootStat.Dev_minor), nil +} + +// IsLikelyNotMountPoint determines if a directory is not a mountpoint. +// It is fast but not necessarily ALWAYS correct. If the path is in fact +// a bind mount from one part of a mount to another it will not be detected. +// It also can not distinguish between mountpoints and symbolic links. +// mkdir /tmp/a /tmp/b; mount --bind /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b") +// will return true. When in fact /tmp/b is a mount point. If this situation +// is of interest to you, don't use this function... +func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { + notMountPoint, err := mounter.isLikelyNotMountPointStatx(file) + if errors.Is(err, errStatxNotSupport) { + // fall back to isLikelyNotMountPointStat + return mounter.isLikelyNotMountPointStat(file) + } + + return notMountPoint, err +} + // CanSafelySkipMountPointCheck relies on the detected behavior of umount when given a target that is not a mount point. func (mounter *Mounter) CanSafelySkipMountPointCheck() bool { return mounter.withSafeNotMountedBehavior @@ -520,7 +623,7 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target sensitiveOptionsLog := sanitizedOptionsForLogging(options, sensitiveOptions) detailedErr := fmt.Sprintf("format of disk %q failed: type:(%q) target:(%q) options:(%q) errcode:(%v) output:(%v) ", source, fstype, target, sensitiveOptionsLog, err, string(output)) klog.Error(detailedErr) - return NewMountError(FormatFailed, detailedErr) + return NewMountError(FormatFailed, "%s", detailedErr) } klog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target) @@ -528,7 +631,7 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target if fstype != existingFormat { // Verify that the disk is formatted with filesystem type we are expecting mountErrorValue = FilesystemMismatch - klog.Warningf("Configured to mount disk %s as %s but current format is %s, things might break", source, existingFormat, fstype) + klog.Warningf("Configured to mount disk %s as %s but current format is %s, things might break", source, fstype, existingFormat) } if !readOnly { @@ -543,7 +646,7 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target // Mount the disk klog.V(4).Infof("Attempting to mount disk %s in %s format at %s", source, fstype, target) if err := mounter.MountSensitive(source, target, fstype, options, sensitiveOptions); err != nil { - return NewMountError(mountErrorValue, err.Error()) + return NewMountError(mountErrorValue, "%s", err.Error()) } return nil diff --git a/e2e/vendor/k8s.io/mount-utils/mount_windows.go b/e2e/vendor/k8s.io/mount-utils/mount_windows.go index be714646e..9c8ad054f 100644 --- a/e2e/vendor/k8s.io/mount-utils/mount_windows.go +++ b/e2e/vendor/k8s.io/mount-utils/mount_windows.go @@ -164,7 +164,7 @@ func (mounter *Mounter) MountSensitive(source string, target string, fstype stri // return (output, error) func newSMBMapping(username, password, remotepath string) (string, error) { if username == "" || password == "" || remotepath == "" { - return "", fmt.Errorf("invalid parameter(username: %s, password: %s, remoteapth: %s)", username, sensitiveOptionsRemoved, remotepath) + return "", fmt.Errorf("invalid parameter(username: %s, password: %s, remotepath: %s)", username, sensitiveOptionsRemoved, remotepath) } // use PowerShell Environment Variables to store user input string to prevent command line injection @@ -193,8 +193,8 @@ func isSMBMappingExist(remotepath string) bool { // check whether remotepath is valid // return (true, nil) if remotepath is valid func isValidPath(remotepath string) (bool, error) { - cmd := exec.Command("powershell", "/c", `Test-Path $Env:remoteapth`) - cmd.Env = append(os.Environ(), fmt.Sprintf("remoteapth=%s", remotepath)) + cmd := exec.Command("powershell", "/c", `Test-Path $Env:remotepath`) + cmd.Env = append(os.Environ(), fmt.Sprintf("remotepath=%s", remotepath)) output, err := cmd.CombinedOutput() if err != nil { return false, fmt.Errorf("returned output: %s, error: %v", string(output), err) diff --git a/e2e/vendor/modules.txt b/e2e/vendor/modules.txt index 729c72a96..ffcc5ce34 100644 --- a/e2e/vendor/modules.txt +++ b/e2e/vendor/modules.txt @@ -1648,8 +1648,8 @@ k8s.io/kubernetes/test/utils/kubeconfig k8s.io/kubernetes/third_party/forked/golang/expansion k8s.io/kubernetes/third_party/forked/libcontainer/apparmor k8s.io/kubernetes/third_party/forked/libcontainer/utils -# k8s.io/mount-utils v0.32.2 => k8s.io/mount-utils v0.29.3 -## explicit; go 1.21 +# k8s.io/mount-utils v0.32.2 +## explicit; go 1.23.0 k8s.io/mount-utils # k8s.io/pod-security-admission v0.32.2 ## explicit; go 1.23.0 @@ -1708,4 +1708,3 @@ sigs.k8s.io/yaml/goyaml.v2 # k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.32.2 # k8s.io/kubectl => k8s.io/kubectl v0.32.2 # k8s.io/kubelet => k8s.io/kubelet v0.32.2 -# k8s.io/mount-utils => k8s.io/mount-utils v0.29.3 diff --git a/go.mod b/go.mod index 0a18db35b..30e55fb31 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( k8s.io/cloud-provider v0.32.2 k8s.io/klog/v2 v2.130.1 k8s.io/kubernetes v1.32.2 + k8s.io/mount-utils v0.32.2 k8s.io/utils v0.0.0-20241210054802-24370beab758 ) @@ -50,11 +51,6 @@ require ( replace k8s.io/client-go => k8s.io/client-go v0.32.2 -// TODO: replaced by v0.29.3 until https://github.com/ceph/ceph-csi/issues/4633 is fixed -require k8s.io/mount-utils v0.32.2 - -replace k8s.io/mount-utils => k8s.io/mount-utils v0.29.3 - exclude ( // missing tag, referred to by github.com/hashicorp/go-kms-wrapping@v0.5.1 github.com/hashicorp/vault/sdk v0.1.14-0.20191229212425-c478d00be0d6 @@ -126,6 +122,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/go.sum b/go.sum index 9d91eb5b1..eaab2b840 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,6 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc= github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -136,10 +134,6 @@ github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4r github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/ceph/go-ceph v0.32.0 h1:iXRUGdPmH7h9Vf/WA1Dg3Wo1tgL7gcUbylfpbxrlGLs= -github.com/ceph/go-ceph v0.32.0/go.mod h1:42eoJzyLS3VREzqrg2ot44NtuluQZi55hFRSoLF36GQ= -github.com/ceph/go-ceph v0.32.1-0.20250303071035-7740b94e7f49 h1:0q5Ye65vYS5szvVsH5VILBGTv80p6v+znzPHsjiheDg= -github.com/ceph/go-ceph v0.32.1-0.20250303071035-7740b94e7f49/go.mod h1:FtN9DMlp/aS7VuO0VXdpdFfaetHNwtvn5q8gIhW/hQ4= github.com/ceph/go-ceph v0.32.1-0.20250307053135-38b9676b1d4e h1:Ykum5wAFYzyUi+yW0qQFDsbP9pAuRKTOf+ttMoo1jZ4= github.com/ceph/go-ceph v0.32.1-0.20250307053135-38b9676b1d4e/go.mod h1:bIS41nqhGmD7gQVoSBu3IYCwCiVEMeIxFU5qaqmC4a8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -511,6 +505,8 @@ github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0Gq github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -731,6 +727,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1383,8 +1380,8 @@ k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8X k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= k8s.io/kubernetes v1.32.2 h1:mShetlA102UpjRVSGzB+5vjJwy8oPy8FMWrkTH5f37o= k8s.io/kubernetes v1.32.2/go.mod h1:tiIKO63GcdPRBHW2WiUFm3C0eoLczl3f7qi56Dm1W8I= -k8s.io/mount-utils v0.29.3 h1:iEcqPP7Vv8UClH8nnMfovtmy/04fIloRW9JuSXykoZ0= -k8s.io/mount-utils v0.29.3/go.mod h1:9IWJTMe8tG0MYMLEp60xK9GYVeCdA3g4LowmnVi+t9Y= +k8s.io/mount-utils v0.32.2 h1:aDwp+ucWiVnDr/LpRg88/dsXf/vm6gI1VZkYH3+3+Vw= +k8s.io/mount-utils v0.32.2/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/internal/cephfs/fuserecovery.go b/internal/cephfs/fuserecovery.go index 41f60aa2a..398a97cef 100644 --- a/internal/cephfs/fuserecovery.go +++ b/internal/cephfs/fuserecovery.go @@ -47,7 +47,7 @@ func (ms mountState) String() string { } func (ns *NodeServer) getMountState(path string) (mountState, error) { - isMnt, err := util.IsMountPoint(ns.Mounter, path) + isMnt, err := ns.Mounter.IsMountPoint(path) if err != nil { if util.IsCorruptedMountError(err) { return msCorrupted, nil diff --git a/internal/cephfs/nodeserver.go b/internal/cephfs/nodeserver.go index 2a525e26d..52d9b39fd 100644 --- a/internal/cephfs/nodeserver.go +++ b/internal/cephfs/nodeserver.go @@ -263,7 +263,7 @@ func (ns *NodeServer) NodeStageVolume( } } - isMnt, err := util.IsMountPoint(ns.Mounter, stagingTargetPath) + isMnt, err := ns.Mounter.IsMountPoint(stagingTargetPath) if err != nil { log.ErrorLog(ctx, "stat failed: %v", err) @@ -539,7 +539,7 @@ func (ns *NodeServer) NodePublishVolume( // Ensure staging target path is a mountpoint. - isMnt, err := util.IsMountPoint(ns.Mounter, stagingTargetPath) + isMnt, err := ns.Mounter.IsMountPoint(stagingTargetPath) if err != nil { log.ErrorLog(ctx, "stat failed: %v", err) @@ -552,7 +552,7 @@ func (ns *NodeServer) NodePublishVolume( // Check if the volume is already mounted - isMnt, err = util.IsMountPoint(ns.Mounter, targetPath) + isMnt, err = ns.Mounter.IsMountPoint(targetPath) if err != nil { log.ErrorLog(ctx, "stat failed: %v", err) @@ -615,7 +615,7 @@ func (ns *NodeServer) NodeUnpublishVolume( // stop the health-checker that may have been started in NodeGetVolumeStats() ns.healthChecker.StopChecker(volID, targetPath) - isMnt, err := util.IsMountPoint(ns.Mounter, targetPath) + isMnt, err := ns.Mounter.IsMountPoint(targetPath) if err != nil { log.ErrorLog(ctx, "stat failed: %v", err) @@ -687,7 +687,7 @@ func (ns *NodeServer) NodeUnstageVolume( return nil, status.Error(codes.Internal, err.Error()) } - isMnt, err := util.IsMountPoint(ns.Mounter, stagingTargetPath) + isMnt, err := ns.Mounter.IsMountPoint(stagingTargetPath) if err != nil { log.ErrorLog(ctx, "stat failed: %v", err) diff --git a/internal/csi-common/utils.go b/internal/csi-common/utils.go index b541e68f6..e9ac2ce69 100644 --- a/internal/csi-common/utils.go +++ b/internal/csi-common/utils.go @@ -25,7 +25,6 @@ import ( "sync/atomic" "time" - "github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/internal/util/log" "github.com/container-storage-interface/spec/lib/go/csi" @@ -351,7 +350,7 @@ func FilesystemNodeGetVolumeStats( targetPath string, includeInodes bool, ) (*csi.NodeGetVolumeStatsResponse, error) { - isMnt, err := util.IsMountPoint(mounter, targetPath) + isMnt, err := mounter.IsMountPoint(targetPath) if err != nil { if os.IsNotExist(err) { return nil, status.Errorf(codes.InvalidArgument, "targetpath %s does not exist", targetPath) diff --git a/internal/nfs/nodeserver/nodeserver.go b/internal/nfs/nodeserver/nodeserver.go index 19baaa80a..994f6e12a 100644 --- a/internal/nfs/nodeserver/nodeserver.go +++ b/internal/nfs/nodeserver/nodeserver.go @@ -200,19 +200,19 @@ func (ns *NodeServer) mountNFS( err error ) - notMnt, err := ns.Mounter.IsLikelyNotMountPoint(mountPoint) + isMnt, err := ns.Mounter.IsMountPoint(mountPoint) if err != nil { if os.IsNotExist(err) { err = os.MkdirAll(mountPoint, defaultMountPermission) if err != nil { return err } - notMnt = true + isMnt = false } else { return err } } - if !notMnt { + if isMnt { log.DebugLog(ctx, "nfs: volume is already mounted to %s", mountPoint) return nil diff --git a/internal/rbd/nodeserver.go b/internal/rbd/nodeserver.go index 1522559c6..3ad62a4eb 100644 --- a/internal/rbd/nodeserver.go +++ b/internal/rbd/nodeserver.go @@ -880,9 +880,9 @@ func (ns *NodeServer) mountVolume(ctx context.Context, stagingPath string, req * func (ns *NodeServer) createTargetMountPath(ctx context.Context, mountPath string, isBlock bool) (bool, error) { // Check if that mount path exists properly - notMnt, err := ns.Mounter.IsLikelyNotMountPoint(mountPath) + isMnt, err := ns.Mounter.IsMountPoint(mountPath) if err == nil { - return notMnt, nil + return !isMnt, nil } if !os.IsNotExist(err) { return false, status.Error(codes.Internal, err.Error()) @@ -893,22 +893,22 @@ func (ns *NodeServer) createTargetMountPath(ctx context.Context, mountPath strin if e != nil { log.DebugLog(ctx, "Failed to create mountPath:%s with error: %v", mountPath, err) - return notMnt, status.Error(codes.Internal, e.Error()) + return !isMnt, status.Error(codes.Internal, e.Error()) } if err = pathFile.Close(); err != nil { log.DebugLog(ctx, "Failed to close mountPath:%s with error: %v", mountPath, err) - return notMnt, status.Error(codes.Internal, err.Error()) + return !isMnt, status.Error(codes.Internal, err.Error()) } } else { // Create a mountpath directory if err = util.CreateMountPoint(mountPath); err != nil { - return notMnt, status.Error(codes.Internal, err.Error()) + return !isMnt, status.Error(codes.Internal, err.Error()) } } - notMnt = true + isMnt = false - return notMnt, err + return !isMnt, err } // NodeUnpublishVolume unmounts the volume from the target path. diff --git a/internal/util/util.go b/internal/util/util.go index 16f8974d2..7c79fd323 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -219,16 +219,6 @@ func checkDirExists(p string) bool { return true } -// IsMountPoint checks if the given path is mountpoint or not. -func IsMountPoint(mounter mount.Interface, p string) (bool, error) { - notMnt, err := mounter.IsLikelyNotMountPoint(p) - if err != nil { - return false, err - } - - return !notMnt, nil -} - // IsCorruptedMountError checks if the given error is a result of a corrupted // mountpoint. func IsCorruptedMountError(err error) bool { diff --git a/vendor/github.com/moby/sys/userns/LICENSE b/vendor/github.com/moby/sys/userns/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/moby/sys/userns/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/moby/sys/userns/userns.go b/vendor/github.com/moby/sys/userns/userns.go new file mode 100644 index 000000000..56b24c44a --- /dev/null +++ b/vendor/github.com/moby/sys/userns/userns.go @@ -0,0 +1,16 @@ +// Package userns provides utilities to detect whether we are currently running +// in a Linux user namespace. +// +// This code was migrated from [libcontainer/runc], which based its implementation +// on code from [lcx/incus]. +// +// [libcontainer/runc]: https://github.com/opencontainers/runc/blob/3778ae603c706494fd1e2c2faf83b406e38d687d/libcontainer/userns/userns_linux.go#L12-L49 +// [lcx/incus]: https://github.com/lxc/incus/blob/e45085dd42f826b3c8c3228e9733c0b6f998eafe/shared/util.go#L678-L700 +package userns + +// RunningInUserNS detects whether we are currently running in a Linux +// user namespace and memoizes the result. It returns false on non-Linux +// platforms. +func RunningInUserNS() bool { + return inUserNS() +} diff --git a/vendor/github.com/moby/sys/userns/userns_linux.go b/vendor/github.com/moby/sys/userns/userns_linux.go new file mode 100644 index 000000000..87c1c38ee --- /dev/null +++ b/vendor/github.com/moby/sys/userns/userns_linux.go @@ -0,0 +1,53 @@ +package userns + +import ( + "bufio" + "fmt" + "os" + "sync" +) + +var inUserNS = sync.OnceValue(runningInUserNS) + +// runningInUserNS detects whether we are currently running in a user namespace. +// +// This code was migrated from [libcontainer/runc] and based on an implementation +// from [lcx/incus]. +// +// [libcontainer/runc]: https://github.com/opencontainers/runc/blob/3778ae603c706494fd1e2c2faf83b406e38d687d/libcontainer/userns/userns_linux.go#L12-L49 +// [lcx/incus]: https://github.com/lxc/incus/blob/e45085dd42f826b3c8c3228e9733c0b6f998eafe/shared/util.go#L678-L700 +func runningInUserNS() bool { + file, err := os.Open("/proc/self/uid_map") + if err != nil { + // This kernel-provided file only exists if user namespaces are supported. + return false + } + defer file.Close() + + buf := bufio.NewReader(file) + l, _, err := buf.ReadLine() + if err != nil { + return false + } + + return uidMapInUserNS(string(l)) +} + +func uidMapInUserNS(uidMap string) bool { + if uidMap == "" { + // File exist but empty (the initial state when userns is created, + // see user_namespaces(7)). + return true + } + + var a, b, c int64 + if _, err := fmt.Sscanf(uidMap, "%d %d %d", &a, &b, &c); err != nil { + // Assume we are in a regular, non user namespace. + return false + } + + // As per user_namespaces(7), /proc/self/uid_map of + // the initial user namespace shows 0 0 4294967295. + initNS := a == 0 && b == 0 && c == 4294967295 + return !initNS +} diff --git a/vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go b/vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go new file mode 100644 index 000000000..26ba2e16e --- /dev/null +++ b/vendor/github.com/moby/sys/userns/userns_linux_fuzzer.go @@ -0,0 +1,8 @@ +//go:build linux && gofuzz + +package userns + +func FuzzUIDMap(uidmap []byte) int { + _ = uidMapInUserNS(string(uidmap)) + return 1 +} diff --git a/vendor/github.com/moby/sys/userns/userns_unsupported.go b/vendor/github.com/moby/sys/userns/userns_unsupported.go new file mode 100644 index 000000000..8ed83072c --- /dev/null +++ b/vendor/github.com/moby/sys/userns/userns_unsupported.go @@ -0,0 +1,6 @@ +//go:build !linux + +package userns + +// inUserNS is a stub for non-Linux systems. Always returns false. +func inUserNS() bool { return false } diff --git a/vendor/k8s.io/mount-utils/mount_helper_unix.go b/vendor/k8s.io/mount-utils/mount_helper_unix.go index 9193e7c8d..1c603dca7 100644 --- a/vendor/k8s.io/mount-utils/mount_helper_unix.go +++ b/vendor/k8s.io/mount-utils/mount_helper_unix.go @@ -61,7 +61,13 @@ func IsCorruptedMnt(err error) bool { underlyingError = err } - return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES || underlyingError == syscall.EHOSTDOWN || underlyingError == syscall.EWOULDBLOCK + return errors.Is(underlyingError, syscall.ENOTCONN) || + errors.Is(underlyingError, syscall.ESTALE) || + errors.Is(underlyingError, syscall.EIO) || + errors.Is(underlyingError, syscall.EACCES) || + errors.Is(underlyingError, syscall.EHOSTDOWN) || + errors.Is(underlyingError, syscall.EWOULDBLOCK) || + errors.Is(underlyingError, syscall.ENODEV) } // MountInfo represents a single line in /proc//mountinfo. diff --git a/vendor/k8s.io/mount-utils/mount_linux.go b/vendor/k8s.io/mount-utils/mount_linux.go index 7d1807230..439d50589 100644 --- a/vendor/k8s.io/mount-utils/mount_linux.go +++ b/vendor/k8s.io/mount-utils/mount_linux.go @@ -33,7 +33,9 @@ import ( "time" "github.com/moby/sys/mountinfo" + "golang.org/x/sys/unix" + inuserns "github.com/moby/sys/userns" "k8s.io/klog/v2" utilexec "k8s.io/utils/exec" ) @@ -55,6 +57,11 @@ const ( errNotMounted = "not mounted" ) +var ( + // Error statx support since Linux 4.11, https://man7.org/linux/man-pages/man2/statx.2.html + errStatxNotSupport = errors.New("the statx syscall is not supported. At least Linux kernel 4.11 is needed") +) + // Mounter provides the default implementation of mount.Interface // for the linux platform. This implementation assumes that the // kubelet is running in the host's root mount namespace. @@ -105,6 +112,59 @@ func (mounter *Mounter) hasSystemd() bool { return *mounter.withSystemd } +// Map unix.Statfs mount flags ro, nodev, noexec, nosuid, noatime, relatime, +// nodiratime to mount option flag strings. +func getUserNSBindMountOptions(path string, statfs func(path string, buf *unix.Statfs_t) (err error)) ([]string, error) { + var s unix.Statfs_t + var mountOpts []string + if err := statfs(path, &s); err != nil { + return nil, &os.PathError{Op: "statfs", Path: path, Err: err} + } + flagMapping := map[int]string{ + unix.MS_RDONLY: "ro", + unix.MS_NODEV: "nodev", + unix.MS_NOEXEC: "noexec", + unix.MS_NOSUID: "nosuid", + unix.MS_NOATIME: "noatime", + unix.MS_RELATIME: "relatime", + unix.MS_NODIRATIME: "nodiratime", + } + for k, v := range flagMapping { + if int(s.Flags)&k == k { + mountOpts = append(mountOpts, v) + } + } + return mountOpts, nil +} + +// Do a bind mount including the needed remount for applying the bind opts. +// If the remount fails and we are running in a user namespace +// figure out if the source filesystem has the ro, nodev, noexec, nosuid, +// noatime, relatime or nodiratime flag set and try another remount with the found flags. +func (mounter *Mounter) bindMountSensitive(mounterPath string, mountCmd string, source string, target string, fstype string, bindOpts []string, bindRemountOpts []string, bindRemountOptsSensitive []string, mountFlags []string, systemdMountRequired bool) error { + err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired) + if err != nil { + return err + } + err = mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired) + if inuserns.RunningInUserNS() { + if err == nil { + return nil + } + // Check if the source has ro, nodev, noexec, nosuid, noatime, relatime, + // nodiratime flag... + fixMountOpts, err := getUserNSBindMountOptions(source, unix.Statfs) + if err != nil { + return &os.PathError{Op: "statfs", Path: source, Err: err} + } + // ... and retry the mount with flags found above. + bindRemountOpts = append(bindRemountOpts, fixMountOpts...) + return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired) + } else { + return err + } +} + // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must // be an empty string in case it's not required, e.g. for remount, or for auto filesystem // type, where kernel handles fstype for you. The mount 'options' is a list of options, @@ -125,11 +185,7 @@ func (mounter *Mounter) MountSensitive(source string, target string, fstype stri mounterPath := "" bind, bindOpts, bindRemountOpts, bindRemountOptsSensitive := MakeBindOptsSensitive(options, sensitiveOptions) if bind { - err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, nil /* mountFlags */, mounter.trySystemd) - if err != nil { - return err - } - return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, nil /* mountFlags */, mounter.trySystemd) + return mounter.bindMountSensitive(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOpts, bindRemountOptsSensitive, nil /* mountFlags */, mounter.trySystemd) } // The list of filesystems that require containerized mounter on GCI image cluster fsTypesNeedMounter := map[string]struct{}{ @@ -154,11 +210,7 @@ func (mounter *Mounter) MountSensitiveWithoutSystemdWithMountFlags(source string mounterPath := "" bind, bindOpts, bindRemountOpts, bindRemountOptsSensitive := MakeBindOptsSensitive(options, sensitiveOptions) if bind { - err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, mountFlags, false) - if err != nil { - return err - } - return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, false) + return mounter.bindMountSensitive(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOpts, bindRemountOptsSensitive, mountFlags, false) } // The list of filesystems that require containerized mounter on GCI image cluster fsTypesNeedMounter := map[string]struct{}{ @@ -385,14 +437,20 @@ func (*Mounter) List() ([]MountPoint, error) { return ListProcMounts(procMountsPath) } -// IsLikelyNotMountPoint determines if a directory is not a mountpoint. -// It is fast but not necessarily ALWAYS correct. If the path is in fact -// a bind mount from one part of a mount to another it will not be detected. -// It also can not distinguish between mountpoints and symbolic links. -// mkdir /tmp/a /tmp/b; mount --bind /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b") -// will return true. When in fact /tmp/b is a mount point. If this situation -// is of interest to you, don't use this function... -func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { +func statx(file string) (unix.Statx_t, error) { + var stat unix.Statx_t + if err := unix.Statx(unix.AT_FDCWD, file, unix.AT_STATX_DONT_SYNC, 0, &stat); err != nil { + if err == unix.ENOSYS { + return stat, errStatxNotSupport + } + + return stat, err + } + + return stat, nil +} + +func (mounter *Mounter) isLikelyNotMountPointStat(file string) (bool, error) { stat, err := os.Stat(file) if err != nil { return true, err @@ -409,6 +467,51 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, nil } +func (mounter *Mounter) isLikelyNotMountPointStatx(file string) (bool, error) { + var stat, rootStat unix.Statx_t + var err error + + if stat, err = statx(file); err != nil { + return true, err + } + + if stat.Attributes_mask != 0 { + if stat.Attributes_mask&unix.STATX_ATTR_MOUNT_ROOT != 0 { + if stat.Attributes&unix.STATX_ATTR_MOUNT_ROOT != 0 { + // file is a mountpoint + return false, nil + } else { + // no need to check rootStat if unix.STATX_ATTR_MOUNT_ROOT supported + return true, nil + } + } + } + + root := filepath.Dir(strings.TrimSuffix(file, "/")) + if rootStat, err = statx(root); err != nil { + return true, err + } + + return (stat.Dev_major == rootStat.Dev_major && stat.Dev_minor == rootStat.Dev_minor), nil +} + +// IsLikelyNotMountPoint determines if a directory is not a mountpoint. +// It is fast but not necessarily ALWAYS correct. If the path is in fact +// a bind mount from one part of a mount to another it will not be detected. +// It also can not distinguish between mountpoints and symbolic links. +// mkdir /tmp/a /tmp/b; mount --bind /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b") +// will return true. When in fact /tmp/b is a mount point. If this situation +// is of interest to you, don't use this function... +func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { + notMountPoint, err := mounter.isLikelyNotMountPointStatx(file) + if errors.Is(err, errStatxNotSupport) { + // fall back to isLikelyNotMountPointStat + return mounter.isLikelyNotMountPointStat(file) + } + + return notMountPoint, err +} + // CanSafelySkipMountPointCheck relies on the detected behavior of umount when given a target that is not a mount point. func (mounter *Mounter) CanSafelySkipMountPointCheck() bool { return mounter.withSafeNotMountedBehavior @@ -520,7 +623,7 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target sensitiveOptionsLog := sanitizedOptionsForLogging(options, sensitiveOptions) detailedErr := fmt.Sprintf("format of disk %q failed: type:(%q) target:(%q) options:(%q) errcode:(%v) output:(%v) ", source, fstype, target, sensitiveOptionsLog, err, string(output)) klog.Error(detailedErr) - return NewMountError(FormatFailed, detailedErr) + return NewMountError(FormatFailed, "%s", detailedErr) } klog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target) @@ -528,7 +631,7 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target if fstype != existingFormat { // Verify that the disk is formatted with filesystem type we are expecting mountErrorValue = FilesystemMismatch - klog.Warningf("Configured to mount disk %s as %s but current format is %s, things might break", source, existingFormat, fstype) + klog.Warningf("Configured to mount disk %s as %s but current format is %s, things might break", source, fstype, existingFormat) } if !readOnly { @@ -543,7 +646,7 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target // Mount the disk klog.V(4).Infof("Attempting to mount disk %s in %s format at %s", source, fstype, target) if err := mounter.MountSensitive(source, target, fstype, options, sensitiveOptions); err != nil { - return NewMountError(mountErrorValue, err.Error()) + return NewMountError(mountErrorValue, "%s", err.Error()) } return nil diff --git a/vendor/k8s.io/mount-utils/mount_windows.go b/vendor/k8s.io/mount-utils/mount_windows.go index be714646e..9c8ad054f 100644 --- a/vendor/k8s.io/mount-utils/mount_windows.go +++ b/vendor/k8s.io/mount-utils/mount_windows.go @@ -164,7 +164,7 @@ func (mounter *Mounter) MountSensitive(source string, target string, fstype stri // return (output, error) func newSMBMapping(username, password, remotepath string) (string, error) { if username == "" || password == "" || remotepath == "" { - return "", fmt.Errorf("invalid parameter(username: %s, password: %s, remoteapth: %s)", username, sensitiveOptionsRemoved, remotepath) + return "", fmt.Errorf("invalid parameter(username: %s, password: %s, remotepath: %s)", username, sensitiveOptionsRemoved, remotepath) } // use PowerShell Environment Variables to store user input string to prevent command line injection @@ -193,8 +193,8 @@ func isSMBMappingExist(remotepath string) bool { // check whether remotepath is valid // return (true, nil) if remotepath is valid func isValidPath(remotepath string) (bool, error) { - cmd := exec.Command("powershell", "/c", `Test-Path $Env:remoteapth`) - cmd.Env = append(os.Environ(), fmt.Sprintf("remoteapth=%s", remotepath)) + cmd := exec.Command("powershell", "/c", `Test-Path $Env:remotepath`) + cmd.Env = append(os.Environ(), fmt.Sprintf("remotepath=%s", remotepath)) output, err := cmd.CombinedOutput() if err != nil { return false, fmt.Errorf("returned output: %s, error: %v", string(output), err) diff --git a/vendor/modules.txt b/vendor/modules.txt index b63461aa2..5b11d5acf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -432,6 +432,9 @@ github.com/mitchellh/mapstructure # github.com/moby/sys/mountinfo v0.7.2 ## explicit; go 1.17 github.com/moby/sys/mountinfo +# github.com/moby/sys/userns v0.1.0 +## explicit; go 1.21 +github.com/moby/sys/userns # github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd ## explicit github.com/modern-go/concurrent @@ -1167,8 +1170,8 @@ k8s.io/kubernetes/pkg/volume/util/hostutil k8s.io/kubernetes/pkg/volume/util/recyclerclient k8s.io/kubernetes/pkg/volume/util/subpath k8s.io/kubernetes/pkg/volume/util/types -# k8s.io/mount-utils v0.32.2 => k8s.io/mount-utils v0.29.3 -## explicit; go 1.21 +# k8s.io/mount-utils v0.32.2 +## explicit; go 1.23.0 k8s.io/mount-utils # k8s.io/utils v0.0.0-20241210054802-24370beab758 ## explicit; go 1.18 @@ -1244,4 +1247,3 @@ sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 # github.com/ceph/ceph-csi/api => ./api # k8s.io/client-go => k8s.io/client-go v0.32.2 -# k8s.io/mount-utils => k8s.io/mount-utils v0.29.3