From 0cebab6e9c09b9ab5f1fe84e60607caae99838ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Thu, 22 Jan 2026 13:29:33 +0100 Subject: [PATCH 1/9] Vendor library-go --- go.mod | 40 +- go.sum | 74 +-- vendor/cyphar.com/go-pathrs/.golangci.yml | 43 ++ vendor/cyphar.com/go-pathrs/COPYING | 373 ++++++++++++ vendor/cyphar.com/go-pathrs/doc.go | 14 + vendor/cyphar.com/go-pathrs/handle_linux.go | 114 ++++ .../go-pathrs/internal/fdutils/fd_linux.go | 75 +++ .../internal/libpathrs/error_unix.go | 40 ++ .../internal/libpathrs/libpathrs_linux.go | 337 +++++++++++ .../go-pathrs/procfs/procfs_linux.go | 246 ++++++++ vendor/cyphar.com/go-pathrs/root_linux.go | 367 ++++++++++++ vendor/cyphar.com/go-pathrs/utils_linux.go | 56 ++ .../cyphar/filepath-securejoin/.golangci.yml | 60 ++ .../cyphar/filepath-securejoin/CHANGELOG.md | 209 ++++++- .../cyphar/filepath-securejoin/COPYING.md | 447 ++++++++++++++ .../{LICENSE => LICENSE.BSD} | 0 .../filepath-securejoin/LICENSE.MPL-2.0 | 373 ++++++++++++ .../cyphar/filepath-securejoin/README.md | 21 +- .../cyphar/filepath-securejoin/VERSION | 2 +- .../cyphar/filepath-securejoin/codecov.yml | 29 + .../cyphar/filepath-securejoin/doc.go | 34 +- .../gocompat_generics_go121.go | 32 -- .../gocompat_generics_unsupported.go | 124 ---- .../internal/consts/consts.go | 15 + .../cyphar/filepath-securejoin/join.go | 23 +- .../cyphar/filepath-securejoin/open_linux.go | 103 ---- .../filepath-securejoin/openat2_linux.go | 127 ---- .../filepath-securejoin/openat_linux.go | 59 -- .../filepath-securejoin/pathrs-lite/README.md | 35 ++ .../filepath-securejoin/pathrs-lite/doc.go | 16 + .../pathrs-lite/internal/assert/assert.go | 30 + .../pathrs-lite/internal/errors_linux.go | 41 ++ .../pathrs-lite/internal/fd/at_linux.go | 148 +++++ .../pathrs-lite/internal/fd/fd.go | 55 ++ .../pathrs-lite/internal/fd/fd_linux.go | 78 +++ .../pathrs-lite/internal/fd/mount_linux.go | 54 ++ .../pathrs-lite/internal/fd/openat2_linux.go | 62 ++ .../pathrs-lite/internal/gocompat/README.md | 10 + .../pathrs-lite/internal/gocompat/doc.go | 13 + .../gocompat}/gocompat_errors_go120.go | 7 +- .../gocompat}/gocompat_errors_unsupported.go | 8 +- .../gocompat/gocompat_generics_go121.go | 53 ++ .../gocompat/gocompat_generics_unsupported.go | 187 ++++++ .../pathrs-lite/internal/gopathrs/doc.go | 16 + .../internal/gopathrs}/lookup_linux.go | 63 +- .../internal/gopathrs}/mkdir_linux.go | 90 ++- .../internal/gopathrs/open_linux.go | 26 + .../internal/gopathrs/openat2_linux.go | 101 ++++ .../internal/kernelversion/kernel_linux.go | 123 ++++ .../pathrs-lite/internal/linux/doc.go | 12 + .../pathrs-lite/internal/linux/mount_linux.go | 47 ++ .../internal/linux/openat2_linux.go | 31 + .../internal/procfs/procfs_linux.go | 544 ++++++++++++++++++ .../internal/procfs/procfs_lookup_linux.go | 222 +++++++ .../filepath-securejoin/pathrs-lite/mkdir.go | 55 ++ .../pathrs-lite/mkdir_libpathrs.go | 52 ++ .../pathrs-lite/mkdir_purego.go | 42 ++ .../filepath-securejoin/pathrs-lite/open.go | 45 ++ .../pathrs-lite/open_libpathrs.go | 57 ++ .../pathrs-lite/open_purego.go | 42 ++ .../pathrs-lite/procfs/procfs_libpathrs.go | 161 ++++++ .../pathrs-lite/procfs/procfs_purego.go | 157 +++++ .../filepath-securejoin/procfs_linux.go | 452 --------------- .../cyphar/filepath-securejoin/vfs.go | 2 + .../selinux/go-selinux/label/label.go | 67 --- .../selinux/go-selinux/label/label_linux.go | 17 +- .../selinux/go-selinux/label/label_stub.go | 6 - .../selinux/go-selinux/selinux.go | 26 +- .../selinux/go-selinux/selinux_linux.go | 307 ++++++---- .../selinux/go-selinux/selinux_stub.go | 12 +- .../pkg/controllerruntime/tls/controller.go | 129 +++++ .../pkg/controllerruntime/tls/tls.go | 156 +++++ .../openshift/library-go/pkg/crypto/crypto.go | 50 +- .../pkg/operator/certrotation/signer.go | 2 +- .../x/crypto/chacha20/chacha_arm64.s | 2 +- vendor/golang.org/x/crypto/ssh/keys.go | 1 + vendor/golang.org/x/crypto/ssh/messages.go | 2 +- vendor/golang.org/x/crypto/ssh/ssh_gss.go | 8 +- vendor/golang.org/x/crypto/ssh/streamlocal.go | 4 +- vendor/golang.org/x/crypto/ssh/tcpip.go | 124 ++-- vendor/golang.org/x/net/context/context.go | 37 +- vendor/golang.org/x/net/http2/frame.go | 76 ++- vendor/golang.org/x/net/http2/transport.go | 96 +++- vendor/golang.org/x/net/http2/writesched.go | 65 ++- .../net/http2/writesched_priority_rfc7540.go | 5 +- ...9128.go => writesched_priority_rfc9218.go} | 2 +- vendor/golang.org/x/sync/errgroup/errgroup.go | 2 +- vendor/golang.org/x/sys/cpu/cpu.go | 3 + vendor/golang.org/x/sys/cpu/cpu_arm64.go | 20 +- vendor/golang.org/x/sys/cpu/cpu_arm64.s | 19 +- vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go | 1 + .../golang.org/x/sys/cpu/cpu_gccgo_arm64.go | 1 + .../golang.org/x/sys/cpu/cpu_netbsd_arm64.go | 2 +- .../golang.org/x/sys/cpu/cpu_openbsd_arm64.go | 2 +- vendor/golang.org/x/sys/unix/mkerrors.sh | 2 + vendor/golang.org/x/sys/unix/syscall_linux.go | 6 + vendor/golang.org/x/sys/unix/zerrors_linux.go | 359 ++++++++++++ .../golang.org/x/sys/unix/zsyscall_linux.go | 10 + vendor/golang.org/x/sys/unix/ztypes_linux.go | 31 + .../x/sys/windows/syscall_windows.go | 15 + .../golang.org/x/sys/windows/types_windows.go | 76 +++ .../x/sys/windows/zsyscall_windows.go | 37 ++ vendor/golang.org/x/term/terminal.go | 6 +- .../go/analysis/passes/asmdecl/asmdecl.go | 2 +- .../tools/go/analysis/passes/assign/assign.go | 6 +- .../tools/go/analysis/passes/atomic/atomic.go | 9 +- .../passes/atomicalign/atomicalign.go | 6 +- .../x/tools/go/analysis/passes/bools/bools.go | 8 +- .../go/analysis/passes/buildtag/buildtag.go | 57 +- .../go/analysis/passes/cgocall/cgocall.go | 4 +- .../go/analysis/passes/copylock/copylock.go | 20 +- .../passes/deepequalerrors/deepequalerrors.go | 6 +- .../tools/go/analysis/passes/defers/defers.go | 6 +- .../go/analysis/passes/errorsas/errorsas.go | 63 +- .../passes/httpresponse/httpresponse.go | 9 +- .../passes/loopclosure/loopclosure.go | 3 +- .../analysis/passes/lostcancel/lostcancel.go | 4 +- .../go/analysis/passes/nilness/nilness.go | 2 +- .../go/analysis/passes/pkgfact/pkgfact.go | 2 +- .../x/tools/go/analysis/passes/printf/doc.go | 10 + .../tools/go/analysis/passes/printf/printf.go | 406 ++++++++----- .../reflectvaluecompare.go | 6 +- .../x/tools/go/analysis/passes/shift/shift.go | 4 +- .../passes/sigchanyzer/sigchanyzer.go | 4 +- .../x/tools/go/analysis/passes/slog/slog.go | 8 +- .../go/analysis/passes/sortslice/analyzer.go | 6 +- .../analysis/passes/stdversion/stdversion.go | 6 - .../analysis/passes/stringintconv/string.go | 4 +- .../go/analysis/passes/structtag/structtag.go | 20 +- .../testinggoroutine/testinggoroutine.go | 3 +- .../x/tools/go/analysis/passes/tests/tests.go | 3 +- .../analysis/passes/timeformat/timeformat.go | 6 +- .../go/analysis/passes/unsafeptr/unsafeptr.go | 6 +- .../passes/unusedresult/unusedresult.go | 2 +- .../go/analysis/passes/waitgroup/waitgroup.go | 6 +- .../x/tools/go/ast/astutil/imports.go | 67 +-- .../x/tools/go/buildutil/allpackages.go | 2 +- .../golang.org/x/tools/go/buildutil/tags.go | 2 +- vendor/golang.org/x/tools/go/cfg/cfg.go | 1 - .../golang.org/x/tools/go/packages/golist.go | 6 - vendor/golang.org/x/tools/go/ssa/builder.go | 8 +- vendor/golang.org/x/tools/go/ssa/subst.go | 73 --- vendor/golang.org/x/tools/imports/forward.go | 6 - .../internal/analysisinternal/analysis.go | 414 +------------ .../internal/analysisinternal/extractdoc.go | 2 +- .../analysisinternal/typeindex/typeindex.go | 33 ++ .../x/tools/internal/astutil/comment.go | 24 +- .../x/tools/internal/astutil/equal.go | 8 + .../x/tools/internal/astutil/util.go | 50 ++ .../x/tools/internal/event/core/event.go | 5 - .../x/tools/internal/gcimporter/iexport.go | 1 - .../x/tools/internal/imports/fix.go | 6 +- .../x/tools/internal/modindex/symbols.go | 3 +- .../x/tools/internal/refactor/delete.go | 433 ++++++++++++++ .../x/tools/internal/refactor/imports.go | 127 ++++ .../x/tools/internal/refactor/refactor.go | 29 + .../x/tools/internal/typesinternal/fx.go | 49 ++ .../x/tools/internal/typesinternal/isnamed.go | 71 +++ .../tools/internal/typesinternal/qualifier.go | 8 + .../typesinternal/typeindex/typeindex.go | 261 +++++++++ .../x/tools/internal/typesinternal/types.go | 48 +- .../tools/internal/typesinternal/zerovalue.go | 17 +- .../client-go/tools/cache/controller.go | 23 +- .../client-go/tools/cache/delta_fifo.go | 3 +- .../k8s.io/client-go/tools/cache/reflector.go | 8 +- .../reflector_data_consistency_detector.go | 4 +- .../client-go/tools/cache/shared_informer.go | 11 +- .../client-go/tools/cache/the_real_fifo.go | 3 +- .../leaderelection/resourcelock/leaselock.go | 3 + vendor/k8s.io/client-go/util/cert/cert.go | 4 +- .../data_consistency_detector.go | 23 +- vendor/modules.txt | 60 +- .../controller-runtime/pkg/cache/cache.go | 84 ++- .../pkg/client/namespaced_client.go | 7 +- .../controller/priorityqueue/priorityqueue.go | 12 +- .../controller-runtime/pkg/envtest/server.go | 63 +- 176 files changed, 8753 insertions(+), 2376 deletions(-) create mode 100644 vendor/cyphar.com/go-pathrs/.golangci.yml create mode 100644 vendor/cyphar.com/go-pathrs/COPYING create mode 100644 vendor/cyphar.com/go-pathrs/doc.go create mode 100644 vendor/cyphar.com/go-pathrs/handle_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go create mode 100644 vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/root_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/utils_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/.golangci.yml create mode 100644 vendor/github.com/cyphar/filepath-securejoin/COPYING.md rename vendor/github.com/cyphar/filepath-securejoin/{LICENSE => LICENSE.BSD} (100%) create mode 100644 vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/codecov.yml delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/open_linux.go delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/openat_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go rename vendor/github.com/cyphar/filepath-securejoin/{ => pathrs-lite/internal/gocompat}/gocompat_errors_go120.go (69%) rename vendor/github.com/cyphar/filepath-securejoin/{ => pathrs-lite/internal/gocompat}/gocompat_errors_unsupported.go (80%) create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go rename vendor/github.com/cyphar/filepath-securejoin/{ => pathrs-lite/internal/gopathrs}/lookup_linux.go (85%) rename vendor/github.com/cyphar/filepath-securejoin/{ => pathrs-lite/internal/gopathrs}/mkdir_linux.go (74%) create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go create mode 100644 vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/controller.go create mode 100644 vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/tls.go rename vendor/golang.org/x/net/http2/{writesched_priority_rfc9128.go => writesched_priority_rfc9218.go} (99%) create mode 100644 vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go create mode 100644 vendor/golang.org/x/tools/internal/refactor/delete.go create mode 100644 vendor/golang.org/x/tools/internal/refactor/imports.go create mode 100644 vendor/golang.org/x/tools/internal/refactor/refactor.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/fx.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/isnamed.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/typeindex/typeindex.go diff --git a/go.mod b/go.mod index bec2870d8..4e0d0fdb4 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,9 @@ replace ( k8s.io/kubernetes => github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd ) +// TODO: remove once https://github.com/openshift/library-go/pull/2082 is merged +replace github.com/openshift/library-go => github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38 + require ( github.com/blang/semver v3.5.1+incompatible github.com/go-logr/logr v1.4.3 @@ -29,15 +32,15 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/vmware/govmomi v0.52.0 - golang.org/x/net v0.46.0 + golang.org/x/net v0.47.0 golang.org/x/time v0.14.0 gopkg.in/gcfg.v1 v1.2.3 // indirect - k8s.io/api v0.34.1 - k8s.io/apimachinery v0.34.1 - k8s.io/apiserver v0.34.1 - k8s.io/client-go v0.34.1 + k8s.io/api v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 k8s.io/cloud-provider-vsphere v1.32.2 - k8s.io/component-base v0.34.1 + k8s.io/component-base v0.34.3 k8s.io/cri-client v0.34.1 // indirect k8s.io/csi-translation-lib v0.34.1 // indirect k8s.io/dynamic-resource-allocation v0.34.1 // indirect @@ -49,7 +52,7 @@ require ( k8s.io/sample-apiserver v0.34.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/cluster-api v1.11.3 - sigs.k8s.io/controller-runtime v0.22.3 + sigs.k8s.io/controller-runtime v0.22.5 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240923090159-236e448db12c sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 sigs.k8s.io/yaml v1.6.0 @@ -61,6 +64,7 @@ require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect cel.dev/expr v0.24.0 // indirect + cyphar.com/go-pathrs v0.2.1 // indirect github.com/4meepo/tagalign v1.4.2 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect @@ -114,7 +118,7 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect @@ -242,7 +246,7 @@ require ( github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runc v1.2.5 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect - github.com/opencontainers/selinux v1.11.1 // indirect + github.com/opencontainers/selinux v1.13.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -321,16 +325,16 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.43.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/mod v0.28.0 // indirect + golang.org/x/mod v0.29.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/term v0.36.0 // indirect - golang.org/x/text v0.30.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect golang.org/x/tools/go/expect v0.1.1-deprecated // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect @@ -346,14 +350,14 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.6.1 // indirect - k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect k8s.io/cli-runtime v0.34.1 // indirect k8s.io/cloud-provider v0.32.0 // indirect k8s.io/cluster-bootstrap v0.33.3 // indirect k8s.io/component-helpers v0.34.1 // indirect k8s.io/controller-manager v0.32.1 // indirect k8s.io/cri-api v0.34.1 // indirect - k8s.io/kms v0.34.1 // indirect + k8s.io/kms v0.34.3 // indirect k8s.io/kube-aggregator v0.34.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/kubelet v0.34.1 // indirect diff --git a/go.sum b/go.sum index daef2f2bf..1c72bf63a 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= +cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= @@ -121,10 +123,12 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= +github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38 h1:G0e8pIhZwxvoJaDeqE3gFzEDyIT4ocUklMEorG9D2Gg= +github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38/go.mod h1:hS7ztbAGo/uXecJwEgofGerQDVqSYnJNrN93+l+oWr8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -445,8 +449,8 @@ github.com/opencontainers/runc v1.2.5 h1:8KAkq3Wrem8bApgOHyhRI/8IeLXIfmZ6Qaw6DNS github.com/opencontainers/runc v1.2.5/go.mod h1:dOQeFo29xZKBNeRBI0B19mJtfHv68YgCTh1X+YphA+4= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= -github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= +github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7 h1:Z1swlS6b3Adm6RPhjqefs3DWnNFLDxRX+WC8GMXhja4= github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M= github.com/openshift/api v0.0.0-20260114133223-6ab113cb7368 h1:kSr3DOlq0NCrHd65HB2o/pBsks7AfRm+fkpf9RLUPoc= @@ -463,8 +467,6 @@ github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-202510151719 github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292/go.mod h1:TRSSqgXggJaDK5vtVtlQ9wEYOk32Pl+9tf0ROf3ljiM= github.com/openshift/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20251015171918-61114aa5a292 h1:ITqtj/xBNlSPChpvV4a+VQ93Sk5RFkwRx+CnIWBrZ98= github.com/openshift/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20251015171918-61114aa5a292/go.mod h1:+bTwPbT5dZB8j6eKQHBZRMfNmY6MEryba1wQljr9VWw= -github.com/openshift/library-go v0.0.0-20251107090138-0de9712313a5 h1:Gq8jCFgSrilZ2ZHjQleFZWlblikc1aaRZ0hqs+yvrP4= -github.com/openshift/library-go v0.0.0-20251107090138-0de9712313a5/go.mod h1:OlFFws1AO51uzfc48MsStGE4SFMWlMZD0+f5a/zCtKI= github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 h1:PMTgifBcBRLJJiM+LgSzPDTk9/Rx4qS09OUrfpY6GBQ= github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= @@ -705,8 +707,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -724,8 +726,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -741,8 +743,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -754,8 +756,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -778,8 +780,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -788,8 +790,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -800,8 +802,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -824,8 +826,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -867,24 +869,24 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= -k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= -k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= -k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/cloud-provider v0.32.0 h1:QXYJGmwME2q2rprymbmw2GroMChQYc/MWN6l/I4Kgp8= k8s.io/cloud-provider v0.32.0/go.mod h1:cz3gVodkhgwi2ugj/JUPglIruLSdDaThxawuDyCHfr8= k8s.io/cloud-provider-vsphere v1.32.2 h1:/OWUMXhRIDACM2j9Loj/Jh3/Z7q6o7kFbE78iCs92Zg= k8s.io/cloud-provider-vsphere v1.32.2/go.mod h1:v+shTeZ4WM232SEePcD+svnV+atFeEAc07Y0EIWn36M= k8s.io/cluster-bootstrap v0.33.3 h1:u2NTxJ5CFSBFXaDxLQoOWMly8eni31psVso+caq6uwI= k8s.io/cluster-bootstrap v0.33.3/go.mod h1:p970f8u8jf273zyQ5raD8WUu2XyAl0SAWOY82o7i/ds= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/component-helpers v0.34.1 h1:gWhH3CCdwAx5P3oJqZKb4Lg5FYZTWVbdWtOI8n9U4XY= k8s.io/component-helpers v0.34.1/go.mod h1:4VgnUH7UA/shuBur+OWoQC0xfb69sy/93ss0ybZqm3c= k8s.io/controller-manager v0.32.1 h1:z3oQp1O5l0cSzM/MKf8V4olhJ9TmnELoJRPcV/v1s+Y= @@ -899,8 +901,8 @@ k8s.io/dynamic-resource-allocation v0.34.1 h1:pd9qhOeAFkn8eOO4BthAiGHQc8pu+N6TK/ k8s.io/dynamic-resource-allocation v0.34.1/go.mod h1:Zlpqyh6EKhTVoQDe5BS31/8oMXGfG6c12ydj3ChXyuw= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.34.1 h1:iCFOvewDPzWM9fMTfyIPO+4MeuZ0tcZbugxLNSHFG4w= -k8s.io/kms v0.34.1/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= +k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= k8s.io/kube-aggregator v0.34.1 h1:WNLV0dVNoFKmuyvdWLd92iDSyD/TSTjqwaPj0U9XAEU= k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= @@ -925,8 +927,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUo sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/cluster-api v1.11.3 h1:apxfugbP1X8AG7THCM74CTarCOW4H2oOc6hlbm1hY80= sigs.k8s.io/cluster-api v1.11.3/go.mod h1:CA471SACi81M8DzRKTlWpHV33G0cfWEj7sC4fALFVok= -sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= -sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-runtime v0.22.5 h1:v3nfSUMowX/2WMp27J9slwGFyAt7IV0YwBxAkrUr0GE= +sigs.k8s.io/controller-runtime v0.22.5/go.mod h1:pc5SoYWnWI6I+cBHYYdZ7B6YHZVY5xNfll88JB+vniI= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240923090159-236e448db12c h1:w1vANkdIpYwbEZH0y1C7iJItgdEGvF9A3eCdRmLhg8I= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240923090159-236e448db12c/go.mod h1:IaDsO8xSPRxRG1/rm9CP7+jPmj0nMNAuNi/yiHnLX8k= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= diff --git a/vendor/cyphar.com/go-pathrs/.golangci.yml b/vendor/cyphar.com/go-pathrs/.golangci.yml new file mode 100644 index 000000000..2778a3268 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/.golangci.yml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# libpathrs: safe path resolution on Linux +# Copyright (C) 2019-2025 Aleksa Sarai +# Copyright (C) 2019-2025 SUSE LLC +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +version: "2" +linters: + enable: + - bidichk + - cyclop + - errname + - errorlint + - exhaustive + - goconst + - godot + - gomoddirectives + - gosec + - mirror + - misspell + - mnd + - nilerr + - nilnil + - perfsprint + - prealloc + - reassign + - revive + - unconvert + - unparam + - usestdlibvars + - wastedassign +formatters: + enable: + - gofumpt + - goimports + settings: + goimports: + local-prefixes: + - cyphar.com/go-pathrs diff --git a/vendor/cyphar.com/go-pathrs/COPYING b/vendor/cyphar.com/go-pathrs/COPYING new file mode 100644 index 000000000..d0a1fa148 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/COPYING @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/cyphar.com/go-pathrs/doc.go b/vendor/cyphar.com/go-pathrs/doc.go new file mode 100644 index 000000000..a7ee4bc48 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/doc.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// Package pathrs provides bindings for libpathrs, a library for safe path +// resolution on Linux. +package pathrs diff --git a/vendor/cyphar.com/go-pathrs/handle_linux.go b/vendor/cyphar.com/go-pathrs/handle_linux.go new file mode 100644 index 000000000..3221ef673 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/handle_linux.go @@ -0,0 +1,114 @@ +//go:build linux + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package pathrs + +import ( + "fmt" + "os" + + "cyphar.com/go-pathrs/internal/fdutils" + "cyphar.com/go-pathrs/internal/libpathrs" +) + +// Handle is a handle for a path within a given [Root]. This handle references +// an already-resolved path which can be used for only one purpose -- to +// "re-open" the handle and get an actual [os.File] which can be used for +// ordinary operations. +// +// If you wish to open a file without having an intermediate [Handle] object, +// you can try to use [Root.Open] or [Root.OpenFile]. +// +// It is critical that perform all relevant operations through this [Handle] +// (rather than fetching the file descriptor yourself with [Handle.IntoRaw]), +// because the security properties of libpathrs depend on users doing all +// relevant filesystem operations through libpathrs. +// +// [os.File]: https://pkg.go.dev/os#File +type Handle struct { + inner *os.File +} + +// HandleFromFile creates a new [Handle] from an existing file handle. The +// handle will be copied by this method, so the original handle should still be +// freed by the caller. +// +// This is effectively the inverse operation of [Handle.IntoRaw], and is used +// for "deserialising" pathrs root handles. +func HandleFromFile(file *os.File) (*Handle, error) { + newFile, err := fdutils.DupFile(file) + if err != nil { + return nil, fmt.Errorf("duplicate handle fd: %w", err) + } + return &Handle{inner: newFile}, nil +} + +// Open creates an "upgraded" file handle to the file referenced by the +// [Handle]. Note that the original [Handle] is not consumed by this operation, +// and can be opened multiple times. +// +// The handle returned is only usable for reading, and this is method is +// shorthand for [Handle.OpenFile] with os.O_RDONLY. +// +// TODO: Rename these to "Reopen" or something. +func (h *Handle) Open() (*os.File, error) { + return h.OpenFile(os.O_RDONLY) +} + +// OpenFile creates an "upgraded" file handle to the file referenced by the +// [Handle]. Note that the original [Handle] is not consumed by this operation, +// and can be opened multiple times. +// +// The provided flags indicate which open(2) flags are used to create the new +// handle. +// +// TODO: Rename these to "Reopen" or something. +func (h *Handle) OpenFile(flags int) (*os.File, error) { + return fdutils.WithFileFd(h.inner, func(fd uintptr) (*os.File, error) { + newFd, err := libpathrs.Reopen(fd, flags) + if err != nil { + return nil, err + } + return os.NewFile(newFd, h.inner.Name()), nil + }) +} + +// IntoFile unwraps the [Handle] into its underlying [os.File]. +// +// You almost certainly want to use [Handle.OpenFile] to get a non-O_PATH +// version of this [Handle]. +// +// This operation returns the internal [os.File] of the [Handle] directly, so +// calling [Handle.Close] will also close any copies of the returned [os.File]. +// If you want to get an independent copy, use [Handle.Clone] followed by +// [Handle.IntoFile] on the cloned [Handle]. +// +// [os.File]: https://pkg.go.dev/os#File +func (h *Handle) IntoFile() *os.File { + // TODO: Figure out if we really don't want to make a copy. + // TODO: We almost certainly want to clear r.inner here, but we can't do + // that easily atomically (we could use atomic.Value but that'll make + // things quite a bit uglier). + return h.inner +} + +// Clone creates a copy of a [Handle], such that it has a separate lifetime to +// the original (while referring to the same underlying file). +func (h *Handle) Clone() (*Handle, error) { + return HandleFromFile(h.inner) +} + +// Close frees all of the resources used by the [Handle]. +func (h *Handle) Close() error { + return h.inner.Close() +} diff --git a/vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go b/vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go new file mode 100644 index 000000000..41aea3e4b --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go @@ -0,0 +1,75 @@ +//go:build linux + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// Package fdutils contains a few helper methods when dealing with *os.File and +// file descriptors. +package fdutils + +import ( + "fmt" + "os" + + "golang.org/x/sys/unix" + + "cyphar.com/go-pathrs/internal/libpathrs" +) + +// DupFd makes a duplicate of the given fd. +func DupFd(fd uintptr, name string) (*os.File, error) { + newFd, err := unix.FcntlInt(fd, unix.F_DUPFD_CLOEXEC, 0) + if err != nil { + return nil, fmt.Errorf("fcntl(F_DUPFD_CLOEXEC): %w", err) + } + return os.NewFile(uintptr(newFd), name), nil +} + +// WithFileFd is a more ergonomic wrapper around file.SyscallConn().Control(). +func WithFileFd[T any](file *os.File, fn func(fd uintptr) (T, error)) (T, error) { + conn, err := file.SyscallConn() + if err != nil { + return *new(T), err + } + var ( + ret T + innerErr error + ) + if err := conn.Control(func(fd uintptr) { + ret, innerErr = fn(fd) + }); err != nil { + return *new(T), err + } + return ret, innerErr +} + +// DupFile makes a duplicate of the given file. +func DupFile(file *os.File) (*os.File, error) { + return WithFileFd(file, func(fd uintptr) (*os.File, error) { + return DupFd(fd, file.Name()) + }) +} + +// MkFile creates a new *os.File from the provided file descriptor. However, +// unlike os.NewFile, the file's Name is based on the real path (provided by +// /proc/self/fd/$n). +func MkFile(fd uintptr) (*os.File, error) { + fdPath := fmt.Sprintf("fd/%d", fd) + fdName, err := libpathrs.ProcReadlinkat(libpathrs.ProcDefaultRootFd, libpathrs.ProcThreadSelf, fdPath) + if err != nil { + _ = unix.Close(int(fd)) + return nil, fmt.Errorf("failed to fetch real name of fd %d: %w", fd, err) + } + // TODO: Maybe we should prefix this name with something to indicate to + // users that they must not use this path as a "safe" path. Something like + // "//pathrs-handle:/foo/bar"? + return os.NewFile(fd, fdName), nil +} diff --git a/vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go b/vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go new file mode 100644 index 000000000..c9f416de0 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go @@ -0,0 +1,40 @@ +//go:build linux + +// TODO: Use "go:build unix" once we bump the minimum Go version 1.19. + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package libpathrs + +import ( + "syscall" +) + +// Error represents an underlying libpathrs error. +type Error struct { + description string + errno syscall.Errno +} + +// Error returns a textual description of the error. +func (err *Error) Error() string { + return err.description +} + +// Unwrap returns the underlying error which was wrapped by this error (if +// applicable). +func (err *Error) Unwrap() error { + if err.errno != 0 { + return err.errno + } + return nil +} diff --git a/vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go b/vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go new file mode 100644 index 000000000..c07b80e30 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go @@ -0,0 +1,337 @@ +//go:build linux + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// Package libpathrs is an internal thin wrapper around the libpathrs C API. +package libpathrs + +import ( + "fmt" + "syscall" + "unsafe" +) + +/* +// TODO: Figure out if we need to add support for linking against libpathrs +// statically even if in dynamically linked builds in order to make +// packaging a bit easier (using "-Wl,-Bstatic -lpathrs -Wl,-Bdynamic" or +// "-l:pathrs.a"). +#cgo pkg-config: pathrs +#include + +// This is a workaround for unsafe.Pointer() not working for non-void pointers. +char *cast_ptr(void *ptr) { return ptr; } +*/ +import "C" + +func fetchError(errID C.int) error { + if errID >= C.__PATHRS_MAX_ERR_VALUE { + return nil + } + cErr := C.pathrs_errorinfo(errID) + defer C.pathrs_errorinfo_free(cErr) + + var err error + if cErr != nil { + err = &Error{ + errno: syscall.Errno(cErr.saved_errno), + description: C.GoString(cErr.description), + } + } + return err +} + +// OpenRoot wraps pathrs_open_root. +func OpenRoot(path string) (uintptr, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + fd := C.pathrs_open_root(cPath) + return uintptr(fd), fetchError(fd) +} + +// Reopen wraps pathrs_reopen. +func Reopen(fd uintptr, flags int) (uintptr, error) { + newFd := C.pathrs_reopen(C.int(fd), C.int(flags)) + return uintptr(newFd), fetchError(newFd) +} + +// InRootResolve wraps pathrs_inroot_resolve. +func InRootResolve(rootFd uintptr, path string) (uintptr, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + fd := C.pathrs_inroot_resolve(C.int(rootFd), cPath) + return uintptr(fd), fetchError(fd) +} + +// InRootResolveNoFollow wraps pathrs_inroot_resolve_nofollow. +func InRootResolveNoFollow(rootFd uintptr, path string) (uintptr, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + fd := C.pathrs_inroot_resolve_nofollow(C.int(rootFd), cPath) + return uintptr(fd), fetchError(fd) +} + +// InRootOpen wraps pathrs_inroot_open. +func InRootOpen(rootFd uintptr, path string, flags int) (uintptr, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + fd := C.pathrs_inroot_open(C.int(rootFd), cPath, C.int(flags)) + return uintptr(fd), fetchError(fd) +} + +// InRootReadlink wraps pathrs_inroot_readlink. +func InRootReadlink(rootFd uintptr, path string) (string, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + size := 128 + for { + linkBuf := make([]byte, size) + n := C.pathrs_inroot_readlink(C.int(rootFd), cPath, C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf))) + switch { + case int(n) < C.__PATHRS_MAX_ERR_VALUE: + return "", fetchError(n) + case int(n) <= len(linkBuf): + return string(linkBuf[:int(n)]), nil + default: + // The contents were truncated. Unlike readlinkat, pathrs returns + // the size of the link when it checked. So use the returned size + // as a basis for the reallocated size (but in order to avoid a DoS + // where a magic-link is growing by a single byte each iteration, + // make sure we are a fair bit larger). + size += int(n) + } + } +} + +// InRootRmdir wraps pathrs_inroot_rmdir. +func InRootRmdir(rootFd uintptr, path string) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + err := C.pathrs_inroot_rmdir(C.int(rootFd), cPath) + return fetchError(err) +} + +// InRootUnlink wraps pathrs_inroot_unlink. +func InRootUnlink(rootFd uintptr, path string) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + err := C.pathrs_inroot_unlink(C.int(rootFd), cPath) + return fetchError(err) +} + +// InRootRemoveAll wraps pathrs_inroot_remove_all. +func InRootRemoveAll(rootFd uintptr, path string) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + err := C.pathrs_inroot_remove_all(C.int(rootFd), cPath) + return fetchError(err) +} + +// InRootCreat wraps pathrs_inroot_creat. +func InRootCreat(rootFd uintptr, path string, flags int, mode uint32) (uintptr, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + fd := C.pathrs_inroot_creat(C.int(rootFd), cPath, C.int(flags), C.uint(mode)) + return uintptr(fd), fetchError(fd) +} + +// InRootRename wraps pathrs_inroot_rename. +func InRootRename(rootFd uintptr, src, dst string, flags uint) error { + cSrc := C.CString(src) + defer C.free(unsafe.Pointer(cSrc)) + + cDst := C.CString(dst) + defer C.free(unsafe.Pointer(cDst)) + + err := C.pathrs_inroot_rename(C.int(rootFd), cSrc, cDst, C.uint(flags)) + return fetchError(err) +} + +// InRootMkdir wraps pathrs_inroot_mkdir. +func InRootMkdir(rootFd uintptr, path string, mode uint32) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + err := C.pathrs_inroot_mkdir(C.int(rootFd), cPath, C.uint(mode)) + return fetchError(err) +} + +// InRootMkdirAll wraps pathrs_inroot_mkdir_all. +func InRootMkdirAll(rootFd uintptr, path string, mode uint32) (uintptr, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + fd := C.pathrs_inroot_mkdir_all(C.int(rootFd), cPath, C.uint(mode)) + return uintptr(fd), fetchError(fd) +} + +// InRootMknod wraps pathrs_inroot_mknod. +func InRootMknod(rootFd uintptr, path string, mode uint32, dev uint64) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + err := C.pathrs_inroot_mknod(C.int(rootFd), cPath, C.uint(mode), C.dev_t(dev)) + return fetchError(err) +} + +// InRootSymlink wraps pathrs_inroot_symlink. +func InRootSymlink(rootFd uintptr, path, target string) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + cTarget := C.CString(target) + defer C.free(unsafe.Pointer(cTarget)) + + err := C.pathrs_inroot_symlink(C.int(rootFd), cPath, cTarget) + return fetchError(err) +} + +// InRootHardlink wraps pathrs_inroot_hardlink. +func InRootHardlink(rootFd uintptr, path, target string) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + cTarget := C.CString(target) + defer C.free(unsafe.Pointer(cTarget)) + + err := C.pathrs_inroot_hardlink(C.int(rootFd), cPath, cTarget) + return fetchError(err) +} + +// ProcBase is pathrs_proc_base_t (uint64_t). +type ProcBase C.pathrs_proc_base_t + +// FIXME: We need to open-code the constants because CGo unfortunately will +// implicitly convert any non-literal constants (i.e. those resolved using gcc) +// to signed integers. See for some +// more information on the underlying issue (though. +const ( + // ProcRoot is PATHRS_PROC_ROOT. + ProcRoot ProcBase = 0xFFFF_FFFE_7072_6F63 // C.PATHRS_PROC_ROOT + // ProcSelf is PATHRS_PROC_SELF. + ProcSelf ProcBase = 0xFFFF_FFFE_091D_5E1F // C.PATHRS_PROC_SELF + // ProcThreadSelf is PATHRS_PROC_THREAD_SELF. + ProcThreadSelf ProcBase = 0xFFFF_FFFE_3EAD_5E1F // C.PATHRS_PROC_THREAD_SELF + + // ProcBaseTypeMask is __PATHRS_PROC_TYPE_MASK. + ProcBaseTypeMask ProcBase = 0xFFFF_FFFF_0000_0000 // C.__PATHRS_PROC_TYPE_MASK + // ProcBaseTypePid is __PATHRS_PROC_TYPE_PID. + ProcBaseTypePid ProcBase = 0x8000_0000_0000_0000 // C.__PATHRS_PROC_TYPE_PID + + // ProcDefaultRootFd is PATHRS_PROC_DEFAULT_ROOTFD. + ProcDefaultRootFd = -int(syscall.EBADF) // C.PATHRS_PROC_DEFAULT_ROOTFD +) + +func assertEqual[T comparable](a, b T, msg string) { + if a != b { + panic(fmt.Sprintf("%s ((%T) %#v != (%T) %#v)", msg, a, a, b, b)) + } +} + +// Verify that the values above match the actual C values. Unfortunately, Go +// only allows us to forcefully cast int64 to uint64 if you use a temporary +// variable, which means we cannot do it in a const context and thus need to do +// it at runtime (even though it is a check that fundamentally could be done at +// compile-time)... +func init() { + var ( + actualProcRoot int64 = C.PATHRS_PROC_ROOT + actualProcSelf int64 = C.PATHRS_PROC_SELF + actualProcThreadSelf int64 = C.PATHRS_PROC_THREAD_SELF + ) + + assertEqual(ProcRoot, ProcBase(actualProcRoot), "PATHRS_PROC_ROOT") + assertEqual(ProcSelf, ProcBase(actualProcSelf), "PATHRS_PROC_SELF") + assertEqual(ProcThreadSelf, ProcBase(actualProcThreadSelf), "PATHRS_PROC_THREAD_SELF") + + var ( + actualProcBaseTypeMask uint64 = C.__PATHRS_PROC_TYPE_MASK + actualProcBaseTypePid uint64 = C.__PATHRS_PROC_TYPE_PID + ) + + assertEqual(ProcBaseTypeMask, ProcBase(actualProcBaseTypeMask), "__PATHRS_PROC_TYPE_MASK") + assertEqual(ProcBaseTypePid, ProcBase(actualProcBaseTypePid), "__PATHRS_PROC_TYPE_PID") + + assertEqual(ProcDefaultRootFd, int(C.PATHRS_PROC_DEFAULT_ROOTFD), "PATHRS_PROC_DEFAULT_ROOTFD") +} + +// ProcPid reimplements the PROC_PID(x) conversion. +func ProcPid(pid uint32) ProcBase { return ProcBaseTypePid | ProcBase(pid) } + +// ProcOpenat wraps pathrs_proc_openat. +func ProcOpenat(procRootFd int, base ProcBase, path string, flags int) (uintptr, error) { + cBase := C.pathrs_proc_base_t(base) + + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + fd := C.pathrs_proc_openat(C.int(procRootFd), cBase, cPath, C.int(flags)) + return uintptr(fd), fetchError(fd) +} + +// ProcReadlinkat wraps pathrs_proc_readlinkat. +func ProcReadlinkat(procRootFd int, base ProcBase, path string) (string, error) { + // TODO: See if we can unify this code with InRootReadlink. + + cBase := C.pathrs_proc_base_t(base) + + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + size := 128 + for { + linkBuf := make([]byte, size) + n := C.pathrs_proc_readlinkat( + C.int(procRootFd), cBase, cPath, + C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf))) + switch { + case int(n) < C.__PATHRS_MAX_ERR_VALUE: + return "", fetchError(n) + case int(n) <= len(linkBuf): + return string(linkBuf[:int(n)]), nil + default: + // The contents were truncated. Unlike readlinkat, pathrs returns + // the size of the link when it checked. So use the returned size + // as a basis for the reallocated size (but in order to avoid a DoS + // where a magic-link is growing by a single byte each iteration, + // make sure we are a fair bit larger). + size += int(n) + } + } +} + +// ProcfsOpenHow is pathrs_procfs_open_how (struct). +type ProcfsOpenHow C.pathrs_procfs_open_how + +const ( + // ProcfsNewUnmasked is PATHRS_PROCFS_NEW_UNMASKED. + ProcfsNewUnmasked = C.PATHRS_PROCFS_NEW_UNMASKED +) + +// Flags returns a pointer to the internal flags field to allow other packages +// to modify structure fields that are internal due to Go's visibility model. +func (how *ProcfsOpenHow) Flags() *C.uint64_t { return &how.flags } + +// ProcfsOpen is pathrs_procfs_open (sizeof(*how) is passed automatically). +func ProcfsOpen(how *ProcfsOpenHow) (uintptr, error) { + fd := C.pathrs_procfs_open((*C.pathrs_procfs_open_how)(how), C.size_t(unsafe.Sizeof(*how))) + return uintptr(fd), fetchError(fd) +} diff --git a/vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go b/vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go new file mode 100644 index 000000000..5533c427c --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go @@ -0,0 +1,246 @@ +//go:build linux + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// Package procfs provides a safe API for operating on /proc on Linux. +package procfs + +import ( + "os" + "runtime" + + "cyphar.com/go-pathrs/internal/fdutils" + "cyphar.com/go-pathrs/internal/libpathrs" +) + +// ProcBase is used with [ProcReadlink] and related functions to indicate what +// /proc subpath path operations should be done relative to. +type ProcBase struct { + inner libpathrs.ProcBase +} + +var ( + // ProcRoot indicates to use /proc. Note that this mode may be more + // expensive because we have to take steps to try to avoid leaking unmasked + // procfs handles, so you should use [ProcBaseSelf] if you can. + ProcRoot = ProcBase{inner: libpathrs.ProcRoot} + // ProcSelf indicates to use /proc/self. For most programs, this is the + // standard choice. + ProcSelf = ProcBase{inner: libpathrs.ProcSelf} + // ProcThreadSelf indicates to use /proc/thread-self. In multi-threaded + // programs where one thread has a different CLONE_FS, it is possible for + // /proc/self to point the wrong thread and so /proc/thread-self may be + // necessary. + ProcThreadSelf = ProcBase{inner: libpathrs.ProcThreadSelf} +) + +// ProcPid returns a ProcBase which indicates to use /proc/$pid for the given +// PID (or TID). Be aware that due to PID recycling, using this is generally +// not safe except in certain circumstances. Namely: +// +// - PID 1 (the init process), as that PID cannot ever get recycled. +// - Your current PID (though you should just use [ProcBaseSelf]). +// - Your current TID if you have used [runtime.LockOSThread] (though you +// should just use [ProcBaseThreadSelf]). +// - PIDs of child processes (as long as you are sure that no other part of +// your program incorrectly catches or ignores SIGCHLD, and that you do it +// *before* you call wait(2)or any equivalent method that could reap +// zombies). +func ProcPid(pid int) ProcBase { + if pid < 0 || pid >= 1<<31 { + panic("invalid ProcBasePid value") // TODO: should this be an error? + } + return ProcBase{inner: libpathrs.ProcPid(uint32(pid))} +} + +// ThreadCloser is a callback that needs to be called when you are done +// operating on an [os.File] fetched using [Handle.OpenThreadSelf]. +// +// [os.File]: https://pkg.go.dev/os#File +type ThreadCloser func() + +// Handle is a wrapper around an *os.File handle to "/proc", which can be +// used to do further procfs-related operations in a safe way. +type Handle struct { + inner *os.File +} + +// Close releases all internal resources for this [Handle]. +// +// Note that if the handle is actually the global cached handle, this operation +// is a no-op. +func (proc *Handle) Close() error { + var err error + if proc.inner != nil { + err = proc.inner.Close() + } + return err +} + +// OpenOption is a configuration function passed as an argument to [Open]. +type OpenOption func(*libpathrs.ProcfsOpenHow) error + +// UnmaskedProcRoot can be passed to [Open] to request an unmasked procfs +// handle be created. +// +// procfs, err := procfs.OpenRoot(procfs.UnmaskedProcRoot) +func UnmaskedProcRoot(how *libpathrs.ProcfsOpenHow) error { + *how.Flags() |= libpathrs.ProcfsNewUnmasked + return nil +} + +// Open creates a new [Handle] to a safe "/proc", based on the passed +// configuration options (in the form of a series of [OpenOption]s). +func Open(opts ...OpenOption) (*Handle, error) { + var how libpathrs.ProcfsOpenHow + for _, opt := range opts { + if err := opt(&how); err != nil { + return nil, err + } + } + fd, err := libpathrs.ProcfsOpen(&how) + if err != nil { + return nil, err + } + var procFile *os.File + if int(fd) >= 0 { + procFile = os.NewFile(fd, "/proc") + } + // TODO: Check that fd == PATHRS_PROC_DEFAULT_ROOTFD in the <0 case? + return &Handle{inner: procFile}, nil +} + +// TODO: Switch to something fdutils.WithFileFd-like. +func (proc *Handle) fd() int { + if proc.inner != nil { + return int(proc.inner.Fd()) + } + return libpathrs.ProcDefaultRootFd +} + +// TODO: Should we expose open? +func (proc *Handle) open(base ProcBase, path string, flags int) (_ *os.File, Closer ThreadCloser, Err error) { + var closer ThreadCloser + if base == ProcThreadSelf { + runtime.LockOSThread() + closer = runtime.UnlockOSThread + } + defer func() { + if closer != nil && Err != nil { + closer() + Closer = nil + } + }() + + fd, err := libpathrs.ProcOpenat(proc.fd(), base.inner, path, flags) + if err != nil { + return nil, nil, err + } + file, err := fdutils.MkFile(fd) + return file, closer, err +} + +// OpenRoot safely opens a given path from inside /proc/. +// +// This function must only be used for accessing global information from procfs +// (such as /proc/cpuinfo) or information about other processes (such as +// /proc/1). Accessing your own process information should be done using +// [Handle.OpenSelf] or [Handle.OpenThreadSelf]. +func (proc *Handle) OpenRoot(path string, flags int) (*os.File, error) { + file, closer, err := proc.open(ProcRoot, path, flags) + if closer != nil { + // should not happen + panic("non-zero closer returned from procOpen(ProcRoot)") + } + return file, err +} + +// OpenSelf safely opens a given path from inside /proc/self/. +// +// This method is recommend for getting process information about the current +// process for almost all Go processes *except* for cases where there are +// [runtime.LockOSThread] threads that have changed some aspect of their state +// (such as through unshare(CLONE_FS) or changing namespaces). +// +// For such non-heterogeneous processes, /proc/self may reference to a task +// that has different state from the current goroutine and so it may be +// preferable to use [Handle.OpenThreadSelf]. The same is true if a user +// really wants to inspect the current OS thread's information (such as +// /proc/thread-self/stack or /proc/thread-self/status which is always uniquely +// per-thread). +// +// Unlike [Handle.OpenThreadSelf], this method does not involve locking +// the goroutine to the current OS thread and so is simpler to use and +// theoretically has slightly less overhead. +// +// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread +func (proc *Handle) OpenSelf(path string, flags int) (*os.File, error) { + file, closer, err := proc.open(ProcSelf, path, flags) + if closer != nil { + // should not happen + panic("non-zero closer returned from procOpen(ProcSelf)") + } + return file, err +} + +// OpenPid safely opens a given path from inside /proc/$pid/, where pid can be +// either a PID or TID. +// +// This is effectively equivalent to calling [Handle.OpenRoot] with the +// pid prefixed to the subpath. +// +// Be aware that due to PID recycling, using this is generally not safe except +// in certain circumstances. See the documentation of [ProcPid] for more +// details. +func (proc *Handle) OpenPid(pid int, path string, flags int) (*os.File, error) { + file, closer, err := proc.open(ProcPid(pid), path, flags) + if closer != nil { + // should not happen + panic("non-zero closer returned from procOpen(ProcPidOpen)") + } + return file, err +} + +// OpenThreadSelf safely opens a given path from inside /proc/thread-self/. +// +// Most Go processes have heterogeneous threads (all threads have most of the +// same kernel state such as CLONE_FS) and so [Handle.OpenSelf] is +// preferable for most users. +// +// For non-heterogeneous threads, or users that actually want thread-specific +// information (such as /proc/thread-self/stack or /proc/thread-self/status), +// this method is necessary. +// +// Because Go can change the running OS thread of your goroutine without notice +// (and then subsequently kill the old thread), this method will lock the +// current goroutine to the OS thread (with [runtime.LockOSThread]) and the +// caller is responsible for unlocking the the OS thread with the +// [ThreadCloser] callback once they are done using the returned file. This +// callback MUST be called AFTER you have finished using the returned +// [os.File]. This callback is completely separate to [os.File.Close], so it +// must be called regardless of how you close the handle. +// +// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread +// [os.File]: https://pkg.go.dev/os#File +// [os.File.Close]: https://pkg.go.dev/os#File.Close +func (proc *Handle) OpenThreadSelf(path string, flags int) (*os.File, ThreadCloser, error) { + return proc.open(ProcThreadSelf, path, flags) +} + +// Readlink safely reads the contents of a symlink from the given procfs base. +// +// This is effectively equivalent to doing an Open*(O_PATH|O_NOFOLLOW) of the +// path and then doing unix.Readlinkat(fd, ""), but with the benefit that +// thread locking is not necessary for [ProcThreadSelf]. +func (proc *Handle) Readlink(base ProcBase, path string) (string, error) { + return libpathrs.ProcReadlinkat(proc.fd(), base.inner, path) +} diff --git a/vendor/cyphar.com/go-pathrs/root_linux.go b/vendor/cyphar.com/go-pathrs/root_linux.go new file mode 100644 index 000000000..edc9e4c87 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/root_linux.go @@ -0,0 +1,367 @@ +//go:build linux + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package pathrs + +import ( + "errors" + "fmt" + "os" + "syscall" + + "cyphar.com/go-pathrs/internal/fdutils" + "cyphar.com/go-pathrs/internal/libpathrs" +) + +// Root is a handle to the root of a directory tree to resolve within. The only +// purpose of this "root handle" is to perform operations within the directory +// tree, or to get a [Handle] to inodes within the directory tree. +// +// At time of writing, it is considered a *VERY BAD IDEA* to open a [Root] +// inside a possibly-attacker-controlled directory tree. While we do have +// protections that should defend against it, it's far more dangerous than just +// opening a directory tree which is not inside a potentially-untrusted +// directory. +type Root struct { + inner *os.File +} + +// OpenRoot creates a new [Root] handle to the directory at the given path. +func OpenRoot(path string) (*Root, error) { + fd, err := libpathrs.OpenRoot(path) + if err != nil { + return nil, err + } + file, err := fdutils.MkFile(fd) + if err != nil { + return nil, err + } + return &Root{inner: file}, nil +} + +// RootFromFile creates a new [Root] handle from an [os.File] referencing a +// directory. The provided file will be duplicated, so the original file should +// still be closed by the caller. +// +// This is effectively the inverse operation of [Root.IntoFile]. +// +// [os.File]: https://pkg.go.dev/os#File +func RootFromFile(file *os.File) (*Root, error) { + newFile, err := fdutils.DupFile(file) + if err != nil { + return nil, fmt.Errorf("duplicate root fd: %w", err) + } + return &Root{inner: newFile}, nil +} + +// Resolve resolves the given path within the [Root]'s directory tree, and +// returns a [Handle] to the resolved path. The path must already exist, +// otherwise an error will occur. +// +// All symlinks (including trailing symlinks) are followed, but they are +// resolved within the rootfs. If you wish to open a handle to the symlink +// itself, use [ResolveNoFollow]. +func (r *Root) Resolve(path string) (*Handle, error) { + return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) { + handleFd, err := libpathrs.InRootResolve(rootFd, path) + if err != nil { + return nil, err + } + handleFile, err := fdutils.MkFile(handleFd) + if err != nil { + return nil, err + } + return &Handle{inner: handleFile}, nil + }) +} + +// ResolveNoFollow is effectively an O_NOFOLLOW version of [Resolve]. Their +// behaviour is identical, except that *trailing* symlinks will not be +// followed. If the final component is a trailing symlink, an O_PATH|O_NOFOLLOW +// handle to the symlink itself is returned. +func (r *Root) ResolveNoFollow(path string) (*Handle, error) { + return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) { + handleFd, err := libpathrs.InRootResolveNoFollow(rootFd, path) + if err != nil { + return nil, err + } + handleFile, err := fdutils.MkFile(handleFd) + if err != nil { + return nil, err + } + return &Handle{inner: handleFile}, nil + }) +} + +// Open is effectively shorthand for [Resolve] followed by [Handle.Open], but +// can be slightly more efficient (it reduces CGo overhead and the number of +// syscalls used when using the openat2-based resolver) and is arguably more +// ergonomic to use. +// +// This is effectively equivalent to [os.Open]. +// +// [os.Open]: https://pkg.go.dev/os#Open +func (r *Root) Open(path string) (*os.File, error) { + return r.OpenFile(path, os.O_RDONLY) +} + +// OpenFile is effectively shorthand for [Resolve] followed by +// [Handle.OpenFile], but can be slightly more efficient (it reduces CGo +// overhead and the number of syscalls used when using the openat2-based +// resolver) and is arguably more ergonomic to use. +// +// However, if flags contains os.O_NOFOLLOW and the path is a symlink, then +// OpenFile's behaviour will match that of openat2. In most cases an error will +// be returned, but if os.O_PATH is provided along with os.O_NOFOLLOW then a +// file equivalent to [ResolveNoFollow] will be returned instead. +// +// This is effectively equivalent to [os.OpenFile], except that os.O_CREAT is +// not supported. +// +// [os.OpenFile]: https://pkg.go.dev/os#OpenFile +func (r *Root) OpenFile(path string, flags int) (*os.File, error) { + return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) { + fd, err := libpathrs.InRootOpen(rootFd, path, flags) + if err != nil { + return nil, err + } + return fdutils.MkFile(fd) + }) +} + +// Create creates a file within the [Root]'s directory tree at the given path, +// and returns a handle to the file. The provided mode is used for the new file +// (the process's umask applies). +// +// Unlike [os.Create], if the file already exists an error is created rather +// than the file being opened and truncated. +// +// [os.Create]: https://pkg.go.dev/os#Create +func (r *Root) Create(path string, flags int, mode os.FileMode) (*os.File, error) { + unixMode, err := toUnixMode(mode, false) + if err != nil { + return nil, err + } + return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) { + handleFd, err := libpathrs.InRootCreat(rootFd, path, flags, unixMode) + if err != nil { + return nil, err + } + return fdutils.MkFile(handleFd) + }) +} + +// Rename two paths within a [Root]'s directory tree. The flags argument is +// identical to the RENAME_* flags to the renameat2(2) system call. +func (r *Root) Rename(src, dst string, flags uint) error { + _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootRename(rootFd, src, dst, flags) + return struct{}{}, err + }) + return err +} + +// RemoveDir removes the named empty directory within a [Root]'s directory +// tree. +func (r *Root) RemoveDir(path string) error { + _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootRmdir(rootFd, path) + return struct{}{}, err + }) + return err +} + +// RemoveFile removes the named file within a [Root]'s directory tree. +func (r *Root) RemoveFile(path string) error { + _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootUnlink(rootFd, path) + return struct{}{}, err + }) + return err +} + +// Remove removes the named file or (empty) directory within a [Root]'s +// directory tree. +// +// This is effectively equivalent to [os.Remove]. +// +// [os.Remove]: https://pkg.go.dev/os#Remove +func (r *Root) Remove(path string) error { + // In order to match os.Remove's implementation we need to also do both + // syscalls unconditionally and adjust the error based on whether + // pathrs_inroot_rmdir() returned ENOTDIR. + unlinkErr := r.RemoveFile(path) + if unlinkErr == nil { + return nil + } + rmdirErr := r.RemoveDir(path) + if rmdirErr == nil { + return nil + } + // Both failed, adjust the error in the same way that os.Remove does. + err := rmdirErr + if errors.Is(err, syscall.ENOTDIR) { + err = unlinkErr + } + return err +} + +// RemoveAll recursively deletes a path and all of its children. +// +// This is effectively equivalent to [os.RemoveAll]. +// +// [os.RemoveAll]: https://pkg.go.dev/os#RemoveAll +func (r *Root) RemoveAll(path string) error { + _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootRemoveAll(rootFd, path) + return struct{}{}, err + }) + return err +} + +// Mkdir creates a directory within a [Root]'s directory tree. The provided +// mode is used for the new directory (the process's umask applies). +// +// This is effectively equivalent to [os.Mkdir]. +// +// [os.Mkdir]: https://pkg.go.dev/os#Mkdir +func (r *Root) Mkdir(path string, mode os.FileMode) error { + unixMode, err := toUnixMode(mode, false) + if err != nil { + return err + } + + _, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootMkdir(rootFd, path, unixMode) + return struct{}{}, err + }) + return err +} + +// MkdirAll creates a directory (and any parent path components if they don't +// exist) within a [Root]'s directory tree. The provided mode is used for any +// directories created by this function (the process's umask applies). +// +// This is effectively equivalent to [os.MkdirAll]. +// +// [os.MkdirAll]: https://pkg.go.dev/os#MkdirAll +func (r *Root) MkdirAll(path string, mode os.FileMode) (*Handle, error) { + unixMode, err := toUnixMode(mode, false) + if err != nil { + return nil, err + } + + return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) { + handleFd, err := libpathrs.InRootMkdirAll(rootFd, path, unixMode) + if err != nil { + return nil, err + } + handleFile, err := fdutils.MkFile(handleFd) + if err != nil { + return nil, err + } + return &Handle{inner: handleFile}, err + }) +} + +// Mknod creates a new device inode of the given type within a [Root]'s +// directory tree. The provided mode is used for the new directory (the +// process's umask applies). +// +// This is effectively equivalent to [unix.Mknod]. +// +// [unix.Mknod]: https://pkg.go.dev/golang.org/x/sys/unix#Mknod +func (r *Root) Mknod(path string, mode os.FileMode, dev uint64) error { + unixMode, err := toUnixMode(mode, true) + if err != nil { + return err + } + + _, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootMknod(rootFd, path, unixMode, dev) + return struct{}{}, err + }) + return err +} + +// Symlink creates a symlink within a [Root]'s directory tree. The symlink is +// created at path and is a link to target. +// +// This is effectively equivalent to [os.Symlink]. +// +// [os.Symlink]: https://pkg.go.dev/os#Symlink +func (r *Root) Symlink(path, target string) error { + _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootSymlink(rootFd, path, target) + return struct{}{}, err + }) + return err +} + +// Hardlink creates a hardlink within a [Root]'s directory tree. The hardlink +// is created at path and is a link to target. Both paths are within the +// [Root]'s directory tree (you cannot hardlink to a different [Root] or the +// host). +// +// This is effectively equivalent to [os.Link]. +// +// [os.Link]: https://pkg.go.dev/os#Link +func (r *Root) Hardlink(path, target string) error { + _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { + err := libpathrs.InRootHardlink(rootFd, path, target) + return struct{}{}, err + }) + return err +} + +// Readlink returns the target of a symlink with a [Root]'s directory tree. +// +// This is effectively equivalent to [os.Readlink]. +// +// [os.Readlink]: https://pkg.go.dev/os#Readlink +func (r *Root) Readlink(path string) (string, error) { + return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (string, error) { + return libpathrs.InRootReadlink(rootFd, path) + }) +} + +// IntoFile unwraps the [Root] into its underlying [os.File]. +// +// It is critical that you do not operate on this file descriptor yourself, +// because the security properties of libpathrs depend on users doing all +// relevant filesystem operations through libpathrs. +// +// This operation returns the internal [os.File] of the [Root] directly, so +// calling [Root.Close] will also close any copies of the returned [os.File]. +// If you want to get an independent copy, use [Root.Clone] followed by +// [Root.IntoFile] on the cloned [Root]. +// +// [os.File]: https://pkg.go.dev/os#File +func (r *Root) IntoFile() *os.File { + // TODO: Figure out if we really don't want to make a copy. + // TODO: We almost certainly want to clear r.inner here, but we can't do + // that easily atomically (we could use atomic.Value but that'll make + // things quite a bit uglier). + return r.inner +} + +// Clone creates a copy of a [Root] handle, such that it has a separate +// lifetime to the original (while referring to the same underlying directory). +func (r *Root) Clone() (*Root, error) { + return RootFromFile(r.inner) +} + +// Close frees all of the resources used by the [Root] handle. +func (r *Root) Close() error { + return r.inner.Close() +} diff --git a/vendor/cyphar.com/go-pathrs/utils_linux.go b/vendor/cyphar.com/go-pathrs/utils_linux.go new file mode 100644 index 000000000..2208d608f --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/utils_linux.go @@ -0,0 +1,56 @@ +//go:build linux + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * Copyright (C) 2019-2025 SUSE LLC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package pathrs + +import ( + "fmt" + "os" + + "golang.org/x/sys/unix" +) + +//nolint:cyclop // this function needs to handle a lot of cases +func toUnixMode(mode os.FileMode, needsType bool) (uint32, error) { + sysMode := uint32(mode.Perm()) + switch mode & os.ModeType { //nolint:exhaustive // we only care about ModeType bits + case 0: + if needsType { + sysMode |= unix.S_IFREG + } + case os.ModeDir: + sysMode |= unix.S_IFDIR + case os.ModeSymlink: + sysMode |= unix.S_IFLNK + case os.ModeCharDevice | os.ModeDevice: + sysMode |= unix.S_IFCHR + case os.ModeDevice: + sysMode |= unix.S_IFBLK + case os.ModeNamedPipe: + sysMode |= unix.S_IFIFO + case os.ModeSocket: + sysMode |= unix.S_IFSOCK + default: + return 0, fmt.Errorf("invalid mode filetype %+o", mode) + } + if mode&os.ModeSetuid != 0 { + sysMode |= unix.S_ISUID + } + if mode&os.ModeSetgid != 0 { + sysMode |= unix.S_ISGID + } + if mode&os.ModeSticky != 0 { + sysMode |= unix.S_ISVTX + } + return sysMode, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml new file mode 100644 index 000000000..3e8dd99bd --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: MPL-2.0 + +# Copyright (C) 2025 Aleksa Sarai +# Copyright (C) 2025 SUSE LLC +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +version: "2" + +run: + build-tags: + - libpathrs + +linters: + enable: + - asasalint + - asciicheck + - containedctx + - contextcheck + - errcheck + - errorlint + - exhaustive + - forcetypeassert + - godot + - goprintffuncname + - govet + - importas + - ineffassign + - makezero + - misspell + - musttag + - nilerr + - nilnesserr + - nilnil + - noctx + - prealloc + - revive + - staticcheck + - testifylint + - unconvert + - unparam + - unused + - usetesting + settings: + govet: + enable: + - nilness + testifylint: + enable-all: true + +formatters: + enable: + - gofumpt + - goimports + settings: + goimports: + local-prefixes: + - github.com/cyphar/filepath-securejoin diff --git a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md index ca0e3c62c..734cf61e3 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md +++ b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md @@ -6,6 +6,208 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ## +## [0.6.0] - 2025-11-03 ## + +> By the Power of Greyskull! + +While quite small code-wise, this release marks a very key point in the +development of filepath-securejoin. + +filepath-securejoin was originally intended (back in 2017) to simply be a +single-purpose library that would take some common code used in container +runtimes (specifically, Docker's `FollowSymlinksInScope`) and make it more +general-purpose (with the eventual goals of it ending up in the Go stdlib). + +Of course, I quickly discovered that this problem was actually far more +complicated to solve when dealing with racing attackers, which lead to me +developing `openat2(2)` and [libpathrs][]. I had originally planned for +libpathrs to completely replace filepath-securejoin "once it was ready" but in +the interim we needed to fix several race attacks in runc as part of security +advisories. Obviously we couldn't require the usage of a pre-0.1 Rust library +in runc so it was necessary to port bits of libpathrs into filepath-securejoin. +(Ironically the first prototypes of libpathrs were originally written in Go and +then rewritten to Rust, so the code in filepath-securejoin is actually Go code +that was rewritten to Rust then re-rewritten to Go.) + +It then became clear that pure-Go libraries will likely not be willing to +require CGo for all of their builds, so it was necessary to accept that +filepath-securejoin will need to stay. As such, in v0.5.0 we provided more +pure-Go implementations of features from libpathrs but moved them into +`pathrs-lite` subpackage to clarify what purpose these helpers serve. + +This release finally closes the loop and makes it so that pathrs-lite can +transparently use libpathrs (via a `libpathrs` build-tag). This means that +upstream libraries can use the pure Go version if they prefer, but downstreams +(either downstream library users or even downstream distributions) are able to +migrate to libpathrs for all usages of pathrs-lite in an entire Go binary. + +I should make it clear that I do not plan to port the rest of libpathrs to Go, +as I do not wish to maintain two copies of the same codebase. pathrs-lite +already provides the core essentials necessary to operate on paths safely for +most modern systems. Users who want additional hardening or more ergonomic APIs +are free to use [`cyphar.com/go-pathrs`][go-pathrs] (libpathrs's Go bindings). + +[libpathrs]: https://github.com/cyphar/libpathrs +[go-pathrs]: https://cyphar.com/go-pathrs + +### Breaking ### +- The deprecated `MkdirAll`, `MkdirAllHandle`, `OpenInRoot`, `OpenatInRoot` and + `Reopen` wrappers have been removed. Please switch to using `pathrs-lite` + directly. + +### Added ### +- `pathrs-lite` now has support for using [libpathrs][libpathrs] as a backend. + This is opt-in and can be enabled at build time with the `libpathrs` build + tag. The intention is to allow for downstream libraries and other projects to + make use of the pure-Go `github.com/cyphar/filepath-securejoin/pathrs-lite` + package and distributors can then opt-in to using `libpathrs` for the entire + binary if they wish. + +## [0.5.1] - 2025-10-31 ## + +> Spooky scary skeletons send shivers down your spine! + +### Changed ### +- `openat2` can return `-EAGAIN` if it detects a possible attack in certain + scenarios (namely if there was a rename or mount while walking a path with a + `..` component). While this is necessary to avoid a denial-of-service in the + kernel, it does require retry loops in userspace. + + In previous versions, `pathrs-lite` would retry `openat2` 32 times before + returning an error, but we've received user reports that this limit can be + hit on systems with very heavy load. In some synthetic benchmarks (testing + the worst-case of an attacker doing renames in a tight loop on every core of + a 16-core machine) we managed to get a ~3% failure rate in runc. We have + improved this situation in two ways: + + * We have now increased this limit to 128, which should be good enough for + most use-cases without becoming a denial-of-service vector (the number of + syscalls called by the `O_PATH` resolver in a typical case is within the + same ballpark). The same benchmarks show a failure rate of ~0.12% which + (while not zero) is probably sufficient for most users. + + * In addition, we now return a `unix.EAGAIN` error that is bubbled up and can + be detected by callers. This means that callers with stricter requirements + to avoid spurious errors can choose to do their own infinite `EAGAIN` retry + loop (though we would strongly recommend users use time-based deadlines in + such retry loops to avoid potentially unbounded denials-of-service). + +## [0.5.0] - 2025-09-26 ## + +> Let the past die. Kill it if you have to. + +> **NOTE**: With this release, some parts of +> `github.com/cyphar/filepath-securejoin` are now licensed under the Mozilla +> Public License (version 2). Please see [COPYING.md][] as well as the the +> license header in each file for more details. + +[COPYING.md]: ./COPYING.md + +### Breaking ### +- The new API introduced in the [0.3.0][] release has been moved to a new + subpackage called `pathrs-lite`. This was primarily done to better indicate + the split between the new and old APIs, as well as indicate to users the + purpose of this subpackage (it is a less complete version of [libpathrs][]). + + We have added some wrappers to the top-level package to ease the transition, + but those are deprecated and will be removed in the next minor release of + filepath-securejoin. Users should update their import paths. + + This new subpackage has also been relicensed under the Mozilla Public License + (version 2), please see [COPYING.md][] for more details. + +### Added ### +- Most of the key bits the safe `procfs` API have now been exported and are + available in `github.com/cyphar/filepath-securejoin/pathrs-lite/procfs`. At + the moment this primarily consists of a new `procfs.Handle` API: + + * `OpenProcRoot` returns a new handle to `/proc`, endeavouring to make it + safe if possible (`subset=pid` to protect against mistaken write attacks + and leaks, as well as using `fsopen(2)` to avoid racing mount attacks). + + `OpenUnsafeProcRoot` returns a handle without attempting to create one + with `subset=pid`, which makes it more dangerous to leak. Most users + should use `OpenProcRoot` (even if you need to use `ProcRoot` as the base + of an operation, as filepath-securejoin will internally open a handle when + necessary). + + * The `(*procfs.Handle).Open*` family of methods lets you get a safe + `O_PATH` handle to subpaths within `/proc` for certain subpaths. + + For `OpenThreadSelf`, the returned `ProcThreadSelfCloser` needs to be + called after you completely finish using the handle (this is necessary + because Go is multi-threaded and `ProcThreadSelf` references + `/proc/thread-self` which may disappear if we do not + `runtime.LockOSThread` -- `ProcThreadSelfCloser` is currently equivalent + to `runtime.UnlockOSThread`). + + Note that you cannot open any `procfs` symlinks (most notably magic-links) + using this API. At the moment, filepath-securejoin does not support this + feature (but [libpathrs][] does). + + * `ProcSelfFdReadlink` lets you get the in-kernel path representation of a + file descriptor (think `readlink("/proc/self/fd/...")`), except that we + verify that there aren't any tricky overmounts that could fool the + process. + + Please be aware that the returned string is simply a snapshot at that + particular moment, and an attacker could move the file being pointed to. + In addition, complex namespace configurations could result in non-sensical + or confusing paths to be returned. The value received from this function + should only be used as secondary verification of some security property, + not as proof that a particular handle has a particular path. + + The procfs handle used internally by the API is the same as the rest of + `filepath-securejoin` (for privileged programs this is usually a private + in-process `procfs` instance created with `fsopen(2)`). + + As before, this is intended as a stop-gap before users migrate to + [libpathrs][], which provides a far more extensive safe `procfs` API and is + generally more robust. + +- Previously, the hardened procfs implementation (used internally within + `Reopen` and `Open(at)InRoot`) only protected against overmount attacks on + systems with `openat2(2)` (Linux 5.6) or systems with `fsopen(2)` or + `open_tree(2)` (Linux 5.2) and programs with privileges to use them (with + some caveats about locked mounts that probably affect very few users). For + other users, an attacker with the ability to create malicious mounts (on most + systems, a sysadmin) could trick you into operating on files you didn't + expect. This attack only really makes sense in the context of container + runtime implementations. + + This was considered a reasonable trade-off, as the long-term intention was to + get all users to just switch to [libpathrs][] if they wanted to use the safe + `procfs` API (which had more extensive protections, and is what these new + protections in `filepath-securejoin` are based on). However, as the API + is now being exported it seems unwise to advertise the API as "safe" if we do + not protect against known attacks. + + The procfs API is now more protected against attackers on systems lacking the + aforementioned protections. However, the most comprehensive of these + protections effectively rely on [`statx(STATX_MNT_ID)`][statx.2] (Linux 5.8). + On older kernel versions, there is no effective protection (there is some + minimal protection against non-`procfs` filesystem components but a + sufficiently clever attacker can work around those). In addition, + `STATX_MNT_ID` is vulnerable to mount ID reuse attacks by sufficiently + motivated and privileged attackers -- this problem is mitigated with + `STATX_MNT_ID_UNIQUE` (Linux 6.8) but that raises the minimum kernel version + for more protection. + + The fact that these protections are quite limited despite needing a fair bit + of extra code to handle was one of the primary reasons we did not initially + implement this in `filepath-securejoin` ([libpathrs][] supports all of this, + of course). + +### Fixed ### +- RHEL 8 kernels have backports of `fsopen(2)` but in some testing we've found + that it has very bad (and very difficult to debug) performance issues, and so + we will explicitly refuse to use `fsopen(2)` if the running kernel version is + pre-5.2 and will instead fallback to `open("/proc")`. + +[CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv +[libpathrs]: https://github.com/cyphar/libpathrs +[statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html + ## [0.4.1] - 2025-01-28 ## ### Fixed ### @@ -173,7 +375,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). safe to start migrating to as we have extensive tests ensuring they behave correctly and are safe against various races and other attacks. -[libpathrs]: https://github.com/openSUSE/libpathrs +[libpathrs]: https://github.com/cyphar/libpathrs [open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html ## [0.2.5] - 2024-05-03 ## @@ -238,7 +440,10 @@ This is our first release of `github.com/cyphar/filepath-securejoin`, containing a full implementation with a coverage of 93.5% (the only missing cases are the error cases, which are hard to mocktest at the moment). -[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD +[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...HEAD +[0.6.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...v0.6.0 +[0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1 +[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0 [0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0 [0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6 diff --git a/vendor/github.com/cyphar/filepath-securejoin/COPYING.md b/vendor/github.com/cyphar/filepath-securejoin/COPYING.md new file mode 100644 index 000000000..520e822b1 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/COPYING.md @@ -0,0 +1,447 @@ +## COPYING ## + +`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` + +This project is made up of code licensed under different licenses. Which code +you use will have an impact on whether only one or both licenses apply to your +usage of this library. + +Note that **each file** in this project individually has a code comment at the +start describing the license of that particular file -- this is the most +accurate license information of this project; in case there is any conflict +between this document and the comment at the start of a file, the comment shall +take precedence. The only purpose of this document is to work around [a known +technical limitation of pkg.go.dev's license checking tool when dealing with +non-trivial project licenses][go75067]. + +[go75067]: https://go.dev/issue/75067 + +### `BSD-3-Clause` ### + +At time of writing, the following files and directories are licensed under the +BSD-3-Clause license: + + * `doc.go` + * `join*.go` + * `vfs.go` + * `internal/consts/*.go` + * `pathrs-lite/internal/gocompat/*.go` + * `pathrs-lite/internal/kernelversion/*.go` + +The text of the BSD-3-Clause license used by this project is the following (the +text is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file): + +``` +Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +Copyright (C) 2017-2024 SUSE LLC. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### `MPL-2.0` ### + +All other files (unless otherwise marked) are licensed under the Mozilla Public +License (version 2.0). + +The text of the Mozilla Public License (version 2.0) is the following (the text +is also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file): + +``` +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. +``` diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD similarity index 100% rename from vendor/github.com/cyphar/filepath-securejoin/LICENSE rename to vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 new file mode 100644 index 000000000..d0a1fa148 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/cyphar/filepath-securejoin/README.md b/vendor/github.com/cyphar/filepath-securejoin/README.md index eaeb53fcd..6673abfc8 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/README.md +++ b/vendor/github.com/cyphar/filepath-securejoin/README.md @@ -67,7 +67,8 @@ func SecureJoin(root, unsafePath string) (string, error) { [libpathrs]: https://github.com/openSUSE/libpathrs [go#20126]: https://github.com/golang/go/issues/20126 -### New API ### +### New API ### +[#new-api]: #new-api While we recommend users switch to [libpathrs][libpathrs] as soon as it has a stable release, some methods implemented by libpathrs have been ported to this @@ -165,5 +166,19 @@ after `MkdirAll`). ### License ### -The license of this project is the same as Go, which is a BSD 3-clause license -available in the `LICENSE` file. +`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` + +Some of the code in this project is derived from Go, and is licensed under a +BSD 3-clause license (available in `LICENSE.BSD`). Other files (many of which +are derived from [libpathrs][libpathrs]) are licensed under the Mozilla Public +License version 2.0 (available in `LICENSE.MPL-2.0`). If you are using the +["New API" described above][#new-api], you are probably using code from files +released under this license. + +Every source file in this project has a copyright header describing its +license. Please check the license headers of each file to see what license +applies to it. + +See [COPYING.md](./COPYING.md) for some more details. + +[umoci]: https://github.com/opencontainers/umoci diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION index 267577d47..a918a2aa1 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/VERSION +++ b/vendor/github.com/cyphar/filepath-securejoin/VERSION @@ -1 +1 @@ -0.4.1 +0.6.0 diff --git a/vendor/github.com/cyphar/filepath-securejoin/codecov.yml b/vendor/github.com/cyphar/filepath-securejoin/codecov.yml new file mode 100644 index 000000000..ff284dbfa --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/codecov.yml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MPL-2.0 + +# Copyright (C) 2025 Aleksa Sarai +# Copyright (C) 2025 SUSE LLC +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +comment: + layout: "condensed_header, reach, diff, components, condensed_files, condensed_footer" + require_changes: true + branches: + - main + +coverage: + range: 60..100 + status: + project: + default: + target: 85% + threshold: 0% + patch: + default: + target: auto + informational: true + +github_checks: + annotations: false diff --git a/vendor/github.com/cyphar/filepath-securejoin/doc.go b/vendor/github.com/cyphar/filepath-securejoin/doc.go index 1ec7d065e..1438fc9c0 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/doc.go +++ b/vendor/github.com/cyphar/filepath-securejoin/doc.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: BSD-3-Clause + // Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. // Copyright (C) 2017-2024 SUSE LLC. All rights reserved. // Use of this source code is governed by a BSD-style @@ -14,14 +16,13 @@ // **not** safe against race conditions where an attacker changes the // filesystem after (or during) the [SecureJoin] operation. // -// The new API is made up of [OpenInRoot] and [MkdirAll] (and derived -// functions). These are safe against racing attackers and have several other -// protections that are not provided by the legacy API. There are many more -// operations that most programs expect to be able to do safely, but we do not -// provide explicit support for them because we want to encourage users to -// switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a -// cross-language next-generation library that is entirely designed around -// operating on paths safely. +// The new API is available in the [pathrs-lite] subpackage, and provide +// protections against racing attackers as well as several other key +// protections against attacks often seen by container runtimes. As the name +// suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of +// [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and +// [procfs.Handle] -- other APIs are not planned to be ported. The long-term +// goal is for users to migrate to [libpathrs] which is more fully-featured. // // securejoin has been used by several container runtimes (Docker, runc, // Kubernetes, etc) for quite a few years as a de-facto standard for operating @@ -31,9 +32,16 @@ // API as soon as possible (or even better, switch to libpathrs). // // This project was initially intended to be included in the Go standard -// library, but [it was rejected](https://go.dev/issue/20126). There is now a -// [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API -// that shares some of the goals of filepath-securejoin. However, that design -// is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the -// usecase of container runtimes and most system tools. +// library, but it was rejected (see https://go.dev/issue/20126). Much later, +// [os.Root] was added to the Go stdlib that shares some of the goals of +// filepath-securejoin. However, its design is intended to work like +// openat2(RESOLVE_BENEATH) which does not fit the usecase of container +// runtimes and most system tools. +// +// [pathrs-lite]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite +// [libpathrs]: https://github.com/openSUSE/libpathrs +// [OpenInRoot]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#OpenInRoot +// [MkdirAll]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#MkdirAll +// [procfs.Handle]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle +// [os.Root]: https:///pkg.go.dev/os#Root package securejoin diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go b/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go deleted file mode 100644 index ddd6fa9a4..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build linux && go1.21 - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "slices" - "sync" -) - -func slices_DeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S { - return slices.DeleteFunc(slice, delFn) -} - -func slices_Contains[S ~[]E, E comparable](slice S, val E) bool { - return slices.Contains(slice, val) -} - -func slices_Clone[S ~[]E, E any](slice S) S { - return slices.Clone(slice) -} - -func sync_OnceValue[T any](f func() T) func() T { - return sync.OnceValue(f) -} - -func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { - return sync.OnceValues(f) -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go deleted file mode 100644 index f1e6fe7e7..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go +++ /dev/null @@ -1,124 +0,0 @@ -//go:build linux && !go1.21 - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "sync" -) - -// These are very minimal implementations of functions that appear in Go 1.21's -// stdlib, included so that we can build on older Go versions. Most are -// borrowed directly from the stdlib, and a few are modified to be "obviously -// correct" without needing to copy too many other helpers. - -// clearSlice is equivalent to the builtin clear from Go 1.21. -// Copied from the Go 1.24 stdlib implementation. -func clearSlice[S ~[]E, E any](slice S) { - var zero E - for i := range slice { - slice[i] = zero - } -} - -// Copied from the Go 1.24 stdlib implementation. -func slices_IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { - for i := range s { - if f(s[i]) { - return i - } - } - return -1 -} - -// Copied from the Go 1.24 stdlib implementation. -func slices_DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { - i := slices_IndexFunc(s, del) - if i == -1 { - return s - } - // Don't start copying elements until we find one to delete. - for j := i + 1; j < len(s); j++ { - if v := s[j]; !del(v) { - s[i] = v - i++ - } - } - clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC - return s[:i] -} - -// Similar to the stdlib slices.Contains, except that we don't have -// slices.Index so we need to use slices.IndexFunc for this non-Func helper. -func slices_Contains[S ~[]E, E comparable](s S, v E) bool { - return slices_IndexFunc(s, func(e E) bool { return e == v }) >= 0 -} - -// Copied from the Go 1.24 stdlib implementation. -func slices_Clone[S ~[]E, E any](s S) S { - // Preserve nil in case it matters. - if s == nil { - return nil - } - return append(S([]E{}), s...) -} - -// Copied from the Go 1.24 stdlib implementation. -func sync_OnceValue[T any](f func() T) func() T { - var ( - once sync.Once - valid bool - p any - result T - ) - g := func() { - defer func() { - p = recover() - if !valid { - panic(p) - } - }() - result = f() - f = nil - valid = true - } - return func() T { - once.Do(g) - if !valid { - panic(p) - } - return result - } -} - -// Copied from the Go 1.24 stdlib implementation. -func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { - var ( - once sync.Once - valid bool - p any - r1 T1 - r2 T2 - ) - g := func() { - defer func() { - p = recover() - if !valid { - panic(p) - } - }() - r1, r2 = f() - f = nil - valid = true - } - return func() (T1, T2) { - once.Do(g) - if !valid { - panic(p) - } - return r1, r2 - } -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go b/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go new file mode 100644 index 000000000..c69c4da91 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +// Copyright (C) 2017-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package consts contains the definitions of internal constants used +// throughout filepath-securejoin. +package consts + +// MaxSymlinkLimit is the maximum number of symlinks that can be encountered +// during a single lookup before returning -ELOOP. At time of writing, Linux +// has an internal limit of 40. +const MaxSymlinkLimit = 255 diff --git a/vendor/github.com/cyphar/filepath-securejoin/join.go b/vendor/github.com/cyphar/filepath-securejoin/join.go index e6634d477..199c1d839 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/join.go +++ b/vendor/github.com/cyphar/filepath-securejoin/join.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: BSD-3-Clause + // Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. // Copyright (C) 2017-2025 SUSE LLC. All rights reserved. // Use of this source code is governed by a BSD-style @@ -11,9 +13,9 @@ import ( "path/filepath" "strings" "syscall" -) -const maxSymlinkLimit = 255 + "github.com/cyphar/filepath-securejoin/internal/consts" +) // IsNotExist tells you if err is an error that implies that either the path // accessed does not exist (or path components don't exist). This is @@ -49,12 +51,13 @@ func hasDotDot(path string) bool { return strings.Contains("/"+path+"/", "/../") } -// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except -// that the returned path is guaranteed to be scoped inside the provided root -// path (when evaluated). Any symbolic links in the path are evaluated with the -// given root treated as the root of the filesystem, similar to a chroot. The -// filesystem state is evaluated through the given [VFS] interface (if nil, the -// standard [os].* family of functions are used). +// SecureJoinVFS joins the two given path components (similar to +// [filepath.Join]) except that the returned path is guaranteed to be scoped +// inside the provided root path (when evaluated). Any symbolic links in the +// path are evaluated with the given root treated as the root of the +// filesystem, similar to a chroot. The filesystem state is evaluated through +// the given [VFS] interface (if nil, the standard [os].* family of functions +// are used). // // Note that the guarantees provided by this function only apply if the path // components in the returned string are not modified (in other words are not @@ -78,7 +81,7 @@ func hasDotDot(path string) bool { // fully resolved using [filepath.EvalSymlinks] or otherwise constructed to // avoid containing symlink components. Of course, the root also *must not* be // attacker-controlled. -func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { +func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:revive // name is part of public API // The root path must not contain ".." components, otherwise when we join // the subpath we will end up with a weird path. We could work around this // in other ways but users shouldn't be giving us non-lexical root paths in @@ -138,7 +141,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { // It's a symlink, so get its contents and expand it by prepending it // to the yet-unparsed path. linksWalked++ - if linksWalked > maxSymlinkLimit { + if linksWalked > consts.MaxSymlinkLimit { return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} } diff --git a/vendor/github.com/cyphar/filepath-securejoin/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/open_linux.go deleted file mode 100644 index 230be73f0..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/open_linux.go +++ /dev/null @@ -1,103 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "fmt" - "os" - "strconv" - - "golang.org/x/sys/unix" -) - -// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided -// using an *[os.File] handle, to ensure that the correct root directory is used. -func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { - handle, err := completeLookupInRoot(root, unsafePath) - if err != nil { - return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err} - } - return handle, nil -} - -// OpenInRoot safely opens the provided unsafePath within the root. -// Effectively, OpenInRoot(root, unsafePath) is equivalent to -// -// path, _ := securejoin.SecureJoin(root, unsafePath) -// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC) -// -// But is much safer. The above implementation is unsafe because if an attacker -// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is -// possible for the returned file to be outside of the root. -// -// Note that the returned handle is an O_PATH handle, meaning that only a very -// limited set of operations will work on the handle. This is done to avoid -// accidentally opening an untrusted file that could cause issues (such as a -// disconnected TTY that could cause a DoS, or some other issue). In order to -// use the returned handle, you can "upgrade" it to a proper handle using -// [Reopen]. -func OpenInRoot(root, unsafePath string) (*os.File, error) { - rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - if err != nil { - return nil, err - } - defer rootDir.Close() - return OpenatInRoot(rootDir, unsafePath) -} - -// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd. -// Reopen(file, flags) is effectively equivalent to -// -// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd()) -// os.OpenFile(fdPath, flags|unix.O_CLOEXEC) -// -// But with some extra hardenings to ensure that we are not tricked by a -// maliciously-configured /proc mount. While this attack scenario is not -// common, in container runtimes it is possible for higher-level runtimes to be -// tricked into configuring an unsafe /proc that can be used to attack file -// operations. See [CVE-2019-19921] for more details. -// -// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw -func Reopen(handle *os.File, flags int) (*os.File, error) { - procRoot, err := getProcRoot() - if err != nil { - return nil, err - } - - // We can't operate on /proc/thread-self/fd/$n directly when doing a - // re-open, so we need to open /proc/thread-self/fd and then open a single - // final component. - procFdDir, closer, err := procThreadSelf(procRoot, "fd/") - if err != nil { - return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err) - } - defer procFdDir.Close() - defer closer() - - // Try to detect if there is a mount on top of the magic-link we are about - // to open. If we are using unsafeHostProcRoot(), this could change after - // we check it (and there's nothing we can do about that) but for - // privateProcRoot() this should be guaranteed to be safe (at least since - // Linux 5.12[1], when anonymous mount namespaces were completely isolated - // from external mounts including mount propagation events). - // - // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts - // onto targets that reside on shared mounts"). - fdStr := strconv.Itoa(int(handle.Fd())) - if err := checkSymlinkOvermount(procRoot, procFdDir, fdStr); err != nil { - return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err) - } - - flags |= unix.O_CLOEXEC - // Rather than just wrapping openatFile, open-code it so we can copy - // handle.Name(). - reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0) - if err != nil { - return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err) - } - return os.NewFile(uintptr(reopenFd), handle.Name()), nil -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go deleted file mode 100644 index f7a13e69c..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go +++ /dev/null @@ -1,127 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - "golang.org/x/sys/unix" -) - -var hasOpenat2 = sync_OnceValue(func() bool { - fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT, - }) - if err != nil { - return false - } - _ = unix.Close(fd) - return true -}) - -func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool { - // RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve - // ".." while a mount or rename occurs anywhere on the system. This could - // happen spuriously, or as the result of an attacker trying to mess with - // us during lookup. - // - // In addition, scoped lookups have a "safety check" at the end of - // complete_walk which will return -EXDEV if the final path is not in the - // root. - return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 && - (errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV)) -} - -const scopedLookupMaxRetries = 10 - -func openat2File(dir *os.File, path string, how *unix.OpenHow) (*os.File, error) { - fullPath := dir.Name() + "/" + path - // Make sure we always set O_CLOEXEC. - how.Flags |= unix.O_CLOEXEC - var tries int - for tries < scopedLookupMaxRetries { - fd, err := unix.Openat2(int(dir.Fd()), path, how) - if err != nil { - if scopedLookupShouldRetry(how, err) { - // We retry a couple of times to avoid the spurious errors, and - // if we are being attacked then returning -EAGAIN is the best - // we can do. - tries++ - continue - } - return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err} - } - // If we are using RESOLVE_IN_ROOT, the name we generated may be wrong. - // NOTE: The procRoot code MUST NOT use RESOLVE_IN_ROOT, otherwise - // you'll get infinite recursion here. - if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT { - if actualPath, err := rawProcSelfFdReadlink(fd); err == nil { - fullPath = actualPath - } - } - return os.NewFile(uintptr(fd), fullPath), nil - } - return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: errPossibleAttack} -} - -func lookupOpenat2(root *os.File, unsafePath string, partial bool) (*os.File, string, error) { - if !partial { - file, err := openat2File(root, unsafePath, &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, - }) - return file, "", err - } - return partialLookupOpenat2(root, unsafePath) -} - -// partialLookupOpenat2 is an alternative implementation of -// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a -// handle to the deepest existing child of the requested path within the root. -func partialLookupOpenat2(root *os.File, unsafePath string) (*os.File, string, error) { - // TODO: Implement this as a git-bisect-like binary search. - - unsafePath = filepath.ToSlash(unsafePath) // noop - endIdx := len(unsafePath) - var lastError error - for endIdx > 0 { - subpath := unsafePath[:endIdx] - - handle, err := openat2File(root, subpath, &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, - }) - if err == nil { - // Jump over the slash if we have a non-"" remainingPath. - if endIdx < len(unsafePath) { - endIdx += 1 - } - // We found a subpath! - return handle, unsafePath[endIdx:], lastError - } - if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) { - // That path doesn't exist, let's try the next directory up. - endIdx = strings.LastIndexByte(subpath, '/') - lastError = err - continue - } - return nil, "", fmt.Errorf("open subpath: %w", err) - } - // If we couldn't open anything, the whole subpath is missing. Return a - // copy of the root fd so that the caller doesn't close this one by - // accident. - rootClone, err := dupFile(root) - if err != nil { - return nil, "", err - } - return rootClone, unsafePath, lastError -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go b/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go deleted file mode 100644 index 949fb5f2d..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "os" - "path/filepath" - - "golang.org/x/sys/unix" -) - -func dupFile(f *os.File) (*os.File, error) { - fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0) - if err != nil { - return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err) - } - return os.NewFile(uintptr(fd), f.Name()), nil -} - -func openatFile(dir *os.File, path string, flags int, mode int) (*os.File, error) { - // Make sure we always set O_CLOEXEC. - flags |= unix.O_CLOEXEC - fd, err := unix.Openat(int(dir.Fd()), path, flags, uint32(mode)) - if err != nil { - return nil, &os.PathError{Op: "openat", Path: dir.Name() + "/" + path, Err: err} - } - // All of the paths we use with openatFile(2) are guaranteed to be - // lexically safe, so we can use path.Join here. - fullPath := filepath.Join(dir.Name(), path) - return os.NewFile(uintptr(fd), fullPath), nil -} - -func fstatatFile(dir *os.File, path string, flags int) (unix.Stat_t, error) { - var stat unix.Stat_t - if err := unix.Fstatat(int(dir.Fd()), path, &stat, flags); err != nil { - return stat, &os.PathError{Op: "fstatat", Path: dir.Name() + "/" + path, Err: err} - } - return stat, nil -} - -func readlinkatFile(dir *os.File, path string) (string, error) { - size := 4096 - for { - linkBuf := make([]byte, size) - n, err := unix.Readlinkat(int(dir.Fd()), path, linkBuf) - if err != nil { - return "", &os.PathError{Op: "readlinkat", Path: dir.Name() + "/" + path, Err: err} - } - if n != size { - return string(linkBuf[:n]), nil - } - // Possible truncation, resize the buffer. - size *= 2 - } -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md new file mode 100644 index 000000000..bb95b028c --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md @@ -0,0 +1,35 @@ +## `pathrs-lite` ## + +`github.com/cyphar/filepath-securejoin/pathrs-lite` provides a minimal **pure +Go** implementation of the core bits of [libpathrs][]. This is not intended to +be a complete replacement for libpathrs, instead it is mainly intended to be +useful as a transition tool for existing Go projects. + +`pathrs-lite` also provides a very easy way to switch to `libpathrs` (even for +downstreams where `pathrs-lite` is being used in a third-party package and is +not interested in using CGo). At build time, if you use the `libpathrs` build +tag then `pathrs-lite` will use `libpathrs` directly instead of the pure Go +implementation. The two backends are functionally equivalent (and we have +integration tests to verify this), so this migration should be very easy with +no user-visible impact. + +[libpathrs]: https://github.com/cyphar/libpathrs + +### License ### + +Most of this subpackage is licensed under the Mozilla Public License (version +2.0). For more information, see the top-level [COPYING.md][] and +[LICENSE.MPL-2.0][] files, as well as the individual license headers for each +file. + +``` +Copyright (C) 2024-2025 Aleksa Sarai +Copyright (C) 2024-2025 SUSE LLC + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +``` + +[COPYING.md]: ../COPYING.md +[LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0 diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go new file mode 100644 index 000000000..61411da37 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package pathrs (pathrs-lite) is a less complete pure Go implementation of +// some of the APIs provided by [libpathrs]. +// +// [libpathrs]: https://github.com/cyphar/libpathrs +package pathrs diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go new file mode 100644 index 000000000..595dfbf1a --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Copyright (C) 2025 Aleksa Sarai +// Copyright (C) 2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package assert provides some basic assertion helpers for Go. +package assert + +import ( + "fmt" +) + +// Assert panics if the predicate is false with the provided argument. +func Assert(predicate bool, msg any) { + if !predicate { + panic(msg) + } +} + +// Assertf panics if the predicate is false and formats the message using the +// same formatting as [fmt.Printf]. +// +// [fmt.Printf]: https://pkg.go.dev/fmt#Printf +func Assertf(predicate bool, fmtMsg string, args ...any) { + Assert(predicate, fmt.Sprintf(fmtMsg, args...)) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go new file mode 100644 index 000000000..d0b200f4f --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package internal contains unexported common code for filepath-securejoin. +package internal + +import ( + "errors" + + "golang.org/x/sys/unix" +) + +type xdevErrorish struct { + description string +} + +func (err xdevErrorish) Error() string { return err.description } +func (err xdevErrorish) Is(target error) bool { return target == unix.EXDEV } + +var ( + // ErrPossibleAttack indicates that some attack was detected. + ErrPossibleAttack error = xdevErrorish{"possible attack detected"} + + // ErrPossibleBreakout indicates that during an operation we ended up in a + // state that could be a breakout but we detected it. + ErrPossibleBreakout error = xdevErrorish{"possible breakout detected"} + + // ErrInvalidDirectory indicates an unlinked directory. + ErrInvalidDirectory = errors.New("wandered into deleted directory") + + // ErrDeletedInode indicates an unlinked file (non-directory). + ErrDeletedInode = errors.New("cannot verify path of deleted inode") +) diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go new file mode 100644 index 000000000..091054913 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" +) + +// prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using +// the dir.Fd(). We use -EBADF because in filepath-securejoin we generally +// don't want to allow relative-to-cwd paths. The returned path is an +// *informational* string that describes a reasonable pathname for the given +// *at(2) arguments. You must not use the full path for any actual filesystem +// operations. +func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) { + dirFd, dirPath := -int(unix.EBADF), "." + if dir != nil { + dirFd, dirPath = int(dir.Fd()), dir.Name() + } + if !filepath.IsAbs(path) { + // only prepend the dirfd path for relative paths + path = dirPath + "/" + path + } + // NOTE: If path is "." or "", the returned path won't be filepath.Clean, + // but that's okay since this path is either used for errors (in which case + // a trailing "/" or "/." is important information) or will be + // filepath.Clean'd later (in the case of fd.Openat). + return dirFd, path +} + +// Openat is an [Fd]-based wrapper around unix.Openat. +func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func + dirFd, fullPath := prepareAt(dir, path) + // Make sure we always set O_CLOEXEC. + flags |= unix.O_CLOEXEC + fd, err := unix.Openat(dirFd, path, flags, uint32(mode)) + if err != nil { + return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + // openat is only used with lexically-safe paths so we can use + // filepath.Clean here, and also the path itself is not going to be used + // for actual path operations. + fullPath = filepath.Clean(fullPath) + return os.NewFile(uintptr(fd), fullPath), nil +} + +// Fstatat is an [Fd]-based wrapper around unix.Fstatat. +func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) { + dirFd, fullPath := prepareAt(dir, path) + var stat unix.Stat_t + if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil { + return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return stat, nil +} + +// Faccessat is an [Fd]-based wrapper around unix.Faccessat. +func Faccessat(dir Fd, path string, mode uint32, flags int) error { + dirFd, fullPath := prepareAt(dir, path) + err := unix.Faccessat(dirFd, path, mode, flags) + if err != nil { + err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return err +} + +// Readlinkat is an [Fd]-based wrapper around unix.Readlinkat. +func Readlinkat(dir Fd, path string) (string, error) { + dirFd, fullPath := prepareAt(dir, path) + size := 4096 + for { + linkBuf := make([]byte, size) + n, err := unix.Readlinkat(dirFd, path, linkBuf) + if err != nil { + return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + if n != size { + return string(linkBuf[:n]), nil + } + // Possible truncation, resize the buffer. + size *= 2 + } +} + +const ( + // STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to + // avoid bumping the requirement for a single constant we can just define it + // ourselves. + _STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name + + // We don't care which mount ID we get. The kernel will give us the unique + // one if it is supported. If the kernel doesn't support + // STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask + // will only contain STATX_MNT_ID (if supported). + wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID +) + +var hasStatxMountID = gocompat.SyncOnceValue(func() bool { + var stx unix.Statx_t + err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx) + return err == nil && stx.Mask&wantStatxMntMask != 0 +}) + +// GetMountID gets the mount identifier associated with the fd and path +// combination. It is effectively a wrapper around fetching +// STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the +// kernel doesn't support the feature. +func GetMountID(dir Fd, path string) (uint64, error) { + // If we don't have statx(STATX_MNT_ID*) support, we can't do anything. + if !hasStatxMountID() { + return 0, nil + } + + dirFd, fullPath := prepareAt(dir, path) + + var stx unix.Statx_t + err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx) + if stx.Mask&wantStatxMntMask == 0 { + // It's not a kernel limitation, for some reason we couldn't get a + // mount ID. Assume it's some kind of attack. + err = fmt.Errorf("could not get mount id: %w", err) + } + if err != nil { + return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return stx.Mnt_id, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go new file mode 100644 index 000000000..d2206a386 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Copyright (C) 2025 Aleksa Sarai +// Copyright (C) 2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package fd provides a drop-in interface-based replacement of [*os.File] that +// allows for things like noop-Close wrappers to be used. +// +// [*os.File]: https://pkg.go.dev/os#File +package fd + +import ( + "io" + "os" +) + +// Fd is an interface that mirrors most of the API of [*os.File], allowing you +// to create wrappers that can be used in place of [*os.File]. +// +// [*os.File]: https://pkg.go.dev/os#File +type Fd interface { + io.Closer + Name() string + Fd() uintptr +} + +// Compile-time interface checks. +var ( + _ Fd = (*os.File)(nil) + _ Fd = noClose{} +) + +type noClose struct{ inner Fd } + +func (f noClose) Name() string { return f.inner.Name() } +func (f noClose) Fd() uintptr { return f.inner.Fd() } + +func (f noClose) Close() error { return nil } + +// NopCloser returns an [*os.File]-like object where the [Close] method is now +// a no-op. +// +// Note that for [*os.File] and similar objects, the Go garbage collector will +// still call [Close] on the underlying file unless you use +// [runtime.SetFinalizer] to disable this behaviour. This is up to the caller +// to do (if necessary). +// +// [*os.File]: https://pkg.go.dev/os#File +// [Close]: https://pkg.go.dev/io#Closer +// [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer +func NopCloser(f Fd) Fd { return noClose{inner: f} } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go new file mode 100644 index 000000000..e1ec3c0b8 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "fmt" + "os" + "runtime" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal" +) + +// DupWithName creates a new file descriptor referencing the same underlying +// file, but with the provided name instead of fd.Name(). +func DupWithName(fd Fd, name string) (*os.File, error) { + fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0) + if err != nil { + return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err) + } + runtime.KeepAlive(fd) + return os.NewFile(uintptr(fd2), name), nil +} + +// Dup creates a new file description referencing the same underlying file. +func Dup(fd Fd) (*os.File, error) { + return DupWithName(fd, fd.Name()) +} + +// Fstat is an [Fd]-based wrapper around unix.Fstat. +func Fstat(fd Fd) (unix.Stat_t, error) { + var stat unix.Stat_t + if err := unix.Fstat(int(fd.Fd()), &stat); err != nil { + return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err} + } + runtime.KeepAlive(fd) + return stat, nil +} + +// Fstatfs is an [Fd]-based wrapper around unix.Fstatfs. +func Fstatfs(fd Fd) (unix.Statfs_t, error) { + var statfs unix.Statfs_t + if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil { + return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err} + } + runtime.KeepAlive(fd) + return statfs, nil +} + +// IsDeadInode detects whether the file has been unlinked from a filesystem and +// is thus a "dead inode" from the kernel's perspective. +func IsDeadInode(file Fd) error { + // If the nlink of a file drops to 0, there is an attacker deleting + // directories during our walk, which could result in weird /proc values. + // It's better to error out in this case. + stat, err := Fstat(file) + if err != nil { + return fmt.Errorf("check for dead inode: %w", err) + } + if stat.Nlink == 0 { + err := internal.ErrDeletedInode + if stat.Mode&unix.S_IFMT == unix.S_IFDIR { + err = internal.ErrInvalidDirectory + } + return fmt.Errorf("%w %q", err, file.Name()) + } + return nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go new file mode 100644 index 000000000..77549c7a9 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "os" + "runtime" + + "golang.org/x/sys/unix" +) + +// Fsopen is an [Fd]-based wrapper around unix.Fsopen. +func Fsopen(fsName string, flags int) (*os.File, error) { + // Make sure we always set O_CLOEXEC. + flags |= unix.FSOPEN_CLOEXEC + fd, err := unix.Fsopen(fsName, flags) + if err != nil { + return nil, os.NewSyscallError("fsopen "+fsName, err) + } + return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil +} + +// Fsmount is an [Fd]-based wrapper around unix.Fsmount. +func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) { + // Make sure we always set O_CLOEXEC. + flags |= unix.FSMOUNT_CLOEXEC + fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs) + if err != nil { + return nil, os.NewSyscallError("fsmount "+ctx.Name(), err) + } + return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil +} + +// OpenTree is an [Fd]-based wrapper around unix.OpenTree. +func OpenTree(dir Fd, path string, flags uint) (*os.File, error) { + dirFd, fullPath := prepareAt(dir, path) + // Make sure we always set O_CLOEXEC. + flags |= unix.OPEN_TREE_CLOEXEC + fd, err := unix.OpenTree(dirFd, path, flags) + if err != nil { + return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return os.NewFile(uintptr(fd), fullPath), nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go new file mode 100644 index 000000000..3e937fe3c --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "errors" + "os" + "runtime" + + "golang.org/x/sys/unix" +) + +func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool { + // RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve + // ".." while a mount or rename occurs anywhere on the system. This could + // happen spuriously, or as the result of an attacker trying to mess with + // us during lookup. + // + // In addition, scoped lookups have a "safety check" at the end of + // complete_walk which will return -EXDEV if the final path is not in the + // root. + return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 && + (errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV)) +} + +// This is a fairly arbitrary limit we have just to avoid an attacker being +// able to make us spin in an infinite retry loop -- callers can choose to +// retry on EAGAIN if they prefer. +const scopedLookupMaxRetries = 128 + +// Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry +// logic in case of EAGAIN errors. +func Openat2(dir Fd, path string, how *unix.OpenHow) (*os.File, error) { + dirFd, fullPath := prepareAt(dir, path) + // Make sure we always set O_CLOEXEC. + how.Flags |= unix.O_CLOEXEC + var tries int + for { + fd, err := unix.Openat2(dirFd, path, how) + if err != nil { + if scopedLookupShouldRetry(how, err) && tries < scopedLookupMaxRetries { + // We retry a couple of times to avoid the spurious errors, and + // if we are being attacked then returning -EAGAIN is the best + // we can do. + tries++ + continue + } + return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return os.NewFile(uintptr(fd), fullPath), nil + } +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md new file mode 100644 index 000000000..5dcb6ae00 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md @@ -0,0 +1,10 @@ +## gocompat ## + +This directory contains backports of stdlib functions from later Go versions so +the filepath-securejoin can continue to be used by projects that are stuck with +Go 1.18 support. Note that often filepath-securejoin is added in security +patches for old releases, so avoiding the need to bump Go compiler requirements +is a huge plus to downstreams. + +The source code is licensed under the same license as the Go stdlib. See the +source files for the precise license information. diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go new file mode 100644 index 000000000..4b1803f58 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build linux && go1.20 + +// Copyright (C) 2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gocompat includes compatibility shims (backported from future Go +// stdlib versions) to permit filepath-securejoin to be used with older Go +// versions (often filepath-securejoin is added in security patches for old +// releases, so avoiding the need to bump Go compiler requirements is a huge +// plus to downstreams). +package gocompat diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_go120.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go similarity index 69% rename from vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_go120.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go index 42452bbf9..4a114bd3d 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_go120.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go @@ -1,18 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause //go:build linux && go1.20 // Copyright (C) 2024 SUSE LLC. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package securejoin +package gocompat import ( "fmt" ) -// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except +// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except // that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) // is only guaranteed to give you baseErr. -func wrapBaseError(baseErr, extraErr error) error { +func WrapBaseError(baseErr, extraErr error) error { return fmt.Errorf("%w: %w", extraErr, baseErr) } diff --git a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go similarity index 80% rename from vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_unsupported.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go index e7adca3fd..3061016a6 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/gocompat_errors_unsupported.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go @@ -1,10 +1,12 @@ +// SPDX-License-Identifier: BSD-3-Clause + //go:build linux && !go1.20 // Copyright (C) 2024 SUSE LLC. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package securejoin +package gocompat import ( "fmt" @@ -27,10 +29,10 @@ func (err wrappedError) Error() string { return fmt.Sprintf("%v: %v", err.isError, err.inner) } -// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except +// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except // that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) // is only guaranteed to give you baseErr. -func wrapBaseError(baseErr, extraErr error) error { +func WrapBaseError(baseErr, extraErr error) error { return wrappedError{ inner: baseErr, isError: extraErr, diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go new file mode 100644 index 000000000..d4a938186 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && go1.21 + +// Copyright (C) 2024-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "cmp" + "slices" + "sync" +) + +// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc. +func SlicesDeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S { + return slices.DeleteFunc(slice, delFn) +} + +// SlicesContains is equivalent to Go 1.21's slices.Contains. +func SlicesContains[S ~[]E, E comparable](slice S, val E) bool { + return slices.Contains(slice, val) +} + +// SlicesClone is equivalent to Go 1.21's slices.Clone. +func SlicesClone[S ~[]E, E any](slice S) S { + return slices.Clone(slice) +} + +// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue. +func SyncOnceValue[T any](f func() T) func() T { + return sync.OnceValue(f) +} + +// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues. +func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { + return sync.OnceValues(f) +} + +// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition. +type CmpOrdered = cmp.Ordered + +// CmpCompare is equivalent to Go 1.21's cmp.Compare. +func CmpCompare[T CmpOrdered](x, y T) int { + return cmp.Compare(x, y) +} + +// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters). +func Max2[T CmpOrdered](x, y T) T { + return max(x, y) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go new file mode 100644 index 000000000..0ea6218aa --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && !go1.21 + +// Copyright (C) 2021, 2022 The Go Authors. All rights reserved. +// Copyright (C) 2024-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.BSD file. + +package gocompat + +import ( + "sync" +) + +// These are very minimal implementations of functions that appear in Go 1.21's +// stdlib, included so that we can build on older Go versions. Most are +// borrowed directly from the stdlib, and a few are modified to be "obviously +// correct" without needing to copy too many other helpers. + +// clearSlice is equivalent to Go 1.21's builtin clear. +// Copied from the Go 1.24 stdlib implementation. +func clearSlice[S ~[]E, E any](slice S) { + var zero E + for i := range slice { + slice[i] = zero + } +} + +// slicesIndexFunc is equivalent to Go 1.21's slices.IndexFunc. +// Copied from the Go 1.24 stdlib implementation. +func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int { + for i := range s { + if f(s[i]) { + return i + } + } + return -1 +} + +// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc. +// Copied from the Go 1.24 stdlib implementation. +func SlicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { + i := slicesIndexFunc(s, del) + if i == -1 { + return s + } + // Don't start copying elements until we find one to delete. + for j := i + 1; j < len(s); j++ { + if v := s[j]; !del(v) { + s[i] = v + i++ + } + } + clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC + return s[:i] +} + +// SlicesContains is equivalent to Go 1.21's slices.Contains. +// Similar to the stdlib slices.Contains, except that we don't have +// slices.Index so we need to use slices.IndexFunc for this non-Func helper. +func SlicesContains[S ~[]E, E comparable](s S, v E) bool { + return slicesIndexFunc(s, func(e E) bool { return e == v }) >= 0 +} + +// SlicesClone is equivalent to Go 1.21's slices.Clone. +// Copied from the Go 1.24 stdlib implementation. +func SlicesClone[S ~[]E, E any](s S) S { + // Preserve nil in case it matters. + if s == nil { + return nil + } + return append(S([]E{}), s...) +} + +// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue. +// Copied from the Go 1.25 stdlib implementation. +func SyncOnceValue[T any](f func() T) func() T { + // Use a struct so that there's a single heap allocation. + d := struct { + f func() T + once sync.Once + valid bool + p any + result T + }{ + f: f, + } + return func() T { + d.once.Do(func() { + defer func() { + d.f = nil + d.p = recover() + if !d.valid { + panic(d.p) + } + }() + d.result = d.f() + d.valid = true + }) + if !d.valid { + panic(d.p) + } + return d.result + } +} + +// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues. +// Copied from the Go 1.25 stdlib implementation. +func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { + // Use a struct so that there's a single heap allocation. + d := struct { + f func() (T1, T2) + once sync.Once + valid bool + p any + r1 T1 + r2 T2 + }{ + f: f, + } + return func() (T1, T2) { + d.once.Do(func() { + defer func() { + d.f = nil + d.p = recover() + if !d.valid { + panic(d.p) + } + }() + d.r1, d.r2 = d.f() + d.valid = true + }) + if !d.valid { + panic(d.p) + } + return d.r1, d.r2 + } +} + +// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition. +// Copied from the Go 1.25 stdlib implementation. +type CmpOrdered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | + ~float32 | ~float64 | + ~string +} + +// isNaN reports whether x is a NaN without requiring the math package. +// This will always return false if T is not floating-point. +// Copied from the Go 1.25 stdlib implementation. +func isNaN[T CmpOrdered](x T) bool { + return x != x +} + +// CmpCompare is equivalent to Go 1.21's cmp.Compare. +// Copied from the Go 1.25 stdlib implementation. +func CmpCompare[T CmpOrdered](x, y T) int { + xNaN := isNaN(x) + yNaN := isNaN(y) + if xNaN { + if yNaN { + return 0 + } + return -1 + } + if yNaN { + return +1 + } + if x < y { + return -1 + } + if x > y { + return +1 + } + return 0 +} + +// Max2 is equivalent to Go 1.21's max builtin for two parameters. +func Max2[T CmpOrdered](x, y T) T { + m := x + if y > m { + m = y + } + return m +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go new file mode 100644 index 000000000..2ddb71e84 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package gopathrs is a less complete pure Go implementation of some of the +// APIs provided by [libpathrs]. +// +// [libpathrs]: https://github.com/cyphar/libpathrs +package gopathrs diff --git a/vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go similarity index 85% rename from vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go index be81e498d..56480f0ce 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go @@ -1,10 +1,15 @@ +// SPDX-License-Identifier: MPL-2.0 + //go:build linux -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. -package securejoin +package gopathrs import ( "errors" @@ -15,6 +20,12 @@ import ( "strings" "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/internal/consts" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" ) type symlinkStackEntry struct { @@ -112,12 +123,12 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro return nil } // Split the link target and clean up any "" parts. - linkTargetParts := slices_DeleteFunc( + linkTargetParts := gocompat.SlicesDeleteFunc( strings.Split(linkTarget, "/"), func(part string) bool { return part == "" || part == "." }) // Copy the directory so the caller doesn't close our copy. - dirCopy, err := dupFile(dir) + dirCopy, err := fd.Dup(dir) if err != nil { return err } @@ -155,15 +166,15 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) { return tailEntry.dir, tailEntry.remainingPath, true } -// partialLookupInRoot tries to lookup as much of the request path as possible +// PartialLookupInRoot tries to lookup as much of the request path as possible // within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing // component of the requested path, returning a file handle to the final // existing component and a string containing the remaining path components. -func partialLookupInRoot(root *os.File, unsafePath string) (*os.File, string, error) { +func PartialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) { return lookupInRoot(root, unsafePath, true) } -func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) { +func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) { handle, remainingPath, err := lookupInRoot(root, unsafePath, false) if remainingPath != "" && err == nil { // should never happen @@ -174,7 +185,7 @@ func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) { return handle, err } -func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) { +func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) { unsafePath = filepath.ToSlash(unsafePath) // noop // This is very similar to SecureJoin, except that we operate on the @@ -182,20 +193,20 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi // managed open, along with the remaining path components not opened. // Try to use openat2 if possible. - if hasOpenat2() { + if linux.HasOpenat2() { return lookupOpenat2(root, unsafePath, partial) } // Get the "actual" root path from /proc/self/fd. This is necessary if the // root is some magic-link like /proc/$pid/root, in which case we want to - // make sure when we do checkProcSelfFdPath that we are using the correct - // root path. - logicalRootPath, err := procSelfFdReadlink(root) + // make sure when we do procfs.CheckProcSelfFdPath that we are using the + // correct root path. + logicalRootPath, err := procfs.ProcSelfFdReadlink(root) if err != nil { return nil, "", fmt.Errorf("get real root path: %w", err) } - currentDir, err := dupFile(root) + currentDir, err := fd.Dup(root) if err != nil { return nil, "", fmt.Errorf("clone root fd: %w", err) } @@ -260,7 +271,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err) } // Jump to root. - rootClone, err := dupFile(root) + rootClone, err := fd.Dup(root) if err != nil { return nil, "", fmt.Errorf("clone root fd: %w", err) } @@ -271,21 +282,21 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi } // Try to open the next component. - nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - switch { - case err == nil: + nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + switch err { + case nil: st, err := nextDir.Stat() if err != nil { _ = nextDir.Close() return nil, "", fmt.Errorf("stat component %q: %w", part, err) } - switch st.Mode() & os.ModeType { + switch st.Mode() & os.ModeType { //nolint:exhaustive // just a glorified if statement case os.ModeSymlink: // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See // Linux commit 65cfc6722361 ("readlinkat(), fchownat() and // fstatat() with empty relative pathnames"). - linkDest, err := readlinkatFile(nextDir, "") + linkDest, err := fd.Readlinkat(nextDir, "") // We don't need the handle anymore. _ = nextDir.Close() if err != nil { @@ -293,7 +304,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi } linksWalked++ - if linksWalked > maxSymlinkLimit { + if linksWalked > consts.MaxSymlinkLimit { return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP} } @@ -307,7 +318,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi // Absolute symlinks reset any work we've already done. if path.IsAbs(linkDest) { // Jump to root. - rootClone, err := dupFile(root) + rootClone, err := fd.Dup(root) if err != nil { return nil, "", fmt.Errorf("clone root fd: %w", err) } @@ -335,12 +346,12 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi // rename or mount on the system. if part == ".." { // Make sure the root hasn't moved. - if err := checkProcSelfFdPath(logicalRootPath, root); err != nil { + if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil { return nil, "", fmt.Errorf("root path moved during lookup: %w", err) } // Make sure the path is what we expect. fullPath := logicalRootPath + nextPath - if err := checkProcSelfFdPath(fullPath, currentDir); err != nil { + if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil { return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err) } } @@ -371,7 +382,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi // context of openat2, a trailing slash and a trailing "/." are completely // equivalent. if strings.HasSuffix(unsafePath, "/") { - nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) if err != nil { if !partial { _ = currentDir.Close() diff --git a/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/mkdir_linux.go similarity index 74% rename from vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/mkdir_linux.go index a17ae3b03..21a5593f4 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/mkdir_linux.go @@ -1,10 +1,15 @@ +// SPDX-License-Identifier: MPL-2.0 + //go:build linux -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. -package securejoin +package gopathrs import ( "errors" @@ -14,13 +19,17 @@ import ( "strings" "golang.org/x/sys/unix" -) -var ( - errInvalidMode = errors.New("invalid permission mode") - errPossibleAttack = errors.New("possible attack detected") + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" ) +// ErrInvalidMode is returned from [MkdirAll] when the requested mode is +// invalid. +var ErrInvalidMode = errors.New("invalid permission mode") + // modePermExt is like os.ModePerm except that it also includes the set[ug]id // and sticky bits. const modePermExt = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky @@ -39,11 +48,11 @@ func toUnixMode(mode os.FileMode) (uint32, error) { } // We don't allow file type bits. if mode&os.ModeType != 0 { - return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode) + return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", ErrInvalidMode, mode, mode) } // We don't allow other unknown modes. if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 { - return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode) + return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", ErrInvalidMode, mode, mode) } return sysMode, nil } @@ -66,6 +75,8 @@ func toUnixMode(mode os.FileMode) (uint32, error) { // a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after // doing [MkdirAll]. If you intend to open the directory after creating it, you // should use MkdirAllHandle. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) { unixMode, err := toUnixMode(mode) if err != nil { @@ -76,11 +87,11 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F // users it seems more prudent to return an error so users notice that // these bits will not be set. if unixMode&^0o1777 != 0 { - return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode) + return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", ErrInvalidMode, mode) } // Try to open as much of the path as possible. - currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath) + currentDir, remainingPath, err := PartialLookupInRoot(root, unsafePath) defer func() { if Err != nil { _ = currentDir.Close() @@ -102,24 +113,24 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F // // This is mostly a quality-of-life check, because mkdir will simply fail // later if the attacker deletes the tree after this check. - if err := isDeadInode(currentDir); err != nil { + if err := fd.IsDeadInode(currentDir); err != nil { return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err) } // Re-open the path to match the O_DIRECTORY reopen loop later (so that we // always return a non-O_PATH handle). We also check that we actually got a // directory. - if reopenDir, err := Reopen(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) { + if reopenDir, err := procfs.ReopenFd(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) { return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR) } else if err != nil { return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err) - } else { + } else { //nolint:revive // indent-error-flow lint doesn't make sense here _ = currentDir.Close() currentDir = reopenDir } remainingParts := strings.Split(remainingPath, string(filepath.Separator)) - if slices_Contains(remainingParts, "..") { + if gocompat.SlicesContains(remainingParts, "..") { // The path contained ".." components after the end of the "real" // components. We could try to safely resolve ".." here but that would // add a bunch of extra logic for something that it's not clear even @@ -150,12 +161,12 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) { err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err} // Make the error a bit nicer if the directory is dead. - if deadErr := isDeadInode(currentDir); deadErr != nil { + if deadErr := fd.IsDeadInode(currentDir); deadErr != nil { // TODO: Once we bump the minimum Go version to 1.20, we can use // multiple %w verbs for this wrapping. For now we need to use a // compatibility shim for older Go versions. - //err = fmt.Errorf("%w (%w)", err, deadErr) - err = wrapBaseError(err, deadErr) + // err = fmt.Errorf("%w (%w)", err, deadErr) + err = gocompat.WrapBaseError(err, deadErr) } return nil, err } @@ -163,13 +174,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F // Get a handle to the next component. O_DIRECTORY means we don't need // to use O_PATH. var nextDir *os.File - if hasOpenat2() { - nextDir, err = openat2File(currentDir, part, &unix.OpenHow{ + if linux.HasOpenat2() { + nextDir, err = openat2(currentDir, part, &unix.OpenHow{ Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, }) } else { - nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + nextDir, err = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) } if err != nil { return nil, err @@ -199,38 +210,3 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F } return currentDir, nil } - -// MkdirAll is a race-safe alternative to the [os.MkdirAll] function, -// where the new directory is guaranteed to be within the root directory (if an -// attacker can move directories from inside the root to outside the root, the -// created directory tree might be outside of the root but the key constraint -// is that at no point will we walk outside of the directory tree we are -// creating). -// -// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to -// -// path, _ := securejoin.SecureJoin(root, unsafePath) -// err := os.MkdirAll(path, mode) -// -// But is much safer. The above implementation is unsafe because if an attacker -// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is -// possible for MkdirAll to resolve unsafe symlink components and create -// directories outside of the root. -// -// If you plan to open the directory after you have created it or want to use -// an open directory handle as the root, you should use [MkdirAllHandle] instead. -// This function is a wrapper around [MkdirAllHandle]. -func MkdirAll(root, unsafePath string, mode os.FileMode) error { - rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - if err != nil { - return err - } - defer rootDir.Close() - - f, err := MkdirAllHandle(rootDir, unsafePath, mode) - if err != nil { - return err - } - _ = f.Close() - return nil -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go new file mode 100644 index 000000000..cd9632a95 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package gopathrs + +import ( + "os" +) + +// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided +// using an *[os.File] handle, to ensure that the correct root directory is used. +func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { + handle, err := completeLookupInRoot(root, unsafePath) + if err != nil { + return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err} + } + return handle, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go new file mode 100644 index 000000000..b80ecd089 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package gopathrs + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/procfs" +) + +func openat2(dir fd.Fd, path string, how *unix.OpenHow) (*os.File, error) { + file, err := fd.Openat2(dir, path, how) + if err != nil { + return nil, err + } + // If we are using RESOLVE_IN_ROOT, the name we generated may be wrong. + if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT { + if actualPath, err := procfs.ProcSelfFdReadlink(file); err == nil { + // TODO: Ideally we would not need to dup the fd, but you cannot + // easily just swap an *os.File with one from the same fd + // (the GC will close the old one, and you cannot clear the + // finaliser easily because it is associated with an internal + // field of *os.File not *os.File itself). + newFile, err := fd.DupWithName(file, actualPath) + if err != nil { + return nil, err + } + file = newFile + } + } + return file, nil +} + +func lookupOpenat2(root fd.Fd, unsafePath string, partial bool) (*os.File, string, error) { + if !partial { + file, err := openat2(root, unsafePath, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, + }) + return file, "", err + } + return partialLookupOpenat2(root, unsafePath) +} + +// partialLookupOpenat2 is an alternative implementation of +// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a +// handle to the deepest existing child of the requested path within the root. +func partialLookupOpenat2(root fd.Fd, unsafePath string) (*os.File, string, error) { + // TODO: Implement this as a git-bisect-like binary search. + + unsafePath = filepath.ToSlash(unsafePath) // noop + endIdx := len(unsafePath) + var lastError error + for endIdx > 0 { + subpath := unsafePath[:endIdx] + + handle, err := openat2(root, subpath, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, + }) + if err == nil { + // Jump over the slash if we have a non-"" remainingPath. + if endIdx < len(unsafePath) { + endIdx++ + } + // We found a subpath! + return handle, unsafePath[endIdx:], lastError + } + if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) { + // That path doesn't exist, let's try the next directory up. + endIdx = strings.LastIndexByte(subpath, '/') + lastError = err + continue + } + return nil, "", fmt.Errorf("open subpath: %w", err) + } + // If we couldn't open anything, the whole subpath is missing. Return a + // copy of the root fd so that the caller doesn't close this one by + // accident. + rootClone, err := fd.Dup(root) + if err != nil { + return nil, "", err + } + return rootClone, unsafePath, lastError +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go new file mode 100644 index 000000000..cb6de4186 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Copyright (C) 2022 The Go Authors. All rights reserved. +// Copyright (C) 2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.BSD file. + +// The parsing logic is very loosely based on the Go stdlib's +// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks +// a bit like runc's libcontainer/system/kernelversion. +// +// TODO(cyphar): This API has been copied around to a lot of different projects +// (Docker, containerd, runc, and now filepath-securejoin) -- maybe we should +// put it in a separate project? + +// Package kernelversion provides a simple mechanism for checking whether the +// running kernel is at least as new as some baseline kernel version. This is +// often useful when checking for features that would be too complicated to +// test support for (or in cases where we know that some kernel features in +// backport-heavy kernels are broken and need to be avoided). +package kernelversion + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" +) + +// KernelVersion is a numeric representation of the key numerical elements of a +// kernel version (for instance, "4.1.2-default-1" would be represented as +// KernelVersion{4, 1, 2}). +type KernelVersion []uint64 + +func (kver KernelVersion) String() string { + var str strings.Builder + for idx, elem := range kver { + if idx != 0 { + _, _ = str.WriteRune('.') + } + _, _ = str.WriteString(strconv.FormatUint(elem, 10)) + } + return str.String() +} + +var errInvalidKernelVersion = errors.New("invalid kernel version") + +// parseKernelVersion parses a string and creates a KernelVersion based on it. +func parseKernelVersion(kverStr string) (KernelVersion, error) { + kver := make(KernelVersion, 1, 3) + for idx, ch := range kverStr { + if '0' <= ch && ch <= '9' { + v := &kver[len(kver)-1] + *v = (*v * 10) + uint64(ch-'0') + } else { + if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] { + // "." must be preceded by a digit while in version section + return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr) + } + if ch != '.' { + break + } + kver = append(kver, 0) + } + } + if len(kver) < 2 { + return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr) + } + return kver, nil +} + +// getKernelVersion gets the current kernel version. +var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) { + var uts unix.Utsname + if err := unix.Uname(&uts); err != nil { + return nil, err + } + // Remove the \x00 from the release. + release := uts.Release[:] + return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)])) +}) + +// GreaterEqualThan returns true if the the host kernel version is greater than +// or equal to the provided [KernelVersion]. When doing this comparison, any +// non-numerical suffixes of the host kernel version are ignored. +// +// If the number of components provided is not equal to the number of numerical +// components of the host kernel version, any missing components are treated as +// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the +// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the +// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will +// return false (because the host version will be treated as "4.0"). +func GreaterEqualThan(wantKver KernelVersion) (bool, error) { + hostKver, err := getKernelVersion() + if err != nil { + return false, err + } + + // Pad out the kernel version lengths to match one another. + cmpLen := gocompat.Max2(len(hostKver), len(wantKver)) + hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...) + wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...) + + for i := 0; i < cmpLen; i++ { + switch gocompat.CmpCompare(hostKver[i], wantKver[i]) { + case -1: + // host < want + return false, nil + case +1: + // host > want + return true, nil + case 0: + continue + } + } + // equal version values + return true, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go new file mode 100644 index 000000000..4635714f6 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package linux returns information about what features are supported on the +// running kernel. +package linux diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go new file mode 100644 index 000000000..b29905bff --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package linux + +import ( + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion" +) + +// HasNewMountAPI returns whether the new fsopen(2) mount API is supported on +// the running kernel. +var HasNewMountAPI = gocompat.SyncOnceValue(func() bool { + // All of the pieces of the new mount API we use (fsopen, fsconfig, + // fsmount, open_tree) were added together in Linux 5.2[1,2], so we can + // just check for one of the syscalls and the others should also be + // available. + // + // Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE. + // This is equivalent to openat(2), but tells us if open_tree is + // available (and thus all of the other basic new mount API syscalls). + // open_tree(2) is most light-weight syscall to test here. + // + // [1]: merge commit 400913252d09 + // [2]: + fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC) + if err != nil { + return false + } + _ = unix.Close(fd) + + // RHEL 8 has a backport of fsopen(2) that appears to have some very + // difficult to debug performance pathology. As such, it seems prudent to + // simply reject pre-5.2 kernels. + isNotBackport, _ := kernelversion.GreaterEqualThan(kernelversion.KernelVersion{5, 2}) + return isNotBackport +}) diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go new file mode 100644 index 000000000..399609dc3 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package linux + +import ( + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" +) + +// HasOpenat2 returns whether openat2(2) is supported on the running kernel. +var HasOpenat2 = gocompat.SyncOnceValue(func() bool { + fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT, + }) + if err != nil { + return false + } + _ = unix.Close(fd) + return true +}) diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go new file mode 100644 index 000000000..21e0a62e8 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package procfs provides a safe API for operating on /proc on Linux. Note +// that this is the *internal* procfs API, mainy needed due to Go's +// restrictions on cyclic dependencies and its incredibly minimal visibility +// system without making a separate internal/ package. +package procfs + +import ( + "errors" + "fmt" + "io" + "os" + "runtime" + "strconv" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" +) + +// The kernel guarantees that the root inode of a procfs mount has an +// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO. +const ( + procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC + procRootIno = 1 // PROC_ROOT_INO +) + +// verifyProcHandle checks that the handle is from a procfs filesystem. +// Contrast this to [verifyProcRoot], which also verifies that the handle is +// the root of a procfs mount. +func verifyProcHandle(procHandle fd.Fd) error { + if statfs, err := fd.Fstatfs(procHandle); err != nil { + return err + } else if statfs.Type != procSuperMagic { + return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type) + } + return nil +} + +// verifyProcRoot verifies that the handle is the root of a procfs filesystem. +// Contrast this to [verifyProcHandle], which only verifies if the handle is +// some file on procfs (regardless of what file it is). +func verifyProcRoot(procRoot fd.Fd) error { + if err := verifyProcHandle(procRoot); err != nil { + return err + } + if stat, err := fd.Fstat(procRoot); err != nil { + return err + } else if stat.Ino != procRootIno { + return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino) + } + return nil +} + +type procfsFeatures struct { + // hasSubsetPid was added in Linux 5.8, along with hidepid=ptraceable (and + // string-based hidepid= values). Before this patchset, it was not really + // safe to try to modify procfs superblock flags because the superblock was + // shared -- so if this feature is not available, **you should not set any + // superblock flags**. + // + // 6814ef2d992a ("proc: add option to mount only a pids subset") + // fa10fed30f25 ("proc: allow to mount many instances of proc in one pid namespace") + // 24a71ce5c47f ("proc: instantiate only pids that we can ptrace on 'hidepid=4' mount option") + // 1c6c4d112e81 ("proc: use human-readable values for hidepid") + // 9ff7258575d5 ("Merge branch 'proc-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace") + hasSubsetPid bool +} + +var getProcfsFeatures = gocompat.SyncOnceValue(func() procfsFeatures { + if !linux.HasNewMountAPI() { + return procfsFeatures{} + } + procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC) + if err != nil { + return procfsFeatures{} + } + defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here + + return procfsFeatures{ + hasSubsetPid: unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") == nil, + } +}) + +func newPrivateProcMount(subset bool) (_ *Handle, Err error) { + procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC) + if err != nil { + return nil, err + } + defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here + + if subset && getProcfsFeatures().hasSubsetPid { + // Try to configure hidepid=ptraceable,subset=pid if possible, but + // ignore errors. + _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable") + _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") + } + + // Get an actual handle. + if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil { + return nil, os.NewSyscallError("fsconfig create procfs", err) + } + // TODO: Output any information from the fscontext log to debug logs. + procRoot, err := fd.Fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID) + if err != nil { + return nil, err + } + defer func() { + if Err != nil { + _ = procRoot.Close() + } + }() + return newHandle(procRoot) +} + +func clonePrivateProcMount() (_ *Handle, Err error) { + // Try to make a clone without using AT_RECURSIVE if we can. If this works, + // we can be sure there are no over-mounts and so if the root is valid then + // we're golden. Otherwise, we have to deal with over-mounts. + procRoot, err := fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE) + if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procRoot) { + procRoot, err = fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE) + } + if err != nil { + return nil, fmt.Errorf("creating a detached procfs clone: %w", err) + } + defer func() { + if Err != nil { + _ = procRoot.Close() + } + }() + return newHandle(procRoot) +} + +func privateProcRoot(subset bool) (*Handle, error) { + if !linux.HasNewMountAPI() || hookForceGetProcRootUnsafe() { + return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP) + } + // Try to create a new procfs mount from scratch if we can. This ensures we + // can get a procfs mount even if /proc is fake (for whatever reason). + procRoot, err := newPrivateProcMount(subset) + if err != nil || hookForcePrivateProcRootOpenTree(procRoot) { + // Try to clone /proc then... + procRoot, err = clonePrivateProcMount() + } + return procRoot, err +} + +func unsafeHostProcRoot() (_ *Handle, Err error) { + procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + defer func() { + if Err != nil { + _ = procRoot.Close() + } + }() + return newHandle(procRoot) +} + +// Handle is a wrapper around an *os.File handle to "/proc", which can be used +// to do further procfs-related operations in a safe way. +type Handle struct { + Inner fd.Fd + // Does this handle have subset=pid set? + isSubset bool +} + +func newHandle(procRoot fd.Fd) (*Handle, error) { + if err := verifyProcRoot(procRoot); err != nil { + // This is only used in methods that + _ = procRoot.Close() + return nil, err + } + proc := &Handle{Inner: procRoot} + // With subset=pid we can be sure that /proc/uptime will not exist. + if err := fd.Faccessat(proc.Inner, "uptime", unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil { + proc.isSubset = errors.Is(err, os.ErrNotExist) + } + return proc, nil +} + +// Close closes the underlying file for the Handle. +func (proc *Handle) Close() error { return proc.Inner.Close() } + +var getCachedProcRoot = gocompat.SyncOnceValue(func() *Handle { + procRoot, err := getProcRoot(true) + if err != nil { + return nil // just don't cache if we see an error + } + if !procRoot.isSubset { + return nil // we only cache verified subset=pid handles + } + + // Disarm (*Handle).Close() to stop someone from accidentally closing + // the global handle. + procRoot.Inner = fd.NopCloser(procRoot.Inner) + return procRoot +}) + +// OpenProcRoot tries to open a "safer" handle to "/proc". +func OpenProcRoot() (*Handle, error) { + if proc := getCachedProcRoot(); proc != nil { + return proc, nil + } + return getProcRoot(true) +} + +// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or +// masked paths (but also without "subset=pid"). +func OpenUnsafeProcRoot() (*Handle, error) { return getProcRoot(false) } + +func getProcRoot(subset bool) (*Handle, error) { + proc, err := privateProcRoot(subset) + if err != nil { + // Fall back to using a /proc handle if making a private mount failed. + // If we have openat2, at least we can avoid some kinds of over-mount + // attacks, but without openat2 there's not much we can do. + proc, err = unsafeHostProcRoot() + } + return proc, err +} + +var hasProcThreadSelf = gocompat.SyncOnceValue(func() bool { + return unix.Access("/proc/thread-self/", unix.F_OK) == nil +}) + +var errUnsafeProcfs = errors.New("unsafe procfs detected") + +// lookup is a very minimal wrapper around [procfsLookupInRoot] which is +// intended to be called from the external API. +func (proc *Handle) lookup(subpath string) (*os.File, error) { + handle, err := procfsLookupInRoot(proc.Inner, subpath) + if err != nil { + return nil, err + } + return handle, nil +} + +// procfsBase is an enum indicating the prefix of a subpath in operations +// involving [Handle]s. +type procfsBase string + +const ( + // ProcRoot refers to the root of the procfs (i.e., "/proc/"). + ProcRoot procfsBase = "/proc" + // ProcSelf refers to the current process' subdirectory (i.e., + // "/proc/self/"). + ProcSelf procfsBase = "/proc/self" + // ProcThreadSelf refers to the current thread's subdirectory (i.e., + // "/proc/thread-self/"). In multi-threaded programs (i.e., all Go + // programs) where one thread has a different CLONE_FS, it is possible for + // "/proc/self" to point the wrong thread and so "/proc/thread-self" may be + // necessary. Note that on pre-3.17 kernels, "/proc/thread-self" doesn't + // exist and so a fallback will be used in that case. + ProcThreadSelf procfsBase = "/proc/thread-self" + // TODO: Switch to an interface setup so we can have a more type-safe + // version of ProcPid and remove the need to worry about invalid string + // values. +) + +// prefix returns a prefix that can be used with the given [Handle]. +func (base procfsBase) prefix(proc *Handle) (string, error) { + switch base { + case ProcRoot: + return ".", nil + case ProcSelf: + return "self", nil + case ProcThreadSelf: + threadSelf := "thread-self" + if !hasProcThreadSelf() || hookForceProcSelfTask() { + // Pre-3.17 kernels don't have /proc/thread-self, so do it + // manually. + threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) + if err := fd.Faccessat(proc.Inner, threadSelf, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() { + // In this case, we running in a pid namespace that doesn't + // match the /proc mount we have. This can happen inside runc. + // + // Unfortunately, there is no nice way to get the correct TID + // to use here because of the age of the kernel, so we have to + // just use /proc/self and hope that it works. + threadSelf = "self" + } + } + return threadSelf, nil + } + return "", fmt.Errorf("invalid procfs base %q", base) +} + +// ProcThreadSelfCloser is a callback that needs to be called when you are done +// operating on an [os.File] fetched using [ProcThreadSelf]. +// +// [os.File]: https://pkg.go.dev/os#File +type ProcThreadSelfCloser func() + +// open is the core lookup operation for [Handle]. It returns a handle to +// "/proc//". If the returned [ProcThreadSelfCloser] is non-nil, +// you should call it after you are done interacting with the returned handle. +// +// In general you should use prefer to use the other helpers, as they remove +// the need to interact with [procfsBase] and do not return a nil +// [ProcThreadSelfCloser] for [procfsBase] values other than [ProcThreadSelf] +// where it is necessary. +func (proc *Handle) open(base procfsBase, subpath string) (_ *os.File, closer ProcThreadSelfCloser, Err error) { + prefix, err := base.prefix(proc) + if err != nil { + return nil, nil, err + } + subpath = prefix + "/" + subpath + + switch base { + case ProcRoot: + file, err := proc.lookup(subpath) + if errors.Is(err, os.ErrNotExist) { + // The Handle handle in use might be a subset=pid one, which will + // result in spurious errors. In this case, just open a temporary + // unmasked procfs handle for this operation. + proc, err2 := OpenUnsafeProcRoot() // !subset=pid + if err2 != nil { + return nil, nil, err + } + defer proc.Close() //nolint:errcheck // close failures aren't critical here + + file, err = proc.lookup(subpath) + } + return file, nil, err + + case ProcSelf: + file, err := proc.lookup(subpath) + return file, nil, err + + case ProcThreadSelf: + // We need to lock our thread until the caller is done with the handle + // because between getting the handle and using it we could get + // interrupted by the Go runtime and hit the case where the underlying + // thread is swapped out and the original thread is killed, resulting + // in pull-your-hair-out-hard-to-debug issues in the caller. + runtime.LockOSThread() + defer func() { + if Err != nil { + runtime.UnlockOSThread() + closer = nil + } + }() + + file, err := proc.lookup(subpath) + return file, runtime.UnlockOSThread, err + } + // should never be reached + return nil, nil, fmt.Errorf("[internal error] invalid procfs base %q", base) +} + +// OpenThreadSelf returns a handle to "/proc/thread-self/" (or an +// equivalent handle on older kernels where "/proc/thread-self" doesn't exist). +// Once finished with the handle, you must call the returned closer function +// (runtime.UnlockOSThread). You must not pass the returned *os.File to other +// Go threads or use the handle after calling the closer. +func (proc *Handle) OpenThreadSelf(subpath string) (_ *os.File, _ ProcThreadSelfCloser, Err error) { + return proc.open(ProcThreadSelf, subpath) +} + +// OpenSelf returns a handle to /proc/self/. +func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { + file, closer, err := proc.open(ProcSelf, subpath) + assert.Assert(closer == nil, "closer for ProcSelf must be nil") + return file, err +} + +// OpenRoot returns a handle to /proc/. +func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { + file, closer, err := proc.open(ProcRoot, subpath) + assert.Assert(closer == nil, "closer for ProcRoot must be nil") + return file, err +} + +// OpenPid returns a handle to /proc/$pid/ (pid can be a pid or tid). +// This is mainly intended for usage when operating on other processes. +func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { + return proc.OpenRoot(strconv.Itoa(pid) + "/" + subpath) +} + +// checkSubpathOvermount checks if the dirfd and path combination is on the +// same mount as the given root. +func checkSubpathOvermount(root, dir fd.Fd, path string) error { + // Get the mntID of our procfs handle. + expectedMountID, err := fd.GetMountID(root, "") + if err != nil { + return fmt.Errorf("get root mount id: %w", err) + } + // Get the mntID of the target magic-link. + gotMountID, err := fd.GetMountID(dir, path) + if err != nil { + return fmt.Errorf("get subpath mount id: %w", err) + } + // As long as the directory mount is alive, even with wrapping mount IDs, + // we would expect to see a different mount ID here. (Of course, if we're + // using unsafeHostProcRoot() then an attaker could change this after we + // did this check.) + if expectedMountID != gotMountID { + return fmt.Errorf("%w: subpath %s/%s has an overmount obscuring the real path (mount ids do not match %d != %d)", + errUnsafeProcfs, dir.Name(), path, expectedMountID, gotMountID) + } + return nil +} + +// Readlink performs a readlink operation on "/proc//" in a way +// that should be free from race attacks. This is most commonly used to get the +// real path of a file by looking at "/proc/self/fd/$n", with the same safety +// protections as [Open] (as well as some additional checks against +// overmounts). +func (proc *Handle) Readlink(base procfsBase, subpath string) (string, error) { + link, closer, err := proc.open(base, subpath) + if closer != nil { + defer closer() + } + if err != nil { + return "", fmt.Errorf("get safe %s/%s handle: %w", base, subpath, err) + } + defer link.Close() //nolint:errcheck // close failures aren't critical here + + // Try to detect if there is a mount on top of the magic-link. This should + // be safe in general (a mount on top of the path afterwards would not + // affect the handle itself) and will definitely be safe if we are using + // privateProcRoot() (at least since Linux 5.12[1], when anonymous mount + // namespaces were completely isolated from external mounts including mount + // propagation events). + // + // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts + // onto targets that reside on shared mounts"). + if err := checkSubpathOvermount(proc.Inner, link, ""); err != nil { + return "", fmt.Errorf("check safety of %s/%s magiclink: %w", base, subpath, err) + } + + // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit + // 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty + // relative pathnames"). + return fd.Readlinkat(link, "") +} + +// ProcSelfFdReadlink gets the real path of the given file by looking at +// readlink(/proc/thread-self/fd/$n). +// +// This is just a wrapper around [Handle.Readlink]. +func ProcSelfFdReadlink(fd fd.Fd) (string, error) { + procRoot, err := OpenProcRoot() // subset=pid + if err != nil { + return "", err + } + defer procRoot.Close() //nolint:errcheck // close failures aren't critical here + + fdPath := "fd/" + strconv.Itoa(int(fd.Fd())) + return procRoot.Readlink(ProcThreadSelf, fdPath) +} + +// CheckProcSelfFdPath returns whether the given file handle matches the +// expected path. (This is inherently racy.) +func CheckProcSelfFdPath(path string, file fd.Fd) error { + if err := fd.IsDeadInode(file); err != nil { + return err + } + actualPath, err := ProcSelfFdReadlink(file) + if err != nil { + return fmt.Errorf("get path of handle: %w", err) + } + if actualPath != path { + return fmt.Errorf("%w: handle path %q doesn't match expected path %q", internal.ErrPossibleBreakout, actualPath, path) + } + return nil +} + +// ReopenFd takes an existing file descriptor and "re-opens" it through +// /proc/thread-self/fd/. This allows for O_PATH file descriptors to be +// upgraded to regular file descriptors, as well as changing the open mode of a +// regular file descriptor. Some filesystems have unique handling of open(2) +// which make this incredibly useful (such as /dev/ptmx). +func ReopenFd(handle fd.Fd, flags int) (*os.File, error) { + procRoot, err := OpenProcRoot() // subset=pid + if err != nil { + return nil, err + } + defer procRoot.Close() //nolint:errcheck // close failures aren't critical here + + // We can't operate on /proc/thread-self/fd/$n directly when doing a + // re-open, so we need to open /proc/thread-self/fd and then open a single + // final component. + procFdDir, closer, err := procRoot.OpenThreadSelf("fd/") + if err != nil { + return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err) + } + defer procFdDir.Close() //nolint:errcheck // close failures aren't critical here + defer closer() + + // Try to detect if there is a mount on top of the magic-link we are about + // to open. If we are using unsafeHostProcRoot(), this could change after + // we check it (and there's nothing we can do about that) but for + // privateProcRoot() this should be guaranteed to be safe (at least since + // Linux 5.12[1], when anonymous mount namespaces were completely isolated + // from external mounts including mount propagation events). + // + // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts + // onto targets that reside on shared mounts"). + fdStr := strconv.Itoa(int(handle.Fd())) + if err := checkSubpathOvermount(procRoot.Inner, procFdDir, fdStr); err != nil { + return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err) + } + + flags |= unix.O_CLOEXEC + // Rather than just wrapping fd.Openat, open-code it so we can copy + // handle.Name(). + reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0) + if err != nil { + return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err) + } + return os.NewFile(uintptr(reopenFd), handle.Name()), nil +} + +// Test hooks used in the procfs tests to verify that the fallback logic works. +// See testing_mocks_linux_test.go and procfs_linux_test.go for more details. +var ( + hookForcePrivateProcRootOpenTree = hookDummyFile + hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile + hookForceGetProcRootUnsafe = hookDummy + + hookForceProcSelfTask = hookDummy + hookForceProcSelf = hookDummy +) + +func hookDummy() bool { return false } +func hookDummyFile(_ io.Closer) bool { return false } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go new file mode 100644 index 000000000..1ad1f18ee --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// This code is adapted to be a minimal version of the libpathrs proc resolver +// . +// As we only need O_PATH|O_NOFOLLOW support, this is not too much to port. + +package procfs + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/internal/consts" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" +) + +// procfsLookupInRoot is a stripped down version of completeLookupInRoot, +// entirely designed to support the very small set of features necessary to +// make procfs handling work. Unlike completeLookupInRoot, we always have +// O_PATH|O_NOFOLLOW behaviour for trailing symlinks. +// +// The main restrictions are: +// +// - ".." is not supported (as it requires either os.Root-style replays, +// which is more bug-prone; or procfs verification, which is not possible +// due to re-entrancy issues). +// - Absolute symlinks for the same reason (and all absolute symlinks in +// procfs are magic-links, which we want to skip anyway). +// - If statx is supported (checkSymlinkOvermount), any mount-point crossings +// (which is the main attack of concern against /proc). +// - Partial lookups are not supported, so the symlink stack is not needed. +// - Trailing slash special handling is not necessary in most cases (if we +// operating on procfs, it's usually with programmer-controlled strings +// that will then be re-opened), so we skip it since whatever re-opens it +// can deal with it. It's a creature comfort anyway. +// +// If the system supports openat2(), this is implemented using equivalent flags +// (RESOLVE_BENEATH | RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS). +func procfsLookupInRoot(procRoot fd.Fd, unsafePath string) (Handle *os.File, _ error) { + unsafePath = filepath.ToSlash(unsafePath) // noop + + // Make sure that an empty unsafe path still returns something sane, even + // with openat2 (which doesn't have AT_EMPTY_PATH semantics yet). + if unsafePath == "" { + unsafePath = "." + } + + // This is already checked by getProcRoot, but make sure here since the + // core security of this lookup is based on this assumption. + if err := verifyProcRoot(procRoot); err != nil { + return nil, err + } + + if linux.HasOpenat2() { + // We prefer being able to use RESOLVE_NO_XDEV if we can, to be + // absolutely sure we are operating on a clean /proc handle that + // doesn't have any cheeky overmounts that could trick us (including + // symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't + // strictly needed, but just use it since we have it. + // + // NOTE: /proc/self is technically a magic-link (the contents of the + // symlink are generated dynamically), but it doesn't use + // nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it. + // + // TODO: It would be nice to have RESOLVE_NO_DOTDOT, purely for + // self-consistency with the backup O_PATH resolver. + handle, err := fd.Openat2(procRoot, unsafePath, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS, + }) + if err != nil { + // TODO: Once we bump the minimum Go version to 1.20, we can use + // multiple %w verbs for this wrapping. For now we need to use a + // compatibility shim for older Go versions. + // err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) + return nil, gocompat.WrapBaseError(err, errUnsafeProcfs) + } + return handle, nil + } + + // To mirror openat2(RESOLVE_BENEATH), we need to return an error if the + // path is absolute. + if path.IsAbs(unsafePath) { + return nil, fmt.Errorf("%w: cannot resolve absolute paths in procfs resolver", internal.ErrPossibleBreakout) + } + + currentDir, err := fd.Dup(procRoot) + if err != nil { + return nil, fmt.Errorf("clone root fd: %w", err) + } + defer func() { + // If a handle is not returned, close the internal handle. + if Handle == nil { + _ = currentDir.Close() + } + }() + + var ( + linksWalked int + currentPath string + remainingPath = unsafePath + ) + for remainingPath != "" { + // Get the next path component. + var part string + if i := strings.IndexByte(remainingPath, '/'); i == -1 { + part, remainingPath = remainingPath, "" + } else { + part, remainingPath = remainingPath[:i], remainingPath[i+1:] + } + if part == "" { + // no-op component, but treat it the same as "." + part = "." + } + if part == ".." { + // not permitted + return nil, fmt.Errorf("%w: cannot walk into '..' in procfs resolver", internal.ErrPossibleBreakout) + } + + // Apply the component lexically to the path we are building. + // currentPath does not contain any symlinks, and we are lexically + // dealing with a single component, so it's okay to do a filepath.Clean + // here. (Not to mention that ".." isn't allowed.) + nextPath := path.Join("/", currentPath, part) + // If we logically hit the root, just clone the root rather than + // opening the part and doing all of the other checks. + if nextPath == "/" { + // Jump to root. + rootClone, err := fd.Dup(procRoot) + if err != nil { + return nil, fmt.Errorf("clone root fd: %w", err) + } + _ = currentDir.Close() + currentDir = rootClone + currentPath = nextPath + continue + } + + // Try to open the next component. + nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + + // Make sure we are still on procfs and haven't crossed mounts. + if err := verifyProcHandle(nextDir); err != nil { + _ = nextDir.Close() + return nil, fmt.Errorf("check %q component is on procfs: %w", part, err) + } + if err := checkSubpathOvermount(procRoot, nextDir, ""); err != nil { + _ = nextDir.Close() + return nil, fmt.Errorf("check %q component is not overmounted: %w", part, err) + } + + // We are emulating O_PATH|O_NOFOLLOW, so we only need to traverse into + // trailing symlinks if we are not the final component. Otherwise we + // can just return the currentDir. + if remainingPath != "" { + st, err := nextDir.Stat() + if err != nil { + _ = nextDir.Close() + return nil, fmt.Errorf("stat component %q: %w", part, err) + } + + if st.Mode()&os.ModeType == os.ModeSymlink { + // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See + // Linux commit 65cfc6722361 ("readlinkat(), fchownat() and + // fstatat() with empty relative pathnames"). + linkDest, err := fd.Readlinkat(nextDir, "") + // We don't need the handle anymore. + _ = nextDir.Close() + if err != nil { + return nil, err + } + + linksWalked++ + if linksWalked > consts.MaxSymlinkLimit { + return nil, &os.PathError{Op: "securejoin.procfsLookupInRoot", Path: "/proc/" + unsafePath, Err: unix.ELOOP} + } + + // Update our logical remaining path. + remainingPath = linkDest + "/" + remainingPath + // Absolute symlinks are probably magiclinks, we reject them. + if path.IsAbs(linkDest) { + return nil, fmt.Errorf("%w: cannot jump to / in procfs resolver -- possible magiclink", internal.ErrPossibleBreakout) + } + continue + } + } + + // Walk into the next component. + _ = currentDir.Close() + currentDir = nextDir + currentPath = nextPath + } + + // One final sanity-check. + if err := verifyProcHandle(currentDir); err != nil { + return nil, fmt.Errorf("check final handle is on procfs: %w", err) + } + if err := checkSubpathOvermount(procRoot, currentDir, ""); err != nil { + return nil, fmt.Errorf("check final handle is not overmounted: %w", err) + } + return currentDir, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go new file mode 100644 index 000000000..b43169564 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "os" + + "golang.org/x/sys/unix" +) + +// MkdirAll is a race-safe alternative to the [os.MkdirAll] function, +// where the new directory is guaranteed to be within the root directory (if an +// attacker can move directories from inside the root to outside the root, the +// created directory tree might be outside of the root but the key constraint +// is that at no point will we walk outside of the directory tree we are +// creating). +// +// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to +// +// path, _ := securejoin.SecureJoin(root, unsafePath) +// err := os.MkdirAll(path, mode) +// +// But is much safer. The above implementation is unsafe because if an attacker +// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is +// possible for MkdirAll to resolve unsafe symlink components and create +// directories outside of the root. +// +// If you plan to open the directory after you have created it or want to use +// an open directory handle as the root, you should use [MkdirAllHandle] instead. +// This function is a wrapper around [MkdirAllHandle]. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin +func MkdirAll(root, unsafePath string, mode os.FileMode) error { + rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + if err != nil { + return err + } + defer rootDir.Close() //nolint:errcheck // close failures aren't critical here + + f, err := MkdirAllHandle(rootDir, unsafePath, mode) + if err != nil { + return err + } + _ = f.Close() + return nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go new file mode 100644 index 000000000..f864dbc8f --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "os" + + "cyphar.com/go-pathrs" +) + +// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use +// in two respects: +// +// - The caller provides the root directory as an *[os.File] (preferably O_PATH) +// handle. This means that the caller can be sure which root directory is +// being used. Note that this can be emulated by using /proc/self/fd/... as +// the root path with [os.MkdirAll]. +// +// - Once all of the directories have been created, an *[os.File] O_PATH handle +// to the directory at unsafePath is returned to the caller. This is done in +// an effectively-race-free way (an attacker would only be able to swap the +// final directory component), which is not possible to emulate with +// [MkdirAll]. +// +// In addition, the returned handle is obtained far more efficiently than doing +// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after +// doing [MkdirAll]. If you intend to open the directory after creating it, you +// should use MkdirAllHandle. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin +func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (*os.File, error) { + rootRef, err := pathrs.RootFromFile(root) + if err != nil { + return nil, err + } + defer rootRef.Close() //nolint:errcheck // close failures aren't critical here + + handle, err := rootRef.MkdirAll(unsafePath, mode) + if err != nil { + return nil, err + } + return handle.IntoFile(), nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go new file mode 100644 index 000000000..0369dfe7e --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux && !libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "os" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs" +) + +// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use +// in two respects: +// +// - The caller provides the root directory as an *[os.File] (preferably O_PATH) +// handle. This means that the caller can be sure which root directory is +// being used. Note that this can be emulated by using /proc/self/fd/... as +// the root path with [os.MkdirAll]. +// +// - Once all of the directories have been created, an *[os.File] O_PATH handle +// to the directory at unsafePath is returned to the caller. This is done in +// an effectively-race-free way (an attacker would only be able to swap the +// final directory component), which is not possible to emulate with +// [MkdirAll]. +// +// In addition, the returned handle is obtained far more efficiently than doing +// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after +// doing [MkdirAll]. If you intend to open the directory after creating it, you +// should use MkdirAllHandle. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin +func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (*os.File, error) { + return gopathrs.MkdirAllHandle(root, unsafePath, mode) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go new file mode 100644 index 000000000..41b628907 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "os" + + "golang.org/x/sys/unix" +) + +// OpenInRoot safely opens the provided unsafePath within the root. +// Effectively, OpenInRoot(root, unsafePath) is equivalent to +// +// path, _ := securejoin.SecureJoin(root, unsafePath) +// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC) +// +// But is much safer. The above implementation is unsafe because if an attacker +// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is +// possible for the returned file to be outside of the root. +// +// Note that the returned handle is an O_PATH handle, meaning that only a very +// limited set of operations will work on the handle. This is done to avoid +// accidentally opening an untrusted file that could cause issues (such as a +// disconnected TTY that could cause a DoS, or some other issue). In order to +// use the returned handle, you can "upgrade" it to a proper handle using +// [Reopen]. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin +func OpenInRoot(root, unsafePath string) (*os.File, error) { + rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + defer rootDir.Close() //nolint:errcheck // close failures aren't critical here + return OpenatInRoot(rootDir, unsafePath) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go new file mode 100644 index 000000000..53352000e --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "os" + + "cyphar.com/go-pathrs" +) + +// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided +// using an *[os.File] handle, to ensure that the correct root directory is used. +func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { + rootRef, err := pathrs.RootFromFile(root) + if err != nil { + return nil, err + } + defer rootRef.Close() //nolint:errcheck // close failures aren't critical here + + handle, err := rootRef.Resolve(unsafePath) + if err != nil { + return nil, err + } + return handle.IntoFile(), nil +} + +// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd. +// Reopen(file, flags) is effectively equivalent to +// +// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd()) +// os.OpenFile(fdPath, flags|unix.O_CLOEXEC) +// +// But with some extra hardenings to ensure that we are not tricked by a +// maliciously-configured /proc mount. While this attack scenario is not +// common, in container runtimes it is possible for higher-level runtimes to be +// tricked into configuring an unsafe /proc that can be used to attack file +// operations. See [CVE-2019-19921] for more details. +// +// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw +func Reopen(file *os.File, flags int) (*os.File, error) { + handle, err := pathrs.HandleFromFile(file) + if err != nil { + return nil, err + } + defer handle.Close() //nolint:errcheck // close failures aren't critical here + + return handle.OpenFile(flags) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go new file mode 100644 index 000000000..6d1be12ce --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux && !libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "os" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" +) + +// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided +// using an *[os.File] handle, to ensure that the correct root directory is used. +func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { + return gopathrs.OpenatInRoot(root, unsafePath) +} + +// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd. +// Reopen(file, flags) is effectively equivalent to +// +// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd()) +// os.OpenFile(fdPath, flags|unix.O_CLOEXEC) +// +// But with some extra hardenings to ensure that we are not tricked by a +// maliciously-configured /proc mount. While this attack scenario is not +// common, in container runtimes it is possible for higher-level runtimes to be +// tricked into configuring an unsafe /proc that can be used to attack file +// operations. See [CVE-2019-19921] for more details. +// +// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw +func Reopen(handle *os.File, flags int) (*os.File, error) { + return procfs.ReopenFd(handle, flags) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go new file mode 100644 index 000000000..6c4df3763 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package procfs provides a safe API for operating on /proc on Linux. +package procfs + +import ( + "os" + "strconv" + + "cyphar.com/go-pathrs/procfs" + "golang.org/x/sys/unix" +) + +// ProcThreadSelfCloser is a callback that needs to be called when you are done +// operating on an [os.File] fetched using [Handle.OpenThreadSelf]. +// +// [os.File]: https://pkg.go.dev/os#File +type ProcThreadSelfCloser = procfs.ThreadCloser + +// Handle is a wrapper around an *os.File handle to "/proc", which can be used +// to do further procfs-related operations in a safe way. +type Handle struct { + inner *procfs.Handle +} + +// Close close the resources associated with this [Handle]. Note that if this +// [Handle] was created with [OpenProcRoot], on some kernels the underlying +// procfs handle is cached and so this Close operation may be a no-op. However, +// you should always call Close on [Handle]s once you are done with them. +func (proc *Handle) Close() error { return proc.inner.Close() } + +// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the +// "subset=pid" mount option applied, available from Linux 5.8). Unless you +// plan to do many [Handle.OpenRoot] operations, users should prefer to use +// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open. +// +// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a +// regular "/proc" handle. +// +// Note that using [Handle.OpenRoot] will still work with handles returned by +// this function. If a subpath cannot be operated on with a safe "/proc" +// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary +// unsafe handle will be used. +func OpenProcRoot() (*Handle, error) { + proc, err := procfs.Open() + if err != nil { + return nil, err + } + return &Handle{inner: proc}, nil +} + +// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or +// masked paths. You must be extremely careful to make sure this handle is +// never leaked to a container and that you program cannot be tricked into +// writing to arbitrary paths within it. +// +// This is not necessary if you just wish to use [Handle.OpenRoot], as handles +// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe +// handle in that case. You should only really use this if you need to do many +// operations with [Handle.OpenRoot] and the performance overhead of making +// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you +// should make sure to close the handle as soon as possible to avoid +// known-fd-number attacks. +func OpenUnsafeProcRoot() (*Handle, error) { + proc, err := procfs.Open(procfs.UnmaskedProcRoot) + if err != nil { + return nil, err + } + return &Handle{inner: proc}, nil +} + +// OpenThreadSelf returns a handle to "/proc/thread-self/" (or an +// equivalent handle on older kernels where "/proc/thread-self" doesn't exist). +// Once finished with the handle, you must call the returned closer function +// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other +// Go threads or use the handle after calling the closer. +// +// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread +func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) { + return proc.inner.OpenThreadSelf(subpath, unix.O_PATH|unix.O_NOFOLLOW) +} + +// OpenSelf returns a handle to /proc/self/. +// +// Note that in Go programs with non-homogenous threads, this may result in +// spurious errors. If you are monkeying around with APIs that are +// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead +// which will guarantee that the handle refers to the same thread as the caller +// is executing on. +func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { + return proc.inner.OpenSelf(subpath, unix.O_PATH|unix.O_NOFOLLOW) +} + +// OpenRoot returns a handle to /proc/. +// +// You should only use this when you need to operate on global procfs files +// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf], +// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally +// for this operation will never use "subset=pid", which makes it a more juicy +// target for [CVE-2024-21626]-style attacks (and doing something like opening +// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as +// the file descriptor is open). +// +// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv +func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { + return proc.inner.OpenRoot(subpath, unix.O_PATH|unix.O_NOFOLLOW) +} + +// OpenPid returns a handle to /proc/$pid/ (pid can be a pid or tid). +// This is mainly intended for usage when operating on other processes. +// +// You should not use this for the current thread, as special handling is +// needed for /proc/thread-self (or /proc/self/task/) when dealing with +// goroutine scheduling -- use [Handle.OpenThreadSelf] instead. +// +// To refer to the current thread-group, you should use prefer +// [Handle.OpenSelf] to passing os.Getpid as the pid argument. +func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { + return proc.inner.OpenPid(pid, subpath, unix.O_PATH|unix.O_NOFOLLOW) +} + +// ProcSelfFdReadlink gets the real path of the given file by looking at +// /proc/self/fd/ with [readlink]. It is effectively just shorthand for +// something along the lines of: +// +// proc, err := procfs.OpenProcRoot() +// if err != nil { +// return err +// } +// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd())) +// if err != nil { +// return err +// } +// defer link.Close() +// var buf [4096]byte +// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:]) +// if err != nil { +// return err +// } +// pathname := buf[:n] +// +// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat +func ProcSelfFdReadlink(f *os.File) (string, error) { + proc, err := procfs.Open() + if err != nil { + return "", err + } + defer proc.Close() //nolint:errcheck // close failures aren't critical here + + fdPath := "fd/" + strconv.Itoa(int(f.Fd())) + return proc.Readlink(procfs.ProcThreadSelf, fdPath) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go new file mode 100644 index 000000000..9383002f9 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux && !libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package procfs provides a safe API for operating on /proc on Linux. +package procfs + +import ( + "os" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" +) + +// This package mostly just wraps internal/procfs APIs. This is necessary +// because we are forced to export some things from internal/procfs in order to +// avoid some dependency cycle issues, but we don't want users to see or use +// them. + +// ProcThreadSelfCloser is a callback that needs to be called when you are done +// operating on an [os.File] fetched using [Handle.OpenThreadSelf]. +// +// [os.File]: https://pkg.go.dev/os#File +type ProcThreadSelfCloser = procfs.ProcThreadSelfCloser + +// Handle is a wrapper around an *os.File handle to "/proc", which can be used +// to do further procfs-related operations in a safe way. +type Handle struct { + inner *procfs.Handle +} + +// Close close the resources associated with this [Handle]. Note that if this +// [Handle] was created with [OpenProcRoot], on some kernels the underlying +// procfs handle is cached and so this Close operation may be a no-op. However, +// you should always call Close on [Handle]s once you are done with them. +func (proc *Handle) Close() error { return proc.inner.Close() } + +// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the +// "subset=pid" mount option applied, available from Linux 5.8). Unless you +// plan to do many [Handle.OpenRoot] operations, users should prefer to use +// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open. +// +// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a +// regular "/proc" handle. +// +// Note that using [Handle.OpenRoot] will still work with handles returned by +// this function. If a subpath cannot be operated on with a safe "/proc" +// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary +// unsafe handle will be used. +func OpenProcRoot() (*Handle, error) { + proc, err := procfs.OpenProcRoot() + if err != nil { + return nil, err + } + return &Handle{inner: proc}, nil +} + +// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or +// masked paths. You must be extremely careful to make sure this handle is +// never leaked to a container and that you program cannot be tricked into +// writing to arbitrary paths within it. +// +// This is not necessary if you just wish to use [Handle.OpenRoot], as handles +// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe +// handle in that case. You should only really use this if you need to do many +// operations with [Handle.OpenRoot] and the performance overhead of making +// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you +// should make sure to close the handle as soon as possible to avoid +// known-fd-number attacks. +func OpenUnsafeProcRoot() (*Handle, error) { + proc, err := procfs.OpenUnsafeProcRoot() + if err != nil { + return nil, err + } + return &Handle{inner: proc}, nil +} + +// OpenThreadSelf returns a handle to "/proc/thread-self/" (or an +// equivalent handle on older kernels where "/proc/thread-self" doesn't exist). +// Once finished with the handle, you must call the returned closer function +// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other +// Go threads or use the handle after calling the closer. +// +// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread +func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) { + return proc.inner.OpenThreadSelf(subpath) +} + +// OpenSelf returns a handle to /proc/self/. +// +// Note that in Go programs with non-homogenous threads, this may result in +// spurious errors. If you are monkeying around with APIs that are +// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead +// which will guarantee that the handle refers to the same thread as the caller +// is executing on. +func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { + return proc.inner.OpenSelf(subpath) +} + +// OpenRoot returns a handle to /proc/. +// +// You should only use this when you need to operate on global procfs files +// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf], +// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally +// for this operation will never use "subset=pid", which makes it a more juicy +// target for [CVE-2024-21626]-style attacks (and doing something like opening +// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as +// the file descriptor is open). +// +// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv +func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { + return proc.inner.OpenRoot(subpath) +} + +// OpenPid returns a handle to /proc/$pid/ (pid can be a pid or tid). +// This is mainly intended for usage when operating on other processes. +// +// You should not use this for the current thread, as special handling is +// needed for /proc/thread-self (or /proc/self/task/) when dealing with +// goroutine scheduling -- use [Handle.OpenThreadSelf] instead. +// +// To refer to the current thread-group, you should use prefer +// [Handle.OpenSelf] to passing os.Getpid as the pid argument. +func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { + return proc.inner.OpenPid(pid, subpath) +} + +// ProcSelfFdReadlink gets the real path of the given file by looking at +// /proc/self/fd/ with [readlink]. It is effectively just shorthand for +// something along the lines of: +// +// proc, err := procfs.OpenProcRoot() +// if err != nil { +// return err +// } +// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd())) +// if err != nil { +// return err +// } +// defer link.Close() +// var buf [4096]byte +// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:]) +// if err != nil { +// return err +// } +// pathname := buf[:n] +// +// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat +func ProcSelfFdReadlink(f *os.File) (string, error) { + return procfs.ProcSelfFdReadlink(f) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go deleted file mode 100644 index 809a579cb..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go +++ /dev/null @@ -1,452 +0,0 @@ -//go:build linux - -// Copyright (C) 2024 SUSE LLC. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package securejoin - -import ( - "errors" - "fmt" - "os" - "runtime" - "strconv" - - "golang.org/x/sys/unix" -) - -func fstat(f *os.File) (unix.Stat_t, error) { - var stat unix.Stat_t - if err := unix.Fstat(int(f.Fd()), &stat); err != nil { - return stat, &os.PathError{Op: "fstat", Path: f.Name(), Err: err} - } - return stat, nil -} - -func fstatfs(f *os.File) (unix.Statfs_t, error) { - var statfs unix.Statfs_t - if err := unix.Fstatfs(int(f.Fd()), &statfs); err != nil { - return statfs, &os.PathError{Op: "fstatfs", Path: f.Name(), Err: err} - } - return statfs, nil -} - -// The kernel guarantees that the root inode of a procfs mount has an -// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO. -const ( - procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC - procRootIno = 1 // PROC_ROOT_INO -) - -func verifyProcRoot(procRoot *os.File) error { - if statfs, err := fstatfs(procRoot); err != nil { - return err - } else if statfs.Type != procSuperMagic { - return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type) - } - if stat, err := fstat(procRoot); err != nil { - return err - } else if stat.Ino != procRootIno { - return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino) - } - return nil -} - -var hasNewMountApi = sync_OnceValue(func() bool { - // All of the pieces of the new mount API we use (fsopen, fsconfig, - // fsmount, open_tree) were added together in Linux 5.1[1,2], so we can - // just check for one of the syscalls and the others should also be - // available. - // - // Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE. - // This is equivalent to openat(2), but tells us if open_tree is - // available (and thus all of the other basic new mount API syscalls). - // open_tree(2) is most light-weight syscall to test here. - // - // [1]: merge commit 400913252d09 - // [2]: - fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC) - if err != nil { - return false - } - _ = unix.Close(fd) - return true -}) - -func fsopen(fsName string, flags int) (*os.File, error) { - // Make sure we always set O_CLOEXEC. - flags |= unix.FSOPEN_CLOEXEC - fd, err := unix.Fsopen(fsName, flags) - if err != nil { - return nil, os.NewSyscallError("fsopen "+fsName, err) - } - return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil -} - -func fsmount(ctx *os.File, flags, mountAttrs int) (*os.File, error) { - // Make sure we always set O_CLOEXEC. - flags |= unix.FSMOUNT_CLOEXEC - fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs) - if err != nil { - return nil, os.NewSyscallError("fsmount "+ctx.Name(), err) - } - return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil -} - -func newPrivateProcMount() (*os.File, error) { - procfsCtx, err := fsopen("proc", unix.FSOPEN_CLOEXEC) - if err != nil { - return nil, err - } - defer procfsCtx.Close() - - // Try to configure hidepid=ptraceable,subset=pid if possible, but ignore errors. - _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable") - _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") - - // Get an actual handle. - if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil { - return nil, os.NewSyscallError("fsconfig create procfs", err) - } - return fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_RDONLY|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID) -} - -func openTree(dir *os.File, path string, flags uint) (*os.File, error) { - dirFd := -int(unix.EBADF) - dirName := "." - if dir != nil { - dirFd = int(dir.Fd()) - dirName = dir.Name() - } - // Make sure we always set O_CLOEXEC. - flags |= unix.OPEN_TREE_CLOEXEC - fd, err := unix.OpenTree(dirFd, path, flags) - if err != nil { - return nil, &os.PathError{Op: "open_tree", Path: path, Err: err} - } - return os.NewFile(uintptr(fd), dirName+"/"+path), nil -} - -func clonePrivateProcMount() (_ *os.File, Err error) { - // Try to make a clone without using AT_RECURSIVE if we can. If this works, - // we can be sure there are no over-mounts and so if the root is valid then - // we're golden. Otherwise, we have to deal with over-mounts. - procfsHandle, err := openTree(nil, "/proc", unix.OPEN_TREE_CLONE) - if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procfsHandle) { - procfsHandle, err = openTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE) - } - if err != nil { - return nil, fmt.Errorf("creating a detached procfs clone: %w", err) - } - defer func() { - if Err != nil { - _ = procfsHandle.Close() - } - }() - if err := verifyProcRoot(procfsHandle); err != nil { - return nil, err - } - return procfsHandle, nil -} - -func privateProcRoot() (*os.File, error) { - if !hasNewMountApi() || hookForceGetProcRootUnsafe() { - return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP) - } - // Try to create a new procfs mount from scratch if we can. This ensures we - // can get a procfs mount even if /proc is fake (for whatever reason). - procRoot, err := newPrivateProcMount() - if err != nil || hookForcePrivateProcRootOpenTree(procRoot) { - // Try to clone /proc then... - procRoot, err = clonePrivateProcMount() - } - return procRoot, err -} - -func unsafeHostProcRoot() (_ *os.File, Err error) { - procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - if err != nil { - return nil, err - } - defer func() { - if Err != nil { - _ = procRoot.Close() - } - }() - if err := verifyProcRoot(procRoot); err != nil { - return nil, err - } - return procRoot, nil -} - -func doGetProcRoot() (*os.File, error) { - procRoot, err := privateProcRoot() - if err != nil { - // Fall back to using a /proc handle if making a private mount failed. - // If we have openat2, at least we can avoid some kinds of over-mount - // attacks, but without openat2 there's not much we can do. - procRoot, err = unsafeHostProcRoot() - } - return procRoot, err -} - -var getProcRoot = sync_OnceValues(func() (*os.File, error) { - return doGetProcRoot() -}) - -var hasProcThreadSelf = sync_OnceValue(func() bool { - return unix.Access("/proc/thread-self/", unix.F_OK) == nil -}) - -var errUnsafeProcfs = errors.New("unsafe procfs detected") - -type procThreadSelfCloser func() - -// procThreadSelf returns a handle to /proc/thread-self/ (or an -// equivalent handle on older kernels where /proc/thread-self doesn't exist). -// Once finished with the handle, you must call the returned closer function -// (runtime.UnlockOSThread). You must not pass the returned *os.File to other -// Go threads or use the handle after calling the closer. -// -// This is similar to ProcThreadSelf from runc, but with extra hardening -// applied and using *os.File. -func procThreadSelf(procRoot *os.File, subpath string) (_ *os.File, _ procThreadSelfCloser, Err error) { - // We need to lock our thread until the caller is done with the handle - // because between getting the handle and using it we could get interrupted - // by the Go runtime and hit the case where the underlying thread is - // swapped out and the original thread is killed, resulting in - // pull-your-hair-out-hard-to-debug issues in the caller. - runtime.LockOSThread() - defer func() { - if Err != nil { - runtime.UnlockOSThread() - } - }() - - // Figure out what prefix we want to use. - threadSelf := "thread-self/" - if !hasProcThreadSelf() || hookForceProcSelfTask() { - /// Pre-3.17 kernels don't have /proc/thread-self, so do it manually. - threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) + "/" - if _, err := fstatatFile(procRoot, threadSelf, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() { - // In this case, we running in a pid namespace that doesn't match - // the /proc mount we have. This can happen inside runc. - // - // Unfortunately, there is no nice way to get the correct TID to - // use here because of the age of the kernel, so we have to just - // use /proc/self and hope that it works. - threadSelf = "self/" - } - } - - // Grab the handle. - var ( - handle *os.File - err error - ) - if hasOpenat2() { - // We prefer being able to use RESOLVE_NO_XDEV if we can, to be - // absolutely sure we are operating on a clean /proc handle that - // doesn't have any cheeky overmounts that could trick us (including - // symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't - // strictly needed, but just use it since we have it. - // - // NOTE: /proc/self is technically a magic-link (the contents of the - // symlink are generated dynamically), but it doesn't use - // nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it. - // - // NOTE: We MUST NOT use RESOLVE_IN_ROOT here, as openat2File uses - // procSelfFdReadlink to clean up the returned f.Name() if we use - // RESOLVE_IN_ROOT (which would lead to an infinite recursion). - handle, err = openat2File(procRoot, threadSelf+subpath, &unix.OpenHow{ - Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, - Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS, - }) - if err != nil { - // TODO: Once we bump the minimum Go version to 1.20, we can use - // multiple %w verbs for this wrapping. For now we need to use a - // compatibility shim for older Go versions. - //err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) - return nil, nil, wrapBaseError(err, errUnsafeProcfs) - } - } else { - handle, err = openatFile(procRoot, threadSelf+subpath, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - if err != nil { - // TODO: Once we bump the minimum Go version to 1.20, we can use - // multiple %w verbs for this wrapping. For now we need to use a - // compatibility shim for older Go versions. - //err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) - return nil, nil, wrapBaseError(err, errUnsafeProcfs) - } - defer func() { - if Err != nil { - _ = handle.Close() - } - }() - // We can't detect bind-mounts of different parts of procfs on top of - // /proc (a-la RESOLVE_NO_XDEV), but we can at least be sure that we - // aren't on the wrong filesystem here. - if statfs, err := fstatfs(handle); err != nil { - return nil, nil, err - } else if statfs.Type != procSuperMagic { - return nil, nil, fmt.Errorf("%w: incorrect /proc/self/fd filesystem type 0x%x", errUnsafeProcfs, statfs.Type) - } - } - return handle, runtime.UnlockOSThread, nil -} - -// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to -// avoid bumping the requirement for a single constant we can just define it -// ourselves. -const STATX_MNT_ID_UNIQUE = 0x4000 - -var hasStatxMountId = sync_OnceValue(func() bool { - var ( - stx unix.Statx_t - // We don't care which mount ID we get. The kernel will give us the - // unique one if it is supported. - wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID - ) - err := unix.Statx(-int(unix.EBADF), "/", 0, int(wantStxMask), &stx) - return err == nil && stx.Mask&wantStxMask != 0 -}) - -func getMountId(dir *os.File, path string) (uint64, error) { - // If we don't have statx(STATX_MNT_ID*) support, we can't do anything. - if !hasStatxMountId() { - return 0, nil - } - - var ( - stx unix.Statx_t - // We don't care which mount ID we get. The kernel will give us the - // unique one if it is supported. - wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID - ) - - err := unix.Statx(int(dir.Fd()), path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, int(wantStxMask), &stx) - if stx.Mask&wantStxMask == 0 { - // It's not a kernel limitation, for some reason we couldn't get a - // mount ID. Assume it's some kind of attack. - err = fmt.Errorf("%w: could not get mount id", errUnsafeProcfs) - } - if err != nil { - return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: dir.Name() + "/" + path, Err: err} - } - return stx.Mnt_id, nil -} - -func checkSymlinkOvermount(procRoot *os.File, dir *os.File, path string) error { - // Get the mntId of our procfs handle. - expectedMountId, err := getMountId(procRoot, "") - if err != nil { - return err - } - // Get the mntId of the target magic-link. - gotMountId, err := getMountId(dir, path) - if err != nil { - return err - } - // As long as the directory mount is alive, even with wrapping mount IDs, - // we would expect to see a different mount ID here. (Of course, if we're - // using unsafeHostProcRoot() then an attaker could change this after we - // did this check.) - if expectedMountId != gotMountId { - return fmt.Errorf("%w: symlink %s/%s has an overmount obscuring the real link (mount ids do not match %d != %d)", errUnsafeProcfs, dir.Name(), path, expectedMountId, gotMountId) - } - return nil -} - -func doRawProcSelfFdReadlink(procRoot *os.File, fd int) (string, error) { - fdPath := fmt.Sprintf("fd/%d", fd) - procFdLink, closer, err := procThreadSelf(procRoot, fdPath) - if err != nil { - return "", fmt.Errorf("get safe /proc/thread-self/%s handle: %w", fdPath, err) - } - defer procFdLink.Close() - defer closer() - - // Try to detect if there is a mount on top of the magic-link. Since we use the handle directly - // provide to the closure. If the closure uses the handle directly, this - // should be safe in general (a mount on top of the path afterwards would - // not affect the handle itself) and will definitely be safe if we are - // using privateProcRoot() (at least since Linux 5.12[1], when anonymous - // mount namespaces were completely isolated from external mounts including - // mount propagation events). - // - // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts - // onto targets that reside on shared mounts"). - if err := checkSymlinkOvermount(procRoot, procFdLink, ""); err != nil { - return "", fmt.Errorf("check safety of /proc/thread-self/fd/%d magiclink: %w", fd, err) - } - - // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit - // 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty - // relative pathnames"). - return readlinkatFile(procFdLink, "") -} - -func rawProcSelfFdReadlink(fd int) (string, error) { - procRoot, err := getProcRoot() - if err != nil { - return "", err - } - return doRawProcSelfFdReadlink(procRoot, fd) -} - -func procSelfFdReadlink(f *os.File) (string, error) { - return rawProcSelfFdReadlink(int(f.Fd())) -} - -var ( - errPossibleBreakout = errors.New("possible breakout detected") - errInvalidDirectory = errors.New("wandered into deleted directory") - errDeletedInode = errors.New("cannot verify path of deleted inode") -) - -func isDeadInode(file *os.File) error { - // If the nlink of a file drops to 0, there is an attacker deleting - // directories during our walk, which could result in weird /proc values. - // It's better to error out in this case. - stat, err := fstat(file) - if err != nil { - return fmt.Errorf("check for dead inode: %w", err) - } - if stat.Nlink == 0 { - err := errDeletedInode - if stat.Mode&unix.S_IFMT == unix.S_IFDIR { - err = errInvalidDirectory - } - return fmt.Errorf("%w %q", err, file.Name()) - } - return nil -} - -func checkProcSelfFdPath(path string, file *os.File) error { - if err := isDeadInode(file); err != nil { - return err - } - actualPath, err := procSelfFdReadlink(file) - if err != nil { - return fmt.Errorf("get path of handle: %w", err) - } - if actualPath != path { - return fmt.Errorf("%w: handle path %q doesn't match expected path %q", errPossibleBreakout, actualPath, path) - } - return nil -} - -// Test hooks used in the procfs tests to verify that the fallback logic works. -// See testing_mocks_linux_test.go and procfs_linux_test.go for more details. -var ( - hookForcePrivateProcRootOpenTree = hookDummyFile - hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile - hookForceGetProcRootUnsafe = hookDummy - - hookForceProcSelfTask = hookDummy - hookForceProcSelf = hookDummy -) - -func hookDummy() bool { return false } -func hookDummyFile(_ *os.File) bool { return false } diff --git a/vendor/github.com/cyphar/filepath-securejoin/vfs.go b/vendor/github.com/cyphar/filepath-securejoin/vfs.go index 36373f8c5..4d89a481c 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/vfs.go +++ b/vendor/github.com/cyphar/filepath-securejoin/vfs.go @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: BSD-3-Clause + // Copyright (C) 2017-2024 SUSE LLC. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go index 07e0f77dc..884a8b805 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go @@ -6,78 +6,11 @@ import ( "github.com/opencontainers/selinux/go-selinux" ) -// Deprecated: use selinux.ROFileLabel -var ROMountLabel = selinux.ROFileLabel - -// SetProcessLabel takes a process label and tells the kernel to assign the -// label to the next program executed by the current process. -// Deprecated: use selinux.SetExecLabel -var SetProcessLabel = selinux.SetExecLabel - -// ProcessLabel returns the process label that the kernel will assign -// to the next program executed by the current process. If "" is returned -// this indicates that the default labeling will happen for the process. -// Deprecated: use selinux.ExecLabel -var ProcessLabel = selinux.ExecLabel - -// SetSocketLabel takes a process label and tells the kernel to assign the -// label to the next socket that gets created -// Deprecated: use selinux.SetSocketLabel -var SetSocketLabel = selinux.SetSocketLabel - -// SocketLabel retrieves the current default socket label setting -// Deprecated: use selinux.SocketLabel -var SocketLabel = selinux.SocketLabel - -// SetKeyLabel takes a process label and tells the kernel to assign the -// label to the next kernel keyring that gets created -// Deprecated: use selinux.SetKeyLabel -var SetKeyLabel = selinux.SetKeyLabel - -// KeyLabel retrieves the current default kernel keyring label setting -// Deprecated: use selinux.KeyLabel -var KeyLabel = selinux.KeyLabel - -// FileLabel returns the label for specified path -// Deprecated: use selinux.FileLabel -var FileLabel = selinux.FileLabel - -// PidLabel will return the label of the process running with the specified pid -// Deprecated: use selinux.PidLabel -var PidLabel = selinux.PidLabel - // Init initialises the labeling system func Init() { _ = selinux.GetEnabled() } -// ClearLabels will clear all reserved labels -// Deprecated: use selinux.ClearLabels -var ClearLabels = selinux.ClearLabels - -// ReserveLabel will record the fact that the MCS label has already been used. -// This will prevent InitLabels from using the MCS label in a newly created -// container -// Deprecated: use selinux.ReserveLabel -func ReserveLabel(label string) error { - selinux.ReserveLabel(label) - return nil -} - -// ReleaseLabel will remove the reservation of the MCS label. -// This will allow InitLabels to use the MCS label in a newly created -// containers -// Deprecated: use selinux.ReleaseLabel -func ReleaseLabel(label string) error { - selinux.ReleaseLabel(label) - return nil -} - -// DupSecOpt takes a process label and returns security options that -// can be used to set duplicate labels on future container processes -// Deprecated: use selinux.DupSecOpt -var DupSecOpt = selinux.DupSecOpt - // FormatMountLabel returns a string to be used by the mount command. Using // the SELinux `context` mount option. Changing labels of files on mount // points with this option can never be changed. diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go index e49e6d53f..95f29e21f 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go @@ -18,7 +18,7 @@ var validOptions = map[string]bool{ "level": true, } -var ErrIncompatibleLabel = errors.New("Bad SELinux option z and Z can not be used together") +var ErrIncompatibleLabel = errors.New("bad SELinux option: z and Z can not be used together") // InitLabels returns the process label and file labels to be used within // the container. A list of options can be passed into this function to alter @@ -52,11 +52,11 @@ func InitLabels(options []string) (plabel string, mlabel string, retErr error) { return "", selinux.PrivContainerMountLabel(), nil } if i := strings.Index(opt, ":"); i == -1 { - return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt) + return "", "", fmt.Errorf("bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt) } con := strings.SplitN(opt, ":", 2) if !validOptions[con[0]] { - return "", "", fmt.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0]) + return "", "", fmt.Errorf("bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0]) } if con[0] == "filetype" { mcon["type"] = con[1] @@ -79,12 +79,6 @@ func InitLabels(options []string) (plabel string, mlabel string, retErr error) { return processLabel, mountLabel, nil } -// Deprecated: The GenLabels function is only to be used during the transition -// to the official API. Use InitLabels(strings.Fields(options)) instead. -func GenLabels(options string) (string, string, error) { - return InitLabels(strings.Fields(options)) -} - // SetFileLabel modifies the "path" label to the specified file label func SetFileLabel(path string, fileLabel string) error { if !selinux.GetEnabled() || fileLabel == "" { @@ -123,11 +117,6 @@ func Relabel(path string, fileLabel string, shared bool) error { return selinux.Chcon(path, fileLabel, true) } -// DisableSecOpt returns a security opt that can disable labeling -// support for future container processes -// Deprecated: use selinux.DisableSecOpt -var DisableSecOpt = selinux.DisableSecOpt - // Validate checks that the label does not include unexpected options func Validate(label string) error { if strings.Contains(label, "z") && strings.Contains(label, "Z") { diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go index 1c260cb27..7a54afc5e 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go @@ -10,12 +10,6 @@ func InitLabels([]string) (string, string, error) { return "", "", nil } -// Deprecated: The GenLabels function is only to be used during the transition -// to the official API. Use InitLabels(strings.Fields(options)) instead. -func GenLabels(string) (string, string, error) { - return "", "", nil -} - func SetFileLabel(string, string) error { return nil } diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go index af058b84b..15150d475 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go @@ -41,6 +41,10 @@ var ( // ErrVerifierNil is returned when a context verifier function is nil. ErrVerifierNil = errors.New("verifier function is nil") + // ErrNotTGLeader is returned by [SetKeyLabel] if the calling thread + // is not the thread group leader. + ErrNotTGLeader = errors.New("calling thread is not the thread group leader") + // CategoryRange allows the upper bound on the category range to be adjusted CategoryRange = DefaultCategoryRange @@ -149,7 +153,7 @@ func CalculateGlbLub(sourceRange, targetRange string) (string, error) { // of the program is finished to guarantee another goroutine does not migrate to the current // thread before execution is complete. func SetExecLabel(label string) error { - return writeCon(attrPath("exec"), label) + return writeConThreadSelf("attr/exec", label) } // SetTaskLabel sets the SELinux label for the current thread, or an error. @@ -157,7 +161,7 @@ func SetExecLabel(label string) error { // be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() to guarantee // the current thread does not run in a new mislabeled thread. func SetTaskLabel(label string) error { - return writeCon(attrPath("current"), label) + return writeConThreadSelf("attr/current", label) } // SetSocketLabel takes a process label and tells the kernel to assign the @@ -166,12 +170,12 @@ func SetTaskLabel(label string) error { // the socket is created to guarantee another goroutine does not migrate // to the current thread before execution is complete. func SetSocketLabel(label string) error { - return writeCon(attrPath("sockcreate"), label) + return writeConThreadSelf("attr/sockcreate", label) } // SocketLabel retrieves the current socket label setting func SocketLabel() (string, error) { - return readCon(attrPath("sockcreate")) + return readConThreadSelf("attr/sockcreate") } // PeerLabel retrieves the label of the client on the other side of a socket @@ -180,17 +184,21 @@ func PeerLabel(fd uintptr) (string, error) { } // SetKeyLabel takes a process label and tells the kernel to assign the -// label to the next kernel keyring that gets created. Calls to SetKeyLabel -// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until -// the kernel keyring is created to guarantee another goroutine does not migrate -// to the current thread before execution is complete. +// label to the next kernel keyring that gets created. +// +// Calls to SetKeyLabel should be wrapped in +// runtime.LockOSThread()/runtime.UnlockOSThread() until the kernel keyring is +// created to guarantee another goroutine does not migrate to the current +// thread before execution is complete. +// +// Only the thread group leader can set key label. func SetKeyLabel(label string) error { return setKeyLabel(label) } // KeyLabel retrieves the current kernel keyring label setting func KeyLabel() (string, error) { - return readCon("/proc/self/attr/keycreate") + return keyLabel() } // Get returns the Context as a string diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index c80c10971..6d7f8e270 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -17,8 +17,11 @@ import ( "strings" "sync" - "github.com/opencontainers/selinux/pkg/pwalkdir" + "github.com/cyphar/filepath-securejoin/pathrs-lite" + "github.com/cyphar/filepath-securejoin/pathrs-lite/procfs" "golang.org/x/sys/unix" + + "github.com/opencontainers/selinux/pkg/pwalkdir" ) const ( @@ -45,7 +48,7 @@ type selinuxState struct { type level struct { cats *big.Int - sens uint + sens int } type mlsRange struct { @@ -73,10 +76,6 @@ var ( mcsList: make(map[string]bool), } - // for attrPath() - attrPathOnce sync.Once - haveThreadSelf bool - // for policyRoot() policyRootOnce sync.Once policyRootVal string @@ -138,6 +137,7 @@ func verifySELinuxfsMount(mnt string) bool { return false } + //#nosec G115 -- there is no overflow here. if uint32(buf.Type) != uint32(unix.SELINUX_MAGIC) { return false } @@ -255,48 +255,183 @@ func readConfig(target string) string { return "" } -func isProcHandle(fh *os.File) error { - var buf unix.Statfs_t +func readConFd(in *os.File) (string, error) { + data, err := io.ReadAll(in) + if err != nil { + return "", err + } + return string(bytes.TrimSuffix(data, []byte{0})), nil +} - for { - err := unix.Fstatfs(int(fh.Fd()), &buf) - if err == nil { - break - } - if err != unix.EINTR { - return &os.PathError{Op: "fstatfs", Path: fh.Name(), Err: err} - } +func writeConFd(out *os.File, val string) error { + var err error + if val != "" { + _, err = out.Write([]byte(val)) + } else { + _, err = out.Write(nil) } - if buf.Type != unix.PROC_SUPER_MAGIC { - return fmt.Errorf("file %q is not on procfs", fh.Name()) + return err +} + +// openProcThreadSelf is a small wrapper around [procfs.Handle.OpenThreadSelf] +// and [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The +// provided mode must be os.O_* flags to indicate what mode the returned file +// should be opened with (flags like os.O_CREAT and os.O_EXCL are not +// supported). +// +// If no error occurred, the returned handle is guaranteed to be exactly +// /proc/thread-self/ with no tricky mounts or symlinks causing you to +// operate on an unexpected path (with some caveats on pre-openat2 or +// pre-fsopen kernels). +func openProcThreadSelf(subpath string, mode int) (*os.File, procfs.ProcThreadSelfCloser, error) { + if subpath == "" { + return nil, nil, ErrEmptyPath } - return nil -} + proc, err := procfs.OpenProcRoot() + if err != nil { + return nil, nil, err + } + defer proc.Close() -func readCon(fpath string) (string, error) { - if fpath == "" { - return "", ErrEmptyPath + handle, closer, err := proc.OpenThreadSelf(subpath) + if err != nil { + return nil, nil, fmt.Errorf("open /proc/thread-self/%s handle: %w", subpath, err) + } + defer handle.Close() // we will return a re-opened handle + + file, err := pathrs.Reopen(handle, mode) + if err != nil { + closer() + return nil, nil, fmt.Errorf("reopen /proc/thread-self/%s handle (%#x): %w", subpath, mode, err) } + return file, closer, nil +} - in, err := os.Open(fpath) +// Read the contents of /proc/thread-self/. +func readConThreadSelf(fpath string) (string, error) { + in, closer, err := openProcThreadSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC) if err != nil { return "", err } + defer closer() defer in.Close() - if err := isProcHandle(in); err != nil { + return readConFd(in) +} + +// Write to /proc/thread-self/. +func writeConThreadSelf(fpath, val string) error { + if val == "" { + if !getEnabled() { + return nil + } + } + + out, closer, err := openProcThreadSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC) + if err != nil { + return err + } + defer closer() + defer out.Close() + + return writeConFd(out, val) +} + +// openProcSelf is a small wrapper around [procfs.Handle.OpenSelf] and +// [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The +// provided mode must be os.O_* flags to indicate what mode the returned file +// should be opened with (flags like os.O_CREAT and os.O_EXCL are not +// supported). +// +// If no error occurred, the returned handle is guaranteed to be exactly +// /proc/self/ with no tricky mounts or symlinks causing you to +// operate on an unexpected path (with some caveats on pre-openat2 or +// pre-fsopen kernels). +func openProcSelf(subpath string, mode int) (*os.File, error) { + if subpath == "" { + return nil, ErrEmptyPath + } + + proc, err := procfs.OpenProcRoot() + if err != nil { + return nil, err + } + defer proc.Close() + + handle, err := proc.OpenSelf(subpath) + if err != nil { + return nil, fmt.Errorf("open /proc/self/%s handle: %w", subpath, err) + } + defer handle.Close() // we will return a re-opened handle + + file, err := pathrs.Reopen(handle, mode) + if err != nil { + return nil, fmt.Errorf("reopen /proc/self/%s handle (%#x): %w", subpath, mode, err) + } + return file, nil +} + +// Read the contents of /proc/self/. +func readConSelf(fpath string) (string, error) { + in, err := openProcSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC) + if err != nil { return "", err } + defer in.Close() + return readConFd(in) } -func readConFd(in *os.File) (string, error) { - data, err := io.ReadAll(in) +// Write to /proc/self/. +func writeConSelf(fpath, val string) error { + if val == "" { + if !getEnabled() { + return nil + } + } + + out, err := openProcSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC) if err != nil { - return "", err + return err } - return string(bytes.TrimSuffix(data, []byte{0})), nil + defer out.Close() + + return writeConFd(out, val) +} + +// openProcPid is a small wrapper around [procfs.Handle.OpenPid] and +// [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The +// provided mode must be os.O_* flags to indicate what mode the returned file +// should be opened with (flags like os.O_CREAT and os.O_EXCL are not +// supported). +// +// If no error occurred, the returned handle is guaranteed to be exactly +// /proc/self/ with no tricky mounts or symlinks causing you to +// operate on an unexpected path (with some caveats on pre-openat2 or +// pre-fsopen kernels). +func openProcPid(pid int, subpath string, mode int) (*os.File, error) { + if subpath == "" { + return nil, ErrEmptyPath + } + + proc, err := procfs.OpenProcRoot() + if err != nil { + return nil, err + } + defer proc.Close() + + handle, err := proc.OpenPid(pid, subpath) + if err != nil { + return nil, fmt.Errorf("open /proc/%d/%s handle: %w", pid, subpath, err) + } + defer handle.Close() // we will return a re-opened handle + + file, err := pathrs.Reopen(handle, mode) + if err != nil { + return nil, fmt.Errorf("reopen /proc/%d/%s handle (%#x): %w", pid, subpath, mode, err) + } + return file, nil } // classIndex returns the int index for an object class in the loaded policy, @@ -392,78 +527,34 @@ func lFileLabel(fpath string) (string, error) { } func setFSCreateLabel(label string) error { - return writeCon(attrPath("fscreate"), label) + return writeConThreadSelf("attr/fscreate", label) } // fsCreateLabel returns the default label the kernel which the kernel is using // for file system objects created by this task. "" indicates default. func fsCreateLabel() (string, error) { - return readCon(attrPath("fscreate")) + return readConThreadSelf("attr/fscreate") } // currentLabel returns the SELinux label of the current process thread, or an error. func currentLabel() (string, error) { - return readCon(attrPath("current")) + return readConThreadSelf("attr/current") } // pidLabel returns the SELinux label of the given pid, or an error. func pidLabel(pid int) (string, error) { - return readCon(fmt.Sprintf("/proc/%d/attr/current", pid)) + it, err := openProcPid(pid, "attr/current", os.O_RDONLY|unix.O_CLOEXEC) + if err != nil { + return "", nil + } + defer it.Close() + return readConFd(it) } // ExecLabel returns the SELinux label that the kernel will use for any programs // that are executed by the current process thread, or an error. func execLabel() (string, error) { - return readCon(attrPath("exec")) -} - -func writeCon(fpath, val string) error { - if fpath == "" { - return ErrEmptyPath - } - if val == "" { - if !getEnabled() { - return nil - } - } - - out, err := os.OpenFile(fpath, os.O_WRONLY, 0) - if err != nil { - return err - } - defer out.Close() - - if err := isProcHandle(out); err != nil { - return err - } - - if val != "" { - _, err = out.Write([]byte(val)) - } else { - _, err = out.Write(nil) - } - if err != nil { - return err - } - return nil -} - -func attrPath(attr string) string { - // Linux >= 3.17 provides this - const threadSelfPrefix = "/proc/thread-self/attr" - - attrPathOnce.Do(func() { - st, err := os.Stat(threadSelfPrefix) - if err == nil && st.Mode().IsDir() { - haveThreadSelf = true - } - }) - - if haveThreadSelf { - return filepath.Join(threadSelfPrefix, attr) - } - - return filepath.Join("/proc/self/task", strconv.Itoa(unix.Gettid()), "attr", attr) + return readConThreadSelf("exec") } // canonicalizeContext takes a context string and writes it to the kernel @@ -501,14 +592,14 @@ func catsToBitset(cats string) (*big.Int, error) { return nil, err } for i := catstart; i <= catend; i++ { - bitset.SetBit(bitset, int(i), 1) + bitset.SetBit(bitset, i, 1) } } else { cat, err := parseLevelItem(ranges[0], category) if err != nil { return nil, err } - bitset.SetBit(bitset, int(cat), 1) + bitset.SetBit(bitset, cat, 1) } } @@ -516,16 +607,17 @@ func catsToBitset(cats string) (*big.Int, error) { } // parseLevelItem parses and verifies that a sensitivity or category are valid -func parseLevelItem(s string, sep levelItem) (uint, error) { +func parseLevelItem(s string, sep levelItem) (int, error) { if len(s) < minSensLen || levelItem(s[0]) != sep { return 0, ErrLevelSyntax } - val, err := strconv.ParseUint(s[1:], 10, 32) + const bitSize = 31 // Make sure the result fits into signed int32. + val, err := strconv.ParseUint(s[1:], 10, bitSize) if err != nil { return 0, err } - return uint(val), nil + return int(val), nil } // parseLevel fills a level from a string that contains @@ -582,7 +674,8 @@ func bitsetToStr(c *big.Int) string { var str string length := 0 - for i := int(c.TrailingZeroBits()); i < c.BitLen(); i++ { + i0 := int(c.TrailingZeroBits()) //#nosec G115 -- don't expect TralingZeroBits to return values with highest bit set. + for i := i0; i < c.BitLen(); i++ { if c.Bit(i) == 0 { continue } @@ -622,7 +715,7 @@ func (l *level) equal(l2 *level) bool { // String returns an mlsRange as a string. func (m mlsRange) String() string { - low := "s" + strconv.Itoa(int(m.low.sens)) + low := "s" + strconv.Itoa(m.low.sens) if m.low.cats != nil && m.low.cats.BitLen() > 0 { low += ":" + bitsetToStr(m.low.cats) } @@ -631,7 +724,7 @@ func (m mlsRange) String() string { return low } - high := "s" + strconv.Itoa(int(m.high.sens)) + high := "s" + strconv.Itoa(m.high.sens) if m.high.cats != nil && m.high.cats.BitLen() > 0 { high += ":" + bitsetToStr(m.high.cats) } @@ -639,15 +732,16 @@ func (m mlsRange) String() string { return low + "-" + high } -// TODO: remove min and max once Go < 1.21 is not supported. -func max(a, b uint) uint { +// TODO: remove these in favor of built-in min/max +// once we stop supporting Go < 1.21. +func maxInt(a, b int) int { if a > b { return a } return b } -func min(a, b uint) uint { +func minInt(a, b int) int { if a < b { return a } @@ -676,10 +770,10 @@ func calculateGlbLub(sourceRange, targetRange string) (string, error) { outrange := &mlsRange{low: &level{}, high: &level{}} /* take the greatest of the low */ - outrange.low.sens = max(s.low.sens, t.low.sens) + outrange.low.sens = maxInt(s.low.sens, t.low.sens) /* take the least of the high */ - outrange.high.sens = min(s.high.sens, t.high.sens) + outrange.high.sens = minInt(s.high.sens, t.high.sens) /* find the intersecting categories */ if s.low.cats != nil && t.low.cats != nil { @@ -724,16 +818,29 @@ func peerLabel(fd uintptr) (string, error) { // setKeyLabel takes a process label and tells the kernel to assign the // label to the next kernel keyring that gets created func setKeyLabel(label string) error { - err := writeCon("/proc/self/attr/keycreate", label) + // Rather than using /proc/thread-self, we want to use /proc/self to + // operate on the thread-group leader. + err := writeConSelf("attr/keycreate", label) if errors.Is(err, os.ErrNotExist) { return nil } if label == "" && errors.Is(err, os.ErrPermission) { return nil } + if errors.Is(err, unix.EACCES) && unix.Getpid() != unix.Gettid() { + return ErrNotTGLeader + } return err } +// KeyLabel retrieves the current kernel keyring label setting for this +// thread-group. +func keyLabel() (string, error) { + // Rather than using /proc/thread-self, we want to use /proc/self to + // operate on the thread-group leader. + return readConSelf("attr/keycreate") +} + // get returns the Context as a string func (c Context) get() string { if l := c["level"]; l != "" { @@ -809,8 +916,7 @@ func enforceMode() int { // setEnforceMode sets the current SELinux mode Enforcing, Permissive. // Disabled is not valid, since this needs to be set at boot time. func setEnforceMode(mode int) error { - //nolint:gosec // ignore G306: permissions to be 0600 or less. - return os.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0o644) + return os.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0) } // defaultEnforceMode returns the systems default SELinux mode Enforcing, @@ -1017,8 +1123,7 @@ func addMcs(processLabel, fileLabel string) (string, string) { // securityCheckContext validates that the SELinux label is understood by the kernel func securityCheckContext(val string) error { - //nolint:gosec // ignore G306: permissions to be 0600 or less. - return os.WriteFile(filepath.Join(getSelinuxMountPoint(), "context"), []byte(val), 0o644) + return os.WriteFile(filepath.Join(getSelinuxMountPoint(), "context"), []byte(val), 0) } // copyLevel returns a label with the MLS/MCS level from src label replaced on diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go index 0889fbe0e..382244e50 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go @@ -3,15 +3,11 @@ package selinux -func attrPath(string) string { - return "" -} - -func readCon(string) (string, error) { +func readConThreadSelf(string) (string, error) { return "", nil } -func writeCon(string, string) error { +func writeConThreadSelf(string, string) error { return nil } @@ -81,6 +77,10 @@ func setKeyLabel(string) error { return nil } +func keyLabel() (string, error) { + return "", nil +} + func (c Context) get() string { return "" } diff --git a/vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/controller.go b/vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/controller.go new file mode 100644 index 000000000..b54e5d565 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/controller.go @@ -0,0 +1,129 @@ +/* +Copyright 2026 Red Hat, Inc. + +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 tls + +import ( + "context" + "fmt" + "reflect" + + "github.com/go-logr/logr" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + configv1 "github.com/openshift/api/config/v1" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// TLSSecurityProfileWatcher watches the APIServer object for TLS profile changes +// and triggers a graceful shutdown when the profile changes. +type TLSSecurityProfileWatcher struct { + client.Client + + // InitialTLSProfileSpec is the TLS profile spec that was configured when the operator started. + InitialTLSProfileSpec configv1.TLSProfileSpec + + // Shutdown is a function that will be called to trigger a graceful shutdown + // when the TLS profile changes. + Shutdown context.CancelFunc +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TLSSecurityProfileWatcher) SetupWithManager(mgr ctrl.Manager) error { + if err := ctrl.NewControllerManagedBy(mgr). + Named("tlssecurityprofilewatcher"). + For(&configv1.APIServer{}, builder.WithPredicates( + predicate.Funcs{ + // Only watch the "cluster" APIServer object. + CreateFunc: func(e event.CreateEvent) bool { + return e.Object.GetName() == APIServerName + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return e.ObjectNew.GetName() == APIServerName + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return e.Object.GetName() == APIServerName + }, + GenericFunc: func(e event.GenericEvent) bool { + return e.Object.GetName() == APIServerName + }, + }, + )). + // Override the default log constructor as it makes the logs very chatty. + WithLogConstructor(func(req *reconcile.Request) logr.Logger { + return mgr.GetLogger().WithValues( + "controller", "tlssecurityprofilewatcher", + ) + }). + Complete(r); err != nil { + return fmt.Errorf("could not set up controller for TLS security profile watcher: %w", err) + } + + return nil +} + +// Reconcile watches for changes to the APIServer TLS profile and triggers a shutdown +// when the profile changes from the initial configuration. +func (r *TLSSecurityProfileWatcher) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx, "name", req.Name) + + logger.V(1).Info("Reconciling APIServer TLS profile") + + // Fetch the APIServer object. + apiServer := &configv1.APIServer{} + if err := r.Get(ctx, req.NamespacedName, apiServer); err != nil { + if apierrors.IsNotFound(err) { + // If the APIServer object is not found, we don't need to do anything. + // This could happen if the object was deleted. + return ctrl.Result{}, nil + } + + return ctrl.Result{}, fmt.Errorf("failed to get APIServer %s: %w", req.NamespacedName.String(), err) + } + + // Get the current TLS profile spec. + currentTLSProfileSpec, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get TLS profile from APIServer %s: %w", req.NamespacedName.String(), err) + } + + // Compare the current TLS profile spec with the initial one. + if !reflect.DeepEqual(r.InitialTLSProfileSpec, currentTLSProfileSpec) { + logger.Info("TLS security profile has changed, initiating a shutdown of the operator to make it pick up the new configuration", + "initialMinTLSVersion", r.InitialTLSProfileSpec.MinTLSVersion, + "currentMinTLSVersion", currentTLSProfileSpec.MinTLSVersion, + "initialCiphers", r.InitialTLSProfileSpec.Ciphers, + "currentCiphers", currentTLSProfileSpec.Ciphers, + ) + + // Trigger the shutdown of the operator to make it pick up the new configuration. + r.Shutdown() + + // Return immediately, no need to requeue. + return ctrl.Result{}, nil + } + + logger.V(1).Info("TLS security profile unchanged") + + return ctrl.Result{}, nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/tls.go b/vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/tls.go new file mode 100644 index 000000000..1b3e0eeda --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/controllerruntime/tls/tls.go @@ -0,0 +1,156 @@ +/* +Copyright 2026 Red Hat, Inc. + +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 tls + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + + configv1 "github.com/openshift/api/config/v1" + libgocrypto "github.com/openshift/library-go/pkg/crypto" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + // APIServerName is the name of the APIServer resource in the cluster. + APIServerName = "cluster" +) + +var ( + // ErrCustomProfileNil is returned when a custom TLS profile is specified but the Custom field is nil. + ErrCustomProfileNil = errors.New("custom TLS profile specified but Custom field is nil") + + // DefaultTLSProfileType is the intermediate profile type. + DefaultTLSProfileType = configv1.TLSProfileIntermediateType //nolint:gochecknoglobals + // DefaultTLSCiphers are the default TLS ciphers for API servers. + DefaultTLSCiphers = configv1.TLSProfiles[DefaultTLSProfileType].Ciphers //nolint:gochecknoglobals + // DefaultMinTLSVersion is the default minimum TLS version for API servers. + DefaultMinTLSVersion = configv1.TLSProfiles[DefaultTLSProfileType].MinTLSVersion //nolint:gochecknoglobals +) + +// FetchAPIServerTLSProfile fetches the TLS profile spec configured in APIServer. +// If no profile is configured, the default profile is returned. +func FetchAPIServerTLSProfile(ctx context.Context, k8sClient client.Client) (configv1.TLSProfileSpec, error) { + apiServer := &configv1.APIServer{} + key := client.ObjectKey{Name: APIServerName} + + if err := k8sClient.Get(ctx, key, apiServer); err != nil { + return configv1.TLSProfileSpec{}, fmt.Errorf("failed to get APIServer %q: %w", key.String(), err) + } + + profile, err := GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile) + if err != nil { + return configv1.TLSProfileSpec{}, fmt.Errorf("failed to get TLS profile from APIServer %q: %w", key.String(), err) + } + + return profile, nil +} + +// GetTLSProfileSpec returns TLSProfileSpec for the given profile. +// If no profile is configured, the default profile is returned. +func GetTLSProfileSpec(profile *configv1.TLSSecurityProfile) (configv1.TLSProfileSpec, error) { + // Define the default profile (at the time of writing, this is the intermediate profile). + defaultProfile := *configv1.TLSProfiles[DefaultTLSProfileType] + // If the profile is nil or the type is empty, return the default profile. + if profile == nil || profile.Type == "" { + return defaultProfile, nil + } + + // Get the profile type. + profileType := profile.Type + + // If the profile type is not custom, return the profile from the map. + if profileType != configv1.TLSProfileCustomType { + if tlsConfig, ok := configv1.TLSProfiles[profileType]; ok { + return *tlsConfig, nil + } + + // If the profile type is not found, return the default profile. + return defaultProfile, nil + } + + if profile.Custom == nil { + // If the custom profile is nil, return an error. + return configv1.TLSProfileSpec{}, ErrCustomProfileNil + } + + // Return the custom profile spec. + return profile.Custom.TLSProfileSpec, nil +} + +// NewTLSConfigFromProfile returns a function that configures a tls.Config based on the provided TLSProfileSpec, +// along with any cipher names from the profile that are not supported by the library-go crypto package. +// The returned function is intended to be used with controller-runtime's TLSOpts. +// +// Note: CipherSuites are only set when MinVersion is below TLS 1.3, as Go's TLS 1.3 implementation +// does not allow configuring cipher suites - all TLS 1.3 ciphers are always enabled. +// See: https://github.com/golang/go/issues/29349 +func NewTLSConfigFromProfile(profile configv1.TLSProfileSpec) (tlsConfig func(*tls.Config), unsupportedCiphers []string) { + minVersion := libgocrypto.TLSVersionOrDie(string(profile.MinTLSVersion)) + cipherSuites, unsupportedCiphers := cipherCodes(profile.Ciphers) + + return func(tlsConf *tls.Config) { + tlsConf.MinVersion = minVersion + // TODO: add curve preferences from profile once https://github.com/openshift/api/pull/2583 merges. + // tlsConf.CurvePreferences <<<<<< profile.Curves + + // TLS 1.3 cipher suites are not configurable in Go (https://github.com/golang/go/issues/29349), so only set CipherSuites accordingly. + // TODO: revisit this once we get an answer on the best way to handle this here: + // https://docs.google.com/document/d/1cMc9E8psHfnoK06ntR8kHSWB8d3rMtmldhnmM4nImjs/edit?disco=AAABu_nPcYg + if minVersion != tls.VersionTLS13 { + tlsConf.CipherSuites = cipherSuites + } + }, unsupportedCiphers +} + +// cipherCode returns the TLS cipher code for an OpenSSL or IANA cipher name. +// Returns 0 if the cipher is not supported. +func cipherCode(cipher string) uint16 { + // First try as IANA name directly. + if code, err := libgocrypto.CipherSuite(cipher); err == nil { + return code + } + + // Try converting from OpenSSL name to IANA name. + ianaCiphers := libgocrypto.OpenSSLToIANACipherSuites([]string{cipher}) + if len(ianaCiphers) == 1 { + if code, err := libgocrypto.CipherSuite(ianaCiphers[0]); err == nil { + return code + } + } + + // Return 0 if the cipher is not supported. + return 0 +} + +// cipherCodes converts a list of cipher names (OpenSSL or IANA format) to their uint16 codes. +// Returns the converted codes and a list of any unsupported cipher names. +func cipherCodes(ciphers []string) (codes []uint16, unsupportedCiphers []string) { + for _, cipher := range ciphers { + code := cipherCode(cipher) + if code == 0 { + unsupportedCiphers = append(unsupportedCiphers, cipher) + continue + } + + codes = append(codes, code) + } + + return codes, unsupportedCiphers +} diff --git a/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go b/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go index 33a09ae16..bff6155c2 100644 --- a/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go +++ b/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go @@ -242,35 +242,41 @@ func ValidCipherSuites() []string { sort.Strings(validCipherSuites) return validCipherSuites } + +// DefaultCiphers returns the default cipher suites for TLS connections. +// +// RECOMMENDATION: Instead of relying on this function directly, consumers should respect +// TLSSecurityProfile settings from one of the OpenShift API configuration resources: +// - For API servers: Use apiserver.config.openshift.io/cluster Spec.TLSSecurityProfile +// - For ingress controllers: Use operator.openshift.io/v1 IngressController Spec.TLSSecurityProfile +// - For kubelet: Use machineconfiguration.openshift.io/v1 KubeletConfig Spec.TLSSecurityProfile +// +// These API resources allow cluster administrators to choose between Old, Intermediate, +// Modern, or Custom TLS profiles. Components should observe these settings. func DefaultCiphers() []uint16 { - // HTTP/2 mandates TLS 1.2 or higher with an AEAD cipher - // suite (GCM, Poly1305) and ephemeral key exchange (ECDHE, DHE) for - // perfect forward secrecy. Servers may provide additional cipher - // suites for backwards compatibility with HTTP/1.1 clients. - // See RFC7540, section 9.2 (Use of TLS Features) and Appendix A - // (TLS 1.2 Cipher Suite Black List). + // Aligned with intermediate profile of the 5.7 version of the Mozilla Server + // Side TLS guidelines found at: https://ssl-config.mozilla.org/guidelines/5.7.json + // + // Latest guidelines: https://ssl-config.mozilla.org/guidelines/latest.json + // + // This profile provides strong security with wide compatibility. + // It requires TLS 1.2+ and uses only AEAD cipher suites (GCM, ChaCha20-Poly1305) + // with ECDHE key exchange for perfect forward secrecy. + // + // All CBC-mode ciphers have been removed due to padding oracle vulnerabilities. + // All RSA key exchange ciphers have been removed due to lack of perfect forward secrecy. + // + // HTTP/2 compliance: All ciphers are compliant with RFC7540, section 9.2. return []uint16{ + // TLS 1.2 cipher suites with ECDHE + AEAD tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // required by http/2 + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // required by HTTP/2 tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // forbidden by http/2, not flagged by http2isBadCipher() in go1.8 - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, // forbidden by http/2, not flagged by http2isBadCipher() in go1.8 - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // forbidden by http/2 - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // forbidden by http/2 - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // forbidden by http/2 - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // forbidden by http/2 - tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // forbidden by http/2 - tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // forbidden by http/2 - // the next one is in the intermediate suite, but go1.8 http2isBadCipher() complains when it is included at the recommended index - // because it comes after ciphers forbidden by the http/2 spec - // tls.TLS_RSA_WITH_AES_128_CBC_SHA256, - // tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, // forbidden by http/2, disabled to mitigate SWEET32 attack - // tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // forbidden by http/2, disabled to mitigate SWEET32 attack - tls.TLS_RSA_WITH_AES_128_CBC_SHA, // forbidden by http/2 - tls.TLS_RSA_WITH_AES_256_CBC_SHA, // forbidden by http/2 + + // TLS 1.3 cipher suites (negotiated automatically, not configurable) tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_CHACHA20_POLY1305_SHA256, diff --git a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go index 1cb4e5554..c2c8b8368 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/certrotation/signer.go @@ -188,7 +188,7 @@ func getValidityFromAnnotations(annotations map[string]string) (notBefore time.T return notBefore, notAfter, fmt.Sprintf("bad expiry: %q", notAfterString) } notBeforeString := annotations[CertificateNotBeforeAnnotation] - if len(notAfterString) == 0 { + if len(notBeforeString) == 0 { return notBefore, notAfter, "missing notBefore" } notBefore, err = time.Parse(time.RFC3339, notBeforeString) diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s index 7dd2638e8..769af387e 100644 --- a/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s +++ b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s @@ -29,7 +29,7 @@ loop: MOVD $NUM_ROUNDS, R21 VLD1 (R11), [V30.S4, V31.S4] - // load contants + // load constants // VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4] WORD $0x4D60E940 diff --git a/vendor/golang.org/x/crypto/ssh/keys.go b/vendor/golang.org/x/crypto/ssh/keys.go index a035956fc..47a07539d 100644 --- a/vendor/golang.org/x/crypto/ssh/keys.go +++ b/vendor/golang.org/x/crypto/ssh/keys.go @@ -1490,6 +1490,7 @@ type openSSHEncryptedPrivateKey struct { NumKeys uint32 PubKey []byte PrivKeyBlock []byte + Rest []byte `ssh:"rest"` } type openSSHPrivateKey struct { diff --git a/vendor/golang.org/x/crypto/ssh/messages.go b/vendor/golang.org/x/crypto/ssh/messages.go index 251b9d06a..ab22c3d38 100644 --- a/vendor/golang.org/x/crypto/ssh/messages.go +++ b/vendor/golang.org/x/crypto/ssh/messages.go @@ -792,7 +792,7 @@ func marshalString(to []byte, s []byte) []byte { return to[len(s):] } -var bigIntType = reflect.TypeOf((*big.Int)(nil)) +var bigIntType = reflect.TypeFor[*big.Int]() // Decode a packet into its corresponding message. func decode(packet []byte) (interface{}, error) { diff --git a/vendor/golang.org/x/crypto/ssh/ssh_gss.go b/vendor/golang.org/x/crypto/ssh/ssh_gss.go index 24bd7c8e8..a6249a122 100644 --- a/vendor/golang.org/x/crypto/ssh/ssh_gss.go +++ b/vendor/golang.org/x/crypto/ssh/ssh_gss.go @@ -106,6 +106,13 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) { if !ok { return nil, errors.New("parse uint32 failed") } + // Each ASN.1 encoded OID must have a minimum + // of 2 bytes; 64 maximum mechanisms is an + // arbitrary, but reasonable ceiling. + const maxMechs = 64 + if n > maxMechs || int(n)*2 > len(rest) { + return nil, errors.New("invalid mechanism count") + } s := &userAuthRequestGSSAPI{ N: n, OIDS: make([]asn1.ObjectIdentifier, n), @@ -122,7 +129,6 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) { if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil { return nil, err } - } return s, nil } diff --git a/vendor/golang.org/x/crypto/ssh/streamlocal.go b/vendor/golang.org/x/crypto/ssh/streamlocal.go index b171b330b..152470fcb 100644 --- a/vendor/golang.org/x/crypto/ssh/streamlocal.go +++ b/vendor/golang.org/x/crypto/ssh/streamlocal.go @@ -44,7 +44,7 @@ func (c *Client) ListenUnix(socketPath string) (net.Listener, error) { if !ok { return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer") } - ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"}) + ch := c.forwards.add("unix", socketPath) return &unixListener{socketPath, c, ch}, nil } @@ -96,7 +96,7 @@ func (l *unixListener) Accept() (net.Conn, error) { // Close closes the listener. func (l *unixListener) Close() error { // this also closes the listener. - l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"}) + l.conn.forwards.remove("unix", l.socketPath) m := streamLocalChannelForwardMsg{ l.socketPath, } diff --git a/vendor/golang.org/x/crypto/ssh/tcpip.go b/vendor/golang.org/x/crypto/ssh/tcpip.go index 93d844f03..78c41fe5a 100644 --- a/vendor/golang.org/x/crypto/ssh/tcpip.go +++ b/vendor/golang.org/x/crypto/ssh/tcpip.go @@ -11,6 +11,7 @@ import ( "io" "math/rand" "net" + "net/netip" "strconv" "strings" "sync" @@ -22,14 +23,21 @@ import ( // the returned net.Listener. The listener must be serviced, or the // SSH connection may hang. // N must be "tcp", "tcp4", "tcp6", or "unix". +// +// If the address is a hostname, it is sent to the remote peer as-is, without +// being resolved locally, and the Listener Addr method will return a zero IP. func (c *Client) Listen(n, addr string) (net.Listener, error) { switch n { case "tcp", "tcp4", "tcp6": - laddr, err := net.ResolveTCPAddr(n, addr) + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseInt(portStr, 10, 32) if err != nil { return nil, err } - return c.ListenTCP(laddr) + return c.listenTCPInternal(host, int(port)) case "unix": return c.ListenUnix(addr) default: @@ -102,15 +110,24 @@ func (c *Client) handleForwards() { // ListenTCP requests the remote peer open a listening socket // on laddr. Incoming connections will be available by calling // Accept on the returned net.Listener. +// +// ListenTCP accepts an IP address, to provide a hostname use [Client.Listen] +// with "tcp", "tcp4", or "tcp6" network instead. func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { c.handleForwardsOnce.Do(c.handleForwards) if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { return c.autoPortListenWorkaround(laddr) } + return c.listenTCPInternal(laddr.IP.String(), laddr.Port) +} + +func (c *Client) listenTCPInternal(host string, port int) (net.Listener, error) { + c.handleForwardsOnce.Do(c.handleForwards) + m := channelForwardMsg{ - laddr.IP.String(), - uint32(laddr.Port), + host, + uint32(port), } // send message ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m)) @@ -123,20 +140,33 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { // If the original port was 0, then the remote side will // supply a real port number in the response. - if laddr.Port == 0 { + if port == 0 { var p struct { Port uint32 } if err := Unmarshal(resp, &p); err != nil { return nil, err } - laddr.Port = int(p.Port) + port = int(p.Port) } + // Construct a local address placeholder for the remote listener. If the + // original host is an IP address, preserve it so that Listener.Addr() + // reports the same IP. If the host is a hostname or cannot be parsed as an + // IP, fall back to IPv4zero. The port field is always set, even if the + // original port was 0, because in that case the remote server will assign + // one, allowing callers to determine which port was selected. + ip := net.IPv4zero + if parsed, err := netip.ParseAddr(host); err == nil { + ip = net.IP(parsed.AsSlice()) + } + laddr := &net.TCPAddr{ + IP: ip, + Port: port, + } + addr := net.JoinHostPort(host, strconv.FormatInt(int64(port), 10)) + ch := c.forwards.add("tcp", addr) - // Register this forward, using the port number we obtained. - ch := c.forwards.add(laddr) - - return &tcpListener{laddr, c, ch}, nil + return &tcpListener{laddr, addr, c, ch}, nil } // forwardList stores a mapping between remote @@ -149,8 +179,9 @@ type forwardList struct { // forwardEntry represents an established mapping of a laddr on a // remote ssh server to a channel connected to a tcpListener. type forwardEntry struct { - laddr net.Addr - c chan forward + addr string // host:port or socket path + network string // tcp or unix + c chan forward } // forward represents an incoming forwarded tcpip connection. The @@ -161,12 +192,13 @@ type forward struct { raddr net.Addr // the raddr of the incoming connection } -func (l *forwardList) add(addr net.Addr) chan forward { +func (l *forwardList) add(n, addr string) chan forward { l.Lock() defer l.Unlock() f := forwardEntry{ - laddr: addr, - c: make(chan forward, 1), + addr: addr, + network: n, + c: make(chan forward, 1), } l.entries = append(l.entries, f) return f.c @@ -185,19 +217,20 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) { if port == 0 || port > 65535 { return nil, fmt.Errorf("ssh: port number out of range: %d", port) } - ip := net.ParseIP(string(addr)) - if ip == nil { + ip, err := netip.ParseAddr(addr) + if err != nil { return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr) } - return &net.TCPAddr{IP: ip, Port: int(port)}, nil + return &net.TCPAddr{IP: net.IP(ip.AsSlice()), Port: int(port)}, nil } func (l *forwardList) handleChannels(in <-chan NewChannel) { for ch := range in { var ( - laddr net.Addr - raddr net.Addr - err error + addr string + network string + raddr net.Addr + err error ) switch channelType := ch.ChannelType(); channelType { case "forwarded-tcpip": @@ -207,40 +240,34 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) { continue } - // RFC 4254 section 7.2 specifies that incoming - // addresses should list the address, in string - // format. It is implied that this should be an IP - // address, as it would be impossible to connect to it - // otherwise. - laddr, err = parseTCPAddr(payload.Addr, payload.Port) - if err != nil { - ch.Reject(ConnectionFailed, err.Error()) - continue - } + // RFC 4254 section 7.2 specifies that incoming addresses should + // list the address that was connected, in string format. It is the + // same address used in the tcpip-forward request. The originator + // address is an IP address instead. + addr = net.JoinHostPort(payload.Addr, strconv.FormatUint(uint64(payload.Port), 10)) + raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort) if err != nil { ch.Reject(ConnectionFailed, err.Error()) continue } - + network = "tcp" case "forwarded-streamlocal@openssh.com": var payload forwardedStreamLocalPayload if err = Unmarshal(ch.ExtraData(), &payload); err != nil { ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error()) continue } - laddr = &net.UnixAddr{ - Name: payload.SocketPath, - Net: "unix", - } + addr = payload.SocketPath raddr = &net.UnixAddr{ Name: "@", Net: "unix", } + network = "unix" default: panic(fmt.Errorf("ssh: unknown channel type %s", channelType)) } - if ok := l.forward(laddr, raddr, ch); !ok { + if ok := l.forward(network, addr, raddr, ch); !ok { // Section 7.2, implementations MUST reject spurious incoming // connections. ch.Reject(Prohibited, "no forward for address") @@ -252,11 +279,11 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) { // remove removes the forward entry, and the channel feeding its // listener. -func (l *forwardList) remove(addr net.Addr) { +func (l *forwardList) remove(n, addr string) { l.Lock() defer l.Unlock() for i, f := range l.entries { - if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() { + if n == f.network && addr == f.addr { l.entries = append(l.entries[:i], l.entries[i+1:]...) close(f.c) return @@ -274,11 +301,11 @@ func (l *forwardList) closeAll() { l.entries = nil } -func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool { +func (l *forwardList) forward(n, addr string, raddr net.Addr, ch NewChannel) bool { l.Lock() defer l.Unlock() for _, f := range l.entries { - if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() { + if n == f.network && addr == f.addr { f.c <- forward{newCh: ch, raddr: raddr} return true } @@ -288,6 +315,7 @@ func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool { type tcpListener struct { laddr *net.TCPAddr + addr string conn *Client in <-chan forward @@ -314,13 +342,21 @@ func (l *tcpListener) Accept() (net.Conn, error) { // Close closes the listener. func (l *tcpListener) Close() error { + host, port, err := net.SplitHostPort(l.addr) + if err != nil { + return err + } + rport, err := strconv.ParseUint(port, 10, 32) + if err != nil { + return err + } m := channelForwardMsg{ - l.laddr.IP.String(), - uint32(l.laddr.Port), + host, + uint32(rport), } // this also closes the listener. - l.conn.forwards.remove(l.laddr) + l.conn.forwards.remove("tcp", l.addr) ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) if err == nil && !ok { err = errors.New("ssh: cancel-tcpip-forward failed") diff --git a/vendor/golang.org/x/net/context/context.go b/vendor/golang.org/x/net/context/context.go index d3cb95175..24cea6882 100644 --- a/vendor/golang.org/x/net/context/context.go +++ b/vendor/golang.org/x/net/context/context.go @@ -2,42 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package context defines the Context type, which carries deadlines, -// cancellation signals, and other request-scoped values across API boundaries -// and between processes. -// As of Go 1.7 this package is available in the standard library under the -// name [context]. -// -// Incoming requests to a server should create a [Context], and outgoing -// calls to servers should accept a Context. The chain of function -// calls between them must propagate the Context, optionally replacing -// it with a derived Context created using [WithCancel], [WithDeadline], -// [WithTimeout], or [WithValue]. -// -// Programs that use Contexts should follow these rules to keep interfaces -// consistent across packages and enable static analysis tools to check context -// propagation: -// -// Do not store Contexts inside a struct type; instead, pass a Context -// explicitly to each function that needs it. This is discussed further in -// https://go.dev/blog/context-and-structs. The Context should be the first -// parameter, typically named ctx: -// -// func DoSomething(ctx context.Context, arg Arg) error { -// // ... use ctx ... -// } -// -// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO] -// if you are unsure about which Context to use. -// -// Use context Values only for request-scoped data that transits processes and -// APIs, not for passing optional parameters to functions. -// -// The same Context may be passed to functions running in different goroutines; -// Contexts are safe for simultaneous use by multiple goroutines. +// Package context has been superseded by the standard library [context] package. // -// See https://go.dev/blog/context for example code for a server that uses -// Contexts. +// Deprecated: Use the standard library context package instead. package context import ( diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index 93bcaab03..9a4bd123c 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -280,6 +280,8 @@ type Framer struct { // lastHeaderStream is non-zero if the last frame was an // unfinished HEADERS/CONTINUATION. lastHeaderStream uint32 + // lastFrameType holds the type of the last frame for verifying frame order. + lastFrameType FrameType maxReadSize uint32 headerBuf [frameHeaderLen]byte @@ -488,30 +490,41 @@ func terminalReadFrameError(err error) bool { return err != nil } -// ReadFrame reads a single frame. The returned Frame is only valid -// until the next call to ReadFrame. +// ReadFrameHeader reads the header of the next frame. +// It reads the 9-byte fixed frame header, and does not read any portion of the +// frame payload. The caller is responsible for consuming the payload, either +// with ReadFrameForHeader or directly from the Framer's io.Reader. // -// If the frame is larger than previously set with SetMaxReadFrameSize, the -// returned error is ErrFrameTooLarge. Other errors may be of type -// ConnectionError, StreamError, or anything else from the underlying -// reader. +// If the frame is larger than previously set with SetMaxReadFrameSize, it +// returns the frame header and ErrFrameTooLarge. // -// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID -// indicates the stream responsible for the error. -func (fr *Framer) ReadFrame() (Frame, error) { +// If the returned FrameHeader.StreamID is non-zero, it indicates the stream +// responsible for the error. +func (fr *Framer) ReadFrameHeader() (FrameHeader, error) { fr.errDetail = nil - if fr.lastFrame != nil { - fr.lastFrame.invalidate() - } fh, err := readFrameHeader(fr.headerBuf[:], fr.r) if err != nil { - return nil, err + return fh, err } if fh.Length > fr.maxReadSize { if fh == invalidHTTP1LookingFrameHeader() { - return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", ErrFrameTooLarge) + return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", ErrFrameTooLarge) } - return nil, ErrFrameTooLarge + return fh, ErrFrameTooLarge + } + if err := fr.checkFrameOrder(fh); err != nil { + return fh, err + } + return fh, nil +} + +// ReadFrameForHeader reads the payload for the frame with the given FrameHeader. +// +// It behaves identically to ReadFrame, other than not checking the maximum +// frame size. +func (fr *Framer) ReadFrameForHeader(fh FrameHeader) (Frame, error) { + if fr.lastFrame != nil { + fr.lastFrame.invalidate() } payload := fr.getReadBuf(fh.Length) if _, err := io.ReadFull(fr.r, payload); err != nil { @@ -527,9 +540,7 @@ func (fr *Framer) ReadFrame() (Frame, error) { } return nil, err } - if err := fr.checkFrameOrder(f); err != nil { - return nil, err - } + fr.lastFrame = f if fr.logReads { fr.debugReadLoggerf("http2: Framer %p: read %v", fr, summarizeFrame(f)) } @@ -539,6 +550,24 @@ func (fr *Framer) ReadFrame() (Frame, error) { return f, nil } +// ReadFrame reads a single frame. The returned Frame is only valid +// until the next call to ReadFrame or ReadFrameBodyForHeader. +// +// If the frame is larger than previously set with SetMaxReadFrameSize, the +// returned error is ErrFrameTooLarge. Other errors may be of type +// ConnectionError, StreamError, or anything else from the underlying +// reader. +// +// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID +// indicates the stream responsible for the error. +func (fr *Framer) ReadFrame() (Frame, error) { + fh, err := fr.ReadFrameHeader() + if err != nil { + return nil, err + } + return fr.ReadFrameForHeader(fh) +} + // connError returns ConnectionError(code) but first // stashes away a public reason to the caller can optionally relay it // to the peer before hanging up on them. This might help others debug @@ -551,20 +580,19 @@ func (fr *Framer) connError(code ErrCode, reason string) error { // checkFrameOrder reports an error if f is an invalid frame to return // next from ReadFrame. Mostly it checks whether HEADERS and // CONTINUATION frames are contiguous. -func (fr *Framer) checkFrameOrder(f Frame) error { - last := fr.lastFrame - fr.lastFrame = f +func (fr *Framer) checkFrameOrder(fh FrameHeader) error { + lastType := fr.lastFrameType + fr.lastFrameType = fh.Type if fr.AllowIllegalReads { return nil } - fh := f.Header() if fr.lastHeaderStream != 0 { if fh.Type != FrameContinuation { return fr.connError(ErrCodeProtocol, fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d", fh.Type, fh.StreamID, - last.Header().Type, fr.lastHeaderStream)) + lastType, fr.lastHeaderStream)) } if fh.StreamID != fr.lastHeaderStream { return fr.connError(ErrCodeProtocol, @@ -1161,7 +1189,7 @@ var defaultRFC9218Priority = PriorityParam{ // PriorityParam struct below is a superset of both schemes. The exported // symbols are from RFC 7540 and the non-exported ones are from RFC 9218. -// PriorityParam are the stream prioritzation parameters. +// PriorityParam are the stream prioritization parameters. type PriorityParam struct { // StreamDep is a 31-bit stream identifier for the // stream that this stream depends on. Zero means no diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index be759b606..1965913e5 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -9,6 +9,7 @@ package http2 import ( "bufio" "bytes" + "compress/flate" "compress/gzip" "context" "crypto/rand" @@ -3076,35 +3077,102 @@ type erringRoundTripper struct{ err error } func (rt erringRoundTripper) RoundTripErr() error { return rt.err } func (rt erringRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { return nil, rt.err } +var errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body") + // gzipReader wraps a response body so it can lazily -// call gzip.NewReader on the first call to Read +// get gzip.Reader from the pool on the first call to Read. +// After Close is called it puts gzip.Reader to the pool immediately +// if there is no Read in progress or later when Read completes. type gzipReader struct { _ incomparable body io.ReadCloser // underlying Response.Body - zr *gzip.Reader // lazily-initialized gzip reader - zerr error // sticky error + mu sync.Mutex // guards zr and zerr + zr *gzip.Reader // stores gzip reader from the pool between reads + zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close } -func (gz *gzipReader) Read(p []byte) (n int, err error) { +type eofReader struct{} + +func (eofReader) Read([]byte) (int, error) { return 0, io.EOF } +func (eofReader) ReadByte() (byte, error) { return 0, io.EOF } + +var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }} + +// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r. +func gzipPoolGet(r io.Reader) (*gzip.Reader, error) { + zr := gzipPool.Get().(*gzip.Reader) + if err := zr.Reset(r); err != nil { + gzipPoolPut(zr) + return nil, err + } + return zr, nil +} + +// gzipPoolPut puts a gzip.Reader back into the pool. +func gzipPoolPut(zr *gzip.Reader) { + // Reset will allocate bufio.Reader if we pass it anything + // other than a flate.Reader, so ensure that it's getting one. + var r flate.Reader = eofReader{} + zr.Reset(r) + gzipPool.Put(zr) +} + +// acquire returns a gzip.Reader for reading response body. +// The reader must be released after use. +func (gz *gzipReader) acquire() (*gzip.Reader, error) { + gz.mu.Lock() + defer gz.mu.Unlock() if gz.zerr != nil { - return 0, gz.zerr + return nil, gz.zerr } if gz.zr == nil { - gz.zr, err = gzip.NewReader(gz.body) - if err != nil { - gz.zerr = err - return 0, err + gz.zr, gz.zerr = gzipPoolGet(gz.body) + if gz.zerr != nil { + return nil, gz.zerr } } - return gz.zr.Read(p) + ret := gz.zr + gz.zr, gz.zerr = nil, errConcurrentReadOnResBody + return ret, nil } -func (gz *gzipReader) Close() error { - if err := gz.body.Close(); err != nil { - return err +// release returns the gzip.Reader to the pool if Close was called during Read. +func (gz *gzipReader) release(zr *gzip.Reader) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == errConcurrentReadOnResBody { + gz.zr, gz.zerr = zr, nil + } else { // fs.ErrClosed + gzipPoolPut(zr) + } +} + +// close returns the gzip.Reader to the pool immediately or +// signals release to do so after Read completes. +func (gz *gzipReader) close() { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == nil && gz.zr != nil { + gzipPoolPut(gz.zr) + gz.zr = nil } gz.zerr = fs.ErrClosed - return nil +} + +func (gz *gzipReader) Read(p []byte) (n int, err error) { + zr, err := gz.acquire() + if err != nil { + return 0, err + } + defer gz.release(zr) + + return zr.Read(p) +} + +func (gz *gzipReader) Close() error { + gz.close() + + return gz.body.Close() } type errorReader struct{ err error } diff --git a/vendor/golang.org/x/net/http2/writesched.go b/vendor/golang.org/x/net/http2/writesched.go index 4d3890f99..7de27be52 100644 --- a/vendor/golang.org/x/net/http2/writesched.go +++ b/vendor/golang.org/x/net/http2/writesched.go @@ -185,45 +185,75 @@ func (wr *FrameWriteRequest) replyToWriter(err error) { } // writeQueue is used by implementations of WriteScheduler. +// +// Each writeQueue contains a queue of FrameWriteRequests, meant to store all +// FrameWriteRequests associated with a given stream. This is implemented as a +// two-stage queue: currQueue[currPos:] and nextQueue. Removing an item is done +// by incrementing currPos of currQueue. Adding an item is done by appending it +// to the nextQueue. If currQueue is empty when trying to remove an item, we +// can swap currQueue and nextQueue to remedy the situation. +// This two-stage queue is analogous to the use of two lists in Okasaki's +// purely functional queue but without the overhead of reversing the list when +// swapping stages. +// +// writeQueue also contains prev and next, this can be used by implementations +// of WriteScheduler to construct data structures that represent the order of +// writing between different streams (e.g. circular linked list). type writeQueue struct { - s []FrameWriteRequest + currQueue []FrameWriteRequest + nextQueue []FrameWriteRequest + currPos int + prev, next *writeQueue } -func (q *writeQueue) empty() bool { return len(q.s) == 0 } +func (q *writeQueue) empty() bool { + return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0 +} func (q *writeQueue) push(wr FrameWriteRequest) { - q.s = append(q.s, wr) + q.nextQueue = append(q.nextQueue, wr) } func (q *writeQueue) shift() FrameWriteRequest { - if len(q.s) == 0 { + if q.empty() { panic("invalid use of queue") } - wr := q.s[0] - // TODO: less copy-happy queue. - copy(q.s, q.s[1:]) - q.s[len(q.s)-1] = FrameWriteRequest{} - q.s = q.s[:len(q.s)-1] + if q.currPos >= len(q.currQueue) { + q.currQueue, q.currPos, q.nextQueue = q.nextQueue, 0, q.currQueue[:0] + } + wr := q.currQueue[q.currPos] + q.currQueue[q.currPos] = FrameWriteRequest{} + q.currPos++ return wr } +func (q *writeQueue) peek() *FrameWriteRequest { + if q.currPos < len(q.currQueue) { + return &q.currQueue[q.currPos] + } + if len(q.nextQueue) > 0 { + return &q.nextQueue[0] + } + return nil +} + // consume consumes up to n bytes from q.s[0]. If the frame is // entirely consumed, it is removed from the queue. If the frame // is partially consumed, the frame is kept with the consumed // bytes removed. Returns true iff any bytes were consumed. func (q *writeQueue) consume(n int32) (FrameWriteRequest, bool) { - if len(q.s) == 0 { + if q.empty() { return FrameWriteRequest{}, false } - consumed, rest, numresult := q.s[0].Consume(n) + consumed, rest, numresult := q.peek().Consume(n) switch numresult { case 0: return FrameWriteRequest{}, false case 1: q.shift() case 2: - q.s[0] = rest + *q.peek() = rest } return consumed, true } @@ -232,10 +262,15 @@ type writeQueuePool []*writeQueue // put inserts an unused writeQueue into the pool. func (p *writeQueuePool) put(q *writeQueue) { - for i := range q.s { - q.s[i] = FrameWriteRequest{} + for i := range q.currQueue { + q.currQueue[i] = FrameWriteRequest{} + } + for i := range q.nextQueue { + q.nextQueue[i] = FrameWriteRequest{} } - q.s = q.s[:0] + q.currQueue = q.currQueue[:0] + q.nextQueue = q.nextQueue[:0] + q.currPos = 0 *p = append(*p, q) } diff --git a/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go b/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go index 6d24d6a1b..4e33c29a2 100644 --- a/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go +++ b/vendor/golang.org/x/net/http2/writesched_priority_rfc7540.go @@ -214,8 +214,8 @@ func (z sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k], z[i func (z sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool { // Prefer the subtree that has sent fewer bytes relative to its weight. // See sections 5.3.2 and 5.3.4. - wi, bi := float64(z[i].weight+1), float64(z[i].subtreeBytes) - wk, bk := float64(z[k].weight+1), float64(z[k].subtreeBytes) + wi, bi := float64(z[i].weight)+1, float64(z[i].subtreeBytes) + wk, bk := float64(z[k].weight)+1, float64(z[k].subtreeBytes) if bi == 0 && bk == 0 { return wi >= wk } @@ -302,7 +302,6 @@ func (ws *priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) { q := n.q ws.queuePool.put(&q) - n.q.s = nil if ws.maxClosedNodesInTree > 0 { ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n) } else { diff --git a/vendor/golang.org/x/net/http2/writesched_priority_rfc9128.go b/vendor/golang.org/x/net/http2/writesched_priority_rfc9218.go similarity index 99% rename from vendor/golang.org/x/net/http2/writesched_priority_rfc9128.go rename to vendor/golang.org/x/net/http2/writesched_priority_rfc9218.go index 9b5b8808e..cb4cadc32 100644 --- a/vendor/golang.org/x/net/http2/writesched_priority_rfc9128.go +++ b/vendor/golang.org/x/net/http2/writesched_priority_rfc9218.go @@ -39,7 +39,7 @@ type priorityWriteSchedulerRFC9218 struct { prioritizeIncremental bool } -func newPriorityWriteSchedulerRFC9128() WriteScheduler { +func newPriorityWriteSchedulerRFC9218() WriteScheduler { ws := &priorityWriteSchedulerRFC9218{ streams: make(map[uint32]streamMetadata), } diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go index 1d8cffae8..2f45dbc86 100644 --- a/vendor/golang.org/x/sync/errgroup/errgroup.go +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package errgroup provides synchronization, error propagation, and Context -// cancelation for groups of goroutines working on subtasks of a common task. +// cancellation for groups of goroutines working on subtasks of a common task. // // [errgroup.Group] is related to [sync.WaitGroup] but adds handling of tasks // returning errors. diff --git a/vendor/golang.org/x/sys/cpu/cpu.go b/vendor/golang.org/x/sys/cpu/cpu.go index 63541994e..34c9ae76e 100644 --- a/vendor/golang.org/x/sys/cpu/cpu.go +++ b/vendor/golang.org/x/sys/cpu/cpu.go @@ -92,6 +92,9 @@ var ARM64 struct { HasSHA2 bool // SHA2 hardware implementation HasCRC32 bool // CRC32 hardware implementation HasATOMICS bool // Atomic memory operation instruction set + HasHPDS bool // Hierarchical permission disables in translations tables + HasLOR bool // Limited ordering regions + HasPAN bool // Privileged access never HasFPHP bool // Half precision floating-point instruction set HasASIMDHP bool // Advanced SIMD half precision instruction set HasCPUID bool // CPUID identification scheme registers diff --git a/vendor/golang.org/x/sys/cpu/cpu_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_arm64.go index af2aa99f9..f449c679f 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_arm64.go @@ -65,10 +65,10 @@ func setMinimalFeatures() { func readARM64Registers() { Initialized = true - parseARM64SystemRegisters(getisar0(), getisar1(), getpfr0()) + parseARM64SystemRegisters(getisar0(), getisar1(), getmmfr1(), getpfr0()) } -func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) { +func parseARM64SystemRegisters(isar0, isar1, mmfr1, pfr0 uint64) { // ID_AA64ISAR0_EL1 switch extractBits(isar0, 4, 7) { case 1: @@ -152,6 +152,22 @@ func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) { ARM64.HasI8MM = true } + // ID_AA64MMFR1_EL1 + switch extractBits(mmfr1, 12, 15) { + case 1, 2: + ARM64.HasHPDS = true + } + + switch extractBits(mmfr1, 16, 19) { + case 1: + ARM64.HasLOR = true + } + + switch extractBits(mmfr1, 20, 23) { + case 1, 2, 3: + ARM64.HasPAN = true + } + // ID_AA64PFR0_EL1 switch extractBits(pfr0, 16, 19) { case 0: diff --git a/vendor/golang.org/x/sys/cpu/cpu_arm64.s b/vendor/golang.org/x/sys/cpu/cpu_arm64.s index 22cc99844..a4f24b3b0 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_arm64.s +++ b/vendor/golang.org/x/sys/cpu/cpu_arm64.s @@ -9,31 +9,34 @@ // func getisar0() uint64 TEXT ·getisar0(SB),NOSPLIT,$0-8 // get Instruction Set Attributes 0 into x0 - // mrs x0, ID_AA64ISAR0_EL1 = d5380600 - WORD $0xd5380600 + MRS ID_AA64ISAR0_EL1, R0 MOVD R0, ret+0(FP) RET // func getisar1() uint64 TEXT ·getisar1(SB),NOSPLIT,$0-8 // get Instruction Set Attributes 1 into x0 - // mrs x0, ID_AA64ISAR1_EL1 = d5380620 - WORD $0xd5380620 + MRS ID_AA64ISAR1_EL1, R0 + MOVD R0, ret+0(FP) + RET + +// func getmmfr1() uint64 +TEXT ·getmmfr1(SB),NOSPLIT,$0-8 + // get Memory Model Feature Register 1 into x0 + MRS ID_AA64MMFR1_EL1, R0 MOVD R0, ret+0(FP) RET // func getpfr0() uint64 TEXT ·getpfr0(SB),NOSPLIT,$0-8 // get Processor Feature Register 0 into x0 - // mrs x0, ID_AA64PFR0_EL1 = d5380400 - WORD $0xd5380400 + MRS ID_AA64PFR0_EL1, R0 MOVD R0, ret+0(FP) RET // func getzfr0() uint64 TEXT ·getzfr0(SB),NOSPLIT,$0-8 // get SVE Feature Register 0 into x0 - // mrs x0, ID_AA64ZFR0_EL1 = d5380480 - WORD $0xd5380480 + MRS ID_AA64ZFR0_EL1, R0 MOVD R0, ret+0(FP) RET diff --git a/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go index 6ac6e1efb..e3fc5a8d3 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go @@ -8,5 +8,6 @@ package cpu func getisar0() uint64 func getisar1() uint64 +func getmmfr1() uint64 func getpfr0() uint64 func getzfr0() uint64 diff --git a/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go index 7f1946780..8df2079e1 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go @@ -8,4 +8,5 @@ package cpu func getisar0() uint64 { return 0 } func getisar1() uint64 { return 0 } +func getmmfr1() uint64 { return 0 } func getpfr0() uint64 { return 0 } diff --git a/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go index ebfb3fc8e..19aea0633 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go @@ -167,7 +167,7 @@ func doinit() { setMinimalFeatures() return } - parseARM64SystemRegisters(cpuid.aa64isar0, cpuid.aa64isar1, cpuid.aa64pfr0) + parseARM64SystemRegisters(cpuid.aa64isar0, cpuid.aa64isar1, cpuid.aa64mmfr1, cpuid.aa64pfr0) Initialized = true } diff --git a/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go index 85b64d5cc..87fd3a778 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go @@ -59,7 +59,7 @@ func doinit() { if !ok { return } - parseARM64SystemRegisters(isar0, isar1, 0) + parseARM64SystemRegisters(isar0, isar1, 0, 0) Initialized = true } diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index d1c8b2640..42517077c 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -226,6 +226,7 @@ struct ltchars { #include #include #include +#include #include #include #include @@ -529,6 +530,7 @@ ccflags="$@" $2 ~ /^O[CNPFPL][A-Z]+[^_][A-Z]+$/ || $2 ~ /^(NL|CR|TAB|BS|VT|FF)DLY$/ || $2 ~ /^(NL|CR|TAB|BS|VT|FF)[0-9]$/ || + $2 ~ /^(DT|EI|ELF|EV|NN|NT|PF|SHF|SHN|SHT|STB|STT|VER)_/ || $2 ~ /^O?XTABS$/ || $2 ~ /^TC[IO](ON|OFF)$/ || $2 ~ /^IN_/ || diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 9439af961..06c0eea6f 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -2643,3 +2643,9 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) { //sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error) //sys Mseal(b []byte, flags uint) (err error) + +//sys setMemPolicy(mode int, mask *CPUSet, size int) (err error) = SYS_SET_MEMPOLICY + +func SetMemPolicy(mode int, mask *CPUSet) error { + return setMemPolicy(mode, mask, _CPU_SETSIZE) +} diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index b6db27d93..d0a75da57 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -853,20 +853,86 @@ const ( DM_VERSION_MAJOR = 0x4 DM_VERSION_MINOR = 0x32 DM_VERSION_PATCHLEVEL = 0x0 + DT_ADDRRNGHI = 0x6ffffeff + DT_ADDRRNGLO = 0x6ffffe00 DT_BLK = 0x6 DT_CHR = 0x2 + DT_DEBUG = 0x15 DT_DIR = 0x4 + DT_ENCODING = 0x20 DT_FIFO = 0x1 + DT_FINI = 0xd + DT_FLAGS_1 = 0x6ffffffb + DT_GNU_HASH = 0x6ffffef5 + DT_HASH = 0x4 + DT_HIOS = 0x6ffff000 + DT_HIPROC = 0x7fffffff + DT_INIT = 0xc + DT_JMPREL = 0x17 DT_LNK = 0xa + DT_LOOS = 0x6000000d + DT_LOPROC = 0x70000000 + DT_NEEDED = 0x1 + DT_NULL = 0x0 + DT_PLTGOT = 0x3 + DT_PLTREL = 0x14 + DT_PLTRELSZ = 0x2 DT_REG = 0x8 + DT_REL = 0x11 + DT_RELA = 0x7 + DT_RELACOUNT = 0x6ffffff9 + DT_RELAENT = 0x9 + DT_RELASZ = 0x8 + DT_RELCOUNT = 0x6ffffffa + DT_RELENT = 0x13 + DT_RELSZ = 0x12 + DT_RPATH = 0xf DT_SOCK = 0xc + DT_SONAME = 0xe + DT_STRSZ = 0xa + DT_STRTAB = 0x5 + DT_SYMBOLIC = 0x10 + DT_SYMENT = 0xb + DT_SYMTAB = 0x6 + DT_TEXTREL = 0x16 DT_UNKNOWN = 0x0 + DT_VALRNGHI = 0x6ffffdff + DT_VALRNGLO = 0x6ffffd00 + DT_VERDEF = 0x6ffffffc + DT_VERDEFNUM = 0x6ffffffd + DT_VERNEED = 0x6ffffffe + DT_VERNEEDNUM = 0x6fffffff + DT_VERSYM = 0x6ffffff0 DT_WHT = 0xe ECHO = 0x8 ECRYPTFS_SUPER_MAGIC = 0xf15f EFD_SEMAPHORE = 0x1 EFIVARFS_MAGIC = 0xde5e81e4 EFS_SUPER_MAGIC = 0x414a53 + EI_CLASS = 0x4 + EI_DATA = 0x5 + EI_MAG0 = 0x0 + EI_MAG1 = 0x1 + EI_MAG2 = 0x2 + EI_MAG3 = 0x3 + EI_NIDENT = 0x10 + EI_OSABI = 0x7 + EI_PAD = 0x8 + EI_VERSION = 0x6 + ELFCLASS32 = 0x1 + ELFCLASS64 = 0x2 + ELFCLASSNONE = 0x0 + ELFCLASSNUM = 0x3 + ELFDATA2LSB = 0x1 + ELFDATA2MSB = 0x2 + ELFDATANONE = 0x0 + ELFMAG = "\177ELF" + ELFMAG0 = 0x7f + ELFMAG1 = 'E' + ELFMAG2 = 'L' + ELFMAG3 = 'F' + ELFOSABI_LINUX = 0x3 + ELFOSABI_NONE = 0x0 EM_386 = 0x3 EM_486 = 0x6 EM_68K = 0x4 @@ -1152,14 +1218,24 @@ const ( ETH_P_WCCP = 0x883e ETH_P_X25 = 0x805 ETH_P_XDSA = 0xf8 + ET_CORE = 0x4 + ET_DYN = 0x3 + ET_EXEC = 0x2 + ET_HIPROC = 0xffff + ET_LOPROC = 0xff00 + ET_NONE = 0x0 + ET_REL = 0x1 EV_ABS = 0x3 EV_CNT = 0x20 + EV_CURRENT = 0x1 EV_FF = 0x15 EV_FF_STATUS = 0x17 EV_KEY = 0x1 EV_LED = 0x11 EV_MAX = 0x1f EV_MSC = 0x4 + EV_NONE = 0x0 + EV_NUM = 0x2 EV_PWR = 0x16 EV_REL = 0x2 EV_REP = 0x14 @@ -2276,7 +2352,167 @@ const ( NLM_F_REPLACE = 0x100 NLM_F_REQUEST = 0x1 NLM_F_ROOT = 0x100 + NN_386_IOPERM = "LINUX" + NN_386_TLS = "LINUX" + NN_ARC_V2 = "LINUX" + NN_ARM_FPMR = "LINUX" + NN_ARM_GCS = "LINUX" + NN_ARM_HW_BREAK = "LINUX" + NN_ARM_HW_WATCH = "LINUX" + NN_ARM_PACA_KEYS = "LINUX" + NN_ARM_PACG_KEYS = "LINUX" + NN_ARM_PAC_ENABLED_KEYS = "LINUX" + NN_ARM_PAC_MASK = "LINUX" + NN_ARM_POE = "LINUX" + NN_ARM_SSVE = "LINUX" + NN_ARM_SVE = "LINUX" + NN_ARM_SYSTEM_CALL = "LINUX" + NN_ARM_TAGGED_ADDR_CTRL = "LINUX" + NN_ARM_TLS = "LINUX" + NN_ARM_VFP = "LINUX" + NN_ARM_ZA = "LINUX" + NN_ARM_ZT = "LINUX" + NN_AUXV = "CORE" + NN_FILE = "CORE" + NN_GNU_PROPERTY_TYPE_0 = "GNU" + NN_LOONGARCH_CPUCFG = "LINUX" + NN_LOONGARCH_CSR = "LINUX" + NN_LOONGARCH_HW_BREAK = "LINUX" + NN_LOONGARCH_HW_WATCH = "LINUX" + NN_LOONGARCH_LASX = "LINUX" + NN_LOONGARCH_LBT = "LINUX" + NN_LOONGARCH_LSX = "LINUX" + NN_MIPS_DSP = "LINUX" + NN_MIPS_FP_MODE = "LINUX" + NN_MIPS_MSA = "LINUX" + NN_PPC_DEXCR = "LINUX" + NN_PPC_DSCR = "LINUX" + NN_PPC_EBB = "LINUX" + NN_PPC_HASHKEYR = "LINUX" + NN_PPC_PKEY = "LINUX" + NN_PPC_PMU = "LINUX" + NN_PPC_PPR = "LINUX" + NN_PPC_SPE = "LINUX" + NN_PPC_TAR = "LINUX" + NN_PPC_TM_CDSCR = "LINUX" + NN_PPC_TM_CFPR = "LINUX" + NN_PPC_TM_CGPR = "LINUX" + NN_PPC_TM_CPPR = "LINUX" + NN_PPC_TM_CTAR = "LINUX" + NN_PPC_TM_CVMX = "LINUX" + NN_PPC_TM_CVSX = "LINUX" + NN_PPC_TM_SPR = "LINUX" + NN_PPC_VMX = "LINUX" + NN_PPC_VSX = "LINUX" + NN_PRFPREG = "CORE" + NN_PRPSINFO = "CORE" + NN_PRSTATUS = "CORE" + NN_PRXFPREG = "LINUX" + NN_RISCV_CSR = "LINUX" + NN_RISCV_TAGGED_ADDR_CTRL = "LINUX" + NN_RISCV_VECTOR = "LINUX" + NN_S390_CTRS = "LINUX" + NN_S390_GS_BC = "LINUX" + NN_S390_GS_CB = "LINUX" + NN_S390_HIGH_GPRS = "LINUX" + NN_S390_LAST_BREAK = "LINUX" + NN_S390_PREFIX = "LINUX" + NN_S390_PV_CPU_DATA = "LINUX" + NN_S390_RI_CB = "LINUX" + NN_S390_SYSTEM_CALL = "LINUX" + NN_S390_TDB = "LINUX" + NN_S390_TIMER = "LINUX" + NN_S390_TODCMP = "LINUX" + NN_S390_TODPREG = "LINUX" + NN_S390_VXRS_HIGH = "LINUX" + NN_S390_VXRS_LOW = "LINUX" + NN_SIGINFO = "CORE" + NN_TASKSTRUCT = "CORE" + NN_VMCOREDD = "LINUX" + NN_X86_SHSTK = "LINUX" + NN_X86_XSAVE_LAYOUT = "LINUX" + NN_X86_XSTATE = "LINUX" NSFS_MAGIC = 0x6e736673 + NT_386_IOPERM = 0x201 + NT_386_TLS = 0x200 + NT_ARC_V2 = 0x600 + NT_ARM_FPMR = 0x40e + NT_ARM_GCS = 0x410 + NT_ARM_HW_BREAK = 0x402 + NT_ARM_HW_WATCH = 0x403 + NT_ARM_PACA_KEYS = 0x407 + NT_ARM_PACG_KEYS = 0x408 + NT_ARM_PAC_ENABLED_KEYS = 0x40a + NT_ARM_PAC_MASK = 0x406 + NT_ARM_POE = 0x40f + NT_ARM_SSVE = 0x40b + NT_ARM_SVE = 0x405 + NT_ARM_SYSTEM_CALL = 0x404 + NT_ARM_TAGGED_ADDR_CTRL = 0x409 + NT_ARM_TLS = 0x401 + NT_ARM_VFP = 0x400 + NT_ARM_ZA = 0x40c + NT_ARM_ZT = 0x40d + NT_AUXV = 0x6 + NT_FILE = 0x46494c45 + NT_GNU_PROPERTY_TYPE_0 = 0x5 + NT_LOONGARCH_CPUCFG = 0xa00 + NT_LOONGARCH_CSR = 0xa01 + NT_LOONGARCH_HW_BREAK = 0xa05 + NT_LOONGARCH_HW_WATCH = 0xa06 + NT_LOONGARCH_LASX = 0xa03 + NT_LOONGARCH_LBT = 0xa04 + NT_LOONGARCH_LSX = 0xa02 + NT_MIPS_DSP = 0x800 + NT_MIPS_FP_MODE = 0x801 + NT_MIPS_MSA = 0x802 + NT_PPC_DEXCR = 0x111 + NT_PPC_DSCR = 0x105 + NT_PPC_EBB = 0x106 + NT_PPC_HASHKEYR = 0x112 + NT_PPC_PKEY = 0x110 + NT_PPC_PMU = 0x107 + NT_PPC_PPR = 0x104 + NT_PPC_SPE = 0x101 + NT_PPC_TAR = 0x103 + NT_PPC_TM_CDSCR = 0x10f + NT_PPC_TM_CFPR = 0x109 + NT_PPC_TM_CGPR = 0x108 + NT_PPC_TM_CPPR = 0x10e + NT_PPC_TM_CTAR = 0x10d + NT_PPC_TM_CVMX = 0x10a + NT_PPC_TM_CVSX = 0x10b + NT_PPC_TM_SPR = 0x10c + NT_PPC_VMX = 0x100 + NT_PPC_VSX = 0x102 + NT_PRFPREG = 0x2 + NT_PRPSINFO = 0x3 + NT_PRSTATUS = 0x1 + NT_PRXFPREG = 0x46e62b7f + NT_RISCV_CSR = 0x900 + NT_RISCV_TAGGED_ADDR_CTRL = 0x902 + NT_RISCV_VECTOR = 0x901 + NT_S390_CTRS = 0x304 + NT_S390_GS_BC = 0x30c + NT_S390_GS_CB = 0x30b + NT_S390_HIGH_GPRS = 0x300 + NT_S390_LAST_BREAK = 0x306 + NT_S390_PREFIX = 0x305 + NT_S390_PV_CPU_DATA = 0x30e + NT_S390_RI_CB = 0x30d + NT_S390_SYSTEM_CALL = 0x307 + NT_S390_TDB = 0x308 + NT_S390_TIMER = 0x301 + NT_S390_TODCMP = 0x302 + NT_S390_TODPREG = 0x303 + NT_S390_VXRS_HIGH = 0x30a + NT_S390_VXRS_LOW = 0x309 + NT_SIGINFO = 0x53494749 + NT_TASKSTRUCT = 0x4 + NT_VMCOREDD = 0x700 + NT_X86_SHSTK = 0x204 + NT_X86_XSAVE_LAYOUT = 0x205 + NT_X86_XSTATE = 0x202 OCFS2_SUPER_MAGIC = 0x7461636f OCRNL = 0x8 OFDEL = 0x80 @@ -2463,6 +2699,59 @@ const ( PERF_RECORD_MISC_USER = 0x2 PERF_SAMPLE_BRANCH_PLM_ALL = 0x7 PERF_SAMPLE_WEIGHT_TYPE = 0x1004000 + PF_ALG = 0x26 + PF_APPLETALK = 0x5 + PF_ASH = 0x12 + PF_ATMPVC = 0x8 + PF_ATMSVC = 0x14 + PF_AX25 = 0x3 + PF_BLUETOOTH = 0x1f + PF_BRIDGE = 0x7 + PF_CAIF = 0x25 + PF_CAN = 0x1d + PF_DECnet = 0xc + PF_ECONET = 0x13 + PF_FILE = 0x1 + PF_IB = 0x1b + PF_IEEE802154 = 0x24 + PF_INET = 0x2 + PF_INET6 = 0xa + PF_IPX = 0x4 + PF_IRDA = 0x17 + PF_ISDN = 0x22 + PF_IUCV = 0x20 + PF_KCM = 0x29 + PF_KEY = 0xf + PF_LLC = 0x1a + PF_LOCAL = 0x1 + PF_MAX = 0x2e + PF_MCTP = 0x2d + PF_MPLS = 0x1c + PF_NETBEUI = 0xd + PF_NETLINK = 0x10 + PF_NETROM = 0x6 + PF_NFC = 0x27 + PF_PACKET = 0x11 + PF_PHONET = 0x23 + PF_PPPOX = 0x18 + PF_QIPCRTR = 0x2a + PF_R = 0x4 + PF_RDS = 0x15 + PF_ROSE = 0xb + PF_ROUTE = 0x10 + PF_RXRPC = 0x21 + PF_SECURITY = 0xe + PF_SMC = 0x2b + PF_SNA = 0x16 + PF_TIPC = 0x1e + PF_UNIX = 0x1 + PF_UNSPEC = 0x0 + PF_VSOCK = 0x28 + PF_W = 0x2 + PF_WANPIPE = 0x19 + PF_X = 0x1 + PF_X25 = 0x9 + PF_XDP = 0x2c PID_FS_MAGIC = 0x50494446 PIPEFS_MAGIC = 0x50495045 PPPIOCGNPMODE = 0xc008744c @@ -2758,6 +3047,23 @@ const ( PTRACE_SYSCALL_INFO_NONE = 0x0 PTRACE_SYSCALL_INFO_SECCOMP = 0x3 PTRACE_TRACEME = 0x0 + PT_AARCH64_MEMTAG_MTE = 0x70000002 + PT_DYNAMIC = 0x2 + PT_GNU_EH_FRAME = 0x6474e550 + PT_GNU_PROPERTY = 0x6474e553 + PT_GNU_RELRO = 0x6474e552 + PT_GNU_STACK = 0x6474e551 + PT_HIOS = 0x6fffffff + PT_HIPROC = 0x7fffffff + PT_INTERP = 0x3 + PT_LOAD = 0x1 + PT_LOOS = 0x60000000 + PT_LOPROC = 0x70000000 + PT_NOTE = 0x4 + PT_NULL = 0x0 + PT_PHDR = 0x6 + PT_SHLIB = 0x5 + PT_TLS = 0x7 P_ALL = 0x0 P_PGID = 0x2 P_PID = 0x1 @@ -3091,6 +3397,47 @@ const ( SEEK_MAX = 0x4 SEEK_SET = 0x0 SELINUX_MAGIC = 0xf97cff8c + SHF_ALLOC = 0x2 + SHF_EXCLUDE = 0x8000000 + SHF_EXECINSTR = 0x4 + SHF_GROUP = 0x200 + SHF_INFO_LINK = 0x40 + SHF_LINK_ORDER = 0x80 + SHF_MASKOS = 0xff00000 + SHF_MASKPROC = 0xf0000000 + SHF_MERGE = 0x10 + SHF_ORDERED = 0x4000000 + SHF_OS_NONCONFORMING = 0x100 + SHF_RELA_LIVEPATCH = 0x100000 + SHF_RO_AFTER_INIT = 0x200000 + SHF_STRINGS = 0x20 + SHF_TLS = 0x400 + SHF_WRITE = 0x1 + SHN_ABS = 0xfff1 + SHN_COMMON = 0xfff2 + SHN_HIPROC = 0xff1f + SHN_HIRESERVE = 0xffff + SHN_LIVEPATCH = 0xff20 + SHN_LOPROC = 0xff00 + SHN_LORESERVE = 0xff00 + SHN_UNDEF = 0x0 + SHT_DYNAMIC = 0x6 + SHT_DYNSYM = 0xb + SHT_HASH = 0x5 + SHT_HIPROC = 0x7fffffff + SHT_HIUSER = 0xffffffff + SHT_LOPROC = 0x70000000 + SHT_LOUSER = 0x80000000 + SHT_NOBITS = 0x8 + SHT_NOTE = 0x7 + SHT_NULL = 0x0 + SHT_NUM = 0xc + SHT_PROGBITS = 0x1 + SHT_REL = 0x9 + SHT_RELA = 0x4 + SHT_SHLIB = 0xa + SHT_STRTAB = 0x3 + SHT_SYMTAB = 0x2 SHUT_RD = 0x0 SHUT_RDWR = 0x2 SHUT_WR = 0x1 @@ -3317,6 +3664,16 @@ const ( STATX_UID = 0x8 STATX_WRITE_ATOMIC = 0x10000 STATX__RESERVED = 0x80000000 + STB_GLOBAL = 0x1 + STB_LOCAL = 0x0 + STB_WEAK = 0x2 + STT_COMMON = 0x5 + STT_FILE = 0x4 + STT_FUNC = 0x2 + STT_NOTYPE = 0x0 + STT_OBJECT = 0x1 + STT_SECTION = 0x3 + STT_TLS = 0x6 SYNC_FILE_RANGE_WAIT_AFTER = 0x4 SYNC_FILE_RANGE_WAIT_BEFORE = 0x1 SYNC_FILE_RANGE_WRITE = 0x2 @@ -3553,6 +3910,8 @@ const ( UTIME_OMIT = 0x3ffffffe V9FS_MAGIC = 0x1021997 VERASE = 0x2 + VER_FLG_BASE = 0x1 + VER_FLG_WEAK = 0x2 VINTR = 0x0 VKILL = 0x3 VLNEXT = 0xf diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 5cc1e8eb2..8935d10a3 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -2238,3 +2238,13 @@ func Mseal(b []byte, flags uint) (err error) { } return } + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func setMemPolicy(mode int, mask *CPUSet, size int) (err error) { + _, _, e1 := Syscall(SYS_SET_MEMPOLICY, uintptr(mode), uintptr(unsafe.Pointer(mask)), uintptr(size)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 944e75a11..c1a467017 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -3590,6 +3590,8 @@ type Nhmsg struct { Flags uint32 } +const SizeofNhmsg = 0x8 + type NexthopGrp struct { Id uint32 Weight uint8 @@ -3597,6 +3599,8 @@ type NexthopGrp struct { Resvd2 uint16 } +const SizeofNexthopGrp = 0x8 + const ( NHA_UNSPEC = 0x0 NHA_ID = 0x1 @@ -6332,3 +6336,30 @@ type SockDiagReq struct { } const RTM_NEWNVLAN = 0x70 + +const ( + MPOL_BIND = 0x2 + MPOL_DEFAULT = 0x0 + MPOL_F_ADDR = 0x2 + MPOL_F_MEMS_ALLOWED = 0x4 + MPOL_F_MOF = 0x8 + MPOL_F_MORON = 0x10 + MPOL_F_NODE = 0x1 + MPOL_F_NUMA_BALANCING = 0x2000 + MPOL_F_RELATIVE_NODES = 0x4000 + MPOL_F_SHARED = 0x1 + MPOL_F_STATIC_NODES = 0x8000 + MPOL_INTERLEAVE = 0x3 + MPOL_LOCAL = 0x4 + MPOL_MAX = 0x7 + MPOL_MF_INTERNAL = 0x10 + MPOL_MF_LAZY = 0x8 + MPOL_MF_MOVE_ALL = 0x4 + MPOL_MF_MOVE = 0x2 + MPOL_MF_STRICT = 0x1 + MPOL_MF_VALID = 0x7 + MPOL_MODE_FLAGS = 0xe000 + MPOL_PREFERRED = 0x1 + MPOL_PREFERRED_MANY = 0x5 + MPOL_WEIGHTED_INTERLEAVE = 0x6 +) diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index bd5133730..69439df2a 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -892,8 +892,12 @@ const socket_error = uintptr(^uint32(0)) //sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar //sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx //sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex +//sys GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) = iphlpapi.GetIpForwardEntry2 +//sys GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) = iphlpapi.GetIpForwardTable2 //sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry +//sys FreeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable //sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange +//sys NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyRouteChange2 //sys NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyUnicastIpAddressChange //sys CancelMibChangeNotify2(notificationHandle Handle) (errcode error) = iphlpapi.CancelMibChangeNotify2 @@ -916,6 +920,17 @@ type RawSockaddrInet6 struct { Scope_id uint32 } +// RawSockaddrInet is a union that contains an IPv4, an IPv6 address, or an address family. See +// https://learn.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-sockaddr_inet. +// +// A [*RawSockaddrInet] may be converted to a [*RawSockaddrInet4] or [*RawSockaddrInet6] using +// unsafe, depending on the address family. +type RawSockaddrInet struct { + Family uint16 + Port uint16 + Data [6]uint32 +} + type RawSockaddr struct { Family uint16 Data [14]int8 diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 358be3c7f..6e4f50eb4 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -2320,6 +2320,82 @@ type MibIfRow2 struct { OutQLen uint64 } +// IP_ADDRESS_PREFIX stores an IP address prefix. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-ip_address_prefix. +type IpAddressPrefix struct { + Prefix RawSockaddrInet + PrefixLength uint8 +} + +// NL_ROUTE_ORIGIN enumeration from nldef.h or +// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_route_origin. +const ( + NlroManual = 0 + NlroWellKnown = 1 + NlroDHCP = 2 + NlroRouterAdvertisement = 3 + Nlro6to4 = 4 +) + +// NL_ROUTE_ORIGIN enumeration from nldef.h or +// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_route_protocol. +const ( + MIB_IPPROTO_OTHER = 1 + MIB_IPPROTO_LOCAL = 2 + MIB_IPPROTO_NETMGMT = 3 + MIB_IPPROTO_ICMP = 4 + MIB_IPPROTO_EGP = 5 + MIB_IPPROTO_GGP = 6 + MIB_IPPROTO_HELLO = 7 + MIB_IPPROTO_RIP = 8 + MIB_IPPROTO_IS_IS = 9 + MIB_IPPROTO_ES_IS = 10 + MIB_IPPROTO_CISCO = 11 + MIB_IPPROTO_BBN = 12 + MIB_IPPROTO_OSPF = 13 + MIB_IPPROTO_BGP = 14 + MIB_IPPROTO_IDPR = 15 + MIB_IPPROTO_EIGRP = 16 + MIB_IPPROTO_DVMRP = 17 + MIB_IPPROTO_RPL = 18 + MIB_IPPROTO_DHCP = 19 + MIB_IPPROTO_NT_AUTOSTATIC = 10002 + MIB_IPPROTO_NT_STATIC = 10006 + MIB_IPPROTO_NT_STATIC_NON_DOD = 10007 +) + +// MIB_IPFORWARD_ROW2 stores information about an IP route entry. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2. +type MibIpForwardRow2 struct { + InterfaceLuid uint64 + InterfaceIndex uint32 + DestinationPrefix IpAddressPrefix + NextHop RawSockaddrInet + SitePrefixLength uint8 + ValidLifetime uint32 + PreferredLifetime uint32 + Metric uint32 + Protocol uint32 + Loopback uint8 + AutoconfigureAddress uint8 + Publish uint8 + Immortal uint8 + Age uint32 + Origin uint32 +} + +// MIB_IPFORWARD_TABLE2 contains a table of IP route entries. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_table2. +type MibIpForwardTable2 struct { + NumEntries uint32 + Table [1]MibIpForwardRow2 +} + +// Rows returns the IP route entries in the table. +func (t *MibIpForwardTable2) Rows() []MibIpForwardRow2 { + return unsafe.Slice(&t.Table[0], t.NumEntries) +} + // MIB_UNICASTIPADDRESS_ROW stores information about a unicast IP address. See // https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_row. type MibUnicastIpAddressRow struct { diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 426151a01..f25b7308a 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -182,13 +182,17 @@ var ( procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute") procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute") procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2") + procFreeMibTable = modiphlpapi.NewProc("FreeMibTable") procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo") procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx") procGetIfEntry = modiphlpapi.NewProc("GetIfEntry") procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex") + procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2") + procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2") procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry") procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange") + procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2") procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange") procAddDllDirectory = modkernel32.NewProc("AddDllDirectory") procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject") @@ -1624,6 +1628,11 @@ func CancelMibChangeNotify2(notificationHandle Handle) (errcode error) { return } +func FreeMibTable(memory unsafe.Pointer) { + syscall.SyscallN(procFreeMibTable.Addr(), uintptr(memory)) + return +} + func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) { r0, _, _ := syscall.SyscallN(procGetAdaptersAddresses.Addr(), uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer))) if r0 != 0 { @@ -1664,6 +1673,22 @@ func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) { return } +func GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) { + r0, _, _ := syscall.SyscallN(procGetIpForwardEntry2.Addr(), uintptr(unsafe.Pointer(row))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) { + r0, _, _ := syscall.SyscallN(procGetIpForwardTable2.Addr(), uintptr(family), uintptr(unsafe.Pointer(table))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) { r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressEntry.Addr(), uintptr(unsafe.Pointer(row))) if r0 != 0 { @@ -1684,6 +1709,18 @@ func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsa return } +func NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) { + var _p0 uint32 + if initialNotification { + _p0 = 1 + } + r0, _, _ := syscall.SyscallN(procNotifyRouteChange2.Addr(), uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) { var _p0 uint32 if initialNotification { diff --git a/vendor/golang.org/x/term/terminal.go b/vendor/golang.org/x/term/terminal.go index bddb2e2ae..9255449b9 100644 --- a/vendor/golang.org/x/term/terminal.go +++ b/vendor/golang.org/x/term/terminal.go @@ -413,7 +413,7 @@ func (t *Terminal) eraseNPreviousChars(n int) { } } -// countToLeftWord returns then number of characters from the cursor to the +// countToLeftWord returns the number of characters from the cursor to the // start of the previous word. func (t *Terminal) countToLeftWord() int { if t.pos == 0 { @@ -438,7 +438,7 @@ func (t *Terminal) countToLeftWord() int { return t.pos - pos } -// countToRightWord returns then number of characters from the cursor to the +// countToRightWord returns the number of characters from the cursor to the // start of the next word. func (t *Terminal) countToRightWord() int { pos := t.pos @@ -478,7 +478,7 @@ func visualLength(runes []rune) int { return length } -// histroryAt unlocks the terminal and relocks it while calling History.At. +// historyAt unlocks the terminal and relocks it while calling History.At. func (t *Terminal) historyAt(idx int) (string, bool) { t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer. defer t.lock.Lock() // panic in At (or Len) protection. diff --git a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go index 1aa7afb9c..efbf05d59 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go @@ -237,7 +237,7 @@ Files: // so accumulate them all and then prefer the one that // matches build.Default.GOARCH. var archCandidates []*asmArch - for _, fld := range strings.Fields(m[1]) { + for fld := range strings.FieldsSeq(m[1]) { for _, a := range arches { if a.name == fld { archCandidates = append(archCandidates, a) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go b/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go index 1914bb476..dfe68d9b1 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go @@ -19,7 +19,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/astutil" ) //go:embed doc.go @@ -66,8 +66,8 @@ func run(pass *analysis.Pass) (any, error) { !isMapIndex(pass.TypesInfo, lhs) && reflect.TypeOf(lhs) == reflect.TypeOf(rhs) { // short-circuit the heavy-weight gofmt check - le = analysisinternal.Format(pass.Fset, lhs) - re := analysisinternal.Format(pass.Fset, rhs) + le = astutil.Format(pass.Fset, lhs) + re := astutil.Format(pass.Fset, rhs) if le == re { isSelfAssign = true } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go b/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go index 82d5439ce..ddd875b23 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go @@ -14,7 +14,8 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/astutil" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -30,7 +31,7 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - if !analysisinternal.Imports(pass.Pkg, "sync/atomic") { + if !typesinternal.Imports(pass.Pkg, "sync/atomic") { return nil, nil // doesn't directly import sync/atomic } @@ -54,7 +55,7 @@ func run(pass *analysis.Pass) (any, error) { continue } obj := typeutil.Callee(pass.TypesInfo, call) - if analysisinternal.IsFunctionNamed(obj, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") { + if typesinternal.IsFunctionNamed(obj, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") { checkAtomicAddAssignment(pass, n.Lhs[i], call) } } @@ -72,7 +73,7 @@ func checkAtomicAddAssignment(pass *analysis.Pass, left ast.Expr, call *ast.Call arg := call.Args[0] broken := false - gofmt := func(e ast.Expr) string { return analysisinternal.Format(pass.Fset, e) } + gofmt := func(e ast.Expr) string { return astutil.Format(pass.Fset, e) } if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND { broken = gofmt(left) == gofmt(uarg.X) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go b/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go index 2508b41f6..84699dd03 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/atomicalign/atomicalign.go @@ -18,7 +18,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) const Doc = "check for non-64-bits-aligned arguments to sync/atomic functions" @@ -35,7 +35,7 @@ func run(pass *analysis.Pass) (any, error) { if 8*pass.TypesSizes.Sizeof(types.Typ[types.Uintptr]) == 64 { return nil, nil // 64-bit platform } - if !analysisinternal.Imports(pass.Pkg, "sync/atomic") { + if !typesinternal.Imports(pass.Pkg, "sync/atomic") { return nil, nil // doesn't directly import sync/atomic } @@ -54,7 +54,7 @@ func run(pass *analysis.Pass) (any, error) { inspect.Preorder(nodeFilter, func(node ast.Node) { call := node.(*ast.CallExpr) obj := typeutil.Callee(pass.TypesInfo, call) - if analysisinternal.IsFunctionNamed(obj, "sync/atomic", funcNames...) { + if typesinternal.IsFunctionNamed(obj, "sync/atomic", funcNames...) { // For all the listed functions, the expression to check is always the first function argument. check64BitAlignment(pass, obj.Name(), call.Args[0]) } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go b/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go index e1cf9f9b7..3c2a82dce 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go @@ -15,7 +15,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/astutil" ) const Doc = "check for common mistakes involving boolean operators" @@ -104,7 +104,7 @@ func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[* func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) { seen := make(map[string]bool) for _, e := range exprs { - efmt := analysisinternal.Format(pass.Fset, e) + efmt := astutil.Format(pass.Fset, e) if seen[efmt] { pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt) } else { @@ -150,8 +150,8 @@ func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) { } // e is of the form 'x != c' or 'x == c'. - xfmt := analysisinternal.Format(pass.Fset, x) - efmt := analysisinternal.Format(pass.Fset, e) + xfmt := astutil.Format(pass.Fset, x) + efmt := astutil.Format(pass.Fset, e) if prev, found := seen[xfmt]; found { // checkRedundant handles the case in which efmt == prev. if efmt != prev { diff --git a/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go b/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go index 6c7a0df58..91aac6762 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go @@ -15,6 +15,8 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" + "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/versions" ) const Doc = "check //go:build and // +build directives" @@ -55,7 +57,6 @@ func runBuildTag(pass *analysis.Pass) (any, error) { func checkGoFile(pass *analysis.Pass, f *ast.File) { var check checker check.init(pass) - defer check.finish() for _, group := range f.Comments { // A +build comment is ignored after or adjoining the package declaration. @@ -77,6 +78,27 @@ func checkGoFile(pass *analysis.Pass, f *ast.File) { check.comment(c.Slash, c.Text) } } + + check.finish() + + // For Go 1.18+ files, offer a fix to remove the +build lines + // if they passed all consistency checks. + if check.crossCheck && !versions.Before(pass.TypesInfo.FileVersions[f], "go1.18") { + for _, rng := range check.plusBuildRanges { + check.pass.Report(analysis.Diagnostic{ + Pos: rng.Pos(), + End: rng.End(), + Message: "+build line is no longer needed", + SuggestedFixes: []analysis.SuggestedFix{{ + Message: "Remove obsolete +build line", + TextEdits: []analysis.TextEdit{{ + Pos: rng.Pos(), + End: rng.End(), + }}, + }}, + }) + } + } } func checkOtherFile(pass *analysis.Pass, filename string) error { @@ -96,15 +118,15 @@ func checkOtherFile(pass *analysis.Pass, filename string) error { } type checker struct { - pass *analysis.Pass - plusBuildOK bool // "+build" lines still OK - goBuildOK bool // "go:build" lines still OK - crossCheck bool // cross-check go:build and +build lines when done reading file - inStar bool // currently in a /* */ comment - goBuildPos token.Pos // position of first go:build line found - plusBuildPos token.Pos // position of first "+build" line found - goBuild constraint.Expr // go:build constraint found - plusBuild constraint.Expr // AND of +build constraints found + pass *analysis.Pass + plusBuildOK bool // "+build" lines still OK + goBuildOK bool // "go:build" lines still OK + crossCheck bool // cross-check go:build and +build lines when done reading file + inStar bool // currently in a /* */ comment + goBuildPos token.Pos // position of first go:build line found + plusBuildRanges []analysis.Range // range of each "+build" line found + goBuild constraint.Expr // go:build constraint found + plusBuild constraint.Expr // AND of +build constraints found } func (check *checker) init(pass *analysis.Pass) { @@ -272,6 +294,8 @@ func (check *checker) goBuildLine(pos token.Pos, line string) { } func (check *checker) plusBuildLine(pos token.Pos, line string) { + plusBuildRange := analysisinternal.Range(pos, pos+token.Pos(len(line))) + line = strings.TrimSpace(line) if !constraint.IsPlusBuild(line) { // Comment with +build but not at beginning. @@ -286,9 +310,7 @@ func (check *checker) plusBuildLine(pos token.Pos, line string) { check.crossCheck = false } - if check.plusBuildPos == token.NoPos { - check.plusBuildPos = pos - } + check.plusBuildRanges = append(check.plusBuildRanges, plusBuildRange) // testing hack: stop at // ERROR if i := strings.Index(line, " // ERROR "); i >= 0 { @@ -298,7 +320,7 @@ func (check *checker) plusBuildLine(pos token.Pos, line string) { fields := strings.Fields(line[len("//"):]) // IsPlusBuildConstraint check above implies fields[0] == "+build" for _, arg := range fields[1:] { - for _, elem := range strings.Split(arg, ",") { + for elem := range strings.SplitSeq(arg, ",") { if strings.HasPrefix(elem, "!!") { check.pass.Reportf(pos, "invalid double negative in build constraint: %s", arg) check.crossCheck = false @@ -336,19 +358,19 @@ func (check *checker) plusBuildLine(pos token.Pos, line string) { } func (check *checker) finish() { - if !check.crossCheck || check.plusBuildPos == token.NoPos || check.goBuildPos == token.NoPos { + if !check.crossCheck || len(check.plusBuildRanges) == 0 || check.goBuildPos == token.NoPos { return } // Have both //go:build and // +build, // with no errors found (crossCheck still true). // Check they match. - var want constraint.Expr lines, err := constraint.PlusBuildLines(check.goBuild) if err != nil { check.pass.Reportf(check.goBuildPos, "%v", err) return } + var want constraint.Expr for _, line := range lines { y, err := constraint.Parse(line) if err != nil { @@ -363,7 +385,8 @@ func (check *checker) finish() { } } if want.String() != check.plusBuild.String() { - check.pass.Reportf(check.plusBuildPos, "+build lines do not match //go:build condition") + check.pass.ReportRangef(check.plusBuildRanges[0], "+build lines do not match //go:build condition") + check.crossCheck = false // don't offer fix to remove +build return } } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go b/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go index d9189b5b6..bf1202b92 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go @@ -18,7 +18,7 @@ import ( "strconv" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) const debug = false @@ -41,7 +41,7 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - if !analysisinternal.Imports(pass.Pkg, "runtime/cgo") { + if !typesinternal.Imports(pass.Pkg, "runtime/cgo") { return nil, nil // doesn't use cgo } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go b/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go index a4e455d9b..4190cc590 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go @@ -16,8 +16,9 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/astutil" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" "golang.org/x/tools/internal/versions" ) @@ -86,7 +87,7 @@ func checkCopyLocksAssign(pass *analysis.Pass, assign *ast.AssignStmt, goversion lhs := assign.Lhs for i, x := range assign.Rhs { if path := lockPathRhs(pass, x); path != nil { - pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisinternal.Format(pass.Fset, assign.Lhs[i]), path) + pass.ReportRangef(x, "assignment copies lock value to %v: %v", astutil.Format(pass.Fset, assign.Lhs[i]), path) lhs = nil // An lhs has been reported. We prefer the assignment warning and do not report twice. } } @@ -100,7 +101,7 @@ func checkCopyLocksAssign(pass *analysis.Pass, assign *ast.AssignStmt, goversion if id, ok := l.(*ast.Ident); ok && id.Name != "_" { if obj := pass.TypesInfo.Defs[id]; obj != nil && obj.Type() != nil { if path := lockPath(pass.Pkg, obj.Type(), nil); path != nil { - pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", analysisinternal.Format(pass.Fset, l), path) + pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", astutil.Format(pass.Fset, l), path) } } } @@ -132,7 +133,7 @@ func checkCopyLocksCompositeLit(pass *analysis.Pass, cl *ast.CompositeLit) { x = node.Value } if path := lockPathRhs(pass, x); path != nil { - pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisinternal.Format(pass.Fset, x), path) + pass.ReportRangef(x, "literal copies lock value from %v: %v", astutil.Format(pass.Fset, x), path) } } } @@ -157,13 +158,16 @@ func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) { } if fun, ok := pass.TypesInfo.Uses[id].(*types.Builtin); ok { switch fun.Name() { - case "new", "len", "cap", "Sizeof", "Offsetof", "Alignof": + case "len", "cap", "Sizeof", "Offsetof", "Alignof": + // The argument of this operation is used only + // for its type (e.g. len(array)), or the operation + // does not copy a lock (e.g. len(slice)). return } } for _, x := range ce.Args { if path := lockPathRhs(pass, x); path != nil { - pass.ReportRangef(x, "call of %s copies lock value: %v", analysisinternal.Format(pass.Fset, ce.Fun), path) + pass.ReportRangef(x, "call of %s copies lock value: %v", astutil.Format(pass.Fset, ce.Fun), path) } } } @@ -230,7 +234,7 @@ func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) { return } if path := lockPath(pass.Pkg, typ, nil); path != nil { - pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisinternal.Format(pass.Fset, e), path) + pass.Reportf(e.Pos(), "range var %s copies lock: %v", astutil.Format(pass.Fset, e), path) } } @@ -350,7 +354,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ // In go1.10, sync.noCopy did not implement Locker. // (The Unlock method was added only in CL 121876.) // TODO(adonovan): remove workaround when we drop go1.10. - if analysisinternal.IsTypeNamed(typ, "sync", "noCopy") { + if typesinternal.IsTypeNamed(typ, "sync", "noCopy") { return []string{typ.String()} } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go b/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go index d15e3bc59..5e3d1a353 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/deepequalerrors/deepequalerrors.go @@ -14,7 +14,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) const Doc = `check for calls of reflect.DeepEqual on error values @@ -35,7 +35,7 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - if !analysisinternal.Imports(pass.Pkg, "reflect") { + if !typesinternal.Imports(pass.Pkg, "reflect") { return nil, nil // doesn't directly import reflect } @@ -47,7 +47,7 @@ func run(pass *analysis.Pass) (any, error) { inspect.Preorder(nodeFilter, func(n ast.Node) { call := n.(*ast.CallExpr) obj := typeutil.Callee(pass.TypesInfo, call) - if analysisinternal.IsFunctionNamed(obj, "reflect", "DeepEqual") && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) { + if typesinternal.IsFunctionNamed(obj, "reflect", "DeepEqual") && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) { pass.ReportRangef(call, "avoid using reflect.DeepEqual with errors") } }) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go b/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go index e11957f2d..bf62d327d 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go @@ -13,7 +13,7 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -29,14 +29,14 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - if !analysisinternal.Imports(pass.Pkg, "time") { + if !typesinternal.Imports(pass.Pkg, "time") { return nil, nil } checkDeferCall := func(node ast.Node) bool { switch v := node.(type) { case *ast.CallExpr: - if analysisinternal.IsFunctionNamed(typeutil.Callee(pass.TypesInfo, v), "time", "Since") { + if typesinternal.IsFunctionNamed(typeutil.Callee(pass.TypesInfo, v), "time", "Since") { pass.Reportf(v.Pos(), "call to time.Since is not deferred") } case *ast.FuncLit: diff --git a/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go b/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go index b8d29d019..b3df99929 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go @@ -12,22 +12,20 @@ import ( "go/types" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" - "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex" + "golang.org/x/tools/internal/typesinternal/typeindex" ) const Doc = `report passing non-pointer or non-error values to errors.As -The errorsas analysis reports calls to errors.As where the type +The errorsas analyzer reports calls to errors.As where the type of the second argument is not a pointer to a type implementing error.` var Analyzer = &analysis.Analyzer{ Name: "errorsas", Doc: Doc, URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas", - Requires: []*analysis.Analyzer{inspect.Analyzer}, + Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer}, Run: run, } @@ -39,38 +37,31 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } - if !analysisinternal.Imports(pass.Pkg, "errors") { - return nil, nil // doesn't directly import errors - } - - inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + var ( + index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index) + info = pass.TypesInfo + ) - nodeFilter := []ast.Node{ - (*ast.CallExpr)(nil), - } - inspect.Preorder(nodeFilter, func(n ast.Node) { - call := n.(*ast.CallExpr) - obj := typeutil.Callee(pass.TypesInfo, call) - if !analysisinternal.IsFunctionNamed(obj, "errors", "As") { - return - } + for curCall := range index.Calls(index.Object("errors", "As")) { + call := curCall.Node().(*ast.CallExpr) if len(call.Args) < 2 { - return // not enough arguments, e.g. called with return values of another function + continue // spread call: errors.As(pair()) } - if err := checkAsTarget(pass, call.Args[1]); err != nil { + + // Check for incorrect arguments. + if err := checkAsTarget(info, call.Args[1]); err != nil { pass.ReportRangef(call, "%v", err) + continue } - }) + } return nil, nil } -var errorType = types.Universe.Lookup("error").Type() - // checkAsTarget reports an error if the second argument to errors.As is invalid. -func checkAsTarget(pass *analysis.Pass, e ast.Expr) error { - t := pass.TypesInfo.Types[e].Type - if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 { - // A target of interface{} is always allowed, since it often indicates +func checkAsTarget(info *types.Info, e ast.Expr) error { + t := info.Types[e].Type + if types.Identical(t.Underlying(), anyType) { + // A target of any is always allowed, since it often indicates // a value forwarded from another source. return nil } @@ -78,12 +69,16 @@ func checkAsTarget(pass *analysis.Pass, e ast.Expr) error { if !ok { return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type") } - if pt.Elem() == errorType { + if types.Identical(pt.Elem(), errorType) { return errors.New("second argument to errors.As should not be *error") } - _, ok = pt.Elem().Underlying().(*types.Interface) - if ok || types.Implements(pt.Elem(), errorType.Underlying().(*types.Interface)) { - return nil + if !types.IsInterface(pt.Elem()) && !types.AssignableTo(pt.Elem(), errorType) { + return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type") } - return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type") + return nil } + +var ( + anyType = types.Universe.Lookup("any").Type() + errorType = types.Universe.Lookup("error").Type() +) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go b/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go index e9acd9654..37ecb6523 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go @@ -13,7 +13,6 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" "golang.org/x/tools/internal/typesinternal" ) @@ -46,7 +45,7 @@ func run(pass *analysis.Pass) (any, error) { // Fast path: if the package doesn't import net/http, // skip the traversal. - if !analysisinternal.Imports(pass.Pkg, "net/http") { + if !typesinternal.Imports(pass.Pkg, "net/http") { return nil, nil } @@ -118,7 +117,7 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool { return false // the function called does not return two values. } isPtr, named := typesinternal.ReceiverNamed(res.At(0)) - if !isPtr || named == nil || !analysisinternal.IsTypeNamed(named, "net/http", "Response") { + if !isPtr || named == nil || !typesinternal.IsTypeNamed(named, "net/http", "Response") { return false // the first return type is not *http.Response. } @@ -133,11 +132,11 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool { return ok && id.Name == "http" // function in net/http package. } - if analysisinternal.IsTypeNamed(typ, "net/http", "Client") { + if typesinternal.IsTypeNamed(typ, "net/http", "Client") { return true // method on http.Client. } ptr, ok := types.Unalias(typ).(*types.Pointer) - return ok && analysisinternal.IsTypeNamed(ptr.Elem(), "net/http", "Client") // method on *http.Client. + return ok && typesinternal.IsTypeNamed(ptr.Elem(), "net/http", "Client") // method on *http.Client. } // restOfBlock, given a traversal stack, finds the innermost containing diff --git a/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go b/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go index 2580a0ac2..8432e963c 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go @@ -14,7 +14,6 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" "golang.org/x/tools/internal/typesinternal" "golang.org/x/tools/internal/versions" ) @@ -369,5 +368,5 @@ func isMethodCall(info *types.Info, expr ast.Expr, pkgPath, typeName, method str // Check that the receiver is a . or // *.. _, named := typesinternal.ReceiverNamed(recv) - return analysisinternal.IsTypeNamed(named, pkgPath, typeName) + return typesinternal.IsTypeNamed(named, pkgPath, typeName) } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go b/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go index c0746789e..cc0bf0fd3 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go @@ -16,8 +16,8 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/cfg" - "golang.org/x/tools/internal/analysisinternal" "golang.org/x/tools/internal/astutil" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -50,7 +50,7 @@ var contextPackage = "context" // checkLostCancel analyzes a single named or literal function. func run(pass *analysis.Pass) (any, error) { // Fast path: bypass check if file doesn't use context.WithCancel. - if !analysisinternal.Imports(pass.Pkg, contextPackage) { + if !typesinternal.Imports(pass.Pkg, contextPackage) { return nil, nil } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go b/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go index af61ae608..8f1cca8f1 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/nilness/nilness.go @@ -52,7 +52,7 @@ func runFunc(pass *analysis.Pass, fn *ssa.Function) { // notNil reports an error if v is provably nil. notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) { if nilnessOf(stack, v) == isnil { - reportf("nilderef", instr.Pos(), descr) + reportf("nilderef", instr.Pos(), "%s", descr) } } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/pkgfact/pkgfact.go b/vendor/golang.org/x/tools/go/analysis/passes/pkgfact/pkgfact.go index 31748795d..2b8add301 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/pkgfact/pkgfact.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/pkgfact/pkgfact.go @@ -41,7 +41,7 @@ var Analyzer = &analysis.Analyzer{ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/pkgfact", Run: run, FactTypes: []analysis.Fact{new(pairsFact)}, - ResultType: reflect.TypeOf(map[string]string{}), + ResultType: reflect.TypeFor[map[string]string](), } // A pairsFact is a package-level fact that records diff --git a/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go b/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go index eebf40208..f04e44143 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go @@ -82,6 +82,16 @@ // ... // } // +// A local function may also be inferred as a printf wrapper. If it +// is assigned to a variable, each call made through that variable will +// be checked just like a call to a function: +// +// logf := func(format string, args ...any) { +// message := fmt.Sprintf(format, args...) +// log.Printf("%s: %s", prefix, message) +// } +// logf("%s", 123) // logf format %s has arg 123 of wrong type int +// // # Specifying printf wrappers by flag // // The -funcs flag specifies a comma-separated list of names of diff --git a/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go b/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go index 9ad18a041..d94e592cf 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go @@ -19,12 +19,14 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" + "golang.org/x/tools/go/ast/edge" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/internal/analysisinternal" "golang.org/x/tools/internal/astutil" "golang.org/x/tools/internal/fmtstr" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" "golang.org/x/tools/internal/versions" ) @@ -41,7 +43,7 @@ var Analyzer = &analysis.Analyzer{ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf", Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, - ResultType: reflect.TypeOf((*Result)(nil)), + ResultType: reflect.TypeFor[*Result](), FactTypes: []analysis.Fact{new(isWrapper)}, } @@ -70,7 +72,7 @@ func (kind Kind) String() string { // Result is the printf analyzer's result type. Clients may query the result // to learn whether a function behaves like fmt.Print or fmt.Printf. type Result struct { - funcs map[*types.Func]Kind + funcs map[types.Object]Kind } // Kind reports whether fn behaves like fmt.Print or fmt.Printf. @@ -111,149 +113,210 @@ func (f *isWrapper) String() string { func run(pass *analysis.Pass) (any, error) { res := &Result{ - funcs: make(map[*types.Func]Kind), + funcs: make(map[types.Object]Kind), } - findPrintfLike(pass, res) - checkCalls(pass) + findPrintLike(pass, res) + checkCalls(pass, res) return res, nil } -type printfWrapper struct { - obj *types.Func - fdecl *ast.FuncDecl - format *types.Var - args *types.Var +// A wrapper is a candidate print/printf wrapper function. +// +// We represent functions generally as types.Object, not *Func, so +// that we can analyze anonymous functions such as +// +// printf := func(format string, args ...any) {...}, +// +// representing them by the *types.Var symbol for the local variable +// 'printf'. +type wrapper struct { + obj types.Object // *Func or *Var + curBody inspector.Cursor // for *ast.BlockStmt + format *types.Var // optional "format string" parameter in the Func{Decl,Lit} + args *types.Var // "args ...any" parameter in the Func{Decl,Lit} callers []printfCaller - failed bool // if true, not a printf wrapper } type printfCaller struct { - w *printfWrapper + w *wrapper call *ast.CallExpr } -// maybePrintfWrapper decides whether decl (a declared function) may be a wrapper -// around a fmt.Printf or fmt.Print function. If so it returns a printfWrapper -// function describing the declaration. Later processing will analyze the -// graph of potential printf wrappers to pick out the ones that are true wrappers. -// A function may be a Printf or Print wrapper if its last argument is ...interface{}. -// If the next-to-last argument is a string, then this may be a Printf wrapper. -// Otherwise it may be a Print wrapper. -func maybePrintfWrapper(info *types.Info, decl ast.Decl) *printfWrapper { - // Look for functions with final argument type ...interface{}. - fdecl, ok := decl.(*ast.FuncDecl) - if !ok || fdecl.Body == nil { - return nil - } - fn, ok := info.Defs[fdecl.Name].(*types.Func) - // Type information may be incomplete. - if !ok { - return nil - } - - sig := fn.Type().(*types.Signature) +// formatArgsParams returns the "format string" and "args ...any" +// parameters of a potential print or printf wrapper function. +// (The format is nil in the print-like case.) +func formatArgsParams(sig *types.Signature) (format, args *types.Var) { if !sig.Variadic() { - return nil // not variadic + return nil, nil // not variadic } params := sig.Params() nparams := params.Len() // variadic => nonzero - // Check final parameter is "args ...interface{}". - args := params.At(nparams - 1) - iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface) - if !ok || !iface.Empty() { - return nil - } - // Is second last param 'format string'? - var format *types.Var if nparams >= 2 { if p := params.At(nparams - 2); p.Type() == types.Typ[types.String] { format = p } } - return &printfWrapper{ - obj: fn, - fdecl: fdecl, - format: format, - args: args, + // Check final parameter is "args ...any". + // (variadic => slice) + args = params.At(nparams - 1) + iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface) + if !ok || !iface.Empty() { + return nil, nil } + + return format, args } -// findPrintfLike scans the entire package to find printf-like functions. -func findPrintfLike(pass *analysis.Pass, res *Result) (any, error) { - // Gather potential wrappers and call graph between them. - byObj := make(map[*types.Func]*printfWrapper) - var wrappers []*printfWrapper - for _, file := range pass.Files { - for _, decl := range file.Decls { - w := maybePrintfWrapper(pass.TypesInfo, decl) - if w == nil { - continue +// findPrintLike scans the entire package to find print or printf-like functions. +// When it returns, all such functions have been identified. +func findPrintLike(pass *analysis.Pass, res *Result) { + var ( + inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + info = pass.TypesInfo + ) + + // Pass 1: gather candidate wrapper functions (and populate wrappers). + var ( + wrappers []*wrapper + byObj = make(map[types.Object]*wrapper) + ) + for cur := range inspect.Root().Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) { + var ( + curBody inspector.Cursor // for *ast.BlockStmt + sig *types.Signature + obj types.Object + ) + switch f := cur.Node().(type) { + case *ast.FuncDecl: + // named function or method: + // + // func wrapf(format string, args ...any) {...} + if f.Body != nil { + curBody = cur.ChildAt(edge.FuncDecl_Body, -1) + obj = info.Defs[f.Name] + sig = obj.Type().(*types.Signature) } - byObj[w.obj] = w - wrappers = append(wrappers, w) - } - } - // Walk the graph to figure out which are really printf wrappers. - for _, w := range wrappers { - // Scan function for calls that could be to other printf-like functions. - ast.Inspect(w.fdecl.Body, func(n ast.Node) bool { - if w.failed { - return false + case *ast.FuncLit: + // anonymous function directly assigned to a variable: + // + // var wrapf = func(format string, args ...any) {...} + // wrapf := func(format string, args ...any) {...} + // wrapf = func(format string, args ...any) {...} + // + // The LHS may also be a struct field x.wrapf or + // an imported var pkg.Wrapf. + // + sig = info.TypeOf(f).(*types.Signature) + curBody = cur.ChildAt(edge.FuncLit_Body, -1) + var lhs ast.Expr + switch ek, idx := cur.ParentEdge(); ek { + case edge.ValueSpec_Values: + curName := cur.Parent().ChildAt(edge.ValueSpec_Names, idx) + lhs = curName.Node().(*ast.Ident) + case edge.AssignStmt_Rhs: + curLhs := cur.Parent().ChildAt(edge.AssignStmt_Lhs, idx) + lhs = curLhs.Node().(ast.Expr) } - // TODO: Relax these checks; issue 26555. - if assign, ok := n.(*ast.AssignStmt); ok { - for _, lhs := range assign.Lhs { - if match(pass.TypesInfo, lhs, w.format) || - match(pass.TypesInfo, lhs, w.args) { - // Modifies the format - // string or args in - // some way, so not a - // simple wrapper. - w.failed = true - return false - } + switch lhs := lhs.(type) { + case *ast.Ident: + // variable: wrapf = func(...) + obj = info.ObjectOf(lhs).(*types.Var) + case *ast.SelectorExpr: + if sel, ok := info.Selections[lhs]; ok { + // struct field: x.wrapf = func(...) + obj = sel.Obj().(*types.Var) + } else { + // imported var: pkg.Wrapf = func(...) + obj = info.Uses[lhs.Sel].(*types.Var) } } - if un, ok := n.(*ast.UnaryExpr); ok && un.Op == token.AND { - if match(pass.TypesInfo, un.X, w.format) || - match(pass.TypesInfo, un.X, w.args) { - // Taking the address of the - // format string or args, - // so not a simple wrapper. - w.failed = true - return false + } + if obj != nil { + format, args := formatArgsParams(sig) + if args != nil { + // obj (the symbol for a function/method, or variable + // assigned to an anonymous function) is a potential + // print or printf wrapper. + // + // Later processing will analyze the graph of potential + // wrappers and their function bodies to pick out the + // ones that are true wrappers. + w := &wrapper{ + obj: obj, + curBody: curBody, + format: format, // non-nil => printf + args: args, } + byObj[w.obj] = w + wrappers = append(wrappers, w) } + } + } - call, ok := n.(*ast.CallExpr) - if !ok || len(call.Args) == 0 || !match(pass.TypesInfo, call.Args[len(call.Args)-1], w.args) { - return true - } + // Pass 2: scan the body of each wrapper function + // for calls to other printf-like functions. + // + // Also, reject tricky cases where the parameters + // are potentially mutated by AssignStmt or UnaryExpr. + // TODO: Relax these checks; issue 26555. + for _, w := range wrappers { + scan: + for cur := range w.curBody.Preorder( + (*ast.AssignStmt)(nil), + (*ast.UnaryExpr)(nil), + (*ast.CallExpr)(nil), + ) { + switch n := cur.Node().(type) { + case *ast.AssignStmt: + // If the wrapper updates format or args + // it is not a simple wrapper. + for _, lhs := range n.Lhs { + if w.format != nil && match(info, lhs, w.format) || + match(info, lhs, w.args) { + break scan + } + } - fn, kind := printfNameAndKind(pass, call) - if kind != 0 { - checkPrintfFwd(pass, w, call, kind, res) - return true - } + case *ast.UnaryExpr: + // If the wrapper computes &format or &args, + // it is not a simple wrapper. + if n.Op == token.AND && + (w.format != nil && match(info, n.X, w.format) || + match(info, n.X, w.args)) { + break scan + } - // If the call is to another function in this package, - // maybe we will find out it is printf-like later. - // Remember this call for later checking. - if fn != nil && fn.Pkg() == pass.Pkg && byObj[fn] != nil { - callee := byObj[fn] - callee.callers = append(callee.callers, printfCaller{w, call}) + case *ast.CallExpr: + if len(n.Args) > 0 && match(info, n.Args[len(n.Args)-1], w.args) { + if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil { + + // Call from one wrapper candidate to another? + // Record the edge so that if callee is found to be + // a true wrapper, w will be too. + if w2, ok := byObj[callee]; ok { + w2.callers = append(w2.callers, printfCaller{w, n}) + } + + // Is the candidate a true wrapper, because it calls + // a known print{,f}-like function from the allowlist + // or an imported fact, or another wrapper found + // to be a true wrapper? + // If so, convert all w's callers to kind. + kind := callKind(pass, callee, res) + if kind != KindNone { + checkForward(pass, w, n, kind, res) + } + } + } } - - return true - }) + } } - return nil, nil } func match(info *types.Info, arg ast.Expr, param *types.Var) bool { @@ -261,9 +324,9 @@ func match(info *types.Info, arg ast.Expr, param *types.Var) bool { return ok && info.ObjectOf(id) == param } -// checkPrintfFwd checks that a printf-forwarding wrapper is forwarding correctly. +// checkForward checks that a forwarding wrapper is forwarding correctly. // It diagnoses writing fmt.Printf(format, args) instead of fmt.Printf(format, args...). -func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind Kind, res *Result) { +func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) { matched := kind == KindPrint || kind != KindNone && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) if !matched { @@ -292,18 +355,39 @@ func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, k pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", desc) return } - fn := w.obj - var fact isWrapper - if !pass.ImportObjectFact(fn, &fact) { - fact.Kind = kind - pass.ExportObjectFact(fn, &fact) - res.funcs[fn] = kind + + // If the candidate's print{,f} status becomes known, + // propagate it back to all its so-far known callers. + if res.funcs[w.obj] != kind { + res.funcs[w.obj] = kind + + // Export a fact. + // (This is a no-op for local symbols.) + // We can't export facts on a symbol of another package, + // but we can treat the symbol as a wrapper within + // the current analysis unit. + if w.obj.Pkg() == pass.Pkg { + // Facts are associated with origins. + pass.ExportObjectFact(origin(w.obj), &isWrapper{Kind: kind}) + } + + // Propagate kind back to known callers. for _, caller := range w.callers { - checkPrintfFwd(pass, caller.w, caller.call, kind, res) + checkForward(pass, caller.w, caller.call, kind, res) } } } +func origin(obj types.Object) types.Object { + switch obj := obj.(type) { + case *types.Func: + return obj.Origin() + case *types.Var: + return obj.Origin() + } + return obj +} + // isPrint records the print functions. // If a key ends in 'f' then it is assumed to be a formatted print. // @@ -412,7 +496,7 @@ func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) { // checkCalls triggers the print-specific checks for calls that invoke a print // function. -func checkCalls(pass *analysis.Pass) { +func checkCalls(pass *analysis.Pass, res *Result) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.File)(nil), @@ -426,48 +510,60 @@ func checkCalls(pass *analysis.Pass) { fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n)) case *ast.CallExpr: - fn, kind := printfNameAndKind(pass, n) - switch kind { - case KindPrintf, KindErrorf: - checkPrintf(pass, fileVersion, kind, n, fn.FullName()) - case KindPrint: - checkPrint(pass, n, fn.FullName()) + if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil { + kind := callKind(pass, callee, res) + switch kind { + case KindPrintf, KindErrorf: + checkPrintf(pass, fileVersion, kind, n, fullname(callee)) + case KindPrint: + checkPrint(pass, n, fullname(callee)) + } } } }) } -func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind Kind) { - fn, _ = typeutil.Callee(pass.TypesInfo, call).(*types.Func) - if fn == nil { - return nil, 0 +func fullname(obj types.Object) string { + if fn, ok := obj.(*types.Func); ok { + return fn.FullName() } + return obj.Name() +} - // Facts are associated with generic declarations, not instantiations. - fn = fn.Origin() - - _, ok := isPrint[fn.FullName()] +// callKind returns the symbol of the called function +// and its print/printf kind, if any. +// (The symbol may be a var for an anonymous function.) +// The result is memoized in res.funcs. +func callKind(pass *analysis.Pass, obj types.Object, res *Result) Kind { + kind, ok := res.funcs[obj] if !ok { - // Next look up just "printf", for use with -printf.funcs. - _, ok = isPrint[strings.ToLower(fn.Name())] - } - if ok { - if fn.FullName() == "fmt.Errorf" { - kind = KindErrorf - } else if strings.HasSuffix(fn.Name(), "f") { - kind = KindPrintf + // cache miss + _, ok := isPrint[fullname(obj)] + if !ok { + // Next look up just "printf", for use with -printf.funcs. + _, ok = isPrint[strings.ToLower(obj.Name())] + } + if ok { + // well-known printf functions + if fullname(obj) == "fmt.Errorf" { + kind = KindErrorf + } else if strings.HasSuffix(obj.Name(), "f") { + kind = KindPrintf + } else { + kind = KindPrint + } } else { - kind = KindPrint + // imported wrappers + // Facts are associated with generic declarations, not instantiations. + obj = origin(obj) + var fact isWrapper + if pass.ImportObjectFact(obj, &fact) { + kind = fact.Kind + } } - return fn, kind + res.funcs[obj] = kind // cache } - - var fact isWrapper - if pass.ImportObjectFact(fn, &fact) { - return fn, fact.Kind - } - - return fn, KindNone + return kind } // isFormatter reports whether t could satisfy fmt.Formatter. @@ -490,7 +586,7 @@ func isFormatter(typ types.Type) bool { sig := fn.Type().(*types.Signature) return sig.Params().Len() == 2 && sig.Results().Len() == 0 && - analysisinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") && + typesinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") && types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune]) } @@ -729,7 +825,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma if reason != "" { details = " (" + reason + ")" } - pass.ReportRangef(rng, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, analysisinternal.Format(pass.Fset, arg), details) + pass.ReportRangef(rng, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, astutil.Format(pass.Fset, arg), details) return false } } @@ -756,7 +852,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma } arg := call.Args[verbArgIndex] if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' { - pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, analysisinternal.Format(pass.Fset, arg)) + pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, astutil.Format(pass.Fset, arg)) return false } if reason, ok := matchArgType(pass, v.typ, arg); !ok { @@ -768,14 +864,14 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma if reason != "" { details = " (" + reason + ")" } - pass.ReportRangef(rng, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, analysisinternal.Format(pass.Fset, arg), typeString, details) + pass.ReportRangef(rng, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, astutil.Format(pass.Fset, arg), typeString, details) return false } // Detect recursive formatting via value's String/Error methods. // The '#' flag suppresses the methods, except with %x, %X, and %q. if v.typ&argString != 0 && v.verb != 'T' && (!strings.Contains(operation.Flags, "#") || strings.ContainsRune("qxX", v.verb)) { if methodName, ok := recursiveStringer(pass, arg); ok { - pass.ReportRangef(rng, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, analysisinternal.Format(pass.Fset, arg), methodName) + pass.ReportRangef(rng, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, astutil.Format(pass.Fset, arg), methodName) return false } } @@ -927,7 +1023,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) { if sel, ok := call.Args[0].(*ast.SelectorExpr); ok { if x, ok := sel.X.(*ast.Ident); ok { if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") { - pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, analysisinternal.Format(pass.Fset, call.Args[0])) + pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, astutil.Format(pass.Fset, call.Args[0])) } } } @@ -961,10 +1057,10 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) { } for _, arg := range args { if isFunctionValue(pass, arg) { - pass.ReportRangef(call, "%s arg %s is a func value, not called", name, analysisinternal.Format(pass.Fset, arg)) + pass.ReportRangef(call, "%s arg %s is a func value, not called", name, astutil.Format(pass.Fset, arg)) } if methodName, ok := recursiveStringer(pass, arg); ok { - pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, analysisinternal.Format(pass.Fset, arg), methodName) + pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, astutil.Format(pass.Fset, arg), methodName) } } } @@ -992,7 +1088,7 @@ func (ss stringSet) String() string { } func (ss stringSet) Set(flag string) error { - for _, name := range strings.Split(flag, ",") { + for name := range strings.SplitSeq(flag, ",") { if len(name) == 0 { return fmt.Errorf("empty string") } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go b/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go index d0632dbda..5626ac1c1 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go @@ -14,7 +14,7 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -50,7 +50,7 @@ func run(pass *analysis.Pass) (any, error) { } case *ast.CallExpr: obj := typeutil.Callee(pass.TypesInfo, n) - if analysisinternal.IsFunctionNamed(obj, "reflect", "DeepEqual") && (isReflectValue(pass, n.Args[0]) || isReflectValue(pass, n.Args[1])) { + if typesinternal.IsFunctionNamed(obj, "reflect", "DeepEqual") && (isReflectValue(pass, n.Args[0]) || isReflectValue(pass, n.Args[1])) { pass.ReportRangef(n, "avoid using reflect.DeepEqual with reflect.Value") } } @@ -65,7 +65,7 @@ func isReflectValue(pass *analysis.Pass, e ast.Expr) bool { return false } // See if the type is reflect.Value - if !analysisinternal.IsTypeNamed(tv.Type, "reflect", "Value") { + if !typesinternal.IsTypeNamed(tv.Type, "reflect", "Value") { return false } if _, ok := e.(*ast.CompositeLit); ok { diff --git a/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go b/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go index 57987b3d2..366927326 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go @@ -20,7 +20,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/astutil" "golang.org/x/tools/internal/typeparams" ) @@ -123,7 +123,7 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { } } if amt >= minSize { - ident := analysisinternal.Format(pass.Fset, x) + ident := astutil.Format(pass.Fset, x) qualifier := "" if len(sizes) > 1 { qualifier = "may be " diff --git a/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go b/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go index 78a2fa5ea..c339fa064 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go @@ -20,7 +20,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -36,7 +36,7 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - if !analysisinternal.Imports(pass.Pkg, "os/signal") { + if !typesinternal.Imports(pass.Pkg, "os/signal") { return nil, nil // doesn't directly import signal } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go b/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go index c1ac96043..cc58396a0 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go @@ -20,7 +20,7 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/astutil" "golang.org/x/tools/internal/typesinternal" ) @@ -115,10 +115,10 @@ func run(pass *analysis.Pass) (any, error) { default: if unknownArg == nil { pass.ReportRangef(arg, "%s arg %q should be a string or a slog.Attr (possible missing key or value)", - shortName(fn), analysisinternal.Format(pass.Fset, arg)) + shortName(fn), astutil.Format(pass.Fset, arg)) } else { pass.ReportRangef(arg, "%s arg %q should probably be a string or a slog.Attr (previous arg %q cannot be a key)", - shortName(fn), analysisinternal.Format(pass.Fset, arg), analysisinternal.Format(pass.Fset, unknownArg)) + shortName(fn), astutil.Format(pass.Fset, arg), astutil.Format(pass.Fset, unknownArg)) } // Stop here so we report at most one missing key per call. return @@ -158,7 +158,7 @@ func run(pass *analysis.Pass) (any, error) { } func isAttr(t types.Type) bool { - return analysisinternal.IsTypeNamed(t, "log/slog", "Attr") + return typesinternal.IsTypeNamed(t, "log/slog", "Attr") } // shortName returns a name for the function that is shorter than FullName. diff --git a/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go b/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go index 9fe0d2092..2b1882041 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go @@ -17,7 +17,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) const Doc = `check the argument type of sort.Slice @@ -34,7 +34,7 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - if !analysisinternal.Imports(pass.Pkg, "sort") { + if !typesinternal.Imports(pass.Pkg, "sort") { return nil, nil // doesn't directly import sort } @@ -47,7 +47,7 @@ func run(pass *analysis.Pass) (any, error) { inspect.Preorder(nodeFilter, func(n ast.Node) { call := n.(*ast.CallExpr) obj := typeutil.Callee(pass.TypesInfo, call) - if !analysisinternal.IsFunctionNamed(obj, "sort", "Slice", "SliceStable", "SliceIsSorted") { + if !typesinternal.IsFunctionNamed(obj, "sort", "Slice", "SliceStable", "SliceIsSorted") { return } callee := obj.(*types.Func) diff --git a/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go b/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go index 429125a8b..314721956 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go @@ -10,7 +10,6 @@ import ( "go/ast" "go/build" "go/types" - "regexp" "slices" "golang.org/x/tools/go/analysis" @@ -114,11 +113,6 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } -// Matches cgo generated comment as well as the proposed standard: -// -// https://golang.org/s/generatedcode -var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`) - // origin returns the original uninstantiated symbol for obj. func origin(obj types.Object) types.Object { switch obj := obj.(type) { diff --git a/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go b/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go index 7dbff1e4d..7a02d85ce 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go @@ -15,7 +15,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/refactor" "golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typesinternal" ) @@ -198,7 +198,7 @@ func run(pass *analysis.Pass) (any, error) { // the type has methods, as some {String,GoString,Format} // may change the behavior of fmt.Sprint. if len(ttypes) == 1 && len(vtypes) == 1 && types.NewMethodSet(V0).Len() == 0 { - _, prefix, importEdits := analysisinternal.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos()) + prefix, importEdits := refactor.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos()) if types.Identical(T0, types.Typ[types.String]) { // string(x) -> fmt.Sprint(x) addFix("Format the number as a decimal", append(importEdits, diff --git a/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go b/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go index cc90f7335..826add2c4 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go @@ -17,6 +17,8 @@ import ( "strconv" "strings" + "fmt" + "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" @@ -100,7 +102,11 @@ func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, s } if err := validateStructTag(tag); err != nil { - pass.Reportf(field.Pos(), "struct field tag %#q not compatible with reflect.StructTag.Get: %s", tag, err) + pass.Report(analysis.Diagnostic{ + Pos: field.Pos(), + End: field.Pos() + token.Pos(len(field.Name())), + Message: fmt.Sprintf("struct field tag %#q not compatible with reflect.StructTag.Get: %s", tag, err), + }) } // Check for use of json or xml tags with unexported fields. @@ -122,7 +128,11 @@ func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, s // ignored. case "", "-": default: - pass.Reportf(field.Pos(), "struct field %s has %s tag but is not exported", field.Name(), enc) + pass.Report(analysis.Diagnostic{ + Pos: field.Pos(), + End: field.Pos() + token.Pos(len(field.Name())), + Message: fmt.Sprintf("struct field %s has %s tag but is not exported", field.Name(), enc), + }) return } } @@ -190,7 +200,11 @@ func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *ty alsoPos.Filename = rel } - pass.Reportf(nearest.Pos(), "struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos) + pass.Report(analysis.Diagnostic{ + Pos: nearest.Pos(), + End: nearest.Pos() + token.Pos(len(nearest.Name())), + Message: fmt.Sprintf("struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos), + }) } else { seen.Set(key, val, level, field.Pos()) } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go index 360ba0e74..400a6960c 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go @@ -16,7 +16,6 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" "golang.org/x/tools/internal/typesinternal" ) @@ -40,7 +39,7 @@ var Analyzer = &analysis.Analyzer{ func run(pass *analysis.Pass) (any, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - if !analysisinternal.Imports(pass.Pkg, "testing") { + if !typesinternal.Imports(pass.Pkg, "testing") { return nil, nil } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go b/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go index d4e9b0253..1c0e92d01 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go @@ -17,6 +17,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -258,7 +259,7 @@ func isTestingType(typ types.Type, testingType string) bool { if !ok { return false } - return analysisinternal.IsTypeNamed(ptr.Elem(), "testing", testingType) + return typesinternal.IsTypeNamed(ptr.Elem(), "testing", testingType) } // Validate that fuzz target function's arguments are of accepted types. diff --git a/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go b/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go index 4fdbb2b54..db91d37c1 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go @@ -19,7 +19,7 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) const badFormat = "2006-02-01" @@ -50,8 +50,8 @@ func run(pass *analysis.Pass) (any, error) { inspect.Preorder(nodeFilter, func(n ast.Node) { call := n.(*ast.CallExpr) obj := typeutil.Callee(pass.TypesInfo, call) - if !analysisinternal.IsMethodNamed(obj, "time", "Time", "Format") && - !analysisinternal.IsFunctionNamed(obj, "time", "Parse") { + if !typesinternal.IsMethodNamed(obj, "time", "Time", "Format") && + !typesinternal.IsFunctionNamed(obj, "time", "Parse") { return } if len(call.Args) > 0 { diff --git a/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go b/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go index 57c6da64f..778010bc0 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go @@ -16,7 +16,7 @@ import ( "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -105,7 +105,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool { } switch sel.Sel.Name { case "Pointer", "UnsafeAddr": - if analysisinternal.IsTypeNamed(info.Types[sel.X].Type, "reflect", "Value") { + if typesinternal.IsTypeNamed(info.Types[sel.X].Type, "reflect", "Value") { return true } } @@ -153,5 +153,5 @@ func hasBasicType(info *types.Info, x ast.Expr, kind types.BasicKind) bool { // isReflectHeader reports whether t is reflect.SliceHeader or reflect.StringHeader. func isReflectHeader(t types.Type) bool { - return analysisinternal.IsTypeNamed(t, "reflect", "SliceHeader", "StringHeader") + return typesinternal.IsTypeNamed(t, "reflect", "SliceHeader", "StringHeader") } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go b/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go index 556ffed7d..ed4cf7ae0 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go @@ -188,7 +188,7 @@ func (ss *stringSetFlag) String() string { func (ss *stringSetFlag) Set(s string) error { m := make(map[string]bool) // clobber previous value if s != "" { - for _, name := range strings.Split(s, ",") { + for name := range strings.SplitSeq(s, ",") { if name == "" { continue // TODO: report error? proceed? } diff --git a/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go b/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go index 14c6986ea..5ed1814f7 100644 --- a/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go +++ b/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go @@ -16,7 +16,7 @@ import ( "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" ) //go:embed doc.go @@ -31,7 +31,7 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - if !analysisinternal.Imports(pass.Pkg, "sync") { + if !typesinternal.Imports(pass.Pkg, "sync") { return nil, nil // doesn't directly import sync } @@ -44,7 +44,7 @@ func run(pass *analysis.Pass) (any, error) { if push { call := n.(*ast.CallExpr) obj := typeutil.Callee(pass.TypesInfo, call) - if analysisinternal.IsMethodNamed(obj, "sync", "WaitGroup", "Add") && + if typesinternal.IsMethodNamed(obj, "sync", "WaitGroup", "Add") && hasSuffix(stack, wantSuffix) && backindex(stack, 1) == backindex(stack, 2).(*ast.BlockStmt).List[0] { // ExprStmt must be Block's first stmt diff --git a/vendor/golang.org/x/tools/go/ast/astutil/imports.go b/vendor/golang.org/x/tools/go/ast/astutil/imports.go index 5e5601aa4..5bacc0fa4 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/imports.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/imports.go @@ -209,48 +209,46 @@ func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) // DeleteNamedImport deletes the import with the given name and path from the file f, if present. // If there are duplicate import declarations, all matching ones are deleted. func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { - var delspecs []*ast.ImportSpec - var delcomments []*ast.CommentGroup + var ( + delspecs = make(map[*ast.ImportSpec]bool) + delcomments = make(map[*ast.CommentGroup]bool) + ) // Find the import nodes that import path, if any. for i := 0; i < len(f.Decls); i++ { - decl := f.Decls[i] - gen, ok := decl.(*ast.GenDecl) + gen, ok := f.Decls[i].(*ast.GenDecl) if !ok || gen.Tok != token.IMPORT { continue } for j := 0; j < len(gen.Specs); j++ { - spec := gen.Specs[j] - impspec := spec.(*ast.ImportSpec) + impspec := gen.Specs[j].(*ast.ImportSpec) if importName(impspec) != name || importPath(impspec) != path { continue } // We found an import spec that imports path. // Delete it. - delspecs = append(delspecs, impspec) + delspecs[impspec] = true deleted = true - copy(gen.Specs[j:], gen.Specs[j+1:]) - gen.Specs = gen.Specs[:len(gen.Specs)-1] + gen.Specs = slices.Delete(gen.Specs, j, j+1) // If this was the last import spec in this decl, // delete the decl, too. if len(gen.Specs) == 0 { - copy(f.Decls[i:], f.Decls[i+1:]) - f.Decls = f.Decls[:len(f.Decls)-1] + f.Decls = slices.Delete(f.Decls, i, i+1) i-- break } else if len(gen.Specs) == 1 { if impspec.Doc != nil { - delcomments = append(delcomments, impspec.Doc) + delcomments[impspec.Doc] = true } if impspec.Comment != nil { - delcomments = append(delcomments, impspec.Comment) + delcomments[impspec.Comment] = true } for _, cg := range f.Comments { // Found comment on the same line as the import spec. if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line { - delcomments = append(delcomments, cg) + delcomments[cg] = true break } } @@ -294,38 +292,21 @@ func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (del } // Delete imports from f.Imports. - for i := 0; i < len(f.Imports); i++ { - imp := f.Imports[i] - for j, del := range delspecs { - if imp == del { - copy(f.Imports[i:], f.Imports[i+1:]) - f.Imports = f.Imports[:len(f.Imports)-1] - copy(delspecs[j:], delspecs[j+1:]) - delspecs = delspecs[:len(delspecs)-1] - i-- - break - } - } + before := len(f.Imports) + f.Imports = slices.DeleteFunc(f.Imports, func(imp *ast.ImportSpec) bool { + _, ok := delspecs[imp] + return ok + }) + if len(f.Imports)+len(delspecs) != before { + // This can happen when the AST is invalid (i.e. imports differ between f.Decls and f.Imports). + panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) } // Delete comments from f.Comments. - for i := 0; i < len(f.Comments); i++ { - cg := f.Comments[i] - for j, del := range delcomments { - if cg == del { - copy(f.Comments[i:], f.Comments[i+1:]) - f.Comments = f.Comments[:len(f.Comments)-1] - copy(delcomments[j:], delcomments[j+1:]) - delcomments = delcomments[:len(delcomments)-1] - i-- - break - } - } - } - - if len(delspecs) > 0 { - panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) - } + f.Comments = slices.DeleteFunc(f.Comments, func(cg *ast.CommentGroup) bool { + _, ok := delcomments[cg] + return ok + }) return } diff --git a/vendor/golang.org/x/tools/go/buildutil/allpackages.go b/vendor/golang.org/x/tools/go/buildutil/allpackages.go index 32886a717..8a7f0fccc 100644 --- a/vendor/golang.org/x/tools/go/buildutil/allpackages.go +++ b/vendor/golang.org/x/tools/go/buildutil/allpackages.go @@ -175,7 +175,7 @@ func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool { for _, pkg := range all { doPkg(pkg, neg) } - } else if dir := strings.TrimSuffix(arg, "/..."); dir != arg { + } else if dir, ok := strings.CutSuffix(arg, "/..."); ok { // dir/... matches all packages beneath dir for _, pkg := range all { if strings.HasPrefix(pkg, dir) && diff --git a/vendor/golang.org/x/tools/go/buildutil/tags.go b/vendor/golang.org/x/tools/go/buildutil/tags.go index 410c8e72d..f66cd5df2 100644 --- a/vendor/golang.org/x/tools/go/buildutil/tags.go +++ b/vendor/golang.org/x/tools/go/buildutil/tags.go @@ -43,7 +43,7 @@ func (v *TagsFlag) Set(s string) error { // Starting in Go 1.13, the -tags flag is a comma-separated list of build tags. *v = []string{} - for _, s := range strings.Split(s, ",") { + for s := range strings.SplitSeq(s, ",") { if s != "" { *v = append(*v, s) } diff --git a/vendor/golang.org/x/tools/go/cfg/cfg.go b/vendor/golang.org/x/tools/go/cfg/cfg.go index 1f2087160..29a39f698 100644 --- a/vendor/golang.org/x/tools/go/cfg/cfg.go +++ b/vendor/golang.org/x/tools/go/cfg/cfg.go @@ -53,7 +53,6 @@ import ( // // The entry point is Blocks[0]; there may be multiple return blocks. type CFG struct { - fset *token.FileSet Blocks []*Block // block[0] is entry; order otherwise undefined } diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 89f89dd2d..680a70ca8 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -364,12 +364,6 @@ type jsonPackage struct { DepsErrors []*packagesinternal.PackageError } -type jsonPackageError struct { - ImportStack []string - Pos string - Err string -} - func otherFiles(p *jsonPackage) [][]string { return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} } diff --git a/vendor/golang.org/x/tools/go/ssa/builder.go b/vendor/golang.org/x/tools/go/ssa/builder.go index a5ef8fb40..41857ffbb 100644 --- a/vendor/golang.org/x/tools/go/ssa/builder.go +++ b/vendor/golang.org/x/tools/go/ssa/builder.go @@ -380,7 +380,13 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ } case "new": - return emitNew(fn, typeparams.MustDeref(typ), pos, "new") + alloc := emitNew(fn, typeparams.MustDeref(typ), pos, "new") + if !fn.info.Types[args[0]].IsType() { + // new(expr), requires go1.26 + v := b.expr(fn, args[0]) + emitStore(fn, alloc, v, pos) + } + return alloc case "len", "cap": // Special case: len or cap of an array or *array is diff --git a/vendor/golang.org/x/tools/go/ssa/subst.go b/vendor/golang.org/x/tools/go/ssa/subst.go index 362dce126..2c465ec0a 100644 --- a/vendor/golang.org/x/tools/go/ssa/subst.go +++ b/vendor/golang.org/x/tools/go/ssa/subst.go @@ -567,76 +567,3 @@ func (subst *subster) signature(t *types.Signature) types.Type { } return t } - -// reaches returns true if a type t reaches any type t' s.t. c[t'] == true. -// It updates c to cache results. -// -// reaches is currently only part of the wellFormed debug logic, and -// in practice c is initially only type parameters. It is not currently -// relied on in production. -func reaches(t types.Type, c map[types.Type]bool) (res bool) { - if c, ok := c[t]; ok { - return c - } - - // c is populated with temporary false entries as types are visited. - // This avoids repeat visits and break cycles. - c[t] = false - defer func() { - c[t] = res - }() - - switch t := t.(type) { - case *types.TypeParam, *types.Basic: - return false - case *types.Array: - return reaches(t.Elem(), c) - case *types.Slice: - return reaches(t.Elem(), c) - case *types.Pointer: - return reaches(t.Elem(), c) - case *types.Tuple: - for i := 0; i < t.Len(); i++ { - if reaches(t.At(i).Type(), c) { - return true - } - } - case *types.Struct: - for i := 0; i < t.NumFields(); i++ { - if reaches(t.Field(i).Type(), c) { - return true - } - } - case *types.Map: - return reaches(t.Key(), c) || reaches(t.Elem(), c) - case *types.Chan: - return reaches(t.Elem(), c) - case *types.Signature: - if t.Recv() != nil && reaches(t.Recv().Type(), c) { - return true - } - return reaches(t.Params(), c) || reaches(t.Results(), c) - case *types.Union: - for i := 0; i < t.Len(); i++ { - if reaches(t.Term(i).Type(), c) { - return true - } - } - case *types.Interface: - for i := 0; i < t.NumEmbeddeds(); i++ { - if reaches(t.Embedded(i), c) { - return true - } - } - for i := 0; i < t.NumExplicitMethods(); i++ { - if reaches(t.ExplicitMethod(i).Type(), c) { - return true - } - } - case *types.Named, *types.Alias: - return reaches(t.Underlying(), c) - default: - panic("unreachable") - } - return false -} diff --git a/vendor/golang.org/x/tools/imports/forward.go b/vendor/golang.org/x/tools/imports/forward.go index cb6db8893..22ae77772 100644 --- a/vendor/golang.org/x/tools/imports/forward.go +++ b/vendor/golang.org/x/tools/imports/forward.go @@ -69,9 +69,3 @@ func Process(filename string, src []byte, opt *Options) ([]byte, error) { } return intimp.Process(filename, src, intopt) } - -// VendorlessPath returns the devendorized version of the import path ipath. -// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". -func VendorlessPath(ipath string) string { - return intimp.VendorlessPath(ipath) -} diff --git a/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go index e48dc3f33..2b4a8ebb6 100644 --- a/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go +++ b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go @@ -7,96 +7,23 @@ package analysisinternal import ( - "bytes" "cmp" "fmt" "go/ast" - "go/printer" - "go/scanner" "go/token" "go/types" - "iter" - pathpkg "path" "slices" "strings" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/moreiters" - "golang.org/x/tools/internal/typesinternal" ) -// Deprecated: this heuristic is ill-defined. -// TODO(adonovan): move to sole use in gopls/internal/cache. -func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { - // Get the end position for the type error. - file := fset.File(start) - if file == nil { - return start - } - if offset := file.PositionFor(start, false).Offset; offset > len(src) { - return start - } else { - src = src[offset:] - } - - // Attempt to find a reasonable end position for the type error. - // - // TODO(rfindley): the heuristic implemented here is unclear. It looks like - // it seeks the end of the primary operand starting at start, but that is not - // quite implemented (for example, given a func literal this heuristic will - // return the range of the func keyword). - // - // We should formalize this heuristic, or deprecate it by finally proposing - // to add end position to all type checker errors. - // - // Nevertheless, ensure that the end position at least spans the current - // token at the cursor (this was golang/go#69505). - end := start - { - var s scanner.Scanner - fset := token.NewFileSet() - f := fset.AddFile("", fset.Base(), len(src)) - s.Init(f, src, nil /* no error handler */, scanner.ScanComments) - pos, tok, lit := s.Scan() - if tok != token.SEMICOLON && token.Pos(f.Base()) <= pos && pos <= token.Pos(f.Base()+f.Size()) { - off := file.Offset(pos) + len(lit) - src = src[off:] - end += token.Pos(off) - } - } - - // Look for bytes that might terminate the current operand. See note above: - // this is imprecise. - if width := bytes.IndexAny(src, " \n,():;[]+-*/"); width > 0 { - end += token.Pos(width) - } - return end -} - -// WalkASTWithParent walks the AST rooted at n. The semantics are -// similar to ast.Inspect except it does not call f(nil). -func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) { - var ancestors []ast.Node - ast.Inspect(n, func(n ast.Node) (recurse bool) { - if n == nil { - ancestors = ancestors[:len(ancestors)-1] - return false - } - - var parent ast.Node - if len(ancestors) > 0 { - parent = ancestors[len(ancestors)-1] - } - ancestors = append(ancestors, n) - return f(n, parent) - }) -} - // MatchingIdents finds the names of all identifiers in 'node' that match any of the given types. // 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within // the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that // is unrecognized. +// +// TODO(adonovan): this is only used by gopls/internal/analysis/fill{returns,struct}. Move closer. func MatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]string { // Initialize matches to contain the variable types we are searching for. @@ -212,185 +139,6 @@ func CheckReadable(pass *analysis.Pass, filename string) error { return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename) } -// AddImport checks whether this file already imports pkgpath and -// that import is in scope at pos. If so, it returns the name under -// which it was imported and a zero edit. Otherwise, it adds a new -// import of pkgpath, using a name derived from the preferred name, -// and returns the chosen name, a prefix to be concatenated with member -// to form a qualified name, and the edit for the new import. -// -// In the special case that pkgpath is dot-imported then member, the -// identifier for which the import is being added, is consulted. If -// member is not shadowed at pos, AddImport returns (".", "", nil). -// (AddImport accepts the caller's implicit claim that the imported -// package declares member.) -// -// It does not mutate its arguments. -func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (name, prefix string, newImport []analysis.TextEdit) { - // Find innermost enclosing lexical block. - scope := info.Scopes[file].Innermost(pos) - if scope == nil { - panic("no enclosing lexical block") - } - - // Is there an existing import of this package? - // If so, are we in its scope? (not shadowed) - for _, spec := range file.Imports { - pkgname := info.PkgNameOf(spec) - if pkgname != nil && pkgname.Imported().Path() == pkgpath { - name = pkgname.Name() - if name == "." { - // The scope of ident must be the file scope. - if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] { - return name, "", nil - } - } else if _, obj := scope.LookupParent(name, pos); obj == pkgname { - return name, name + ".", nil - } - } - } - - // We must add a new import. - // Ensure we have a fresh name. - newName := FreshName(scope, pos, preferredName) - - // Create a new import declaration either before the first existing - // declaration (which must exist), including its comments; or - // inside the declaration, if it is an import group. - // - // Use a renaming import whenever the preferred name is not - // available, or the chosen name does not match the last - // segment of its path. - newText := fmt.Sprintf("%q", pkgpath) - if newName != preferredName || newName != pathpkg.Base(pkgpath) { - newText = fmt.Sprintf("%s %q", newName, pkgpath) - } - decl0 := file.Decls[0] - var before ast.Node = decl0 - switch decl0 := decl0.(type) { - case *ast.GenDecl: - if decl0.Doc != nil { - before = decl0.Doc - } - case *ast.FuncDecl: - if decl0.Doc != nil { - before = decl0.Doc - } - } - // If the first decl is an import group, add this new import at the end. - if gd, ok := before.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() { - pos = gd.Rparen - // if it's a std lib, we should append it at the beginning of import group. - // otherwise we may see the std package is put at the last behind a 3rd module which doesn't follow our convention. - // besides, gofmt doesn't help in this case. - if IsStdPackage(pkgpath) && len(gd.Specs) != 0 { - pos = gd.Specs[0].Pos() - newText += "\n\t" - } else { - newText = "\t" + newText + "\n" - } - } else { - pos = before.Pos() - newText = "import " + newText + "\n\n" - } - return newName, newName + ".", []analysis.TextEdit{{ - Pos: pos, - End: pos, - NewText: []byte(newText), - }} -} - -// FreshName returns the name of an identifier that is undefined -// at the specified position, based on the preferred name. -func FreshName(scope *types.Scope, pos token.Pos, preferred string) string { - newName := preferred - for i := 0; ; i++ { - if _, obj := scope.LookupParent(newName, pos); obj == nil { - break // fresh - } - newName = fmt.Sprintf("%s%d", preferred, i) - } - return newName -} - -// Format returns a string representation of the node n. -func Format(fset *token.FileSet, n ast.Node) string { - var buf strings.Builder - printer.Fprint(&buf, fset, n) // ignore errors - return buf.String() -} - -// Imports returns true if path is imported by pkg. -func Imports(pkg *types.Package, path string) bool { - for _, imp := range pkg.Imports() { - if imp.Path() == path { - return true - } - } - return false -} - -// IsTypeNamed reports whether t is (or is an alias for) a -// package-level defined type with the given package path and one of -// the given names. It returns false if t is nil. -// -// This function avoids allocating the concatenation of "pkg.Name", -// which is important for the performance of syntax matching. -func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool { - if named, ok := types.Unalias(t).(*types.Named); ok { - tname := named.Obj() - return tname != nil && - typesinternal.IsPackageLevel(tname) && - tname.Pkg().Path() == pkgPath && - slices.Contains(names, tname.Name()) - } - return false -} - -// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a -// package-level defined type with the given package path and one of the given -// names. It returns false if t is not a pointer type. -func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool { - r := typesinternal.Unpointer(t) - if r == t { - return false - } - return IsTypeNamed(r, pkgPath, names...) -} - -// IsFunctionNamed reports whether obj is a package-level function -// defined in the given package and has one of the given names. -// It returns false if obj is nil. -// -// This function avoids allocating the concatenation of "pkg.Name", -// which is important for the performance of syntax matching. -func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool { - f, ok := obj.(*types.Func) - return ok && - typesinternal.IsPackageLevel(obj) && - f.Pkg().Path() == pkgPath && - f.Type().(*types.Signature).Recv() == nil && - slices.Contains(names, f.Name()) -} - -// IsMethodNamed reports whether obj is a method defined on a -// package-level type with the given package and type name, and has -// one of the given names. It returns false if obj is nil. -// -// This function avoids allocating the concatenation of "pkg.TypeName.Name", -// which is important for the performance of syntax matching. -func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool { - if fn, ok := obj.(*types.Func); ok { - if recv := fn.Type().(*types.Signature).Recv(); recv != nil { - _, T := typesinternal.ReceiverNamed(recv) - return T != nil && - IsTypeNamed(T, pkgPath, typeName) && - slices.Contains(names, fn.Name()) - } - } - return false -} - // ValidateFixes validates the set of fixes for a single diagnostic. // Any error indicates a bug in the originating analyzer. // @@ -493,6 +241,20 @@ func validateFix(fset *token.FileSet, fix *analysis.SuggestedFix) error { return nil } +// Range returns an [analysis.Range] for the specified start and end positions. +func Range(pos, end token.Pos) analysis.Range { + return tokenRange{pos, end} +} + +// tokenRange is an implementation of the [analysis.Range] interface. +type tokenRange struct{ StartPos, EndPos token.Pos } + +func (r tokenRange) Pos() token.Pos { return r.StartPos } +func (r tokenRange) End() token.Pos { return r.EndPos } + +// TODO(adonovan): the import-related functions below don't depend on +// analysis (or even on go/types or go/ast). Move somewhere more logical. + // CanImport reports whether one package is allowed to import another. // // TODO(adonovan): allow customization of the accessibility relation @@ -520,133 +282,6 @@ func CanImport(from, to string) bool { return true } -// DeleteStmt returns the edits to remove the [ast.Stmt] identified by -// curStmt, if it is contained within a BlockStmt, CaseClause, -// CommClause, or is the STMT in switch STMT; ... {...}. It returns nil otherwise. -func DeleteStmt(fset *token.FileSet, curStmt inspector.Cursor) []analysis.TextEdit { - stmt := curStmt.Node().(ast.Stmt) - // if the stmt is on a line by itself delete the whole line - // otherwise just delete the statement. - - // this logic would be a lot simpler with the file contents, and somewhat simpler - // if the cursors included the comments. - - tokFile := fset.File(stmt.Pos()) - lineOf := tokFile.Line - stmtStartLine, stmtEndLine := lineOf(stmt.Pos()), lineOf(stmt.End()) - - var from, to token.Pos - // bounds of adjacent syntax/comments on same line, if any - limits := func(left, right token.Pos) { - if lineOf(left) == stmtStartLine { - from = left - } - if lineOf(right) == stmtEndLine { - to = right - } - } - // TODO(pjw): there are other places a statement might be removed: - // IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] . - // (removing the blocks requires more rewriting than this routine would do) - // CommCase = "case" ( SendStmt | RecvStmt ) | "default" . - // (removing the stmt requires more rewriting, and it's unclear what the user means) - switch parent := curStmt.Parent().Node().(type) { - case *ast.SwitchStmt: - limits(parent.Switch, parent.Body.Lbrace) - case *ast.TypeSwitchStmt: - limits(parent.Switch, parent.Body.Lbrace) - if parent.Assign == stmt { - return nil // don't let the user break the type switch - } - case *ast.BlockStmt: - limits(parent.Lbrace, parent.Rbrace) - case *ast.CommClause: - limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace) - if parent.Comm == stmt { - return nil // maybe the user meant to remove the entire CommClause? - } - case *ast.CaseClause: - limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace) - case *ast.ForStmt: - limits(parent.For, parent.Body.Lbrace) - - default: - return nil // not one of ours - } - - if prev, found := curStmt.PrevSibling(); found && lineOf(prev.Node().End()) == stmtStartLine { - from = prev.Node().End() // preceding statement ends on same line - } - if next, found := curStmt.NextSibling(); found && lineOf(next.Node().Pos()) == stmtEndLine { - to = next.Node().Pos() // following statement begins on same line - } - // and now for the comments -Outer: - for _, cg := range enclosingFile(curStmt).Comments { - for _, co := range cg.List { - if lineOf(co.End()) < stmtStartLine { - continue - } else if lineOf(co.Pos()) > stmtEndLine { - break Outer // no more are possible - } - if lineOf(co.End()) == stmtStartLine && co.End() < stmt.Pos() { - if !from.IsValid() || co.End() > from { - from = co.End() - continue // maybe there are more - } - } - if lineOf(co.Pos()) == stmtEndLine && co.Pos() > stmt.End() { - if !to.IsValid() || co.Pos() < to { - to = co.Pos() - continue // maybe there are more - } - } - } - } - // if either from or to is valid, just remove the statement - // otherwise remove the line - edit := analysis.TextEdit{Pos: stmt.Pos(), End: stmt.End()} - if from.IsValid() || to.IsValid() { - // remove just the statement. - // we can't tell if there is a ; or whitespace right after the statement - // ideally we'd like to remove the former and leave the latter - // (if gofmt has run, there likely won't be a ;) - // In type switches we know there's a semicolon somewhere after the statement, - // but the extra work for this special case is not worth it, as gofmt will fix it. - return []analysis.TextEdit{edit} - } - // remove the whole line - for lineOf(edit.Pos) == stmtStartLine { - edit.Pos-- - } - edit.Pos++ // get back tostmtStartLine - for lineOf(edit.End) == stmtEndLine { - edit.End++ - } - return []analysis.TextEdit{edit} -} - -// Comments returns an iterator over the comments overlapping the specified interval. -func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] { - // TODO(adonovan): optimize use binary O(log n) instead of linear O(n) search. - return func(yield func(*ast.Comment) bool) { - for _, cg := range file.Comments { - for _, co := range cg.List { - if co.Pos() > end { - return - } - if co.End() < start { - continue - } - - if !yield(co) { - return - } - } - } - } -} - // IsStdPackage reports whether the specified package path belongs to a // package in the standard library (including internal dependencies). func IsStdPackage(path string) bool { @@ -658,20 +293,3 @@ func IsStdPackage(path string) bool { } return !strings.Contains(path[:slash], ".") && path != "testdata" } - -// Range returns an [analysis.Range] for the specified start and end positions. -func Range(pos, end token.Pos) analysis.Range { - return tokenRange{pos, end} -} - -// tokenRange is an implementation of the [analysis.Range] interface. -type tokenRange struct{ StartPos, EndPos token.Pos } - -func (r tokenRange) Pos() token.Pos { return r.StartPos } -func (r tokenRange) End() token.Pos { return r.EndPos } - -// enclosingFile returns the syntax tree for the file enclosing c. -func enclosingFile(c inspector.Cursor) *ast.File { - c, _ = moreiters.First(c.Enclosing((*ast.File)(nil))) - return c.Node().(*ast.File) -} diff --git a/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go b/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go index 39507723d..bfb5900f1 100644 --- a/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go +++ b/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go @@ -97,7 +97,7 @@ func ExtractDoc(content, name string) (string, error) { if f.Doc == nil { return "", fmt.Errorf("Go source file has no package doc comment") } - for _, section := range strings.Split(f.Doc.Text(), "\n# ") { + for section := range strings.SplitSeq(f.Doc.Text(), "\n# ") { if body := strings.TrimPrefix(section, "Analyzer "+name); body != section && body != "" && body[0] == '\r' || body[0] == '\n' { diff --git a/vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go b/vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go new file mode 100644 index 000000000..bba21c6ea --- /dev/null +++ b/vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go @@ -0,0 +1,33 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeindex defines an analyzer that provides a +// [golang.org/x/tools/internal/typesinternal/typeindex.Index]. +// +// Like [golang.org/x/tools/go/analysis/passes/inspect], it is +// intended to be used as a helper by other analyzers; it reports no +// diagnostics of its own. +package typeindex + +import ( + "reflect" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/internal/typesinternal/typeindex" +) + +var Analyzer = &analysis.Analyzer{ + Name: "typeindex", + Doc: "indexes of type information for later passes", + URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysisinternal/typeindex", + Run: func(pass *analysis.Pass) (any, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil + }, + RunDespiteErrors: true, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + ResultType: reflect.TypeOf(new(typeindex.Index)), +} diff --git a/vendor/golang.org/x/tools/internal/astutil/comment.go b/vendor/golang.org/x/tools/internal/astutil/comment.go index ee4be23f2..7e52aeaaa 100644 --- a/vendor/golang.org/x/tools/internal/astutil/comment.go +++ b/vendor/golang.org/x/tools/internal/astutil/comment.go @@ -7,6 +7,7 @@ package astutil import ( "go/ast" "go/token" + "iter" "strings" ) @@ -15,7 +16,7 @@ import ( // https://go.dev/wiki/Deprecated, or "" if the documented symbol is not // deprecated. func Deprecation(doc *ast.CommentGroup) string { - for _, p := range strings.Split(doc.Text(), "\n\n") { + for p := range strings.SplitSeq(doc.Text(), "\n\n") { // There is still some ambiguity for deprecation message. This function // only returns the paragraph introduced by "Deprecated: ". More // information related to the deprecation may follow in additional @@ -111,3 +112,24 @@ func Directives(g *ast.CommentGroup) (res []*Directive) { } return } + +// Comments returns an iterator over the comments overlapping the specified interval. +func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] { + // TODO(adonovan): optimize use binary O(log n) instead of linear O(n) search. + return func(yield func(*ast.Comment) bool) { + for _, cg := range file.Comments { + for _, co := range cg.List { + if co.Pos() > end { + return + } + if co.End() < start { + continue + } + + if !yield(co) { + return + } + } + } + } +} diff --git a/vendor/golang.org/x/tools/internal/astutil/equal.go b/vendor/golang.org/x/tools/internal/astutil/equal.go index c945de02d..210f39238 100644 --- a/vendor/golang.org/x/tools/internal/astutil/equal.go +++ b/vendor/golang.org/x/tools/internal/astutil/equal.go @@ -26,6 +26,14 @@ func Equal(x, y ast.Node, identical func(x, y *ast.Ident) bool) bool { return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical) } +// EqualSyntax reports whether x and y are equal. +// Identifiers are considered equal if they are spelled the same. +// Comments are ignored. +func EqualSyntax(x, y ast.Expr) bool { + sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name } + return Equal(x, y, sameName) +} + func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool { // Ensure types are the same if x.Type() != y.Type() { diff --git a/vendor/golang.org/x/tools/internal/astutil/util.go b/vendor/golang.org/x/tools/internal/astutil/util.go index 14189155e..a1c098350 100644 --- a/vendor/golang.org/x/tools/internal/astutil/util.go +++ b/vendor/golang.org/x/tools/internal/astutil/util.go @@ -6,7 +6,13 @@ package astutil import ( "go/ast" + "go/printer" "go/token" + "strings" + + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/internal/moreiters" ) // PreorderStack traverses the tree rooted at root, @@ -67,3 +73,47 @@ func NodeContains(n ast.Node, pos token.Pos) bool { } return start <= pos && pos <= end } + +// IsChildOf reports whether cur.ParentEdge is ek. +// +// TODO(adonovan): promote to a method of Cursor. +func IsChildOf(cur inspector.Cursor, ek edge.Kind) bool { + got, _ := cur.ParentEdge() + return got == ek +} + +// EnclosingFile returns the syntax tree for the file enclosing c. +// +// TODO(adonovan): promote this to a method of Cursor. +func EnclosingFile(c inspector.Cursor) *ast.File { + c, _ = moreiters.First(c.Enclosing((*ast.File)(nil))) + return c.Node().(*ast.File) +} + +// DocComment returns the doc comment for a node, if any. +func DocComment(n ast.Node) *ast.CommentGroup { + switch n := n.(type) { + case *ast.FuncDecl: + return n.Doc + case *ast.GenDecl: + return n.Doc + case *ast.ValueSpec: + return n.Doc + case *ast.TypeSpec: + return n.Doc + case *ast.File: + return n.Doc + case *ast.ImportSpec: + return n.Doc + case *ast.Field: + return n.Doc + } + return nil +} + +// Format returns a string representation of the node n. +func Format(fset *token.FileSet, n ast.Node) string { + var buf strings.Builder + printer.Fprint(&buf, fset, n) // ignore errors + return buf.String() +} diff --git a/vendor/golang.org/x/tools/internal/event/core/event.go b/vendor/golang.org/x/tools/internal/event/core/event.go index a6cf0e64a..ade5d1e79 100644 --- a/vendor/golang.org/x/tools/internal/event/core/event.go +++ b/vendor/golang.org/x/tools/internal/event/core/event.go @@ -28,11 +28,6 @@ type Event struct { dynamic []label.Label // dynamically sized storage for remaining labels } -// eventLabelMap implements label.Map for a the labels of an Event. -type eventLabelMap struct { - event Event -} - func (ev Event) At() time.Time { return ev.at } func (ev Event) Format(f fmt.State, r rune) { diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index 780873e3a..4a4357d2b 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -569,7 +569,6 @@ func (p *iexporter) exportName(obj types.Object) (res string) { type iexporter struct { fset *token.FileSet - out *bytes.Buffer version int shallow bool // don't put types from other packages in the index diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go index 50b6ca51a..1b4dc0cb5 100644 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -16,6 +16,7 @@ import ( "go/types" "io/fs" "io/ioutil" + "maps" "os" "path" "path/filepath" @@ -27,8 +28,6 @@ import ( "unicode" "unicode/utf8" - "maps" - "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/gocommand" @@ -43,7 +42,7 @@ var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){ if localPrefix == "" { return } - for _, p := range strings.Split(localPrefix, ",") { + for p := range strings.SplitSeq(localPrefix, ",") { if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath { return 3, true } @@ -1251,7 +1250,6 @@ func ImportPathToAssumedName(importPath string) string { // gopathResolver implements resolver for GOPATH workspaces. type gopathResolver struct { env *ProcessEnv - walked bool cache *DirInfoCache scanSema chan struct{} // scanSema prevents concurrent scans. } diff --git a/vendor/golang.org/x/tools/internal/modindex/symbols.go b/vendor/golang.org/x/tools/internal/modindex/symbols.go index fe24db9b1..8e9702d84 100644 --- a/vendor/golang.org/x/tools/internal/modindex/symbols.go +++ b/vendor/golang.org/x/tools/internal/modindex/symbols.go @@ -206,8 +206,7 @@ func isDeprecated(doc *ast.CommentGroup) bool { // go.dev/wiki/Deprecated Paragraph starting 'Deprecated:' // This code fails for /* Deprecated: */, but it's the code from // gopls/internal/analysis/deprecated - lines := strings.Split(doc.Text(), "\n\n") - for _, line := range lines { + for line := range strings.SplitSeq(doc.Text(), "\n\n") { if strings.HasPrefix(line, "Deprecated:") { return true } diff --git a/vendor/golang.org/x/tools/internal/refactor/delete.go b/vendor/golang.org/x/tools/internal/refactor/delete.go new file mode 100644 index 000000000..aa8ba5af4 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/refactor/delete.go @@ -0,0 +1,433 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package refactor + +// This file defines operations for computing deletion edits. + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "slices" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/internal/astutil" + "golang.org/x/tools/internal/typesinternal" +) + +// DeleteVar returns edits to delete the declaration of a variable +// whose defining identifier is curId. +// +// It handles variants including: +// - GenDecl > ValueSpec versus AssignStmt; +// - RHS expression has effects, or not; +// - entire statement/declaration may be eliminated; +// and removes associated comments. +// +// If it cannot make the necessary edits, such as for a function +// parameter or result, it returns nil. +func DeleteVar(tokFile *token.File, info *types.Info, curId inspector.Cursor) []analysis.TextEdit { + switch ek, _ := curId.ParentEdge(); ek { + case edge.ValueSpec_Names: + return deleteVarFromValueSpec(tokFile, info, curId) + + case edge.AssignStmt_Lhs: + return deleteVarFromAssignStmt(tokFile, info, curId) + } + + // e.g. function receiver, parameter, or result, + // or "switch v := expr.(T) {}" (which has no object). + return nil +} + +// Precondition: curId is Ident beneath ValueSpec.Names beneath GenDecl. +// +// See also [deleteVarFromAssignStmt], which has parallel structure. +func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []analysis.TextEdit { + var ( + id = curIdent.Node().(*ast.Ident) + curSpec = curIdent.Parent() + spec = curSpec.Node().(*ast.ValueSpec) + ) + + declaresOtherNames := slices.ContainsFunc(spec.Names, func(name *ast.Ident) bool { + return name != id && name.Name != "_" + }) + noRHSEffects := !slices.ContainsFunc(spec.Values, func(rhs ast.Expr) bool { + return !typesinternal.NoEffects(info, rhs) + }) + if !declaresOtherNames && noRHSEffects { + // The spec is no longer needed, either to declare + // other variables, or for its side effects. + return DeleteSpec(tokFile, curSpec) + } + + // The spec is still needed, either for + // at least one LHS, or for effects on RHS. + // Blank out or delete just one LHS. + + _, index := curIdent.ParentEdge() // index of LHS within ValueSpec.Names + + // If there is no RHS, we can delete the LHS. + if len(spec.Values) == 0 { + var pos, end token.Pos + if index == len(spec.Names)-1 { + // Delete final name. + // + // var _, lhs1 T + // ------ + pos = spec.Names[index-1].End() + end = spec.Names[index].End() + } else { + // Delete non-final name. + // + // var lhs0, _ T + // ------ + pos = spec.Names[index].Pos() + end = spec.Names[index+1].Pos() + } + return []analysis.TextEdit{{ + Pos: pos, + End: end, + }} + } + + // If the assignment is 1:1 and the RHS has no effects, + // we can delete the LHS and its corresponding RHS. + if len(spec.Names) == len(spec.Values) && + typesinternal.NoEffects(info, spec.Values[index]) { + + if index == len(spec.Names)-1 { + // Delete final items. + // + // var _, lhs1 = rhs0, rhs1 + // ------ ------ + return []analysis.TextEdit{ + { + Pos: spec.Names[index-1].End(), + End: spec.Names[index].End(), + }, + { + Pos: spec.Values[index-1].End(), + End: spec.Values[index].End(), + }, + } + } else { + // Delete non-final items. + // + // var lhs0, _ = rhs0, rhs1 + // ------ ------ + return []analysis.TextEdit{ + { + Pos: spec.Names[index].Pos(), + End: spec.Names[index+1].Pos(), + }, + { + Pos: spec.Values[index].Pos(), + End: spec.Values[index+1].Pos(), + }, + } + } + } + + // We cannot delete the RHS. + // Blank out the LHS. + return []analysis.TextEdit{{ + Pos: id.Pos(), + End: id.End(), + NewText: []byte("_"), + }} +} + +// Precondition: curId is Ident beneath AssignStmt.Lhs. +// +// See also [deleteVarFromValueSpec], which has parallel structure. +func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []analysis.TextEdit { + var ( + id = curIdent.Node().(*ast.Ident) + curStmt = curIdent.Parent() + assign = curStmt.Node().(*ast.AssignStmt) + ) + + declaresOtherNames := slices.ContainsFunc(assign.Lhs, func(lhs ast.Expr) bool { + lhsId, ok := lhs.(*ast.Ident) + return ok && lhsId != id && lhsId.Name != "_" + }) + noRHSEffects := !slices.ContainsFunc(assign.Rhs, func(rhs ast.Expr) bool { + return !typesinternal.NoEffects(info, rhs) + }) + if !declaresOtherNames && noRHSEffects { + // The assignment is no longer needed, either to + // declare other variables, or for its side effects. + if edits := DeleteStmt(tokFile, curStmt); edits != nil { + return edits + } + // Statement could not not be deleted in this context. + // Fall back to conservative deletion. + } + + // The assign is still needed, either for + // at least one LHS, or for effects on RHS, + // or because it cannot deleted because of its context. + // Blank out or delete just one LHS. + + // If the assignment is 1:1 and the RHS has no effects, + // we can delete the LHS and its corresponding RHS. + _, index := curIdent.ParentEdge() + if len(assign.Lhs) > 1 && + len(assign.Lhs) == len(assign.Rhs) && + typesinternal.NoEffects(info, assign.Rhs[index]) { + + if index == len(assign.Lhs)-1 { + // Delete final items. + // + // _, lhs1 := rhs0, rhs1 + // ------ ------ + return []analysis.TextEdit{ + { + Pos: assign.Lhs[index-1].End(), + End: assign.Lhs[index].End(), + }, + { + Pos: assign.Rhs[index-1].End(), + End: assign.Rhs[index].End(), + }, + } + } else { + // Delete non-final items. + // + // lhs0, _ := rhs0, rhs1 + // ------ ------ + return []analysis.TextEdit{ + { + Pos: assign.Lhs[index].Pos(), + End: assign.Lhs[index+1].Pos(), + }, + { + Pos: assign.Rhs[index].Pos(), + End: assign.Rhs[index+1].Pos(), + }, + } + } + } + + // We cannot delete the RHS. + // Blank out the LHS. + edits := []analysis.TextEdit{{ + Pos: id.Pos(), + End: id.End(), + NewText: []byte("_"), + }} + + // If this eliminates the final variable declared by + // an := statement, we need to turn it into an = + // assignment to avoid a "no new variables on left + // side of :=" error. + if !declaresOtherNames { + edits = append(edits, analysis.TextEdit{ + Pos: assign.TokPos, + End: assign.TokPos + token.Pos(len(":=")), + NewText: []byte("="), + }) + } + + return edits +} + +// DeleteSpec returns edits to delete the ValueSpec identified by curSpec. +// +// TODO(adonovan): add test suite. Test for consts as well. +func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []analysis.TextEdit { + var ( + spec = curSpec.Node().(*ast.ValueSpec) + curDecl = curSpec.Parent() + decl = curDecl.Node().(*ast.GenDecl) + ) + + // If it is the sole spec in the decl, + // delete the entire decl. + if len(decl.Specs) == 1 { + return DeleteDecl(tokFile, curDecl) + } + + // Delete the spec and its comments. + _, index := curSpec.ParentEdge() // index of ValueSpec within GenDecl.Specs + pos, end := spec.Pos(), spec.End() + if spec.Doc != nil { + pos = spec.Doc.Pos() // leading comment + } + if index == len(decl.Specs)-1 { + // Delete final spec. + if spec.Comment != nil { + // var (v int // comment \n) + end = spec.Comment.End() + } + } else { + // Delete non-final spec. + // var ( a T; b T ) + // ----- + end = decl.Specs[index+1].Pos() + } + return []analysis.TextEdit{{ + Pos: pos, + End: end, + }} +} + +// DeleteDecl returns edits to delete the ast.Decl identified by curDecl. +// +// TODO(adonovan): add test suite. +func DeleteDecl(tokFile *token.File, curDecl inspector.Cursor) []analysis.TextEdit { + decl := curDecl.Node().(ast.Decl) + + ek, _ := curDecl.ParentEdge() + switch ek { + case edge.DeclStmt_Decl: + return DeleteStmt(tokFile, curDecl.Parent()) + + case edge.File_Decls: + pos, end := decl.Pos(), decl.End() + if doc := astutil.DocComment(decl); doc != nil { + pos = doc.Pos() + } + + // Delete free-floating comments on same line as rparen. + // var (...) // comment + var ( + file = curDecl.Parent().Node().(*ast.File) + lineOf = tokFile.Line + declEndLine = lineOf(decl.End()) + ) + for _, cg := range file.Comments { + for _, c := range cg.List { + if c.Pos() < end { + continue // too early + } + commentEndLine := lineOf(c.End()) + if commentEndLine > declEndLine { + break // too late + } else if lineOf(c.Pos()) == declEndLine && commentEndLine == declEndLine { + end = c.End() + } + } + } + + return []analysis.TextEdit{{ + Pos: pos, + End: end, + }} + + default: + panic(fmt.Sprintf("Decl parent is %v, want DeclStmt or File", ek)) + } +} + +// DeleteStmt returns the edits to remove the [ast.Stmt] identified by +// curStmt, if it is contained within a BlockStmt, CaseClause, +// CommClause, or is the STMT in switch STMT; ... {...}. It returns nil otherwise. +func DeleteStmt(tokFile *token.File, curStmt inspector.Cursor) []analysis.TextEdit { + stmt := curStmt.Node().(ast.Stmt) + // if the stmt is on a line by itself delete the whole line + // otherwise just delete the statement. + + // this logic would be a lot simpler with the file contents, and somewhat simpler + // if the cursors included the comments. + + lineOf := tokFile.Line + stmtStartLine, stmtEndLine := lineOf(stmt.Pos()), lineOf(stmt.End()) + + var from, to token.Pos + // bounds of adjacent syntax/comments on same line, if any + limits := func(left, right token.Pos) { + if lineOf(left) == stmtStartLine { + from = left + } + if lineOf(right) == stmtEndLine { + to = right + } + } + // TODO(pjw): there are other places a statement might be removed: + // IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] . + // (removing the blocks requires more rewriting than this routine would do) + // CommCase = "case" ( SendStmt | RecvStmt ) | "default" . + // (removing the stmt requires more rewriting, and it's unclear what the user means) + switch parent := curStmt.Parent().Node().(type) { + case *ast.SwitchStmt: + limits(parent.Switch, parent.Body.Lbrace) + case *ast.TypeSwitchStmt: + limits(parent.Switch, parent.Body.Lbrace) + if parent.Assign == stmt { + return nil // don't let the user break the type switch + } + case *ast.BlockStmt: + limits(parent.Lbrace, parent.Rbrace) + case *ast.CommClause: + limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace) + if parent.Comm == stmt { + return nil // maybe the user meant to remove the entire CommClause? + } + case *ast.CaseClause: + limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace) + case *ast.ForStmt: + limits(parent.For, parent.Body.Lbrace) + + default: + return nil // not one of ours + } + + if prev, found := curStmt.PrevSibling(); found && lineOf(prev.Node().End()) == stmtStartLine { + from = prev.Node().End() // preceding statement ends on same line + } + if next, found := curStmt.NextSibling(); found && lineOf(next.Node().Pos()) == stmtEndLine { + to = next.Node().Pos() // following statement begins on same line + } + // and now for the comments +Outer: + for _, cg := range astutil.EnclosingFile(curStmt).Comments { + for _, co := range cg.List { + if lineOf(co.End()) < stmtStartLine { + continue + } else if lineOf(co.Pos()) > stmtEndLine { + break Outer // no more are possible + } + if lineOf(co.End()) == stmtStartLine && co.End() < stmt.Pos() { + if !from.IsValid() || co.End() > from { + from = co.End() + continue // maybe there are more + } + } + if lineOf(co.Pos()) == stmtEndLine && co.Pos() > stmt.End() { + if !to.IsValid() || co.Pos() < to { + to = co.Pos() + continue // maybe there are more + } + } + } + } + // if either from or to is valid, just remove the statement + // otherwise remove the line + edit := analysis.TextEdit{Pos: stmt.Pos(), End: stmt.End()} + if from.IsValid() || to.IsValid() { + // remove just the statement. + // we can't tell if there is a ; or whitespace right after the statement + // ideally we'd like to remove the former and leave the latter + // (if gofmt has run, there likely won't be a ;) + // In type switches we know there's a semicolon somewhere after the statement, + // but the extra work for this special case is not worth it, as gofmt will fix it. + return []analysis.TextEdit{edit} + } + // remove the whole line + for lineOf(edit.Pos) == stmtStartLine { + edit.Pos-- + } + edit.Pos++ // get back tostmtStartLine + for lineOf(edit.End) == stmtEndLine { + edit.End++ + } + return []analysis.TextEdit{edit} +} diff --git a/vendor/golang.org/x/tools/internal/refactor/imports.go b/vendor/golang.org/x/tools/internal/refactor/imports.go new file mode 100644 index 000000000..1ba3a9609 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/refactor/imports.go @@ -0,0 +1,127 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package refactor + +// This file defines operations for computing edits to imports. + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + pathpkg "path" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/internal/analysisinternal" +) + +// AddImport returns the prefix (either "pkg." or "") that should be +// used to qualify references to the desired symbol (member) imported +// from the specified package, plus any necessary edits to the file's +// import declaration to add a new import. +// +// If the import already exists, and is accessible at pos, AddImport +// returns the existing name and no edits. (If the existing import is +// a dot import, the prefix is "".) +// +// Otherwise, it adds a new import, using a local name derived from +// the preferred name. To request a blank import, use a preferredName +// of "_", and discard the prefix result; member is ignored in this +// case. +// +// AddImport accepts the caller's implicit claim that the imported +// package declares member. +// +// AddImport does not mutate its arguments. +func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (prefix string, edits []analysis.TextEdit) { + // Find innermost enclosing lexical block. + scope := info.Scopes[file].Innermost(pos) + if scope == nil { + panic("no enclosing lexical block") + } + + // Is there an existing import of this package? + // If so, are we in its scope? (not shadowed) + for _, spec := range file.Imports { + pkgname := info.PkgNameOf(spec) + if pkgname != nil && pkgname.Imported().Path() == pkgpath { + name := pkgname.Name() + if preferredName == "_" { + // Request for blank import; any existing import will do. + return "", nil + } + if name == "." { + // The scope of ident must be the file scope. + if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] { + return "", nil + } + } else if _, obj := scope.LookupParent(name, pos); obj == pkgname { + return name + ".", nil + } + } + } + + // We must add a new import. + + // Ensure we have a fresh name. + newName := preferredName + if preferredName != "_" { + newName = FreshName(scope, pos, preferredName) + } + + // Create a new import declaration either before the first existing + // declaration (which must exist), including its comments; or + // inside the declaration, if it is an import group. + // + // Use a renaming import whenever the preferred name is not + // available, or the chosen name does not match the last + // segment of its path. + newText := fmt.Sprintf("%q", pkgpath) + if newName != preferredName || newName != pathpkg.Base(pkgpath) { + newText = fmt.Sprintf("%s %q", newName, pkgpath) + } + + decl0 := file.Decls[0] + var before ast.Node = decl0 + switch decl0 := decl0.(type) { + case *ast.GenDecl: + if decl0.Doc != nil { + before = decl0.Doc + } + case *ast.FuncDecl: + if decl0.Doc != nil { + before = decl0.Doc + } + } + if gd, ok := before.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() { + // Have existing grouped import ( ... ) decl. + if analysisinternal.IsStdPackage(pkgpath) && len(gd.Specs) > 0 { + // Add spec for a std package before + // first existing spec, followed by + // a blank line if the next one is non-std. + first := gd.Specs[0].(*ast.ImportSpec) + pos = first.Pos() + if !analysisinternal.IsStdPackage(first.Path.Value) { + newText += "\n" + } + newText += "\n\t" + } else { + // Add spec at end of group. + pos = gd.Rparen + newText = "\t" + newText + "\n" + } + } else { + // No import decl, or non-grouped import. + // Add a new import decl before first decl. + // (gofmt will merge multiple import decls.) + pos = before.Pos() + newText = "import " + newText + "\n\n" + } + return newName + ".", []analysis.TextEdit{{ + Pos: pos, + End: pos, + NewText: []byte(newText), + }} +} diff --git a/vendor/golang.org/x/tools/internal/refactor/refactor.go b/vendor/golang.org/x/tools/internal/refactor/refactor.go new file mode 100644 index 000000000..27b975089 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/refactor/refactor.go @@ -0,0 +1,29 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package refactor provides operators to compute common textual edits +// for refactoring tools. +// +// This package should not use features of the analysis API +// other than [analysis.TextEdit]. +package refactor + +import ( + "fmt" + "go/token" + "go/types" +) + +// FreshName returns the name of an identifier that is undefined +// at the specified position, based on the preferred name. +func FreshName(scope *types.Scope, pos token.Pos, preferred string) string { + newName := preferred + for i := 0; ; i++ { + if _, obj := scope.LookupParent(newName, pos); obj == nil { + break // fresh + } + newName = fmt.Sprintf("%s%d", preferred, i) + } + return newName +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/fx.go b/vendor/golang.org/x/tools/internal/typesinternal/fx.go new file mode 100644 index 000000000..93acff217 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/fx.go @@ -0,0 +1,49 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/ast" + "go/token" + "go/types" +) + +// NoEffects reports whether the expression has no side effects, i.e., it +// does not modify the memory state. This function is conservative: it may +// return false even when the expression has no effect. +func NoEffects(info *types.Info, expr ast.Expr) bool { + noEffects := true + ast.Inspect(expr, func(n ast.Node) bool { + switch v := n.(type) { + case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr, + *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr, + *ast.StarExpr, *ast.CompositeLit, *ast.ArrayType, *ast.StructType, + *ast.MapType, *ast.InterfaceType, *ast.KeyValueExpr: + // No effect + case *ast.UnaryExpr: + // Channel send <-ch has effects + if v.Op == token.ARROW { + noEffects = false + } + case *ast.CallExpr: + // Type conversion has no effects + if !info.Types[v.Fun].IsType() { + // TODO(adonovan): Add a case for built-in functions without side + // effects (by using callsPureBuiltin from tools/internal/refactor/inline) + + noEffects = false + } + case *ast.FuncLit: + // A FuncLit has no effects, but do not descend into it. + return false + default: + // All other expressions have effects + noEffects = false + } + + return noEffects + }) + return noEffects +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go b/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go new file mode 100644 index 000000000..f2affec4f --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go @@ -0,0 +1,71 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/types" + "slices" +) + +// IsTypeNamed reports whether t is (or is an alias for) a +// package-level defined type with the given package path and one of +// the given names. It returns false if t is nil. +// +// This function avoids allocating the concatenation of "pkg.Name", +// which is important for the performance of syntax matching. +func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool { + if named, ok := types.Unalias(t).(*types.Named); ok { + tname := named.Obj() + return tname != nil && + IsPackageLevel(tname) && + tname.Pkg().Path() == pkgPath && + slices.Contains(names, tname.Name()) + } + return false +} + +// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a +// package-level defined type with the given package path and one of the given +// names. It returns false if t is not a pointer type. +func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool { + r := Unpointer(t) + if r == t { + return false + } + return IsTypeNamed(r, pkgPath, names...) +} + +// IsFunctionNamed reports whether obj is a package-level function +// defined in the given package and has one of the given names. +// It returns false if obj is nil. +// +// This function avoids allocating the concatenation of "pkg.Name", +// which is important for the performance of syntax matching. +func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool { + f, ok := obj.(*types.Func) + return ok && + IsPackageLevel(obj) && + f.Pkg().Path() == pkgPath && + f.Type().(*types.Signature).Recv() == nil && + slices.Contains(names, f.Name()) +} + +// IsMethodNamed reports whether obj is a method defined on a +// package-level type with the given package and type name, and has +// one of the given names. It returns false if obj is nil. +// +// This function avoids allocating the concatenation of "pkg.TypeName.Name", +// which is important for the performance of syntax matching. +func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool { + if fn, ok := obj.(*types.Func); ok { + if recv := fn.Type().(*types.Signature).Recv(); recv != nil { + _, T := ReceiverNamed(recv) + return T != nil && + IsTypeNamed(T, pkgPath, typeName) && + slices.Contains(names, fn.Name()) + } + } + return false +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go b/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go index b64f714eb..64f47919f 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go @@ -15,6 +15,14 @@ import ( // file. // If the same package is imported multiple times, the last appearance is // recorded. +// +// TODO(adonovan): this function ignores the effect of shadowing. It +// should accept a [token.Pos] and a [types.Info] and compute only the +// set of imports that are not shadowed at that point, analogous to +// [analysisinternal.AddImport]. It could also compute (as a side +// effect) the set of additional imports required to ensure that there +// is an accessible import for each necessary package, making it +// converge even more closely with AddImport. func FileQualifier(f *ast.File, pkg *types.Package) types.Qualifier { // Construct mapping of import paths to their defined names. // It is only necessary to look at renaming imports. diff --git a/vendor/golang.org/x/tools/internal/typesinternal/typeindex/typeindex.go b/vendor/golang.org/x/tools/internal/typesinternal/typeindex/typeindex.go new file mode 100644 index 000000000..01ad7b9cf --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/typeindex/typeindex.go @@ -0,0 +1,261 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeindex provides an [Index] of type information for a +// package, allowing efficient lookup of, say, whether a given symbol +// is referenced and, if so, where from; or of the [inspector.Cursor] for +// the declaration of a particular [types.Object] symbol. +package typeindex + +import ( + "encoding/binary" + "go/ast" + "go/types" + "iter" + + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/typesinternal" +) + +// New constructs an Index for the package of type-annotated syntax +// +// TODO(adonovan): accept a FileSet too? +// We regret not requiring one in inspector.New. +func New(inspect *inspector.Inspector, pkg *types.Package, info *types.Info) *Index { + ix := &Index{ + inspect: inspect, + info: info, + packages: make(map[string]*types.Package), + def: make(map[types.Object]inspector.Cursor), + uses: make(map[types.Object]*uses), + } + + addPackage := func(pkg2 *types.Package) { + if pkg2 != nil && pkg2 != pkg { + ix.packages[pkg2.Path()] = pkg2 + } + } + + for cur := range inspect.Root().Preorder((*ast.ImportSpec)(nil), (*ast.Ident)(nil)) { + switch n := cur.Node().(type) { + case *ast.ImportSpec: + // Index direct imports, including blank ones. + if pkgname := info.PkgNameOf(n); pkgname != nil { + addPackage(pkgname.Imported()) + } + + case *ast.Ident: + // Index all defining and using identifiers. + if obj := info.Defs[n]; obj != nil { + ix.def[obj] = cur + } + + if obj := info.Uses[n]; obj != nil { + // Index indirect dependencies (via fields and methods). + if !typesinternal.IsPackageLevel(obj) { + addPackage(obj.Pkg()) + } + + for { + us, ok := ix.uses[obj] + if !ok { + us = &uses{} + us.code = us.initial[:0] + ix.uses[obj] = us + } + delta := cur.Index() - us.last + if delta < 0 { + panic("non-monotonic") + } + us.code = binary.AppendUvarint(us.code, uint64(delta)) + us.last = cur.Index() + + // If n is a selection of a field or method of an instantiated + // type, also record a use of the generic field or method. + obj, ok = objectOrigin(obj) + if !ok { + break + } + } + } + } + } + return ix +} + +// objectOrigin returns the generic object for obj if it is a field or +// method of an instantied type; zero otherwise. +// +// (This operation is appropriate only for selections. +// Lexically resolved references always resolve to the generic. +// Although Named and Alias types also use Origin to express +// an instance/generic distinction, that's in the domain +// of Types; their TypeName objects always refer to the generic.) +func objectOrigin(obj types.Object) (types.Object, bool) { + var origin types.Object + switch obj := obj.(type) { + case *types.Func: + if obj.Signature().Recv() != nil { + origin = obj.Origin() // G[int].method -> G[T].method + } + case *types.Var: + if obj.IsField() { + origin = obj.Origin() // G[int].field -> G[T].field + } + } + if origin != nil && origin != obj { + return origin, true + } + return nil, false +} + +// An Index holds an index mapping [types.Object] symbols to their syntax. +// In effect, it is the inverse of [types.Info]. +type Index struct { + inspect *inspector.Inspector + info *types.Info + packages map[string]*types.Package // packages of all symbols referenced from this package + def map[types.Object]inspector.Cursor // Cursor of *ast.Ident that defines the Object + uses map[types.Object]*uses // Cursors of *ast.Idents that use the Object +} + +// A uses holds the list of Cursors of Idents that use a given symbol. +// +// The Uses map of [types.Info] is substantial, so it pays to compress +// its inverse mapping here, both in space and in CPU due to reduced +// allocation. A Cursor is 2 words; a Cursor.Index is 4 bytes; but +// since Cursors are naturally delivered in ascending order, we can +// use varint-encoded deltas at a cost of only ~1.7-2.2 bytes per use. +// +// Many variables have only one or two uses, so their encoded uses may +// fit in the 4 bytes of initial, saving further CPU and space +// essentially for free since the struct's size class is 4 words. +type uses struct { + code []byte // varint-encoded deltas of successive Cursor.Index values + last int32 // most recent Cursor.Index value; used during encoding + initial [4]byte // use slack in size class as initial space for code +} + +// Uses returns the sequence of Cursors of [*ast.Ident]s in this package +// that refer to obj. If obj is nil, the sequence is empty. +// +// Uses, unlike the Uses field of [types.Info], records additional +// entries mapping fields and methods of generic types to references +// through their corresponding instantiated objects. +func (ix *Index) Uses(obj types.Object) iter.Seq[inspector.Cursor] { + return func(yield func(inspector.Cursor) bool) { + if uses := ix.uses[obj]; uses != nil { + var last int32 + for code := uses.code; len(code) > 0; { + delta, n := binary.Uvarint(code) + last += int32(delta) + if !yield(ix.inspect.At(last)) { + return + } + code = code[n:] + } + } + } +} + +// Used reports whether any of the specified objects are used, in +// other words, obj != nil && Uses(obj) is non-empty for some obj in objs. +// +// (This treatment of nil allows Used to be called directly on the +// result of [Index.Object] so that analyzers can conveniently skip +// packages that don't use a symbol of interest.) +func (ix *Index) Used(objs ...types.Object) bool { + for _, obj := range objs { + if obj != nil && ix.uses[obj] != nil { + return true + } + } + return false +} + +// Def returns the Cursor of the [*ast.Ident] in this package +// that declares the specified object, if any. +func (ix *Index) Def(obj types.Object) (inspector.Cursor, bool) { + cur, ok := ix.def[obj] + return cur, ok +} + +// Package returns the package of the specified path, +// or nil if it is not referenced from this package. +func (ix *Index) Package(path string) *types.Package { + return ix.packages[path] +} + +// Object returns the package-level symbol name within the package of +// the specified path, or nil if the package or symbol does not exist +// or is not visible from this package. +func (ix *Index) Object(path, name string) types.Object { + if pkg := ix.Package(path); pkg != nil { + return pkg.Scope().Lookup(name) + } + return nil +} + +// Selection returns the named method or field belonging to the +// package-level type returned by Object(path, typename). +func (ix *Index) Selection(path, typename, name string) types.Object { + if obj := ix.Object(path, typename); obj != nil { + if tname, ok := obj.(*types.TypeName); ok { + obj, _, _ := types.LookupFieldOrMethod(tname.Type(), true, obj.Pkg(), name) + return obj + } + } + return nil +} + +// Calls returns the sequence of cursors for *ast.CallExpr nodes that +// call the specified callee, as defined by [typeutil.Callee]. +// If callee is nil, the sequence is empty. +func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] { + return func(yield func(inspector.Cursor) bool) { + for cur := range ix.Uses(callee) { + ek, _ := cur.ParentEdge() + + // The call may be of the form f() or x.f(), + // optionally with parens; ascend from f to call. + // + // It is tempting but wrong to use the first + // CallExpr ancestor: we have to make sure the + // ident is in the CallExpr.Fun position, otherwise + // f(f, f) would have two spurious matches. + // Avoiding Enclosing is also significantly faster. + + // inverse unparen: f -> (f) + for ek == edge.ParenExpr_X { + cur = cur.Parent() + ek, _ = cur.ParentEdge() + } + + // ascend selector: f -> x.f + if ek == edge.SelectorExpr_Sel { + cur = cur.Parent() + ek, _ = cur.ParentEdge() + } + + // inverse unparen again + for ek == edge.ParenExpr_X { + cur = cur.Parent() + ek, _ = cur.ParentEdge() + } + + // ascend from f or x.f to call + if ek == edge.CallExpr_Fun { + curCall := cur.Parent() + call := curCall.Node().(*ast.CallExpr) + if typeutil.Callee(ix.info, call) == callee { + if !yield(curCall) { + return + } + } + } + } + } +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go index a5cd7e8db..fef74a785 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/types.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -2,8 +2,20 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package typesinternal provides access to internal go/types APIs that are not -// yet exported. +// Package typesinternal provides helpful operators for dealing with +// go/types: +// +// - operators for querying typed syntax trees (e.g. [Imports], [IsFunctionNamed]); +// - functions for converting types to strings or syntax (e.g. [TypeExpr], FileQualifier]); +// - helpers for working with the [go/types] API (e.g. [NewTypesInfo]); +// - access to internal go/types APIs that are not yet +// exported (e.g. [SetUsesCgo], [ErrorCodeStartEnd], [VarKind]); and +// - common algorithms related to types (e.g. [TooNewStdSymbols]). +// +// See also: +// - [golang.org/x/tools/internal/astutil], for operations on untyped syntax; +// - [golang.org/x/tools/internal/analysisinernal], for helpers for analyzers; +// - [golang.org/x/tools/internal/refactor], for operators to compute text edits. package typesinternal import ( @@ -13,6 +25,7 @@ import ( "reflect" "unsafe" + "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/internal/aliases" ) @@ -60,6 +73,9 @@ func ErrorCodeStartEnd(err types.Error) (code ErrorCode, start, end token.Pos, o // which is often excessive.) // // If pkg is nil, it is equivalent to [*types.Package.Name]. +// +// TODO(adonovan): all uses of this with TypeString should be +// eliminated when https://go.dev/issues/75604 is resolved. func NameRelativeTo(pkg *types.Package) types.Qualifier { return func(other *types.Package) string { if pkg != nil && pkg == other { @@ -153,3 +169,31 @@ func NewTypesInfo() *types.Info { FileVersions: map[*ast.File]string{}, } } + +// EnclosingScope returns the innermost block logically enclosing the cursor. +func EnclosingScope(info *types.Info, cur inspector.Cursor) *types.Scope { + for cur := range cur.Enclosing() { + n := cur.Node() + // A function's Scope is associated with its FuncType. + switch f := n.(type) { + case *ast.FuncDecl: + n = f.Type + case *ast.FuncLit: + n = f.Type + } + if b := info.Scopes[n]; b != nil { + return b + } + } + panic("no Scope for *ast.File") +} + +// Imports reports whether path is imported by pkg. +func Imports(pkg *types.Package, path string) bool { + for _, imp := range pkg.Imports() { + if imp.Path() == path { + return true + } + } + return false +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go b/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go index d272949c1..453bba2ad 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go @@ -204,23 +204,12 @@ func ZeroExpr(t types.Type, qual types.Qualifier) (_ ast.Expr, isValid bool) { } } -// IsZeroExpr uses simple syntactic heuristics to report whether expr -// is a obvious zero value, such as 0, "", nil, or false. -// It cannot do better without type information. -func IsZeroExpr(expr ast.Expr) bool { - switch e := expr.(type) { - case *ast.BasicLit: - return e.Value == "0" || e.Value == `""` - case *ast.Ident: - return e.Name == "nil" || e.Name == "false" - default: - return false - } -} - // TypeExpr returns syntax for the specified type. References to named types // are qualified by an appropriate (optional) qualifier function. // It may panic for types such as Tuple or Union. +// +// See also https://go.dev/issues/75604, which will provide a robust +// Type-to-valid-Go-syntax formatter. func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr { switch t := t.(type) { case *types.Basic: diff --git a/vendor/k8s.io/client-go/tools/cache/controller.go b/vendor/k8s.io/client-go/tools/cache/controller.go index 5f983b6b6..e07c04e62 100644 --- a/vendor/k8s.io/client-go/tools/cache/controller.go +++ b/vendor/k8s.io/client-go/tools/cache/controller.go @@ -596,16 +596,7 @@ func newInformer(clientState Store, options InformerOptions) Controller { // KeyLister, that way resync operations will result in the correct set // of update/delete deltas. - var fifo Queue - if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InOrderInformers) { - fifo = NewRealFIFO(MetaNamespaceKeyFunc, clientState, options.Transform) - } else { - fifo = NewDeltaFIFOWithOptions(DeltaFIFOOptions{ - KnownObjects: clientState, - EmitDeltaTypeReplaced: true, - Transformer: options.Transform, - }) - } + fifo := newQueueFIFO(clientState, options.Transform) cfg := &Config{ Queue: fifo, @@ -623,3 +614,15 @@ func newInformer(clientState Store, options InformerOptions) Controller { } return New(cfg) } + +func newQueueFIFO(clientState Store, transform TransformFunc) Queue { + if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InOrderInformers) { + return NewRealFIFO(MetaNamespaceKeyFunc, clientState, transform) + } else { + return NewDeltaFIFOWithOptions(DeltaFIFOOptions{ + KnownObjects: clientState, + EmitDeltaTypeReplaced: true, + Transformer: transform, + }) + } +} diff --git a/vendor/k8s.io/client-go/tools/cache/delta_fifo.go b/vendor/k8s.io/client-go/tools/cache/delta_fifo.go index 9d9e238cc..a0d7a834a 100644 --- a/vendor/k8s.io/client-go/tools/cache/delta_fifo.go +++ b/vendor/k8s.io/client-go/tools/cache/delta_fifo.go @@ -270,7 +270,8 @@ func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO { } var ( - _ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue + _ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue + _ = TransformingStore(&DeltaFIFO{}) // DeltaFIFO implements TransformingStore to allow memory optimizations ) var ( diff --git a/vendor/k8s.io/client-go/tools/cache/reflector.go b/vendor/k8s.io/client-go/tools/cache/reflector.go index ee9be7727..6fd43375f 100644 --- a/vendor/k8s.io/client-go/tools/cache/reflector.go +++ b/vendor/k8s.io/client-go/tools/cache/reflector.go @@ -80,7 +80,7 @@ type ReflectorStore interface { // TransformingStore is an optional interface that can be implemented by the provided store. // If implemented on the provided store reflector will use the same transformer in its internal stores. type TransformingStore interface { - Store + ReflectorStore Transformer() TransformFunc } @@ -726,9 +726,11 @@ func (r *Reflector) watchList(ctx context.Context) (watch.Interface, error) { return false } + var transformer TransformFunc storeOpts := []StoreOption{} if tr, ok := r.store.(TransformingStore); ok && tr.Transformer() != nil { - storeOpts = append(storeOpts, WithTransformer(tr.Transformer())) + transformer = tr.Transformer() + storeOpts = append(storeOpts, WithTransformer(transformer)) } initTrace := trace.New("Reflector WatchList", trace.Field{Key: "name", Value: r.name}) @@ -788,7 +790,7 @@ func (r *Reflector) watchList(ctx context.Context) (watch.Interface, error) { // we utilize the temporaryStore to ensure independence from the current store implementation. // as of today, the store is implemented as a queue and will be drained by the higher-level // component as soon as it finishes replacing the content. - checkWatchListDataConsistencyIfRequested(ctx, r.name, resourceVersion, r.listerWatcher.ListWithContext, temporaryStore.List) + checkWatchListDataConsistencyIfRequested(ctx, r.name, resourceVersion, r.listerWatcher.ListWithContext, transformer, temporaryStore.List) if err := r.store.Replace(temporaryStore.List(), resourceVersion); err != nil { return nil, fmt.Errorf("unable to sync watch-list result: %w", err) diff --git a/vendor/k8s.io/client-go/tools/cache/reflector_data_consistency_detector.go b/vendor/k8s.io/client-go/tools/cache/reflector_data_consistency_detector.go index a7e0d9c43..4119c78a6 100644 --- a/vendor/k8s.io/client-go/tools/cache/reflector_data_consistency_detector.go +++ b/vendor/k8s.io/client-go/tools/cache/reflector_data_consistency_detector.go @@ -33,11 +33,11 @@ import ( // // Note that this function will panic when data inconsistency is detected. // This is intentional because we want to catch it in the CI. -func checkWatchListDataConsistencyIfRequested[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn consistencydetector.ListFunc[T], retrieveItemsFn consistencydetector.RetrieveItemsFunc[U]) { +func checkWatchListDataConsistencyIfRequested[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn consistencydetector.ListFunc[T], listItemTransformFunc func(interface{}) (interface{}, error), retrieveItemsFn consistencydetector.RetrieveItemsFunc[U]) { if !consistencydetector.IsDataConsistencyDetectionForWatchListEnabled() { return } // for informers we pass an empty ListOptions because // listFn might be wrapped for filtering during informer construction. - consistencydetector.CheckDataConsistency(ctx, identity, lastSyncedResourceVersion, listFn, metav1.ListOptions{}, retrieveItemsFn) + consistencydetector.CheckDataConsistency(ctx, identity, lastSyncedResourceVersion, listFn, listItemTransformFunc, metav1.ListOptions{}, retrieveItemsFn) } diff --git a/vendor/k8s.io/client-go/tools/cache/shared_informer.go b/vendor/k8s.io/client-go/tools/cache/shared_informer.go index 99e5fcd18..1c12aa2d6 100644 --- a/vendor/k8s.io/client-go/tools/cache/shared_informer.go +++ b/vendor/k8s.io/client-go/tools/cache/shared_informer.go @@ -539,16 +539,7 @@ func (s *sharedIndexInformer) RunWithContext(ctx context.Context) { s.startedLock.Lock() defer s.startedLock.Unlock() - var fifo Queue - if clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.InOrderInformers) { - fifo = NewRealFIFO(MetaNamespaceKeyFunc, s.indexer, s.transform) - } else { - fifo = NewDeltaFIFOWithOptions(DeltaFIFOOptions{ - KnownObjects: s.indexer, - EmitDeltaTypeReplaced: true, - Transformer: s.transform, - }) - } + fifo := newQueueFIFO(s.indexer, s.transform) cfg := &Config{ Queue: fifo, diff --git a/vendor/k8s.io/client-go/tools/cache/the_real_fifo.go b/vendor/k8s.io/client-go/tools/cache/the_real_fifo.go index ef322bea8..b907410dc 100644 --- a/vendor/k8s.io/client-go/tools/cache/the_real_fifo.go +++ b/vendor/k8s.io/client-go/tools/cache/the_real_fifo.go @@ -61,7 +61,8 @@ type RealFIFO struct { } var ( - _ = Queue(&RealFIFO{}) // RealFIFO is a Queue + _ = Queue(&RealFIFO{}) // RealFIFO is a Queue + _ = TransformingStore(&RealFIFO{}) // RealFIFO implements TransformingStore to allow memory optimizations ) // Close the queue. diff --git a/vendor/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go b/vendor/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go index 5d2054155..79a748b74 100644 --- a/vendor/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go +++ b/vendor/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go @@ -77,6 +77,9 @@ func (ll *LeaseLock) Update(ctx context.Context, ler LeaderElectionRecord) error ll.lease.Spec = LeaderElectionRecordToLeaseSpec(&ler) if ll.Labels != nil { + if ll.lease.Labels == nil { + ll.lease.Labels = map[string]string{} + } // Only overwrite the labels that are specifically set for k, v := range ll.Labels { ll.lease.Labels[k] = v diff --git a/vendor/k8s.io/client-go/util/cert/cert.go b/vendor/k8s.io/client-go/util/cert/cert.go index 122046126..48c78b595 100644 --- a/vendor/k8s.io/client-go/util/cert/cert.go +++ b/vendor/k8s.io/client-go/util/cert/cert.go @@ -75,13 +75,15 @@ func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, erro CommonName: cfg.CommonName, Organization: cfg.Organization, }, - DNSNames: []string{cfg.CommonName}, NotBefore: notBefore, NotAfter: now.Add(duration365d * 10).UTC(), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, IsCA: true, } + if len(cfg.CommonName) > 0 { + tmpl.DNSNames = []string{cfg.CommonName} + } certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key) if err != nil { diff --git a/vendor/k8s.io/client-go/util/consistencydetector/data_consistency_detector.go b/vendor/k8s.io/client-go/util/consistencydetector/data_consistency_detector.go index 06f172d82..72c0124a0 100644 --- a/vendor/k8s.io/client-go/util/consistencydetector/data_consistency_detector.go +++ b/vendor/k8s.io/client-go/util/consistencydetector/data_consistency_detector.go @@ -45,16 +45,28 @@ func IsDataConsistencyDetectionForWatchListEnabled() bool { return dataConsistencyDetectionForWatchListEnabled } +// SetDataConsistencyDetectionForWatchListEnabledForTest allows to enable/disable data consistency detection for testing purposes. +// It returns a function that restores the original value. +func SetDataConsistencyDetectionForWatchListEnabledForTest(enabled bool) func() { + original := dataConsistencyDetectionForWatchListEnabled + dataConsistencyDetectionForWatchListEnabled = enabled + return func() { + dataConsistencyDetectionForWatchListEnabled = original + } +} + type RetrieveItemsFunc[U any] func() []U type ListFunc[T runtime.Object] func(ctx context.Context, options metav1.ListOptions) (T, error) +type TransformFunc func(interface{}) (interface{}, error) + // CheckDataConsistency exists solely for testing purposes. // we cannot use checkWatchListDataConsistencyIfRequested because // it is guarded by an environmental variable. // we cannot manipulate the environmental variable because // it will affect other tests in this package. -func CheckDataConsistency[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn ListFunc[T], listOptions metav1.ListOptions, retrieveItemsFn RetrieveItemsFunc[U]) { +func CheckDataConsistency[T runtime.Object, U any](ctx context.Context, identity string, lastSyncedResourceVersion string, listFn ListFunc[T], listItemTransformFunc TransformFunc, listOptions metav1.ListOptions, retrieveItemsFn RetrieveItemsFunc[U]) { if !canFormAdditionalListCall(lastSyncedResourceVersion, listOptions) { klog.V(4).Infof("data consistency check for %s is enabled but the parameters (RV, ListOptions) doesn't allow for creating a valid LIST request. Skipping the data consistency check.", identity) return @@ -84,6 +96,15 @@ func CheckDataConsistency[T runtime.Object, U any](ctx context.Context, identity if err != nil { panic(err) // this should never happen } + if listItemTransformFunc != nil { + for i := range rawListItems { + obj, err := listItemTransformFunc(rawListItems[i]) + if err != nil { + panic(err) + } + rawListItems[i] = obj.(runtime.Object) + } + } listItems := toMetaObjectSliceOrDie(rawListItems) sort.Sort(byUID(listItems)) diff --git a/vendor/modules.txt b/vendor/modules.txt index 3eb1d928a..db6fc8c9f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,6 +7,12 @@ # cel.dev/expr v0.24.0 ## explicit; go 1.22.0 cel.dev/expr +# cyphar.com/go-pathrs v0.2.1 +## explicit; go 1.18 +cyphar.com/go-pathrs +cyphar.com/go-pathrs/internal/fdutils +cyphar.com/go-pathrs/internal/libpathrs +cyphar.com/go-pathrs/procfs # github.com/4meepo/tagalign v1.4.2 ## explicit; go 1.22.0 github.com/4meepo/tagalign @@ -210,9 +216,20 @@ github.com/coreos/go-systemd/v22/journal ## explicit; go 1.21 github.com/curioswitch/go-reassign github.com/curioswitch/go-reassign/internal/analyzer -# github.com/cyphar/filepath-securejoin v0.4.1 +# github.com/cyphar/filepath-securejoin v0.6.0 ## explicit; go 1.18 github.com/cyphar/filepath-securejoin +github.com/cyphar/filepath-securejoin/internal/consts +github.com/cyphar/filepath-securejoin/pathrs-lite +github.com/cyphar/filepath-securejoin/pathrs-lite/internal +github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert +github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd +github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat +github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs +github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion +github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux +github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs +github.com/cyphar/filepath-securejoin/pathrs-lite/procfs # github.com/daixiang0/gci v0.13.5 ## explicit; go 1.21 github.com/daixiang0/gci/pkg/config @@ -955,7 +972,7 @@ github.com/opencontainers/runc/libcontainer/utils # github.com/opencontainers/runtime-spec v1.2.0 ## explicit github.com/opencontainers/runtime-spec/specs-go -# github.com/opencontainers/selinux v1.11.1 +# github.com/opencontainers/selinux v1.13.0 ## explicit; go 1.19 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label @@ -1110,7 +1127,7 @@ github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/machine/ github.com/openshift/cluster-control-plane-machine-set-operator/pkg/machineproviders/providers/openshift/machine/v1beta1/failuredomain github.com/openshift/cluster-control-plane-machine-set-operator/pkg/machineproviders/providers/openshift/machine/v1beta1/providerconfig github.com/openshift/cluster-control-plane-machine-set-operator/test/e2e/framework -# github.com/openshift/library-go v0.0.0-20251107090138-0de9712313a5 +# github.com/openshift/library-go v0.0.0-20251107090138-0de9712313a5 => github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38 ## explicit; go 1.24.0 github.com/openshift/library-go/pkg/apiserver/jsonpatch github.com/openshift/library-go/pkg/authorization/hardcodedauthorizer @@ -1120,6 +1137,7 @@ github.com/openshift/library-go/pkg/config/clusteroperator/v1helpers github.com/openshift/library-go/pkg/config/clusterstatus github.com/openshift/library-go/pkg/config/leaderelection github.com/openshift/library-go/pkg/controller/factory +github.com/openshift/library-go/pkg/controllerruntime/tls github.com/openshift/library-go/pkg/crypto github.com/openshift/library-go/pkg/features github.com/openshift/library-go/pkg/operator/certrotation @@ -1563,7 +1581,7 @@ go.yaml.in/yaml/v2 # go.yaml.in/yaml/v3 v3.0.4 ## explicit; go 1.16 go.yaml.in/yaml/v3 -# golang.org/x/crypto v0.43.0 +# golang.org/x/crypto v0.45.0 ## explicit; go 1.24.0 golang.org/x/crypto/blowfish golang.org/x/crypto/chacha20 @@ -1586,13 +1604,13 @@ golang.org/x/exp/slices # golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac ## explicit; go 1.18 golang.org/x/exp/typeparams -# golang.org/x/mod v0.28.0 +# golang.org/x/mod v0.29.0 ## explicit; go 1.24.0 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.46.0 +# golang.org/x/net v0.47.0 ## explicit; go 1.24.0 golang.org/x/net/context golang.org/x/net/html @@ -1612,12 +1630,12 @@ golang.org/x/net/websocket ## explicit; go 1.23.0 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sync v0.17.0 +# golang.org/x/sync v0.18.0 ## explicit; go 1.24.0 golang.org/x/sync/errgroup golang.org/x/sync/semaphore golang.org/x/sync/singleflight -# golang.org/x/sys v0.37.0 +# golang.org/x/sys v0.38.0 ## explicit; go 1.24.0 golang.org/x/sys/cpu golang.org/x/sys/plan9 @@ -1626,10 +1644,10 @@ golang.org/x/sys/windows golang.org/x/sys/windows/registry golang.org/x/sys/windows/svc golang.org/x/sys/windows/svc/mgr -# golang.org/x/term v0.36.0 +# golang.org/x/term v0.37.0 ## explicit; go 1.24.0 golang.org/x/term -# golang.org/x/text v0.30.0 +# golang.org/x/text v0.31.0 ## explicit; go 1.24.0 golang.org/x/text/encoding golang.org/x/text/encoding/charmap @@ -1663,7 +1681,7 @@ golang.org/x/text/width # golang.org/x/time v0.14.0 ## explicit; go 1.24.0 golang.org/x/time/rate -# golang.org/x/tools v0.37.0 +# golang.org/x/tools v0.38.0 ## explicit; go 1.24.0 golang.org/x/tools/cover golang.org/x/tools/go/analysis @@ -1731,6 +1749,7 @@ golang.org/x/tools/go/types/typeutil golang.org/x/tools/imports golang.org/x/tools/internal/aliases golang.org/x/tools/internal/analysisinternal +golang.org/x/tools/internal/analysisinternal/typeindex golang.org/x/tools/internal/astutil golang.org/x/tools/internal/event golang.org/x/tools/internal/event/core @@ -1745,9 +1764,11 @@ golang.org/x/tools/internal/modindex golang.org/x/tools/internal/moreiters golang.org/x/tools/internal/packagesinternal golang.org/x/tools/internal/pkgbits +golang.org/x/tools/internal/refactor golang.org/x/tools/internal/stdlib golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal +golang.org/x/tools/internal/typesinternal/typeindex golang.org/x/tools/internal/versions # golang.org/x/tools/go/expect v0.1.1-deprecated ## explicit; go 1.23.0 @@ -2085,7 +2106,7 @@ honnef.co/go/tools/stylecheck/st1021 honnef.co/go/tools/stylecheck/st1022 honnef.co/go/tools/stylecheck/st1023 honnef.co/go/tools/unused -# k8s.io/api v0.34.1 +# k8s.io/api v0.34.3 ## explicit; go 1.24.0 k8s.io/api/admission/v1 k8s.io/api/admission/v1beta1 @@ -2147,7 +2168,7 @@ k8s.io/api/storage/v1 k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 k8s.io/api/storagemigration/v1alpha1 -# k8s.io/apiextensions-apiserver v0.34.1 +# k8s.io/apiextensions-apiserver v0.34.3 ## explicit; go 1.24.0 k8s.io/apiextensions-apiserver/pkg/apihelpers k8s.io/apiextensions-apiserver/pkg/apis/apiextensions @@ -2197,7 +2218,7 @@ k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition k8s.io/apiextensions-apiserver/test/integration k8s.io/apiextensions-apiserver/test/integration/fixtures -# k8s.io/apimachinery v0.34.1 +# k8s.io/apimachinery v0.34.3 ## explicit; go 1.24.0 k8s.io/apimachinery/pkg/api/apitesting k8s.io/apimachinery/pkg/api/apitesting/fuzzer @@ -2278,7 +2299,7 @@ k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect -# k8s.io/apiserver v0.34.1 => github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292 +# k8s.io/apiserver v0.34.3 => github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292 ## explicit; go 1.24.0 k8s.io/apiserver/pkg/admission k8s.io/apiserver/pkg/admission/configuration @@ -2452,7 +2473,7 @@ k8s.io/cli-runtime/pkg/genericclioptions k8s.io/cli-runtime/pkg/genericiooptions k8s.io/cli-runtime/pkg/printers k8s.io/cli-runtime/pkg/resource -# k8s.io/client-go v0.34.1 +# k8s.io/client-go v0.34.3 ## explicit; go 1.24.0 k8s.io/client-go/applyconfigurations k8s.io/client-go/applyconfigurations/admissionregistration/v1 @@ -2846,7 +2867,7 @@ k8s.io/cloud-provider-vsphere/pkg/common/config # k8s.io/cluster-bootstrap v0.33.3 ## explicit; go 1.24.0 k8s.io/cluster-bootstrap/token/api -# k8s.io/component-base v0.34.1 +# k8s.io/component-base v0.34.3 ## explicit; go 1.24.0 k8s.io/component-base/cli/flag k8s.io/component-base/codec @@ -2947,7 +2968,7 @@ k8s.io/klog/v2/internal/severity k8s.io/klog/v2/internal/sloghandler k8s.io/klog/v2/internal/verbosity k8s.io/klog/v2/textlogger -# k8s.io/kms v0.34.1 +# k8s.io/kms v0.34.3 ## explicit; go 1.24.0 k8s.io/kms/apis/v1beta1 k8s.io/kms/apis/v2 @@ -3497,7 +3518,7 @@ sigs.k8s.io/cluster-api/api/ipam/v1beta1 sigs.k8s.io/cluster-api/api/ipam/v1beta2 sigs.k8s.io/cluster-api/errors sigs.k8s.io/cluster-api/util/conversion -# sigs.k8s.io/controller-runtime v0.22.3 +# sigs.k8s.io/controller-runtime v0.22.5 ## explicit; go 1.24.0 sigs.k8s.io/controller-runtime sigs.k8s.io/controller-runtime/pkg/builder @@ -3668,3 +3689,4 @@ sigs.k8s.io/yaml/kyaml # k8s.io/apiserver => github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292 # k8s.io/kubelet => github.com/openshift/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20251015171918-61114aa5a292 # k8s.io/kubernetes => github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd +# github.com/openshift/library-go => github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38 diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go index a7e491855..a94ec6cc3 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go @@ -308,6 +308,42 @@ type ByObject struct { // // Defaults to true. EnableWatchBookmarks *bool + + // SyncPeriod determines the minimum frequency at which watched resources are + // reconciled. A lower period will correct entropy more quickly, but reduce + // responsiveness to change if there are many watched resources. Change this + // value only if you know what you are doing. Defaults to 10 hours if unset. + // there will a 10 percent jitter between the SyncPeriod of all controllers + // so that all controllers will not send list requests simultaneously. + // + // This applies to all controllers. + // + // A period sync happens for two reasons: + // 1. To insure against a bug in the controller that causes an object to not + // be requeued, when it otherwise should be requeued. + // 2. To insure against an unknown bug in controller-runtime, or its dependencies, + // that causes an object to not be requeued, when it otherwise should be + // requeued, or to be removed from the queue, when it otherwise should not + // be removed. + // + // If you want + // 1. to insure against missed watch events, or + // 2. to poll services that cannot be watched, + // then we recommend that, instead of changing the default period, the + // controller requeue, with a constant duration `t`, whenever the controller + // is "done" with an object, and would otherwise not requeue it, i.e., we + // recommend the `Reconcile` function return `reconcile.Result{RequeueAfter: t}`, + // instead of `reconcile.Result{}`. + // + // SyncPeriod will locally trigger an artificial Update event with the same + // object in both ObjectOld and ObjectNew for everything that is in the + // cache. + // + // Predicates or Handlers that expect ObjectOld and ObjectNew to be different + // (such as GenerationChangedPredicate) will filter out this event, preventing + // it from triggering a reconciliation. + // SyncPeriod does not sync between the local cache and the server. + SyncPeriod *time.Duration } // Config describes all potential options for a given watch. @@ -343,6 +379,42 @@ type Config struct { // // Defaults to true. EnableWatchBookmarks *bool + + // SyncPeriod determines the minimum frequency at which watched resources are + // reconciled. A lower period will correct entropy more quickly, but reduce + // responsiveness to change if there are many watched resources. Change this + // value only if you know what you are doing. Defaults to 10 hours if unset. + // there will a 10 percent jitter between the SyncPeriod of all controllers + // so that all controllers will not send list requests simultaneously. + // + // This applies to all controllers. + // + // A period sync happens for two reasons: + // 1. To insure against a bug in the controller that causes an object to not + // be requeued, when it otherwise should be requeued. + // 2. To insure against an unknown bug in controller-runtime, or its dependencies, + // that causes an object to not be requeued, when it otherwise should be + // requeued, or to be removed from the queue, when it otherwise should not + // be removed. + // + // If you want + // 1. to insure against missed watch events, or + // 2. to poll services that cannot be watched, + // then we recommend that, instead of changing the default period, the + // controller requeue, with a constant duration `t`, whenever the controller + // is "done" with an object, and would otherwise not requeue it, i.e., we + // recommend the `Reconcile` function return `reconcile.Result{RequeueAfter: t}`, + // instead of `reconcile.Result{}`. + // + // SyncPeriod will locally trigger an artificial Update event with the same + // object in both ObjectOld and ObjectNew for everything that is in the + // cache. + // + // Predicates or Handlers that expect ObjectOld and ObjectNew to be different + // (such as GenerationChangedPredicate) will filter out this event, preventing + // it from triggering a reconciliation. + // SyncPeriod does not sync between the local cache and the server. + SyncPeriod *time.Duration } // NewCacheFunc - Function for creating a new cache from the options and a rest config. @@ -413,6 +485,7 @@ func optionDefaultsToConfig(opts *Options) Config { Transform: opts.DefaultTransform, UnsafeDisableDeepCopy: opts.DefaultUnsafeDisableDeepCopy, EnableWatchBookmarks: opts.DefaultEnableWatchBookmarks, + SyncPeriod: opts.SyncPeriod, } } @@ -423,6 +496,7 @@ func byObjectToConfig(byObject ByObject) Config { Transform: byObject.Transform, UnsafeDisableDeepCopy: byObject.UnsafeDisableDeepCopy, EnableWatchBookmarks: byObject.EnableWatchBookmarks, + SyncPeriod: byObject.SyncPeriod, } } @@ -436,7 +510,7 @@ func newCache(restConfig *rest.Config, opts Options) newCacheFunc { HTTPClient: opts.HTTPClient, Scheme: opts.Scheme, Mapper: opts.Mapper, - ResyncPeriod: *opts.SyncPeriod, + ResyncPeriod: ptr.Deref(config.SyncPeriod, defaultSyncPeriod), Namespace: namespace, Selector: internal.Selector{ Label: config.LabelSelector, @@ -534,6 +608,7 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) { byObject.Transform = defaultedConfig.Transform byObject.UnsafeDisableDeepCopy = defaultedConfig.UnsafeDisableDeepCopy byObject.EnableWatchBookmarks = defaultedConfig.EnableWatchBookmarks + byObject.SyncPeriod = defaultedConfig.SyncPeriod } opts.ByObject[obj] = byObject @@ -555,10 +630,6 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) { opts.DefaultNamespaces[namespace] = cfg } - // Default the resync period to 10 hours if unset - if opts.SyncPeriod == nil { - opts.SyncPeriod = &defaultSyncPeriod - } return opts, nil } @@ -578,6 +649,9 @@ func defaultConfig(toDefault, defaultFrom Config) Config { if toDefault.EnableWatchBookmarks == nil { toDefault.EnableWatchBookmarks = defaultFrom.EnableWatchBookmarks } + if toDefault.SyncPeriod == nil { + toDefault.SyncPeriod = defaultFrom.SyncPeriod + } return toDefault } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go index cacba4a9c..d4223eda2 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/namespaced_client.go @@ -213,7 +213,12 @@ func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object, o // List implements client.Client. func (n *namespacedClient) List(ctx context.Context, obj ObjectList, opts ...ListOption) error { - if n.namespace != "" { + isNamespaceScoped, err := n.IsObjectNamespaced(obj) + if err != nil { + return fmt.Errorf("error finding the scope of the object: %w", err) + } + + if isNamespaceScoped && n.namespace != "" { opts = append(opts, InNamespace(n.namespace)) } return n.client.List(ctx, obj, opts...) diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue/priorityqueue.go b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue/priorityqueue.go index 98df84c56..71363f0d1 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue/priorityqueue.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue/priorityqueue.go @@ -124,8 +124,8 @@ type priorityqueue[T comparable] struct { get chan item[T] // waiters is the number of routines blocked in Get, we use it to determine - // if we can push items. - waiters atomic.Int64 + // if we can push items. Every manipulation has to be protected with the lock. + waiters int64 // Configurable for testing now func() time.Time @@ -269,7 +269,7 @@ func (w *priorityqueue[T]) spin() { } } - if w.waiters.Load() == 0 { + if w.waiters == 0 { // Have to keep iterating here to ensure we update metrics // for further items that became ready and set nextReady. return true @@ -277,7 +277,7 @@ func (w *priorityqueue[T]) spin() { w.metrics.get(item.Key, item.Priority) w.locked.Insert(item.Key) - w.waiters.Add(-1) + w.waiters-- delete(w.items, item.Key) toDelete = append(toDelete, item) w.becameReady.Delete(item.Key) @@ -316,7 +316,9 @@ func (w *priorityqueue[T]) GetWithPriority() (_ T, priority int, shutdown bool) return zero, 0, true } - w.waiters.Add(1) + w.lock.Lock() + w.waiters++ + w.lock.Unlock() w.notifyItemOrWaiterAdded() diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/envtest/server.go b/vendor/sigs.k8s.io/controller-runtime/pkg/envtest/server.go index 9bb81ed2a..c9f19da97 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/envtest/server.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/envtest/server.go @@ -109,7 +109,11 @@ var ( // Environment creates a Kubernetes test environment that will start / stop the Kubernetes control plane and // install extension APIs. type Environment struct { - // ControlPlane is the ControlPlane including the apiserver and etcd + // ControlPlane is the ControlPlane including the apiserver and etcd. + // Binary paths (APIServer.Path, Etcd.Path, KubectlPath) can be pre-configured in ControlPlane. + // If DownloadBinaryAssets is true, the downloaded paths will always be used. + // If DownloadBinaryAssets is false and paths are not pre-configured (default is empty), they will be + // automatically resolved using BinaryAssetsDirectory. ControlPlane controlplane.ControlPlane // Scheme is used to determine if conversion webhooks should be enabled @@ -211,6 +215,40 @@ func (te *Environment) Stop() error { return te.ControlPlane.Stop() } +// configureBinaryPaths configures the binary paths for the API server, etcd, and kubectl. +// If DownloadBinaryAssets is true, it downloads and uses those paths. +// If DownloadBinaryAssets is false, it only sets paths that are not already configured (empty). +func (te *Environment) configureBinaryPaths() error { + apiServer := te.ControlPlane.GetAPIServer() + + if te.ControlPlane.Etcd == nil { + te.ControlPlane.Etcd = &controlplane.Etcd{} + } + + if te.DownloadBinaryAssets { + apiServerPath, etcdPath, kubectlPath, err := downloadBinaryAssets(context.TODO(), + te.BinaryAssetsDirectory, te.DownloadBinaryAssetsVersion, te.DownloadBinaryAssetsIndexURL) + if err != nil { + return err + } + + apiServer.Path = apiServerPath + te.ControlPlane.Etcd.Path = etcdPath + te.ControlPlane.KubectlPath = kubectlPath + } else { + if apiServer.Path == "" { + apiServer.Path = process.BinPathFinder("kube-apiserver", te.BinaryAssetsDirectory) + } + if te.ControlPlane.Etcd.Path == "" { + te.ControlPlane.Etcd.Path = process.BinPathFinder("etcd", te.BinaryAssetsDirectory) + } + if te.ControlPlane.KubectlPath == "" { + te.ControlPlane.KubectlPath = process.BinPathFinder("kubectl", te.BinaryAssetsDirectory) + } + } + return nil +} + // Start starts a local Kubernetes server and updates te.ApiserverPort with the port it is listening on. func (te *Environment) Start() (*rest.Config, error) { if te.useExistingCluster() { @@ -229,10 +267,6 @@ func (te *Environment) Start() (*rest.Config, error) { } else { apiServer := te.ControlPlane.GetAPIServer() - if te.ControlPlane.Etcd == nil { - te.ControlPlane.Etcd = &controlplane.Etcd{} - } - if os.Getenv(envAttachOutput) == "true" { te.AttachControlPlaneOutput = true } @@ -243,6 +277,9 @@ func (te *Environment) Start() (*rest.Config, error) { if apiServer.Err == nil { apiServer.Err = os.Stderr } + if te.ControlPlane.Etcd == nil { + te.ControlPlane.Etcd = &controlplane.Etcd{} + } if te.ControlPlane.Etcd.Out == nil { te.ControlPlane.Etcd.Out = os.Stdout } @@ -251,20 +288,8 @@ func (te *Environment) Start() (*rest.Config, error) { } } - if te.DownloadBinaryAssets { - apiServerPath, etcdPath, kubectlPath, err := downloadBinaryAssets(context.TODO(), - te.BinaryAssetsDirectory, te.DownloadBinaryAssetsVersion, te.DownloadBinaryAssetsIndexURL) - if err != nil { - return nil, err - } - - apiServer.Path = apiServerPath - te.ControlPlane.Etcd.Path = etcdPath - te.ControlPlane.KubectlPath = kubectlPath - } else { - apiServer.Path = process.BinPathFinder("kube-apiserver", te.BinaryAssetsDirectory) - te.ControlPlane.Etcd.Path = process.BinPathFinder("etcd", te.BinaryAssetsDirectory) - te.ControlPlane.KubectlPath = process.BinPathFinder("kubectl", te.BinaryAssetsDirectory) + if err := te.configureBinaryPaths(); err != nil { + return nil, fmt.Errorf("failed to configure binary paths: %w", err) } if err := te.defaultTimeouts(); err != nil { From b6a60bec73613c780d7e3356d03d6c1a1604341f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Mon, 26 Jan 2026 14:09:22 +0100 Subject: [PATCH 2/9] Serve MAO metrics with direct TLS and profile reload. Remove the kube-rbac-proxy sidecar, mount the serving cert, and restart the operator on APIServer TLS profile changes. --- cmd/machine-api-operator/start.go | 140 ++++++++++++++++-- .../0000_30_machine-api-operator_09_rbac.yaml | 2 + ...30_machine-api-operator_11_deployment.yaml | 37 +---- 3 files changed, 140 insertions(+), 39 deletions(-) diff --git a/cmd/machine-api-operator/start.go b/cmd/machine-api-operator/start.go index dce09c53b..0f2fe1c65 100644 --- a/cmd/machine-api-operator/start.go +++ b/cmd/machine-api-operator/start.go @@ -2,18 +2,23 @@ package main import ( "context" + "crypto/tls" "errors" "flag" "fmt" "net/http" "os" + "reflect" "strconv" + "sync" + "sync/atomic" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" "github.com/spf13/pflag" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" coreclientsetv1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -24,16 +29,21 @@ import ( "k8s.io/utils/clock" osconfigv1 "github.com/openshift/api/config/v1" + osclientset "github.com/openshift/client-go/config/clientset/versioned" + utiltls "github.com/openshift/library-go/pkg/controllerruntime/tls" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/machine-api-operator/pkg/metrics" "github.com/openshift/machine-api-operator/pkg/operator" "github.com/openshift/machine-api-operator/pkg/util" "github.com/openshift/machine-api-operator/pkg/version" + "sigs.k8s.io/controller-runtime/pkg/client" ) const ( // defaultMetricsPort is the default port to expose metrics. - defaultMetricsPort = 8080 + defaultMetricsPort = 8443 + metricsCertFile = "/etc/tls/private/tls.crt" + metricsKeyFile = "/etc/tls/private/tls.key" ) var ( @@ -82,10 +92,20 @@ func runStartCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("error creating clients: %v", err) } stopCh := make(chan struct{}) + leaderElectionCtx, leaderElectionCancel := context.WithCancel(context.Background()) + var shutdownOnce sync.Once + var shuttingDown atomic.Bool + shutdown := func() { + shutdownOnce.Do(func() { + shuttingDown.Store(true) + close(stopCh) + leaderElectionCancel() + }) + } le := util.GetLeaderElectionConfig(cb.config, osconfigv1.LeaderElection{}) - leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{ + leaderelection.RunOrDie(leaderElectionCtx, leaderelection.LeaderElectionConfig{ Lock: CreateResourceLock(cb, componentNamespace, componentName), RenewDeadline: le.RenewDeadline.Duration, RetryPeriod: le.RetryPeriod.Duration, @@ -93,6 +113,9 @@ func runStartCmd(cmd *cobra.Command, args []string) error { Callbacks: leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { ctrlCtx := CreateControllerContext(cb, stopCh, componentNamespace) + if err := setupTLSProfileWatcher(ctrlCtx, shutdown); err != nil { + klog.Fatalf("Unable to set up TLS profile watcher: %v", err) + } startControllersOrDie(ctrlCtx) ctrlCtx.KubeNamespacedInformerFactory.Start(ctrlCtx.Stop) ctrlCtx.ConfigInformerFactory.Start(ctrlCtx.Stop) @@ -100,15 +123,19 @@ func runStartCmd(cmd *cobra.Command, args []string) error { startMetricsCollectionAndServer(ctrlCtx) close(ctrlCtx.InformersStarted) - select {} + <-stopCh }, OnStoppedLeading: func() { + if shuttingDown.Load() { + klog.Info("Leader election stopped due to shutdown") + return + } klog.Fatalf("Leader election lost") }, }, ReleaseOnCancel: true, }) - panic("unreachable") + return nil } func initMachineAPIInformers(ctx *ControllerContext) { @@ -196,16 +223,111 @@ func startMetricsCollectionAndServer(ctx *ControllerContext) { metricsPort = v } klog.V(4).Info("Starting server to serve prometheus metrics") - go startHTTPMetricServer(fmt.Sprintf("localhost:%d", metricsPort)) + tlsConfig, err := metricsTLSConfig(ctx) + if err != nil { + klog.Fatalf("Unable to configure metrics TLS: %v", err) + } + go startHTTPSMetricServer(fmt.Sprintf(":%d", metricsPort), tlsConfig) +} + +func metricsTLSConfig(ctx *ControllerContext) (*tls.Config, error) { + scheme := runtime.NewScheme() + if err := osconfigv1.Install(scheme); err != nil { + return nil, fmt.Errorf("unable to add config.openshift.io scheme: %w", err) + } + + k8sClient, err := client.New(ctx.ClientBuilder.config, client.Options{Scheme: scheme}) + if err != nil { + return nil, fmt.Errorf("unable to create Kubernetes client: %w", err) + } + + tlsSecurityProfileSpec, err := utiltls.FetchAPIServerTLSProfile(context.Background(), k8sClient) + if err != nil { + return nil, fmt.Errorf("unable to get TLS profile from API server: %w", err) + } + + tlsConfigFn, unsupportedCiphers := utiltls.NewTLSConfigFromProfile(tlsSecurityProfileSpec) + if len(unsupportedCiphers) > 0 { + klog.Infof("TLS configuration contains unsupported ciphers that will be ignored: %v", unsupportedCiphers) + } + + tlsConfig := &tls.Config{} + tlsConfigFn(tlsConfig) + + return tlsConfig, nil +} + +func setupTLSProfileWatcher(ctx *ControllerContext, shutdown func()) error { + configClient := ctx.ClientBuilder.OpenshiftClientOrDie("tls-profile-watcher") + initialProfile, err := fetchAPIServerTLSProfileSpec(context.Background(), configClient) + if err != nil { + return err + } + + apiServerInformer := ctx.ConfigInformerFactory.Config().V1().APIServers().Informer() + apiServerInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + handleTLSProfileEvent(obj, initialProfile, shutdown) + }, + UpdateFunc: func(_, newObj interface{}) { + handleTLSProfileEvent(newObj, initialProfile, shutdown) + }, + }) + + return nil +} + +func fetchAPIServerTLSProfileSpec(ctx context.Context, configClient osclientset.Interface) (osconfigv1.TLSProfileSpec, error) { + apiServer, err := configClient.ConfigV1().APIServers().Get(ctx, utiltls.APIServerName, metav1.GetOptions{}) + if err != nil { + return osconfigv1.TLSProfileSpec{}, fmt.Errorf("failed to get APIServer %q: %w", utiltls.APIServerName, err) + } + + profile, err := utiltls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile) + if err != nil { + return osconfigv1.TLSProfileSpec{}, fmt.Errorf("failed to get TLS profile from APIServer %q: %w", utiltls.APIServerName, err) + } + + return profile, nil +} + +func handleTLSProfileEvent(obj interface{}, initialProfile osconfigv1.TLSProfileSpec, shutdown func()) { + apiServer, ok := obj.(*osconfigv1.APIServer) + if !ok { + return + } + if apiServer.Name != utiltls.APIServerName { + return + } + + currentProfile, err := utiltls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile) + if err != nil { + klog.Errorf("Failed to get TLS profile from APIServer %q: %v", apiServer.Name, err) + return + } + + if reflect.DeepEqual(initialProfile, currentProfile) { + klog.V(2).Info("TLS security profile unchanged") + return + } + + klog.Infof("TLS security profile has changed, initiating a shutdown to pick up the new configuration: initialMinTLSVersion=%s currentMinTLSVersion=%s initialCiphers=%v currentCiphers=%v", + initialProfile.MinTLSVersion, + currentProfile.MinTLSVersion, + initialProfile.Ciphers, + currentProfile.Ciphers, + ) + shutdown() } -func startHTTPMetricServer(metricsPort string) { +func startHTTPSMetricServer(metricsAddr string, tlsConfig *tls.Config) { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) server := &http.Server{ - Addr: metricsPort, - Handler: mux, + Addr: metricsAddr, + Handler: mux, + TLSConfig: tlsConfig, } - klog.Fatal(server.ListenAndServe()) + klog.Fatal(server.ListenAndServeTLS(metricsCertFile, metricsKeyFile)) } diff --git a/install/0000_30_machine-api-operator_09_rbac.yaml b/install/0000_30_machine-api-operator_09_rbac.yaml index ae24aa41f..ee64f254e 100644 --- a/install/0000_30_machine-api-operator_09_rbac.yaml +++ b/install/0000_30_machine-api-operator_09_rbac.yaml @@ -257,6 +257,7 @@ rules: - apiGroups: - config.openshift.io resources: + - apiservers - infrastructures - dnses - clusterversions @@ -426,6 +427,7 @@ rules: - apiGroups: - config.openshift.io resources: + - apiservers - featuregates - featuregates/status - proxies diff --git a/install/0000_30_machine-api-operator_11_deployment.yaml b/install/0000_30_machine-api-operator_11_deployment.yaml index 893b17f89..add4645cd 100644 --- a/install/0000_30_machine-api-operator_11_deployment.yaml +++ b/install/0000_30_machine-api-operator_11_deployment.yaml @@ -28,31 +28,6 @@ spec: priorityClassName: system-node-critical serviceAccountName: machine-api-operator containers: - - name: kube-rbac-proxy - image: quay.io/openshift/origin-kube-rbac-proxy - args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://localhost:8080/" - - "--tls-cert-file=/etc/tls/private/tls.crt" - - "--tls-private-key-file=/etc/tls/private/tls.key" - - "--config-file=/etc/kube-rbac-proxy/config-file.yaml" - - "--tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305" - - "--logtostderr=true" - - "--v=3" - ports: - - containerPort: 8443 - name: https - protocol: TCP - resources: - requests: - memory: 20Mi - cpu: 10m - terminationMessagePolicy: FallbackToLogsOnError - volumeMounts: - - name: config - mountPath: /etc/kube-rbac-proxy - - mountPath: /etc/tls/private - name: machine-api-operator-tls - name: machine-api-operator image: quay.io/openshift/origin-machine-api-operator command: @@ -75,7 +50,11 @@ spec: fieldRef: fieldPath: metadata.name - name: METRICS_PORT - value: "8080" + value: "8443" + ports: + - containerPort: 8443 + name: https + protocol: TCP resources: requests: cpu: 10m @@ -84,6 +63,8 @@ spec: volumeMounts: - name: images mountPath: /etc/machine-api-operator-config/images + - mountPath: /etc/tls/private + name: machine-api-operator-tls nodeSelector: node-role.kubernetes.io/master: "" restartPolicy: Always @@ -100,10 +81,6 @@ spec: effect: "NoExecute" tolerationSeconds: 120 volumes: - - name: config - configMap: - name: kube-rbac-proxy - defaultMode: 420 - name: images configMap: defaultMode: 420 From 4dfdda3cc5093e59949ea20682698fb776e036b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Mon, 26 Jan 2026 14:09:27 +0100 Subject: [PATCH 3/9] Propagate TLS profile to controller proxies. Capture the APIServer TLS profile in operator config and use it to configure kube-rbac-proxy TLS args, with unit coverage. --- pkg/operator/config.go | 1 + pkg/operator/operator.go | 16 ++++++++++ pkg/operator/operator_test.go | 60 ++++++++++++++++++++++++++++------- pkg/operator/sync.go | 43 +++++++++++++++++++------ pkg/operator/sync_test.go | 51 ++++++++++++++++++++++++++--- 5 files changed, 145 insertions(+), 26 deletions(-) diff --git a/pkg/operator/config.go b/pkg/operator/config.go index 953eccd33..451c6518d 100644 --- a/pkg/operator/config.go +++ b/pkg/operator/config.go @@ -25,6 +25,7 @@ type OperatorConfig struct { Proxy *configv1.Proxy PlatformType configv1.PlatformType Features map[string]bool + TLSProfile *configv1.TLSProfileSpec } type Controllers struct { diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 8672165d9..8979d80e3 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -14,6 +14,7 @@ import ( configinformersv1 "github.com/openshift/client-go/config/informers/externalversions/config/v1" configlistersv1 "github.com/openshift/client-go/config/listers/config/v1" machineclientset "github.com/openshift/client-go/machine/clientset/versioned" + utiltls "github.com/openshift/library-go/pkg/controllerruntime/tls" "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/library-go/pkg/operator/resource/resourceapply" @@ -482,6 +483,20 @@ func (optr *Operator) maoConfigFromInfrastructure() (*OperatorConfig, error) { klog.V(2).Info("Enabling MachineAPIMigration for provider controller and machinesets") } + // Fetch TLS security profile from APIServer + var tlsProfile *osconfigv1.TLSProfileSpec + apiServer, err := optr.osClient.ConfigV1().APIServers().Get(context.Background(), "cluster", metav1.GetOptions{}) + if err != nil { + klog.Warningf("Failed to fetch APIServer, using default TLS profile: %v", err) + } else { + profile, err := utiltls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile) + if err != nil { + klog.Warningf("Failed to get TLS profile spec, using defaults: %v", err) + } else { + tlsProfile = &profile + } + } + return &OperatorConfig{ TargetNamespace: optr.namespace, Proxy: clusterWideProxy, @@ -495,5 +510,6 @@ func (optr *Operator) maoConfigFromInfrastructure() (*OperatorConfig, error) { }, PlatformType: provider, Features: features, + TLSProfile: tlsProfile, }, nil } diff --git a/pkg/operator/operator_test.go b/pkg/operator/operator_test.go index 8188a8999..ff4c95da7 100644 --- a/pkg/operator/operator_test.go +++ b/pkg/operator/operator_test.go @@ -354,12 +354,20 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, } + // Default APIServer with Intermediate TLS profile + defaultAPIServer := &openshiftv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster", + }, + } + testCases := []struct { name string platform openshiftv1.PlatformType infra *openshiftv1.Infrastructure featureGate *openshiftv1.FeatureGate proxy *openshiftv1.Proxy + apiServer *openshiftv1.APIServer imagesFile string expectedConfig *OperatorConfig expectedError error @@ -382,7 +390,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -396,6 +405,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.AWSPlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -416,7 +426,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -430,6 +441,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.LibvirtPlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -450,7 +462,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -464,6 +477,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.OpenStackPlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -484,7 +498,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -498,6 +513,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.AzurePlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -518,7 +534,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -532,6 +549,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.BareMetalPlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -552,7 +570,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -566,6 +585,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.GCPPlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -586,7 +606,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -600,6 +621,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: kubemarkPlatform, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -620,7 +642,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -634,6 +657,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.VSpherePlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -654,7 +678,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -668,6 +693,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.NonePlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -690,7 +716,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -704,6 +731,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.BareMetalPlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -724,7 +752,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -738,6 +767,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.BareMetalPlatformType, Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -758,7 +788,8 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, }, }, - proxy: proxy, + proxy: proxy, + apiServer: defaultAPIServer, expectedConfig: &OperatorConfig{ TargetNamespace: targetNamespace, Proxy: proxy, @@ -772,6 +803,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: "bad-platform", Features: enabledFeatureMap, + TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -832,6 +864,10 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { proxy := tc.proxy.DeepCopy() objects = append(objects, proxy) } + if tc.apiServer != nil { + apiServer := tc.apiServer.DeepCopy() + objects = append(objects, apiServer) + } stopCh := make(chan struct{}) defer close(stopCh) diff --git a/pkg/operator/sync.go b/pkg/operator/sync.go index 4fbf9ac42..2be84294e 100644 --- a/pkg/operator/sync.go +++ b/pkg/operator/sync.go @@ -20,8 +20,9 @@ import ( "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" - v1 "github.com/openshift/api/config/v1" + configv1 "github.com/openshift/api/config/v1" machinev1beta1 "github.com/openshift/api/machine/v1beta1" + utiltls "github.com/openshift/library-go/pkg/controllerruntime/tls" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/library-go/pkg/operator/resource/resourceapply" "github.com/openshift/library-go/pkg/operator/resource/resourcehash" @@ -237,7 +238,7 @@ func (optr *Operator) syncWebhookConfiguration(config *OperatorConfig) error { if err := optr.syncMachineMutatingWebhook(); err != nil { return err } - if config.PlatformType == v1.BareMetalPlatformType { + if config.PlatformType == configv1.BareMetalPlatformType { if err := optr.syncMetal3RemediationValidatingWebhook(); err != nil { return err } @@ -510,7 +511,7 @@ func newRBACConfigVolumes() []corev1.Volume { func newPodTemplateSpec(config *OperatorConfig, features map[string]bool) *corev1.PodTemplateSpec { containers := newContainers(config, features) withMHCProxy := config.Controllers.MachineHealthCheck != "" - proxyContainers := newKubeProxyContainers(config.Controllers.KubeRBACProxy, withMHCProxy) + proxyContainers := newKubeProxyContainers(config.Controllers.KubeRBACProxy, withMHCProxy, config.TLSProfile) tolerations := []corev1.Toleration{ { Key: "node-role.kubernetes.io/master", @@ -673,7 +674,7 @@ func newContainers(config *OperatorConfig, features map[string]bool) []corev1.Co machineControllerArgs := append([]string{}, featureGateArgs...) switch config.PlatformType { - case v1.AzurePlatformType, v1.GCPPlatformType: + case configv1.AzurePlatformType, configv1.GCPPlatformType: machineControllerArgs = append(machineControllerArgs, "--max-concurrent-reconciles=10") } @@ -853,20 +854,42 @@ func newContainers(config *OperatorConfig, features map[string]bool) []corev1.Co return containers } -func newKubeProxyContainers(image string, withMHCProxy bool) []corev1.Container { +func newKubeProxyContainers(image string, withMHCProxy bool, tlsProfile *configv1.TLSProfileSpec) []corev1.Container { proxyContainers := []corev1.Container{ - newKubeProxyContainer(image, "machineset-mtrc", metrics.DefaultMachineSetMetricsAddress, machineSetExposeMetricsPort), - newKubeProxyContainer(image, "machine-mtrc", metrics.DefaultMachineMetricsAddress, machineExposeMetricsPort), + newKubeProxyContainer(image, "machineset-mtrc", metrics.DefaultMachineSetMetricsAddress, machineSetExposeMetricsPort, tlsProfile), + newKubeProxyContainer(image, "machine-mtrc", metrics.DefaultMachineMetricsAddress, machineExposeMetricsPort, tlsProfile), } if withMHCProxy { proxyContainers = append(proxyContainers, - newKubeProxyContainer(image, "mhc-mtrc", metrics.DefaultHealthCheckMetricsAddress, machineHealthCheckExposeMetricsPort), + newKubeProxyContainer(image, "mhc-mtrc", metrics.DefaultHealthCheckMetricsAddress, machineHealthCheckExposeMetricsPort, tlsProfile), ) } return proxyContainers } -func newKubeProxyContainer(image, portName, upstreamPort string, exposePort int32) corev1.Container { +// buildKubeRBACProxyTLSArgs constructs TLS arguments for kube-rbac-proxy +// based on the cluster's TLS security profile. +func buildKubeRBACProxyTLSArgs(tlsProfile *configv1.TLSProfileSpec) []string { + // Use defaults if no profile provided + ciphers := utiltls.DefaultTLSCiphers + minVersion := utiltls.DefaultMinTLSVersion + + if tlsProfile != nil { + if len(tlsProfile.Ciphers) > 0 { + ciphers = tlsProfile.Ciphers + } + if tlsProfile.MinTLSVersion != "" { + minVersion = tlsProfile.MinTLSVersion + } + } + + return []string{ + fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(ciphers, ",")), + fmt.Sprintf("--tls-min-version=%s", minVersion), + } +} + +func newKubeProxyContainer(image, portName, upstreamPort string, exposePort int32, tlsProfile *configv1.TLSProfileSpec) corev1.Container { configMountPath := "/etc/kube-rbac-proxy" tlsCertMountPath := "/etc/tls/private" resources := corev1.ResourceRequirements{ @@ -881,10 +904,10 @@ func newKubeProxyContainer(image, portName, upstreamPort string, exposePort int3 fmt.Sprintf("--config-file=%s/config-file.yaml", configMountPath), fmt.Sprintf("--tls-cert-file=%s/tls.crt", tlsCertMountPath), fmt.Sprintf("--tls-private-key-file=%s/tls.key", tlsCertMountPath), - "--tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "--logtostderr=true", "--v=3", } + args = append(args, buildKubeRBACProxyTLSArgs(tlsProfile)...) ports := []corev1.ContainerPort{{ Name: portName, ContainerPort: exposePort, diff --git a/pkg/operator/sync_test.go b/pkg/operator/sync_test.go index 7c28c04cb..1324c88c2 100644 --- a/pkg/operator/sync_test.go +++ b/pkg/operator/sync_test.go @@ -7,7 +7,7 @@ import ( "time" . "github.com/onsi/gomega" - v1 "github.com/openshift/api/config/v1" + configv1 "github.com/openshift/api/config/v1" machinev1beta1 "github.com/openshift/api/machine/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -456,20 +456,20 @@ func TestSyncWebhookConfiguration(t *testing.T) { testCases := []struct { name string - platformType v1.PlatformType + platformType configv1.PlatformType expectedNrMutatingWebhooks int expectedNrValidatingWebhooks int }{ { name: "webhooks on non baremetal", // using AWS as random non baremetal platform - platformType: v1.AWSPlatformType, + platformType: configv1.AWSPlatformType, expectedNrMutatingWebhooks: 1, expectedNrValidatingWebhooks: 1, }, { name: "webhooks on baremetal", - platformType: v1.BareMetalPlatformType, + platformType: configv1.BareMetalPlatformType, expectedNrMutatingWebhooks: 2, expectedNrValidatingWebhooks: 2, }, @@ -504,3 +504,46 @@ func TestSyncWebhookConfiguration(t *testing.T) { }) } } + +func TestBuildKubeRBACProxyTLSArgs(t *testing.T) { + testCases := []struct { + name string + tlsProfile *configv1.TLSProfileSpec + expectedCiphers string + expectedMin configv1.TLSProtocolVersion + }{ + { + name: "nil profile uses defaults", + tlsProfile: nil, + expectedMin: configv1.VersionTLS12, + }, + { + name: "custom profile with specific ciphers", + tlsProfile: &configv1.TLSProfileSpec{ + Ciphers: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"}, + MinTLSVersion: configv1.VersionTLS12, + }, + expectedCiphers: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + expectedMin: configv1.VersionTLS12, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + args := buildKubeRBACProxyTLSArgs(tc.tlsProfile) + + g.Expect(args).To(HaveLen(2), "should return exactly 2 args (cipher-suites and min-version)") + g.Expect(args[0]).To(HavePrefix("--tls-cipher-suites=")) + g.Expect(args[1]).To(HavePrefix("--tls-min-version=")) + + if tc.expectedCiphers != "" { + g.Expect(args[0]).To(Equal("--tls-cipher-suites=" + tc.expectedCiphers)) + } + if tc.expectedMin != "" { + g.Expect(args[1]).To(Equal("--tls-min-version=" + string(tc.expectedMin))) + } + }) + } +} From 3a20bb938a92248a09d66ac8669d717cb07b8485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Tue, 27 Jan 2026 13:28:58 +0100 Subject: [PATCH 4/9] Fix build script --- hack/go-build.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hack/go-build.sh b/hack/go-build.sh index 728e01c0e..86e8a88bf 100755 --- a/hack/go-build.sh +++ b/hack/go-build.sh @@ -12,7 +12,10 @@ eval $(go env | grep -e "GOHOSTOS" -e "GOHOSTARCH") : "${GOARCH:=${GOHOSTARCH}}" # Go to the root of the repo -cd "$(git rev-parse --show-cdup)" +cdup="$(git rev-parse --show-cdup)" +if [ -n "$cdup" ]; then + cd "$cdup" +fi if [ -z ${VERSION_OVERRIDE+a} ]; then if [ -n "${BUILD_VERSION+a}" ] && [ -n "${BUILD_RELEASE+a}" ]; then From 83e41569cd5d96e20a32e10616b947b89085233f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Tue, 27 Jan 2026 13:33:13 +0100 Subject: [PATCH 5/9] DNM: dependency fix --- go.mod | 11 +- go.sum | 21 +- .../coreos/go-systemd/v22/dbus/dbus.go | 3 +- .../coreos/go-systemd/v22/dbus/methods.go | 112 ++-- .../go-systemd/v22/dbus/subscription.go | 4 +- .../go-systemd/v22/dbus/subscription_set.go | 4 +- .../coreos/go-systemd/v22/journal/journal.go | 2 +- .../go-systemd/v22/journal/journal_unix.go | 8 +- .../cyphar/filepath-securejoin/CHANGELOG.md | 88 ++- .../cyphar/filepath-securejoin/VERSION | 2 +- .../pathrs-lite/internal/fd/openat2_linux.go | 4 +- .../gocompat/gocompat_atomic_go119.go | 19 + .../gocompat/gocompat_atomic_unsupported.go | 48 ++ .../internal/gopathrs/lookup_linux.go | 9 +- .../internal/gopathrs/openat2_linux.go | 1 + .../internal/linux/openat2_linux.go | 16 +- .../opencontainers/cgroups/cgroups.go | 5 + .../opencontainers/cgroups/config_linux.go | 4 +- .../opencontainers/cgroups/fs/cpuacct.go | 12 +- .../opencontainers/cgroups/fs/fs.go | 29 + .../opencontainers/cgroups/fs/pids.go | 27 +- .../opencontainers/cgroups/fs2/freezer.go | 32 +- .../opencontainers/cgroups/fs2/fs2.go | 13 + .../opencontainers/cgroups/fs2/hugetlb.go | 5 +- .../opencontainers/cgroups/fs2/io.go | 19 +- .../opencontainers/cgroups/fs2/pids.go | 20 +- .../opencontainers/cgroups/stats.go | 4 + .../opencontainers/cgroups/systemd/common.go | 15 + .../opencontainers/cgroups/systemd/dbus.go | 26 +- .../opencontainers/cgroups/systemd/v1.go | 32 +- .../opencontainers/cgroups/systemd/v2.go | 25 +- vendor/github.com/opencontainers/runc/LICENSE | 191 ------- vendor/github.com/opencontainers/runc/NOTICE | 17 - .../runc/libcontainer/cgroups/cgroups.go | 80 --- .../runc/libcontainer/cgroups/file.go | 216 -------- .../runc/libcontainer/cgroups/getallpids.go | 27 - .../runc/libcontainer/cgroups/stats.go | 200 ------- .../runc/libcontainer/cgroups/utils.go | 468 ---------------- .../runc/libcontainer/cgroups/v1_utils.go | 277 ---------- .../runc/libcontainer/configs/blkio_device.go | 66 --- .../runc/libcontainer/configs/cgroup_linux.go | 169 ------ .../configs/cgroup_unsupported.go | 8 - .../runc/libcontainer/configs/config.go | 508 ------------------ .../runc/libcontainer/configs/config_linux.go | 97 ---- .../libcontainer/configs/configs_fuzzer.go | 9 - .../libcontainer/configs/hugepage_limit.go | 9 - .../runc/libcontainer/configs/intelrdt.go | 16 - .../configs/interface_priority_map.go | 14 - .../runc/libcontainer/configs/mount.go | 7 - .../runc/libcontainer/configs/mount_linux.go | 66 --- .../libcontainer/configs/mount_unsupported.go | 9 - .../runc/libcontainer/configs/namespaces.go | 5 - .../libcontainer/configs/namespaces_linux.go | 133 ----- .../configs/namespaces_syscall.go | 45 -- .../configs/namespaces_syscall_unsupported.go | 13 - .../configs/namespaces_unsupported.go | 7 - .../runc/libcontainer/configs/network.go | 75 --- .../runc/libcontainer/configs/rdma.go | 9 - .../runc/libcontainer/devices/device.go | 174 ------ .../runc/libcontainer/devices/device_unix.go | 119 ---- .../runc/libcontainer/utils/cmsg.go | 119 ---- .../runc/libcontainer/utils/utils.go | 115 ---- .../runc/libcontainer/utils/utils_unix.go | 360 ------------- .../runtime-spec/specs-go/config.go | 234 +++++++- .../runtime-spec/specs-go/version.go | 2 +- .../kubernetes/openshift-hack/e2e/include.go | 1 - .../kubernetes/pkg/api/service/warnings.go | 2 +- .../pkg/controller/controller_utils.go | 65 ++- .../controller/daemon/daemon_controller.go | 4 +- .../pkg/controller/replicaset/replica_set.go | 4 +- .../kubernetes/pkg/features/kube_features.go | 14 +- .../pkg/kubelet/cm/cgroup_manager_linux.go | 4 +- .../cm/dra/plugin/dra_plugin_manager.go | 20 +- .../kubernetes/pkg/kubelet/prober/worker.go | 17 +- .../tainttoleration/taint_toleration.go | 4 +- .../framework/preemption/preemption.go | 94 ++-- .../kubernetes/pkg/volume/csi/csi_attacher.go | 2 +- .../k8s.io/kubernetes/pkg/volume/plugins.go | 2 +- .../pkg/volume/portworx/portworx.go | 33 +- .../node/framework/cgroups/cgroups_linux.go | 24 +- .../test/e2e/common/node/pod_resize.go | 12 +- .../e2e/framework/metrics/kubelet_metrics.go | 1 - .../e2e/framework/metrics/metrics_grabber.go | 31 +- .../kubernetes/test/e2e/framework/pod/wait.go | 6 +- .../kubernetes/test/e2e/framework/pv/wait.go | 9 +- .../kubernetes/test/e2e/kubectl/kubectl.go | 4 +- .../test/e2e/scheduling/preemption.go | 31 +- .../csimock/mutable_csinode_allocatable.go | 65 +-- .../test/e2e/storage/drivers/csi.go | 111 ++-- .../test/e2e/storage/testsuites/base.go | 1 - .../storage/testsuites/readwriteoncepod.go | 2 +- .../testsuites/volume_group_snapshottable.go | 11 +- .../storage/utils/volume_group_snapshot.go | 8 +- .../storage-csi/external-attacher/rbac.yaml | 4 +- .../rbac.yaml | 4 +- .../external-provisioner/rbac.yaml | 4 +- .../storage-csi/external-resizer/rbac.yaml | 5 +- .../csi-snapshotter/rbac-csi-snapshotter.yaml | 9 +- ...age.k8s.io_volumegroupsnapshotclasses.yaml | 86 ++- ...ge.k8s.io_volumegroupsnapshotcontents.yaml | 339 +++++++++++- ...t.storage.k8s.io_volumegroupsnapshots.yaml | 223 +++++++- .../csi-hostpath-plugin.yaml | 2 +- .../run_group_snapshot_e2e.sh | 15 +- .../storage-csi/hostpath/README.md | 2 +- .../hostpath/csi-hostpath-plugin.yaml | 34 +- .../hostpath/csi-hostpath-testing.yaml | 2 +- .../mock/csi-mock-driver-attacher.yaml | 2 +- .../mock/csi-mock-driver-resizer.yaml | 2 +- .../mock/csi-mock-driver-snapshotter.yaml | 2 +- .../storage-csi/mock/csi-mock-driver.yaml | 6 +- .../storage-csi/mock/csi-mock-proxy.yaml | 6 +- .../storage-csi/update-hostpath.sh | 15 +- .../kubernetes/test/utils/image/manifest.go | 2 +- vendor/modules.txt | 20 +- 114 files changed, 1677 insertions(+), 4173 deletions(-) create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_go119.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/LICENSE delete mode 100644 vendor/github.com/opencontainers/runc/NOTICE delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/cgroups/file.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/cgroups/getallpids.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/config.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/config_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/configs_fuzzer.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/mount_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/mount_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_linux.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/network.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/configs/rdma.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/devices/device.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/devices/device_unix.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go delete mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go diff --git a/go.mod b/go.mod index 4e0d0fdb4..7ff4851a3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ replace ( github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 k8s.io/apiserver => github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292 // openshift kubernetes has very old copy of k8s.io/kubernetes/pkg/kubelet/server/server.go k8s.io/kubelet => github.com/openshift/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20251015171918-61114aa5a292 // openshift kubernetes has very old copy of k8s.io/kubernetes/cmd/kubelet/app/options/options.go - k8s.io/kubernetes => github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd + k8s.io/kubernetes => /home/rmanak/work-git/kubernetes ) // TODO: remove once https://github.com/openshift/library-go/pull/2082 is merged @@ -116,9 +116,9 @@ require ( github.com/containerd/ttrpc v1.2.6 // indirect github.com/containerd/typeurl/v2 v2.2.2 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/cyphar/filepath-securejoin v0.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect @@ -241,11 +241,10 @@ require ( github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.19.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/opencontainers/cgroups v0.0.3 // indirect + github.com/opencontainers/cgroups v0.0.6 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/opencontainers/runc v1.2.5 // indirect - github.com/opencontainers/runtime-spec v1.2.0 // indirect + github.com/opencontainers/runtime-spec v1.3.0 // indirect github.com/opencontainers/selinux v1.13.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect diff --git a/go.sum b/go.sum index 1c72bf63a..e6bdf2797 100644 --- a/go.sum +++ b/go.sum @@ -116,15 +116,15 @@ github.com/containerd/typeurl/v2 v2.2.2 h1:3jN/k2ysKuPCsln5Qv8bzR9cxal8XjkxPogJf github.com/containerd/typeurl/v2 v2.2.2/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= -github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= -github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38 h1:G0e8pIhZwxvoJaDeqE3gFzEDyIT4ocUklMEorG9D2Gg= @@ -227,7 +227,6 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= @@ -439,16 +438,14 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/opencontainers/cgroups v0.0.3 h1:Jc9dWh/0YLGjdy6J/9Ln8NM5BfTA4W2BY0GMozy3aDU= -github.com/opencontainers/cgroups v0.0.3/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= +github.com/opencontainers/cgroups v0.0.6 h1:tfZFWTIIGaUUFImTyuTg+Mr5x8XRiSdZESgEBW7UxuI= +github.com/opencontainers/cgroups v0.0.6/go.mod h1:oWVzJsKK0gG9SCRBfTpnn16WcGEqDI8PAcpMGbqWxcs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runc v1.2.5 h1:8KAkq3Wrem8bApgOHyhRI/8IeLXIfmZ6Qaw6DNSLnA4= -github.com/opencontainers/runc v1.2.5/go.mod h1:dOQeFo29xZKBNeRBI0B19mJtfHv68YgCTh1X+YphA+4= -github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= -github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= +github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7 h1:Z1swlS6b3Adm6RPhjqefs3DWnNFLDxRX+WC8GMXhja4= @@ -461,8 +458,6 @@ github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21 github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21d03d30056d/go.mod h1:9+FWWWLkVrnBo1eYhA/0Ehlq5JMgIAHtcB0IF+qV1AA= github.com/openshift/cluster-control-plane-machine-set-operator v0.0.0-20251029084908-344babe6a957 h1:eVnkMTFnirnoUOlAUT3Hy8WriIi1JoSrilWym3Dl8Q4= github.com/openshift/cluster-control-plane-machine-set-operator v0.0.0-20251029084908-344babe6a957/go.mod h1:TBlORAAtNZ/Tl86pO7GjNXKsH/g0QAW5GnvYstdOhYI= -github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd h1:WCCP41uY1QoqrDeykXFF/Dmf8NV3fnnAXUnh1oVFxW8= -github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd/go.mod h1:w3+IfrXNp5RosdDXg3LB55yijJqR/FwouvVntYHQf0o= github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292 h1:Uq4CTGAl32NYNK7KD35oRg7pdZOFkUF+/2wGSyR0VYA= github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292/go.mod h1:TRSSqgXggJaDK5vtVtlQ9wEYOk32Pl+9tf0ROf3ljiM= github.com/openshift/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20251015171918-61114aa5a292 h1:ITqtj/xBNlSPChpvV4a+VQ93Sk5RFkwRx+CnIWBrZ98= diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go index 147f756fe..22ce8f1df 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ +// Package dbus provides integration with the systemd D-Bus API. +// See http://www.freedesktop.org/wiki/Software/systemd/dbus/ package dbus import ( diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go index 074148cb4..a64f0b3ea 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go @@ -24,15 +24,15 @@ import ( "github.com/godbus/dbus/v5" ) -// Who can be used to specify which process to kill in the unit via the KillUnitWithTarget API +// Who specifies which process to send a signal to via the [KillUnitWithTarget]. type Who string const ( - // All sends the signal to all processes in the unit + // All sends the signal to all processes in the unit. All Who = "all" - // Main sends the signal to the main process of the unit + // Main sends the signal to the main process of the unit. Main Who = "main" - // Control sends the signal to the control process of the unit + // Control sends the signal to the control process of the unit. Control Who = "control" ) @@ -41,7 +41,8 @@ func (c *Conn) jobComplete(signal *dbus.Signal) { var job dbus.ObjectPath var unit string var result string - dbus.Store(signal.Body, &id, &job, &unit, &result) + + _ = dbus.Store(signal.Body, &id, &job, &unit, &result) c.jobListener.Lock() out, ok := c.jobListener.jobs[job] if ok { @@ -51,7 +52,7 @@ func (c *Conn) jobComplete(signal *dbus.Signal) { c.jobListener.Unlock() } -func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args ...interface{}) (int, error) { +func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args ...any) (int, error) { if ch != nil { c.jobListener.Lock() defer c.jobListener.Unlock() @@ -102,6 +103,10 @@ func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error // has been removed too. skipped indicates that a job was skipped because it // didn't apply to the units current state. // +// Important: It is the caller's responsibility to unblock the provided channel write, +// either by reading from the channel or by using a buffered channel. Until the write +// is unblocked, the Conn object cannot handle other jobs. +// // If no error occurs, the ID of the underlying systemd job will be returned. There // does exist the possibility for no error to be returned, but for the returned job // ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint @@ -192,19 +197,21 @@ func (c *Conn) StartTransientUnitContext(ctx context.Context, name string, mode return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) } -// Deprecated: use KillUnitContext instead. +// Deprecated: use [KillUnitWithTarget] instead. func (c *Conn) KillUnit(name string, signal int32) { c.KillUnitContext(context.Background(), name, signal) } // KillUnitContext takes the unit name and a UNIX signal number to send. // All of the unit's processes are killed. +// +// Deprecated: use [KillUnitWithTarget] instead, with target argument set to [All]. func (c *Conn) KillUnitContext(ctx context.Context, name string, signal int32) { - c.KillUnitWithTarget(ctx, name, All, signal) + _ = c.KillUnitWithTarget(ctx, name, All, signal) } -// KillUnitWithTarget is like KillUnitContext, but allows you to specify which -// process in the unit to send the signal to. +// KillUnitWithTarget sends a signal to the specified unit. +// The target argument can be one of [All], [Main], or [Control]. func (c *Conn) KillUnitWithTarget(ctx context.Context, name string, target Who, signal int32) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.KillUnit", 0, name, string(target), signal).Store() } @@ -240,7 +247,7 @@ func (c *Conn) SystemStateContext(ctx context.Context) (*Property, error) { } // getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface. -func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]interface{}, error) { +func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]any, error) { var err error var props map[string]dbus.Variant @@ -254,7 +261,7 @@ func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInte return nil, err } - out := make(map[string]interface{}, len(props)) + out := make(map[string]any, len(props)) for k, v := range props { out[k] = v.Value() } @@ -263,36 +270,36 @@ func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInte } // Deprecated: use GetUnitPropertiesContext instead. -func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { +func (c *Conn) GetUnitProperties(unit string) (map[string]any, error) { return c.GetUnitPropertiesContext(context.Background(), unit) } // GetUnitPropertiesContext takes the (unescaped) unit name and returns all of // its dbus object properties. -func (c *Conn) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { +func (c *Conn) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]any, error) { path := unitPath(unit) return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") } // Deprecated: use GetUnitPathPropertiesContext instead. -func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) { +func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]any, error) { return c.GetUnitPathPropertiesContext(context.Background(), path) } // GetUnitPathPropertiesContext takes the (escaped) unit path and returns all // of its dbus object properties. -func (c *Conn) GetUnitPathPropertiesContext(ctx context.Context, path dbus.ObjectPath) (map[string]interface{}, error) { +func (c *Conn) GetUnitPathPropertiesContext(ctx context.Context, path dbus.ObjectPath) (map[string]any, error) { return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") } // Deprecated: use GetAllPropertiesContext instead. -func (c *Conn) GetAllProperties(unit string) (map[string]interface{}, error) { +func (c *Conn) GetAllProperties(unit string) (map[string]any, error) { return c.GetAllPropertiesContext(context.Background(), unit) } // GetAllPropertiesContext takes the (unescaped) unit name and returns all of // its dbus object properties. -func (c *Conn) GetAllPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { +func (c *Conn) GetAllPropertiesContext(ctx context.Context, unit string) (map[string]any, error) { path := unitPath(unit) return c.getProperties(ctx, path, "") } @@ -331,20 +338,20 @@ func (c *Conn) GetServiceProperty(service string, propertyName string) (*Propert return c.GetServicePropertyContext(context.Background(), service, propertyName) } -// GetServiceProperty returns property for given service name and property name. +// GetServicePropertyContext returns property for given service name and property name. func (c *Conn) GetServicePropertyContext(ctx context.Context, service string, propertyName string) (*Property, error) { return c.getProperty(ctx, service, "org.freedesktop.systemd1.Service", propertyName) } // Deprecated: use GetUnitTypePropertiesContext instead. -func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { +func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]any, error) { return c.GetUnitTypePropertiesContext(context.Background(), unit, unitType) } // GetUnitTypePropertiesContext returns the extra properties for a unit, specific to the unit type. // Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope. // Returns "dbus.Error: Unknown interface" error if the unitType is not the correct type of the unit. -func (c *Conn) GetUnitTypePropertiesContext(ctx context.Context, unit string, unitType string) (map[string]interface{}, error) { +func (c *Conn) GetUnitTypePropertiesContext(ctx context.Context, unit string, unitType string) (map[string]any, error) { path := unitPath(unit) return c.getProperties(ctx, path, "org.freedesktop.systemd1."+unitType) } @@ -389,22 +396,22 @@ type UnitStatus struct { JobPath dbus.ObjectPath // The job object path } -type storeFunc func(retvalues ...interface{}) error +type storeFunc func(retvalues ...any) error func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) { - result := make([][]interface{}, 0) + result := make([][]any, 0) err := f(&result) if err != nil { return nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } status := make([]UnitStatus, len(result)) - statusInterface := make([]interface{}, len(status)) + statusInterface := make([]any, len(status)) for i := range status { statusInterface[i] = &status[i] } @@ -499,19 +506,19 @@ type UnitFile struct { } func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) { - result := make([][]interface{}, 0) + result := make([][]any, 0) err := f(&result) if err != nil { return nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } files := make([]UnitFile, len(result)) - fileInterface := make([]interface{}, len(files)) + fileInterface := make([]any, len(files)) for i := range files { fileInterface[i] = &files[i] } @@ -529,7 +536,7 @@ func (c *Conn) ListUnitFiles() ([]UnitFile, error) { return c.ListUnitFilesContext(context.Background()) } -// ListUnitFiles returns an array of all available units on disk. +// ListUnitFilesContext returns an array of all available units on disk. func (c *Conn) ListUnitFilesContext(ctx context.Context) ([]UnitFile, error) { return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store) } @@ -569,19 +576,19 @@ func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUn // or unlink), the file name of the symlink and the destination of the // symlink. func (c *Conn) LinkUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { - result := make([][]interface{}, 0) + result := make([][]any, 0) err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) if err != nil { return nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]LinkUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) + changesInterface := make([]any, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } @@ -618,19 +625,19 @@ func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, func (c *Conn) EnableUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { var carries_install_info bool - result := make([][]interface{}, 0) + result := make([][]any, 0) err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) if err != nil { return false, nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]EnableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) + changesInterface := make([]any, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } @@ -667,19 +674,19 @@ func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFile // symlink or unlink), the file name of the symlink and the destination of the // symlink. func (c *Conn) DisableUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]DisableUnitFileChange, error) { - result := make([][]interface{}, 0) + result := make([][]any, 0) err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) if err != nil { return nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]DisableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) + changesInterface := make([]any, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } @@ -713,19 +720,19 @@ func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUn // runtime only (true, /run/systemd/..), or persistently (false, // /etc/systemd/..). func (c *Conn) MaskUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { - result := make([][]interface{}, 0) + result := make([][]any, 0) err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store(&result) if err != nil { return nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]MaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) + changesInterface := make([]any, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } @@ -757,19 +764,19 @@ func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileCh // for runtime only (true, /run/systemd/..), or persistently (false, // /etc/systemd/..). func (c *Conn) UnmaskUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]UnmaskUnitFileChange, error) { - result := make([][]interface{}, 0) + result := make([][]any, 0) err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store(&result) if err != nil { return nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]UnmaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) + changesInterface := make([]any, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } @@ -829,18 +836,18 @@ func (c *Conn) ListJobsContext(ctx context.Context) ([]JobStatus, error) { } func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) { - result := make([][]interface{}, 0) + result := make([][]any, 0) if err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListJobs", 0).Store(&result); err != nil { return nil, err } - resultInterface := make([]interface{}, len(result)) + resultInterface := make([]any, len(result)) for i := range result { resultInterface[i] = result[i] } status := make([]JobStatus, len(result)) - statusInterface := make([]interface{}, len(status)) + statusInterface := make([]any, len(status)) for i := range status { statusInterface[i] = &status[i] } @@ -852,13 +859,18 @@ func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) { return status, nil } -// Freeze the cgroup associated with the unit. -// Note that FreezeUnit and ThawUnit are only supported on systems running with cgroup v2. +// FreezeUnit freezes the cgroup associated with the unit. +// Note that FreezeUnit and [ThawUnit] are only supported on systems running with cgroup v2. func (c *Conn) FreezeUnit(ctx context.Context, unit string) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.FreezeUnit", 0, unit).Store() } -// Unfreeze the cgroup associated with the unit. +// ThawUnit unfreezes the cgroup associated with the unit. func (c *Conn) ThawUnit(ctx context.Context, unit string) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store() } + +// AttachProcessesToUnit moves existing processes, identified by pids, into an existing systemd unit. +func (c *Conn) AttachProcessesToUnit(ctx context.Context, unit, subcgroup string, pids []uint32) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.AttachProcessesToUnit", 0, unit, subcgroup, pids).Store() +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go index 7e370fea2..f0f6aad9d 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go @@ -70,7 +70,7 @@ func (c *Conn) dispatch() { switch signal.Name { case "org.freedesktop.systemd1.Manager.JobRemoved": unitName := signal.Body[2].(string) - c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) + _ = c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) case "org.freedesktop.systemd1.Manager.UnitNew": unitPath = signal.Body[1].(dbus.ObjectPath) case "org.freedesktop.DBus.Properties.PropertiesChanged": @@ -262,7 +262,7 @@ func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { return ok && t >= time.Now().UnixNano() } -func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { +func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]any) { loadState, ok := info["LoadState"].(string) if !ok { return diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go index 5b408d584..dbe4aa887 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go @@ -40,8 +40,8 @@ func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan err } // NewSubscriptionSet returns a new subscription set. -func (conn *Conn) NewSubscriptionSet() *SubscriptionSet { - return &SubscriptionSet{newSet(), conn} +func (c *Conn) NewSubscriptionSet() *SubscriptionSet { + return &SubscriptionSet{newSet(), c} } // mismatchUnitStatus returns true if the provided UnitStatus objects diff --git a/vendor/github.com/coreos/go-systemd/v22/journal/journal.go b/vendor/github.com/coreos/go-systemd/v22/journal/journal.go index ac24c7767..16c4e4775 100644 --- a/vendor/github.com/coreos/go-systemd/v22/journal/journal.go +++ b/vendor/github.com/coreos/go-systemd/v22/journal/journal.go @@ -41,6 +41,6 @@ const ( ) // Print prints a message to the local systemd journal using Send(). -func Print(priority Priority, format string, a ...interface{}) error { +func Print(priority Priority, format string, a ...any) error { return Send(fmt.Sprintf(format, a...), priority, nil) } diff --git a/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go b/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go index c5b23a819..6266e16e5 100644 --- a/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go +++ b/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go @@ -13,7 +13,6 @@ // limitations under the License. //go:build !windows -// +build !windows // Package journal provides write bindings to the local systemd journal. // It is implemented in pure Go and connects to the journal directly over its @@ -31,7 +30,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "os" "strconv" @@ -194,7 +192,7 @@ func appendVariable(w io.Writer, name, value string) { * - the data, followed by a newline */ fmt.Fprintln(w, name) - binary.Write(w, binary.LittleEndian, uint64(len(value))) + _ = binary.Write(w, binary.LittleEndian, uint64(len(value))) fmt.Fprintln(w, value) } else { /* just write the variable and value all on one line */ @@ -214,7 +212,7 @@ func validVarName(name string) error { } for _, c := range name { - if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') { + if ('A' > c || c > 'Z') && ('0' > c || c > '9') && c != '_' { return errors.New("Variable name contains invalid characters") } } @@ -239,7 +237,7 @@ func isSocketSpaceError(err error) bool { // tempFd creates a temporary, unlinked file under `/dev/shm`. func tempFd() (*os.File, error) { - file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX") + file, err := os.CreateTemp("/dev/shm/", "journal.XXXXX") if err != nil { return nil, err } diff --git a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md index 734cf61e3..6d016d05c 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md +++ b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md @@ -6,49 +6,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ## -## [0.6.0] - 2025-11-03 ## +## [0.6.1] - 2025-11-19 ## -> By the Power of Greyskull! +> At last up jumped the cunning spider, and fiercely held her fast. + +### Fixed ### +- Our logic for deciding whether to use `openat2(2)` or fallback to an `O_PATH` + resolver would cache the result to avoid doing needless test runs of + `openat2(2)`. However, this causes issues when `pathrs-lite` is being used by + a program that applies new seccomp-bpf filters onto itself -- if the filter + denies `openat2(2)` then we would return that error rather than falling back + to the `O_PATH` resolver. To resolve this issue, we no longer cache the + result if `openat2(2)` was successful, only if there was an error. +- A file descriptor leak in our `openat2` wrapper (when doing the necessary + `dup` for `RESOLVE_IN_ROOT`) has been removed. -While quite small code-wise, this release marks a very key point in the -development of filepath-securejoin. - -filepath-securejoin was originally intended (back in 2017) to simply be a -single-purpose library that would take some common code used in container -runtimes (specifically, Docker's `FollowSymlinksInScope`) and make it more -general-purpose (with the eventual goals of it ending up in the Go stdlib). - -Of course, I quickly discovered that this problem was actually far more -complicated to solve when dealing with racing attackers, which lead to me -developing `openat2(2)` and [libpathrs][]. I had originally planned for -libpathrs to completely replace filepath-securejoin "once it was ready" but in -the interim we needed to fix several race attacks in runc as part of security -advisories. Obviously we couldn't require the usage of a pre-0.1 Rust library -in runc so it was necessary to port bits of libpathrs into filepath-securejoin. -(Ironically the first prototypes of libpathrs were originally written in Go and -then rewritten to Rust, so the code in filepath-securejoin is actually Go code -that was rewritten to Rust then re-rewritten to Go.) - -It then became clear that pure-Go libraries will likely not be willing to -require CGo for all of their builds, so it was necessary to accept that -filepath-securejoin will need to stay. As such, in v0.5.0 we provided more -pure-Go implementations of features from libpathrs but moved them into -`pathrs-lite` subpackage to clarify what purpose these helpers serve. - -This release finally closes the loop and makes it so that pathrs-lite can -transparently use libpathrs (via a `libpathrs` build-tag). This means that -upstream libraries can use the pure Go version if they prefer, but downstreams -(either downstream library users or even downstream distributions) are able to -migrate to libpathrs for all usages of pathrs-lite in an entire Go binary. - -I should make it clear that I do not plan to port the rest of libpathrs to Go, -as I do not wish to maintain two copies of the same codebase. pathrs-lite -already provides the core essentials necessary to operate on paths safely for -most modern systems. Users who want additional hardening or more ergonomic APIs -are free to use [`cyphar.com/go-pathrs`][go-pathrs] (libpathrs's Go bindings). +## [0.5.2] - 2025-11-19 ## -[libpathrs]: https://github.com/cyphar/libpathrs -[go-pathrs]: https://cyphar.com/go-pathrs +> "Will you walk into my parlour?" said a spider to a fly. + +### Fixed ### +- Our logic for deciding whether to use `openat2(2)` or fallback to an `O_PATH` + resolver would cache the result to avoid doing needless test runs of + `openat2(2)`. However, this causes issues when `pathrs-lite` is being used by + a program that applies new seccomp-bpf filters onto itself -- if the filter + denies `openat2(2)` then we would return that error rather than falling back + to the `O_PATH` resolver. To resolve this issue, we no longer cache the + result if `openat2(2)` was successful, only if there was an error. +- A file descriptor leak in our `openat2` wrapper (when doing the necessary + `dup` for `RESOLVE_IN_ROOT`) has been removed. + +## [0.6.0] - 2025-11-03 ## + +> By the Power of Greyskull! ### Breaking ### - The deprecated `MkdirAll`, `MkdirAllHandle`, `OpenInRoot`, `OpenatInRoot` and @@ -56,12 +46,12 @@ are free to use [`cyphar.com/go-pathrs`][go-pathrs] (libpathrs's Go bindings). directly. ### Added ### -- `pathrs-lite` now has support for using [libpathrs][libpathrs] as a backend. - This is opt-in and can be enabled at build time with the `libpathrs` build - tag. The intention is to allow for downstream libraries and other projects to - make use of the pure-Go `github.com/cyphar/filepath-securejoin/pathrs-lite` - package and distributors can then opt-in to using `libpathrs` for the entire - binary if they wish. +- `pathrs-lite` now has support for using libpathrs as a backend. This is + opt-in and can be enabled at build time with the `libpathrs` build tag. The + intention is to allow for downstream libraries and other projects to make use + of the pure-Go `github.com/cyphar/filepath-securejoin/pathrs-lite` package + and distributors can then opt-in to using `libpathrs` for the entire binary + if they wish. ## [0.5.1] - 2025-10-31 ## @@ -440,8 +430,10 @@ This is our first release of `github.com/cyphar/filepath-securejoin`, containing a full implementation with a coverage of 93.5% (the only missing cases are the error cases, which are hard to mocktest at the moment). -[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...HEAD -[0.6.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...v0.6.0 +[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.1...HEAD +[0.6.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...v0.6.1 +[0.6.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.6.0 +[0.5.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0 [0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1 diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION index a918a2aa1..ee6cdce3c 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/VERSION +++ b/vendor/github.com/cyphar/filepath-securejoin/VERSION @@ -1 +1 @@ -0.6.0 +0.6.1 diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go index 3e937fe3c..63863647d 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go @@ -39,7 +39,9 @@ const scopedLookupMaxRetries = 128 // Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry // logic in case of EAGAIN errors. -func Openat2(dir Fd, path string, how *unix.OpenHow) (*os.File, error) { +// +// NOTE: This is a variable so that the lookup tests can force openat2 to fail. +var Openat2 = func(dir Fd, path string, how *unix.OpenHow) (*os.File, error) { dirFd, fullPath := prepareAt(dir, path) // Make sure we always set O_CLOEXEC. how.Flags |= unix.O_CLOEXEC diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_go119.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_go119.go new file mode 100644 index 000000000..ac93cb045 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_go119.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && go1.19 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "sync/atomic" +) + +// A Bool is an atomic boolean value. +// The zero value is false. +// +// Bool must not be copied after first use. +type Bool = atomic.Bool diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_unsupported.go new file mode 100644 index 000000000..21b5b29ad --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_unsupported.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && !go1.19 + +// Copyright (C) 2024-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "sync/atomic" +) + +// noCopy may be added to structs which must not be copied +// after the first use. +// +// See https://golang.org/issues/8005#issuecomment-190753527 +// for details. +// +// Note that it must not be embedded, due to the Lock and Unlock methods. +type noCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*noCopy) Lock() {} + +// b32 returns a uint32 0 or 1 representing b. +func b32(b bool) uint32 { + if b { + return 1 + } + return 0 +} + +// A Bool is an atomic boolean value. +// The zero value is false. +// +// Bool must not be copied after first use. +type Bool struct { + _ noCopy + v uint32 +} + +// Load atomically loads and returns the value stored in x. +func (x *Bool) Load() bool { return atomic.LoadUint32(&x.v) != 0 } + +// Store atomically stores val into x. +func (x *Bool) Store(val bool) { atomic.StoreUint32(&x.v, b32(val)) } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go index 56480f0ce..ad233f140 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go @@ -193,8 +193,13 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, // managed open, along with the remaining path components not opened. // Try to use openat2 if possible. - if linux.HasOpenat2() { - return lookupOpenat2(root, unsafePath, partial) + // + // NOTE: If openat2(2) works normally but fails for this lookup, it is + // probably not a good idea to fall-back to the O_PATH resolver. An + // attacker could find a bug in the O_PATH resolver and uncontionally + // falling back to the O_PATH resolver would form a downgrade attack. + if handle, remainingPath, err := lookupOpenat2(root, unsafePath, partial); err == nil || linux.HasOpenat2() { + return handle, remainingPath, err } // Get the "actual" root path from /proc/self/fd. This is necessary if the diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go index b80ecd089..9c5c268f6 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go @@ -41,6 +41,7 @@ func openat2(dir fd.Fd, path string, how *unix.OpenHow) (*os.File, error) { if err != nil { return nil, err } + _ = file.Close() file = newFile } } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go index 399609dc3..dc5f65cef 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go @@ -17,15 +17,27 @@ import ( "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" ) +// sawOpenat2Error stores whether we have seen an error from HasOpenat2. This +// is a one-way toggle, so as soon as we see an error we "lock" into that mode. +// We cannot use sync.OnceValue to store the success/fail state once because it +// is possible for the program we are running in to apply a seccomp-bpf filter +// and thus disable openat2 during execution. +var sawOpenat2Error gocompat.Bool + // HasOpenat2 returns whether openat2(2) is supported on the running kernel. -var HasOpenat2 = gocompat.SyncOnceValue(func() bool { +var HasOpenat2 = func() bool { + if sawOpenat2Error.Load() { + return false + } + fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{ Flags: unix.O_PATH | unix.O_CLOEXEC, Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT, }) if err != nil { + sawOpenat2Error.Store(true) // doesn't matter if we race here return false } _ = unix.Close(fd) return true -}) +} diff --git a/vendor/github.com/opencontainers/cgroups/cgroups.go b/vendor/github.com/opencontainers/cgroups/cgroups.go index 1f127550c..5a97bd36b 100644 --- a/vendor/github.com/opencontainers/cgroups/cgroups.go +++ b/vendor/github.com/opencontainers/cgroups/cgroups.go @@ -29,6 +29,11 @@ type Manager interface { // can be used to merely create a cgroup. Apply(pid int) error + // AddPid adds a process with a given pid to an existing cgroup. + // The subcgroup argument is either empty, or a path relative to + // a cgroup under under the manager's cgroup. + AddPid(subcgroup string, pid int) error + // GetPids returns the PIDs of all processes inside the cgroup. GetPids() ([]int, error) diff --git a/vendor/github.com/opencontainers/cgroups/config_linux.go b/vendor/github.com/opencontainers/cgroups/config_linux.go index 9bc58a378..3d29d938b 100644 --- a/vendor/github.com/opencontainers/cgroups/config_linux.go +++ b/vendor/github.com/opencontainers/cgroups/config_linux.go @@ -90,8 +90,8 @@ type Resources struct { // Cgroup's SCHED_IDLE value. CPUIdle *int64 `json:"cpu_idle,omitempty"` - // Process limit; set <= `0' to disable limit. - PidsLimit int64 `json:"pids_limit,omitempty"` + // Process limit; set < `0' to disable limit. `nil` means "keep current limit". + PidsLimit *int64 `json:"pids_limit,omitempty"` // Specifies per cgroup weight, range is from 10 to 1000. BlkioWeight uint16 `json:"blkio_weight,omitempty"` diff --git a/vendor/github.com/opencontainers/cgroups/fs/cpuacct.go b/vendor/github.com/opencontainers/cgroups/fs/cpuacct.go index 391a023c7..bde25b075 100644 --- a/vendor/github.com/opencontainers/cgroups/fs/cpuacct.go +++ b/vendor/github.com/opencontainers/cgroups/fs/cpuacct.go @@ -129,12 +129,16 @@ func getPercpuUsageInModes(path string) ([]uint64, []uint64, error) { defer fd.Close() scanner := bufio.NewScanner(fd) - scanner.Scan() // skipping header line + scanner.Scan() // Read header line. + const want = "cpu user system" + if hdr := scanner.Text(); !strings.HasPrefix(hdr, want) { + return nil, nil, malformedLine(path, file, hdr) + } for scanner.Scan() { - // Each line is: cpu user system - fields := strings.SplitN(scanner.Text(), " ", 3) - if len(fields) != 3 { + // Each line is: cpu user system. Keep N at 4 to ignore extra fields. + fields := strings.SplitN(scanner.Text(), " ", 4) + if len(fields) < 3 { continue } diff --git a/vendor/github.com/opencontainers/cgroups/fs/fs.go b/vendor/github.com/opencontainers/cgroups/fs/fs.go index 23a8fb874..625931193 100644 --- a/vendor/github.com/opencontainers/cgroups/fs/fs.go +++ b/vendor/github.com/opencontainers/cgroups/fs/fs.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "os" + "path" + "strings" "sync" "golang.org/x/sys/unix" @@ -139,6 +141,33 @@ func (m *Manager) Apply(pid int) (retErr error) { return retErr } +// AddPid adds a process with a given pid to an existing cgroup. +// The subcgroup argument is either empty, or a path relative to +// a cgroup under under the manager's cgroup. +func (m *Manager) AddPid(subcgroup string, pid int) (retErr error) { + m.mu.Lock() + defer m.mu.Unlock() + + c := m.cgroups + + for _, dir := range m.paths { + path := path.Join(dir, subcgroup) + if !strings.HasPrefix(path, dir) { + return fmt.Errorf("bad sub cgroup path: %s", subcgroup) + } + + if err := cgroups.WriteCgroupProc(path, pid); err != nil { + if isIgnorableError(c.Rootless, err) && c.Path == "" { + retErr = cgroups.ErrRootless + continue + } + return err + } + } + + return retErr +} + func (m *Manager) Destroy() error { m.mu.Lock() defer m.mu.Unlock() diff --git a/vendor/github.com/opencontainers/cgroups/fs/pids.go b/vendor/github.com/opencontainers/cgroups/fs/pids.go index 9319761e6..36bd339af 100644 --- a/vendor/github.com/opencontainers/cgroups/fs/pids.go +++ b/vendor/github.com/opencontainers/cgroups/fs/pids.go @@ -19,19 +19,24 @@ func (s *PidsGroup) Apply(path string, _ *cgroups.Resources, pid int) error { } func (s *PidsGroup) Set(path string, r *cgroups.Resources) error { - if r.PidsLimit != 0 { - // "max" is the fallback value. - limit := "max" - - if r.PidsLimit > 0 { - limit = strconv.FormatInt(r.PidsLimit, 10) - } - - if err := cgroups.WriteFile(path, "pids.max", limit); err != nil { - return err - } + if r.PidsLimit == nil { + return nil } + // "max" is the fallback value. + val := "max" + if limit := *r.PidsLimit; limit > 0 { + val = strconv.FormatInt(limit, 10) + } else if limit == 0 { + // systemd doesn't support setting pids.max to "0", so when setting + // TasksMax we need to remap it to "1". We do the same thing here to + // avoid flip-flop behaviour between the fs and systemd drivers. In + // practice, the pids cgroup behaviour is basically identical. + val = "1" + } + if err := cgroups.WriteFile(path, "pids.max", val); err != nil { + return err + } return nil } diff --git a/vendor/github.com/opencontainers/cgroups/fs2/freezer.go b/vendor/github.com/opencontainers/cgroups/fs2/freezer.go index f0192f095..6307e68db 100644 --- a/vendor/github.com/opencontainers/cgroups/fs2/freezer.go +++ b/vendor/github.com/opencontainers/cgroups/fs2/freezer.go @@ -53,12 +53,9 @@ func setFreezer(dirPath string, state cgroups.FreezerState) error { func getFreezer(dirPath string) (cgroups.FreezerState, error) { fd, err := cgroups.OpenFile(dirPath, "cgroup.freeze", unix.O_RDONLY) if err != nil { - // If the kernel is too old, then we just treat the freezer as being in - // an "undefined" state. - if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) { - err = nil - } - return cgroups.Undefined, err + // If the kernel is too old, then we just treat the freezer as + // being in an "undefined" state and ignore the error. + return cgroups.Undefined, ignoreNotExistOrNoDeviceError(err) } defer fd.Close() @@ -67,11 +64,15 @@ func getFreezer(dirPath string) (cgroups.FreezerState, error) { func readFreezer(dirPath string, fd *os.File) (cgroups.FreezerState, error) { if _, err := fd.Seek(0, 0); err != nil { - return cgroups.Undefined, err + // If the cgroup path is deleted at this point, then we just treat the freezer as + // being in an "undefined" state and ignore the error. + return cgroups.Undefined, ignoreNotExistOrNoDeviceError(err) } state := make([]byte, 2) if _, err := fd.Read(state); err != nil { - return cgroups.Undefined, err + // If the cgroup path is deleted at this point, then we just treat the freezer as + // being in an "undefined" state and ignore the error. + return cgroups.Undefined, ignoreNotExistOrNoDeviceError(err) } switch string(state) { case "0\n": @@ -83,6 +84,21 @@ func readFreezer(dirPath string, fd *os.File) (cgroups.FreezerState, error) { } } +// ignoreNotExistOrNoDeviceError checks if the error is either a "not exist" error +// or a "no device" error, and returns nil in those cases. Otherwise, it returns the error. +func ignoreNotExistOrNoDeviceError(err error) error { + // We can safely ignore the error in the following two common situations: + // 1. The cgroup path does not exist at the time of opening(eg: the kernel is too old) + // — indicated by os.IsNotExist. + // 2. The cgroup path is deleted during the seek/read operation — indicated by + // errors.Is(err, unix.ENODEV). + // These conditions are expected and do not require special handling. + if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) { + return nil + } + return err +} + // waitFrozen polls cgroup.events until it sees "frozen 1" in it. func waitFrozen(dirPath string) (cgroups.FreezerState, error) { fd, err := cgroups.OpenFile(dirPath, "cgroup.events", unix.O_RDONLY) diff --git a/vendor/github.com/opencontainers/cgroups/fs2/fs2.go b/vendor/github.com/opencontainers/cgroups/fs2/fs2.go index c5d5a1f8e..356d08798 100644 --- a/vendor/github.com/opencontainers/cgroups/fs2/fs2.go +++ b/vendor/github.com/opencontainers/cgroups/fs2/fs2.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" "github.com/opencontainers/cgroups" @@ -83,6 +84,18 @@ func (m *Manager) Apply(pid int) error { return nil } +// AddPid adds a process with a given pid to an existing cgroup. +// The subcgroup argument is either empty, or a path relative to +// a cgroup under under the manager's cgroup. +func (m *Manager) AddPid(subcgroup string, pid int) error { + path := filepath.Join(m.dirPath, subcgroup) + if !strings.HasPrefix(path, m.dirPath) { + return fmt.Errorf("bad sub cgroup path: %s", subcgroup) + } + + return cgroups.WriteCgroupProc(path, pid) +} + func (m *Manager) GetPids() ([]int, error) { return cgroups.GetPids(m.dirPath) } diff --git a/vendor/github.com/opencontainers/cgroups/fs2/hugetlb.go b/vendor/github.com/opencontainers/cgroups/fs2/hugetlb.go index 8e1ac874a..bab9b5568 100644 --- a/vendor/github.com/opencontainers/cgroups/fs2/hugetlb.go +++ b/vendor/github.com/opencontainers/cgroups/fs2/hugetlb.go @@ -43,10 +43,11 @@ func setHugeTlb(dirPath string, r *cgroups.Resources) error { func statHugeTlb(dirPath string, stats *cgroups.Stats) error { hugetlbStats := cgroups.HugetlbStats{} rsvd := ".rsvd" + for _, pagesize := range cgroups.HugePageSizes() { + prefix := "hugetlb." + pagesize again: - prefix := "hugetlb." + pagesize + rsvd - value, err := fscommon.GetCgroupParamUint(dirPath, prefix+".current") + value, err := fscommon.GetCgroupParamUint(dirPath, prefix+rsvd+".current") if err != nil { if rsvd != "" && errors.Is(err, os.ErrNotExist) { rsvd = "" diff --git a/vendor/github.com/opencontainers/cgroups/fs2/io.go b/vendor/github.com/opencontainers/cgroups/fs2/io.go index 0f6ef7fea..3c6dcc3bf 100644 --- a/vendor/github.com/opencontainers/cgroups/fs2/io.go +++ b/vendor/github.com/opencontainers/cgroups/fs2/io.go @@ -165,11 +165,22 @@ func statIo(dirPath string, stats *cgroups.Stats) error { case "wios": op = "Write" targetTable = &parsedStats.IoServicedRecursive + + case "cost.usage": + op = "Count" + targetTable = &parsedStats.IoCostUsage + case "cost.wait": + op = "Count" + targetTable = &parsedStats.IoCostWait + case "cost.indebt": + op = "Count" + targetTable = &parsedStats.IoCostIndebt + case "cost.indelay": + op = "Count" + targetTable = &parsedStats.IoCostIndelay + default: - // Skip over entries we cannot map to cgroupv1 stats for now. - // In the future we should expand the stats struct to include - // them. - logrus.Debugf("cgroupv2 io stats: skipping over unmappable %s entry", item) + logrus.Debugf("cgroupv2 io stats: unknown entry %s", item) continue } diff --git a/vendor/github.com/opencontainers/cgroups/fs2/pids.go b/vendor/github.com/opencontainers/cgroups/fs2/pids.go index 9b82b9011..f932259ad 100644 --- a/vendor/github.com/opencontainers/cgroups/fs2/pids.go +++ b/vendor/github.com/opencontainers/cgroups/fs2/pids.go @@ -4,6 +4,7 @@ import ( "errors" "math" "os" + "strconv" "strings" "golang.org/x/sys/unix" @@ -13,19 +14,26 @@ import ( ) func isPidsSet(r *cgroups.Resources) bool { - return r.PidsLimit != 0 + return r.PidsLimit != nil } func setPids(dirPath string, r *cgroups.Resources) error { if !isPidsSet(r) { return nil } - if val := numToStr(r.PidsLimit); val != "" { - if err := cgroups.WriteFile(dirPath, "pids.max", val); err != nil { - return err - } + val := "max" + if limit := *r.PidsLimit; limit > 0 { + val = strconv.FormatInt(limit, 10) + } else if limit == 0 { + // systemd doesn't support setting pids.max to "0", so when setting + // TasksMax we need to remap it to "1". We do the same thing here to + // avoid flip-flop behaviour between the fs and systemd drivers. In + // practice, the pids cgroup behaviour is basically identical. + val = "1" + } + if err := cgroups.WriteFile(dirPath, "pids.max", val); err != nil { + return err } - return nil } diff --git a/vendor/github.com/opencontainers/cgroups/stats.go b/vendor/github.com/opencontainers/cgroups/stats.go index 6cd6253ee..01701333a 100644 --- a/vendor/github.com/opencontainers/cgroups/stats.go +++ b/vendor/github.com/opencontainers/cgroups/stats.go @@ -159,6 +159,10 @@ type BlkioStats struct { IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"` SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"` PSI *PSIStats `json:"psi,omitempty"` + IoCostUsage []BlkioStatEntry `json:"io_cost_usage,omitempty"` + IoCostWait []BlkioStatEntry `json:"io_cost_wait,omitempty"` + IoCostIndebt []BlkioStatEntry `json:"io_cost_indebt,omitempty"` + IoCostIndelay []BlkioStatEntry `json:"io_cost_indelay,omitempty"` } type HugetlbStats struct { diff --git a/vendor/github.com/opencontainers/cgroups/systemd/common.go b/vendor/github.com/opencontainers/cgroups/systemd/common.go index 875a589e3..537defbf2 100644 --- a/vendor/github.com/opencontainers/cgroups/systemd/common.go +++ b/vendor/github.com/opencontainers/cgroups/systemd/common.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "os" + "path" "strconv" "strings" "sync" @@ -208,6 +209,20 @@ func stopUnit(cm *dbusConnManager, unitName string) error { return nil } +func addPid(cm *dbusConnManager, unitName, subcgroup string, pid int) error { + absSubcgroup := subcgroup + if !path.IsAbs(absSubcgroup) { + absSubcgroup = "/" + subcgroup + } + if absSubcgroup != path.Clean(absSubcgroup) { + return fmt.Errorf("bad sub cgroup path: %s", subcgroup) + } + + return cm.retryOnDisconnect(func(c *systemdDbus.Conn) error { + return c.AttachProcessesToUnit(context.TODO(), unitName, absSubcgroup, []uint32{uint32(pid)}) + }) +} + func resetFailedUnit(cm *dbusConnManager, name string) error { return cm.retryOnDisconnect(func(c *systemdDbus.Conn) error { return c.ResetFailedUnitContext(context.TODO(), name) diff --git a/vendor/github.com/opencontainers/cgroups/systemd/dbus.go b/vendor/github.com/opencontainers/cgroups/systemd/dbus.go index bb87ae83a..c492372a7 100644 --- a/vendor/github.com/opencontainers/cgroups/systemd/dbus.go +++ b/vendor/github.com/opencontainers/cgroups/systemd/dbus.go @@ -4,10 +4,13 @@ import ( "context" "errors" "fmt" + "math/rand/v2" "sync" + "time" systemdDbus "github.com/coreos/go-systemd/v22/dbus" dbus "github.com/godbus/dbus/v5" + "golang.org/x/sys/unix" ) var ( @@ -64,10 +67,27 @@ func (d *dbusConnManager) getConnection() (*systemdDbus.Conn, error) { } func (d *dbusConnManager) newConnection() (*systemdDbus.Conn, error) { - if dbusRootless { - return newUserSystemdDbus() + newDbusConn := func() (*systemdDbus.Conn, error) { + if dbusRootless { + return newUserSystemdDbus() + } + return systemdDbus.NewWithContext(context.TODO()) + } + + var err error + for retry := range 7 { + var conn *systemdDbus.Conn + conn, err = newDbusConn() + if !errors.Is(err, unix.EAGAIN) { + return conn, err + } + // Exponential backoff (100ms * 2^attempt + ~12.5% jitter). + // At most we would expect 15 seconds of delay with 7 attempts. + delay := 100 * time.Millisecond << retry + delay += time.Duration(rand.Int64N(1 + (delay.Milliseconds() >> 3))) + time.Sleep(delay) } - return systemdDbus.NewWithContext(context.TODO()) + return nil, fmt.Errorf("dbus connection failed after several retries: %w", err) } // resetConnection resets the connection to its initial state diff --git a/vendor/github.com/opencontainers/cgroups/systemd/v1.go b/vendor/github.com/opencontainers/cgroups/systemd/v1.go index b8959adbf..96e69bb86 100644 --- a/vendor/github.com/opencontainers/cgroups/systemd/v1.go +++ b/vendor/github.com/opencontainers/cgroups/systemd/v1.go @@ -2,6 +2,7 @@ package systemd import ( "errors" + "math" "os" "path/filepath" "strings" @@ -97,9 +98,17 @@ func genV1ResourcesProperties(r *cgroups.Resources, cm *dbusConnManager) ([]syst newProp("BlockIOWeight", uint64(r.BlkioWeight))) } - if r.PidsLimit > 0 || r.PidsLimit == -1 { + if r.PidsLimit != nil { + var tasksMax uint64 + if limit := *r.PidsLimit; limit < 0 { + tasksMax = math.MaxUint64 // "infinity" + } else if limit == 0 { + tasksMax = 1 // systemd does not accept "0" for TasksMax + } else { + tasksMax = uint64(limit) + } properties = append(properties, - newProp("TasksMax", uint64(r.PidsLimit))) + newProp("TasksMax", tasksMax)) } err = addCpuset(cm, &properties, r.CpusetCpus, r.CpusetMems) @@ -215,6 +224,25 @@ func (m *LegacyManager) Apply(pid int) error { return nil } +// AddPid adds a process with a given pid to an existing cgroup. +// The subcgroup argument is either empty, or a path relative to +// a cgroup under under the manager's cgroup. +func (m *LegacyManager) AddPid(subcgroup string, pid int) error { + m.mu.Lock() + defer m.mu.Unlock() + + if err := addPid(m.dbus, getUnitName(m.cgroups), subcgroup, pid); err != nil { + return err + } + + // Since systemd only joins controllers it knows, use cgroupfs for the rest. + fsMgr, err := fs.NewManager(m.cgroups, m.paths) + if err != nil { + return err + } + return fsMgr.AddPid(subcgroup, pid) +} + func (m *LegacyManager) Destroy() error { m.mu.Lock() defer m.mu.Unlock() diff --git a/vendor/github.com/opencontainers/cgroups/systemd/v2.go b/vendor/github.com/opencontainers/cgroups/systemd/v2.go index 636b9cb01..f76c93e84 100644 --- a/vendor/github.com/opencontainers/cgroups/systemd/v2.go +++ b/vendor/github.com/opencontainers/cgroups/systemd/v2.go @@ -176,6 +176,9 @@ func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props return nil, fmt.Errorf("unified resource %q value conversion error: %w", k, err) } } + if num == 0 { + num = 1 // systemd does not accept "0" for TasksMax + } props = append(props, newProp("TasksMax", num)) @@ -256,9 +259,17 @@ func genV2ResourcesProperties(dirPath string, r *cgroups.Resources, cm *dbusConn addCPUQuota(cm, &properties, &r.CpuQuota, r.CpuPeriod) - if r.PidsLimit > 0 || r.PidsLimit == -1 { + if r.PidsLimit != nil { + var tasksMax uint64 + if limit := *r.PidsLimit; limit < 0 { + tasksMax = math.MaxUint64 // "infinity" + } else if limit == 0 { + tasksMax = 1 // systemd does not accept "0" for TasksMax + } else { + tasksMax = uint64(limit) + } properties = append(properties, - newProp("TasksMax", uint64(r.PidsLimit))) + newProp("TasksMax", tasksMax)) } err = addCpuset(cm, &properties, r.CpusetCpus, r.CpusetMems) @@ -383,6 +394,16 @@ func cgroupFilesToChown() ([]string, error) { return filesToChown, nil } +// AddPid adds a process with a given pid to an existing cgroup. +// The subcgroup argument is either empty, or a path relative to +// a cgroup under under the manager's cgroup. +func (m *UnifiedManager) AddPid(subcgroup string, pid int) error { + m.mu.Lock() + defer m.mu.Unlock() + + return addPid(m.dbus, getUnitName(m.cgroups), subcgroup, pid) +} + func (m *UnifiedManager) Destroy() error { m.mu.Lock() defer m.mu.Unlock() diff --git a/vendor/github.com/opencontainers/runc/LICENSE b/vendor/github.com/opencontainers/runc/LICENSE deleted file mode 100644 index 27448585a..000000000 --- a/vendor/github.com/opencontainers/runc/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - - 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 - - Copyright 2014 Docker, Inc. - - 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/opencontainers/runc/NOTICE b/vendor/github.com/opencontainers/runc/NOTICE deleted file mode 100644 index c29775c0d..000000000 --- a/vendor/github.com/opencontainers/runc/NOTICE +++ /dev/null @@ -1,17 +0,0 @@ -runc - -Copyright 2012-2015 Docker, Inc. - -This product includes software developed at Docker, Inc. (http://www.docker.com). - -The following is courtesy of our legal counsel: - - -Use and transfer of Docker may be subject to certain restrictions by the -United States and other governments. -It is your responsibility to ensure that your use and/or transfer does not -violate applicable laws. - -For more information, please see http://www.bis.doc.gov - -See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go deleted file mode 100644 index 53e194c74..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go +++ /dev/null @@ -1,80 +0,0 @@ -package cgroups - -import ( - "errors" - - "github.com/opencontainers/runc/libcontainer/configs" -) - -var ( - // ErrDevicesUnsupported is an error returned when a cgroup manager - // is not configured to set device rules. - ErrDevicesUnsupported = errors.New("cgroup manager is not configured to set device rules") - - // ErrRootless is returned by [Manager.Apply] when there is an error - // creating cgroup directory, and cgroup.Rootless is set. In general, - // this error is to be ignored. - ErrRootless = errors.New("cgroup manager can not access cgroup (rootless container)") - - // DevicesSetV1 and DevicesSetV2 are functions to set devices for - // cgroup v1 and v2, respectively. Unless - // [github.com/opencontainers/runc/libcontainer/cgroups/devices] - // package is imported, it is set to nil, so cgroup managers can't - // manage devices. - DevicesSetV1 func(path string, r *configs.Resources) error - DevicesSetV2 func(path string, r *configs.Resources) error -) - -type Manager interface { - // Apply creates a cgroup, if not yet created, and adds a process - // with the specified pid into that cgroup. A special value of -1 - // can be used to merely create a cgroup. - Apply(pid int) error - - // GetPids returns the PIDs of all processes inside the cgroup. - GetPids() ([]int, error) - - // GetAllPids returns the PIDs of all processes inside the cgroup - // any all its sub-cgroups. - GetAllPids() ([]int, error) - - // GetStats returns cgroups statistics. - GetStats() (*Stats, error) - - // Freeze sets the freezer cgroup to the specified state. - Freeze(state configs.FreezerState) error - - // Destroy removes cgroup. - Destroy() error - - // Path returns a cgroup path to the specified controller/subsystem. - // For cgroupv2, the argument is unused and can be empty. - Path(string) string - - // Set sets cgroup resources parameters/limits. If the argument is nil, - // the resources specified during Manager creation (or the previous call - // to Set) are used. - Set(r *configs.Resources) error - - // GetPaths returns cgroup path(s) to save in a state file in order to - // restore later. - // - // For cgroup v1, a key is cgroup subsystem name, and the value is the - // path to the cgroup for this subsystem. - // - // For cgroup v2 unified hierarchy, a key is "", and the value is the - // unified path. - GetPaths() map[string]string - - // GetCgroups returns the cgroup data as configured. - GetCgroups() (*configs.Cgroup, error) - - // GetFreezerState retrieves the current FreezerState of the cgroup. - GetFreezerState() (configs.FreezerState, error) - - // Exists returns whether the cgroup path exists or not. - Exists() bool - - // OOMKillCount reports OOM kill count for the cgroup. - OOMKillCount() (uint64, error) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/file.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/file.go deleted file mode 100644 index 78c5bcf0d..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/file.go +++ /dev/null @@ -1,216 +0,0 @@ -package cgroups - -import ( - "bytes" - "errors" - "fmt" - "os" - "path" - "strconv" - "strings" - "sync" - - "github.com/opencontainers/runc/libcontainer/utils" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -// OpenFile opens a cgroup file in a given dir with given flags. -// It is supposed to be used for cgroup files only, and returns -// an error if the file is not a cgroup file. -// -// Arguments dir and file are joined together to form an absolute path -// to a file being opened. -func OpenFile(dir, file string, flags int) (*os.File, error) { - if dir == "" { - return nil, fmt.Errorf("no directory specified for %s", file) - } - return openFile(dir, file, flags) -} - -// ReadFile reads data from a cgroup file in dir. -// It is supposed to be used for cgroup files only. -func ReadFile(dir, file string) (string, error) { - fd, err := OpenFile(dir, file, unix.O_RDONLY) - if err != nil { - return "", err - } - defer fd.Close() - var buf bytes.Buffer - - _, err = buf.ReadFrom(fd) - return buf.String(), err -} - -// WriteFile writes data to a cgroup file in dir. -// It is supposed to be used for cgroup files only. -func WriteFile(dir, file, data string) error { - fd, err := OpenFile(dir, file, unix.O_WRONLY) - if err != nil { - return err - } - defer fd.Close() - if _, err := fd.WriteString(data); err != nil { - // Having data in the error message helps in debugging. - return fmt.Errorf("failed to write %q: %w", data, err) - } - return nil -} - -// WriteFileByLine is the same as WriteFile, except if data contains newlines, -// it is written line by line. -func WriteFileByLine(dir, file, data string) error { - i := strings.Index(data, "\n") - if i == -1 { - return WriteFile(dir, file, data) - } - - fd, err := OpenFile(dir, file, unix.O_WRONLY) - if err != nil { - return err - } - defer fd.Close() - start := 0 - for { - var line string - if i == -1 { - line = data[start:] - } else { - line = data[start : start+i+1] - } - _, err := fd.WriteString(line) - if err != nil { - return fmt.Errorf("failed to write %q: %w", line, err) - } - if i == -1 { - break - } - start += i + 1 - i = strings.Index(data[start:], "\n") - } - return nil -} - -const ( - cgroupfsDir = "/sys/fs/cgroup" - cgroupfsPrefix = cgroupfsDir + "/" -) - -var ( - // TestMode is set to true by unit tests that need "fake" cgroupfs. - TestMode bool - - cgroupRootHandle *os.File - prepOnce sync.Once - prepErr error - resolveFlags uint64 -) - -func prepareOpenat2() error { - prepOnce.Do(func() { - fd, err := unix.Openat2(-1, cgroupfsDir, &unix.OpenHow{ - Flags: unix.O_DIRECTORY | unix.O_PATH | unix.O_CLOEXEC, - }) - if err != nil { - prepErr = &os.PathError{Op: "openat2", Path: cgroupfsDir, Err: err} - if err != unix.ENOSYS { - logrus.Warnf("falling back to securejoin: %s", prepErr) - } else { - logrus.Debug("openat2 not available, falling back to securejoin") - } - return - } - file := os.NewFile(uintptr(fd), cgroupfsDir) - - var st unix.Statfs_t - if err := unix.Fstatfs(int(file.Fd()), &st); err != nil { - prepErr = &os.PathError{Op: "statfs", Path: cgroupfsDir, Err: err} - logrus.Warnf("falling back to securejoin: %s", prepErr) - return - } - - cgroupRootHandle = file - resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS - if st.Type == unix.CGROUP2_SUPER_MAGIC { - // cgroupv2 has a single mountpoint and no "cpu,cpuacct" symlinks - resolveFlags |= unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_SYMLINKS - } - }) - - return prepErr -} - -func openFile(dir, file string, flags int) (*os.File, error) { - mode := os.FileMode(0) - if TestMode && flags&os.O_WRONLY != 0 { - // "emulate" cgroup fs for unit tests - flags |= os.O_TRUNC | os.O_CREATE - mode = 0o600 - } - path := path.Join(dir, utils.CleanPath(file)) - if prepareOpenat2() != nil { - return openFallback(path, flags, mode) - } - relPath := strings.TrimPrefix(path, cgroupfsPrefix) - if len(relPath) == len(path) { // non-standard path, old system? - return openFallback(path, flags, mode) - } - - fd, err := unix.Openat2(int(cgroupRootHandle.Fd()), relPath, - &unix.OpenHow{ - Resolve: resolveFlags, - Flags: uint64(flags) | unix.O_CLOEXEC, - Mode: uint64(mode), - }) - if err != nil { - err = &os.PathError{Op: "openat2", Path: path, Err: err} - // Check if cgroupRootHandle is still opened to cgroupfsDir - // (happens when this package is incorrectly used - // across the chroot/pivot_root/mntns boundary, or - // when /sys/fs/cgroup is remounted). - // - // TODO: if such usage will ever be common, amend this - // to reopen cgroupRootHandle and retry openat2. - fdPath, closer := utils.ProcThreadSelf("fd/" + strconv.Itoa(int(cgroupRootHandle.Fd()))) - defer closer() - fdDest, _ := os.Readlink(fdPath) - if fdDest != cgroupfsDir { - // Wrap the error so it is clear that cgroupRootHandle - // is opened to an unexpected/wrong directory. - err = fmt.Errorf("cgroupRootHandle %d unexpectedly opened to %s != %s: %w", - cgroupRootHandle.Fd(), fdDest, cgroupfsDir, err) - } - return nil, err - } - - return os.NewFile(uintptr(fd), path), nil -} - -var errNotCgroupfs = errors.New("not a cgroup file") - -// Can be changed by unit tests. -var openFallback = openAndCheck - -// openAndCheck is used when openat2(2) is not available. It checks the opened -// file is on cgroupfs, returning an error otherwise. -func openAndCheck(path string, flags int, mode os.FileMode) (*os.File, error) { - fd, err := os.OpenFile(path, flags, mode) - if err != nil { - return nil, err - } - if TestMode { - return fd, nil - } - // Check this is a cgroupfs file. - var st unix.Statfs_t - if err := unix.Fstatfs(int(fd.Fd()), &st); err != nil { - _ = fd.Close() - return nil, &os.PathError{Op: "statfs", Path: path, Err: err} - } - if st.Type != unix.CGROUP_SUPER_MAGIC && st.Type != unix.CGROUP2_SUPER_MAGIC { - _ = fd.Close() - return nil, &os.PathError{Op: "open", Path: path, Err: errNotCgroupfs} - } - - return fd, nil -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/getallpids.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/getallpids.go deleted file mode 100644 index 1355a5101..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/getallpids.go +++ /dev/null @@ -1,27 +0,0 @@ -package cgroups - -import ( - "io/fs" - "path/filepath" -) - -// GetAllPids returns all pids from the cgroup identified by path, and all its -// sub-cgroups. -func GetAllPids(path string) ([]int, error) { - var pids []int - err := filepath.WalkDir(path, func(p string, d fs.DirEntry, iErr error) error { - if iErr != nil { - return iErr - } - if !d.IsDir() { - return nil - } - cPids, err := readProcsFile(p) - if err != nil { - return err - } - pids = append(pids, cPids...) - return nil - }) - return pids, err -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go deleted file mode 100644 index b475567d8..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go +++ /dev/null @@ -1,200 +0,0 @@ -package cgroups - -type ThrottlingData struct { - // Number of periods with throttling active - Periods uint64 `json:"periods,omitempty"` - // Number of periods when the container hit its throttling limit. - ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` - // Aggregate time the container was throttled for in nanoseconds. - ThrottledTime uint64 `json:"throttled_time,omitempty"` -} - -// CpuUsage denotes the usage of a CPU. -// All CPU stats are aggregate since container inception. -type CpuUsage struct { - // Total CPU time consumed. - // Units: nanoseconds. - TotalUsage uint64 `json:"total_usage,omitempty"` - // Total CPU time consumed per core. - // Units: nanoseconds. - PercpuUsage []uint64 `json:"percpu_usage,omitempty"` - // CPU time consumed per core in kernel mode - // Units: nanoseconds. - PercpuUsageInKernelmode []uint64 `json:"percpu_usage_in_kernelmode"` - // CPU time consumed per core in user mode - // Units: nanoseconds. - PercpuUsageInUsermode []uint64 `json:"percpu_usage_in_usermode"` - // Time spent by tasks of the cgroup in kernel mode. - // Units: nanoseconds. - UsageInKernelmode uint64 `json:"usage_in_kernelmode"` - // Time spent by tasks of the cgroup in user mode. - // Units: nanoseconds. - UsageInUsermode uint64 `json:"usage_in_usermode"` -} - -type PSIData struct { - Avg10 float64 `json:"avg10"` - Avg60 float64 `json:"avg60"` - Avg300 float64 `json:"avg300"` - Total uint64 `json:"total"` -} - -type PSIStats struct { - Some PSIData `json:"some,omitempty"` - Full PSIData `json:"full,omitempty"` -} - -type CpuStats struct { - CpuUsage CpuUsage `json:"cpu_usage,omitempty"` - ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` - PSI *PSIStats `json:"psi,omitempty"` -} - -type CPUSetStats struct { - // List of the physical numbers of the CPUs on which processes - // in that cpuset are allowed to execute - CPUs []uint16 `json:"cpus,omitempty"` - // cpu_exclusive flag - CPUExclusive uint64 `json:"cpu_exclusive"` - // List of memory nodes on which processes in that cpuset - // are allowed to allocate memory - Mems []uint16 `json:"mems,omitempty"` - // mem_hardwall flag - MemHardwall uint64 `json:"mem_hardwall"` - // mem_exclusive flag - MemExclusive uint64 `json:"mem_exclusive"` - // memory_migrate flag - MemoryMigrate uint64 `json:"memory_migrate"` - // memory_spread page flag - MemorySpreadPage uint64 `json:"memory_spread_page"` - // memory_spread slab flag - MemorySpreadSlab uint64 `json:"memory_spread_slab"` - // memory_pressure - MemoryPressure uint64 `json:"memory_pressure"` - // sched_load balance flag - SchedLoadBalance uint64 `json:"sched_load_balance"` - // sched_relax_domain_level - SchedRelaxDomainLevel int64 `json:"sched_relax_domain_level"` -} - -type MemoryData struct { - Usage uint64 `json:"usage,omitempty"` - MaxUsage uint64 `json:"max_usage,omitempty"` - Failcnt uint64 `json:"failcnt"` - Limit uint64 `json:"limit"` -} - -type MemoryStats struct { - // memory used for cache - Cache uint64 `json:"cache,omitempty"` - // usage of memory - Usage MemoryData `json:"usage,omitempty"` - // usage of memory + swap - SwapUsage MemoryData `json:"swap_usage,omitempty"` - // usage of swap only - SwapOnlyUsage MemoryData `json:"swap_only_usage,omitempty"` - // usage of kernel memory - KernelUsage MemoryData `json:"kernel_usage,omitempty"` - // usage of kernel TCP memory - KernelTCPUsage MemoryData `json:"kernel_tcp_usage,omitempty"` - // usage of memory pages by NUMA node - // see chapter 5.6 of memory controller documentation - PageUsageByNUMA PageUsageByNUMA `json:"page_usage_by_numa,omitempty"` - // if true, memory usage is accounted for throughout a hierarchy of cgroups. - UseHierarchy bool `json:"use_hierarchy"` - - Stats map[string]uint64 `json:"stats,omitempty"` - PSI *PSIStats `json:"psi,omitempty"` -} - -type PageUsageByNUMA struct { - // Embedding is used as types can't be recursive. - PageUsageByNUMAInner - Hierarchical PageUsageByNUMAInner `json:"hierarchical,omitempty"` -} - -type PageUsageByNUMAInner struct { - Total PageStats `json:"total,omitempty"` - File PageStats `json:"file,omitempty"` - Anon PageStats `json:"anon,omitempty"` - Unevictable PageStats `json:"unevictable,omitempty"` -} - -type PageStats struct { - Total uint64 `json:"total,omitempty"` - Nodes map[uint8]uint64 `json:"nodes,omitempty"` -} - -type PidsStats struct { - // number of pids in the cgroup - Current uint64 `json:"current,omitempty"` - // active pids hard limit - Limit uint64 `json:"limit,omitempty"` -} - -type BlkioStatEntry struct { - Major uint64 `json:"major,omitempty"` - Minor uint64 `json:"minor,omitempty"` - Op string `json:"op,omitempty"` - Value uint64 `json:"value,omitempty"` -} - -type BlkioStats struct { - // number of bytes transferred to and from the block device - IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"` - IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"` - IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"` - IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"` - IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"` - IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"` - IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"` - SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"` - PSI *PSIStats `json:"psi,omitempty"` -} - -type HugetlbStats struct { - // current res_counter usage for hugetlb - Usage uint64 `json:"usage,omitempty"` - // maximum usage ever recorded. - MaxUsage uint64 `json:"max_usage,omitempty"` - // number of times hugetlb usage allocation failure. - Failcnt uint64 `json:"failcnt"` -} - -type RdmaEntry struct { - Device string `json:"device,omitempty"` - HcaHandles uint32 `json:"hca_handles,omitempty"` - HcaObjects uint32 `json:"hca_objects,omitempty"` -} - -type RdmaStats struct { - RdmaLimit []RdmaEntry `json:"rdma_limit,omitempty"` - RdmaCurrent []RdmaEntry `json:"rdma_current,omitempty"` -} - -type MiscStats struct { - // current resource usage for a key in misc - Usage uint64 `json:"usage,omitempty"` - // number of times the resource usage was about to go over the max boundary - Events uint64 `json:"events,omitempty"` -} - -type Stats struct { - CpuStats CpuStats `json:"cpu_stats,omitempty"` - CPUSetStats CPUSetStats `json:"cpuset_stats,omitempty"` - MemoryStats MemoryStats `json:"memory_stats,omitempty"` - PidsStats PidsStats `json:"pids_stats,omitempty"` - BlkioStats BlkioStats `json:"blkio_stats,omitempty"` - // the map is in the format "size of hugepage: stats of the hugepage" - HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"` - RdmaStats RdmaStats `json:"rdma_stats,omitempty"` - // the map is in the format "misc resource name: stats of the key" - MiscStats map[string]MiscStats `json:"misc_stats,omitempty"` -} - -func NewStats() *Stats { - memoryStats := MemoryStats{Stats: make(map[string]uint64)} - hugetlbStats := make(map[string]HugetlbStats) - miscStats := make(map[string]MiscStats) - return &Stats{MemoryStats: memoryStats, HugetlbStats: hugetlbStats, MiscStats: miscStats} -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go deleted file mode 100644 index d404647c8..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go +++ /dev/null @@ -1,468 +0,0 @@ -package cgroups - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - "time" - - "github.com/moby/sys/userns" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -const ( - CgroupProcesses = "cgroup.procs" - unifiedMountpoint = "/sys/fs/cgroup" - hybridMountpoint = "/sys/fs/cgroup/unified" -) - -var ( - isUnifiedOnce sync.Once - isUnified bool - isHybridOnce sync.Once - isHybrid bool -) - -// IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode. -func IsCgroup2UnifiedMode() bool { - isUnifiedOnce.Do(func() { - var st unix.Statfs_t - err := unix.Statfs(unifiedMountpoint, &st) - if err != nil { - level := logrus.WarnLevel - if os.IsNotExist(err) && userns.RunningInUserNS() { - // For rootless containers, sweep it under the rug. - level = logrus.DebugLevel - } - logrus.StandardLogger().Logf(level, - "statfs %s: %v; assuming cgroup v1", unifiedMountpoint, err) - } - isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC - }) - return isUnified -} - -// IsCgroup2HybridMode returns whether we are running in cgroup v2 hybrid mode. -func IsCgroup2HybridMode() bool { - isHybridOnce.Do(func() { - var st unix.Statfs_t - err := unix.Statfs(hybridMountpoint, &st) - if err != nil { - isHybrid = false - if !os.IsNotExist(err) { - // Report unexpected errors. - logrus.WithError(err).Debugf("statfs(%q) failed", hybridMountpoint) - } - return - } - isHybrid = st.Type == unix.CGROUP2_SUPER_MAGIC - }) - return isHybrid -} - -type Mount struct { - Mountpoint string - Root string - Subsystems []string -} - -// GetCgroupMounts returns the mounts for the cgroup subsystems. -// all indicates whether to return just the first instance or all the mounts. -// This function should not be used from cgroupv2 code, as in this case -// all the controllers are available under the constant unifiedMountpoint. -func GetCgroupMounts(all bool) ([]Mount, error) { - if IsCgroup2UnifiedMode() { - // TODO: remove cgroupv2 case once all external users are converted - availableControllers, err := GetAllSubsystems() - if err != nil { - return nil, err - } - m := Mount{ - Mountpoint: unifiedMountpoint, - Root: unifiedMountpoint, - Subsystems: availableControllers, - } - return []Mount{m}, nil - } - - return getCgroupMountsV1(all) -} - -// GetAllSubsystems returns all the cgroup subsystems supported by the kernel -func GetAllSubsystems() ([]string, error) { - // /proc/cgroups is meaningless for v2 - // https://github.com/torvalds/linux/blob/v5.3/Documentation/admin-guide/cgroup-v2.rst#deprecated-v1-core-features - if IsCgroup2UnifiedMode() { - // "pseudo" controllers do not appear in /sys/fs/cgroup/cgroup.controllers. - // - devices: implemented in kernel 4.15 - // - freezer: implemented in kernel 5.2 - // We assume these are always available, as it is hard to detect availability. - pseudo := []string{"devices", "freezer"} - data, err := ReadFile("/sys/fs/cgroup", "cgroup.controllers") - if err != nil { - return nil, err - } - subsystems := append(pseudo, strings.Fields(data)...) - return subsystems, nil - } - f, err := os.Open("/proc/cgroups") - if err != nil { - return nil, err - } - defer f.Close() - - subsystems := []string{} - - s := bufio.NewScanner(f) - for s.Scan() { - text := s.Text() - if text[0] != '#' { - parts := strings.Fields(text) - if len(parts) >= 4 && parts[3] != "0" { - subsystems = append(subsystems, parts[0]) - } - } - } - if err := s.Err(); err != nil { - return nil, err - } - return subsystems, nil -} - -func readProcsFile(dir string) (out []int, _ error) { - file := CgroupProcesses - retry := true - -again: - f, err := OpenFile(dir, file, os.O_RDONLY) - if err != nil { - return nil, err - } - defer f.Close() - - s := bufio.NewScanner(f) - for s.Scan() { - if t := s.Text(); t != "" { - pid, err := strconv.Atoi(t) - if err != nil { - return nil, err - } - out = append(out, pid) - } - } - if errors.Is(s.Err(), unix.ENOTSUP) && retry { - // For a threaded cgroup, read returns ENOTSUP, and we should - // read from cgroup.threads instead. - file = "cgroup.threads" - retry = false - goto again - } - return out, s.Err() -} - -// ParseCgroupFile parses the given cgroup file, typically /proc/self/cgroup -// or /proc//cgroup, into a map of subsystems to cgroup paths, e.g. -// -// "cpu": "/user.slice/user-1000.slice" -// "pids": "/user.slice/user-1000.slice" -// -// etc. -// -// Note that for cgroup v2 unified hierarchy, there are no per-controller -// cgroup paths, so the resulting map will have a single element where the key -// is empty string ("") and the value is the cgroup path the is in. -func ParseCgroupFile(path string) (map[string]string, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - return parseCgroupFromReader(f) -} - -// helper function for ParseCgroupFile to make testing easier -func parseCgroupFromReader(r io.Reader) (map[string]string, error) { - s := bufio.NewScanner(r) - cgroups := make(map[string]string) - - for s.Scan() { - text := s.Text() - // from cgroups(7): - // /proc/[pid]/cgroup - // ... - // For each cgroup hierarchy ... there is one entry - // containing three colon-separated fields of the form: - // hierarchy-ID:subsystem-list:cgroup-path - parts := strings.SplitN(text, ":", 3) - if len(parts) < 3 { - return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text) - } - - for _, subs := range strings.Split(parts[1], ",") { - cgroups[subs] = parts[2] - } - } - if err := s.Err(); err != nil { - return nil, err - } - - return cgroups, nil -} - -func PathExists(path string) bool { - if _, err := os.Stat(path); err != nil { - return false - } - return true -} - -// rmdir tries to remove a directory, optionally retrying on EBUSY. -func rmdir(path string, retry bool) error { - delay := time.Millisecond - tries := 10 - -again: - err := unix.Rmdir(path) - switch err { // nolint:errorlint // unix errors are bare - case nil, unix.ENOENT: - return nil - case unix.EINTR: - goto again - case unix.EBUSY: - if retry && tries > 0 { - time.Sleep(delay) - delay *= 2 - tries-- - goto again - - } - } - return &os.PathError{Op: "rmdir", Path: path, Err: err} -} - -// RemovePath aims to remove cgroup path. It does so recursively, -// by removing any subdirectories (sub-cgroups) first. -func RemovePath(path string) error { - // Try the fast path first; don't retry on EBUSY yet. - if err := rmdir(path, false); err == nil { - return nil - } - - // There are many reasons why rmdir can fail, including: - // 1. cgroup have existing sub-cgroups; - // 2. cgroup (still) have some processes (that are about to vanish); - // 3. lack of permission (one example is read-only /sys/fs/cgroup mount, - // in which case rmdir returns EROFS even for for a non-existent path, - // see issue 4518). - // - // Using os.ReadDir here kills two birds with one stone: check if - // the directory exists (handling scenario 3 above), and use - // directory contents to remove sub-cgroups (handling scenario 1). - infos, err := os.ReadDir(path) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - // Let's remove sub-cgroups, if any. - for _, info := range infos { - if info.IsDir() { - if err = RemovePath(filepath.Join(path, info.Name())); err != nil { - return err - } - } - } - // Finally, try rmdir again, this time with retries on EBUSY, - // which may help with scenario 2 above. - return rmdir(path, true) -} - -// RemovePaths iterates over the provided paths removing them. -func RemovePaths(paths map[string]string) (err error) { - for s, p := range paths { - if err := RemovePath(p); err == nil { - delete(paths, s) - } - } - if len(paths) == 0 { - clear(paths) - return nil - } - return fmt.Errorf("Failed to remove paths: %v", paths) -} - -var ( - hugePageSizes []string - initHPSOnce sync.Once -) - -func HugePageSizes() []string { - initHPSOnce.Do(func() { - dir, err := os.OpenFile("/sys/kernel/mm/hugepages", unix.O_DIRECTORY|unix.O_RDONLY, 0) - if err != nil { - return - } - files, err := dir.Readdirnames(0) - dir.Close() - if err != nil { - return - } - - hugePageSizes, err = getHugePageSizeFromFilenames(files) - if err != nil { - logrus.Warn("HugePageSizes: ", err) - } - }) - - return hugePageSizes -} - -func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) { - pageSizes := make([]string, 0, len(fileNames)) - var warn error - - for _, file := range fileNames { - // example: hugepages-1048576kB - val := strings.TrimPrefix(file, "hugepages-") - if len(val) == len(file) { - // Unexpected file name: no prefix found, ignore it. - continue - } - // The suffix is always "kB" (as of Linux 5.13). If we find - // something else, produce an error but keep going. - eLen := len(val) - 2 - val = strings.TrimSuffix(val, "kB") - if len(val) != eLen { - // Highly unlikely. - if warn == nil { - warn = errors.New(file + `: invalid suffix (expected "kB")`) - } - continue - } - size, err := strconv.Atoi(val) - if err != nil { - // Highly unlikely. - if warn == nil { - warn = fmt.Errorf("%s: %w", file, err) - } - continue - } - // Model after https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/hugetlb_cgroup.c?id=eff48ddeab782e35e58ccc8853f7386bbae9dec4#n574 - // but in our case the size is in KB already. - if size >= (1 << 20) { - val = strconv.Itoa(size>>20) + "GB" - } else if size >= (1 << 10) { - val = strconv.Itoa(size>>10) + "MB" - } else { - val += "KB" - } - pageSizes = append(pageSizes, val) - } - - return pageSizes, warn -} - -// GetPids returns all pids, that were added to cgroup at path. -func GetPids(dir string) ([]int, error) { - return readProcsFile(dir) -} - -// WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file -func WriteCgroupProc(dir string, pid int) error { - // Normally dir should not be empty, one case is that cgroup subsystem - // is not mounted, we will get empty dir, and we want it fail here. - if dir == "" { - return fmt.Errorf("no such directory for %s", CgroupProcesses) - } - - // Dont attach any pid to the cgroup if -1 is specified as a pid - if pid == -1 { - return nil - } - - file, err := OpenFile(dir, CgroupProcesses, os.O_WRONLY) - if err != nil { - return fmt.Errorf("failed to write %v: %w", pid, err) - } - defer file.Close() - - for i := 0; i < 5; i++ { - _, err = file.WriteString(strconv.Itoa(pid)) - if err == nil { - return nil - } - - // EINVAL might mean that the task being added to cgroup.procs is in state - // TASK_NEW. We should attempt to do so again. - if errors.Is(err, unix.EINVAL) { - time.Sleep(30 * time.Millisecond) - continue - } - - return fmt.Errorf("failed to write %v: %w", pid, err) - } - return err -} - -// Since the OCI spec is designed for cgroup v1, in some cases -// there is need to convert from the cgroup v1 configuration to cgroup v2 -// the formula for cpuShares is y = (1 + ((x - 2) * 9999) / 262142) -// convert from [2-262144] to [1-10000] -// 262144 comes from Linux kernel definition "#define MAX_SHARES (1UL << 18)" -func ConvertCPUSharesToCgroupV2Value(cpuShares uint64) uint64 { - if cpuShares == 0 { - return 0 - } - return (1 + ((cpuShares-2)*9999)/262142) -} - -// ConvertMemorySwapToCgroupV2Value converts MemorySwap value from OCI spec -// for use by cgroup v2 drivers. A conversion is needed since Resources.MemorySwap -// is defined as memory+swap combined, while in cgroup v2 swap is a separate value, -// so we need to subtract memory from it where it makes sense. -func ConvertMemorySwapToCgroupV2Value(memorySwap, memory int64) (int64, error) { - switch { - case memory == -1 && memorySwap == 0: - // For compatibility with cgroup1 controller, set swap to unlimited in - // case the memory is set to unlimited and the swap is not explicitly set, - // treating the request as "set both memory and swap to unlimited". - return -1, nil - case memorySwap == -1, memorySwap == 0: - // Treat -1 ("max") and 0 ("unset") swap as is. - return memorySwap, nil - case memory == -1: - // Unlimited memory, so treat swap as is. - return memorySwap, nil - case memory == 0: - // Unset or unknown memory, can't calculate swap. - return 0, errors.New("unable to set swap limit without memory limit") - case memory < 0: - // Does not make sense to subtract a negative value. - return 0, fmt.Errorf("invalid memory value: %d", memory) - case memorySwap < memory: - // Sanity check. - return 0, errors.New("memory+swap limit should be >= memory limit") - } - - return memorySwap - memory, nil -} - -// Since the OCI spec is designed for cgroup v1, in some cases -// there is need to convert from the cgroup v1 configuration to cgroup v2 -// the formula for BlkIOWeight to IOWeight is y = (1 + (x - 10) * 9999 / 990) -// convert linearly from [10-1000] to [1-10000] -func ConvertBlkIOToIOWeightValue(blkIoWeight uint16) uint64 { - if blkIoWeight == 0 { - return 0 - } - return 1 + (uint64(blkIoWeight)-10)*9999/990 -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go deleted file mode 100644 index 81193e209..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/v1_utils.go +++ /dev/null @@ -1,277 +0,0 @@ -package cgroups - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "sync" - "syscall" - - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/moby/sys/mountinfo" - "golang.org/x/sys/unix" -) - -// Code in this source file are specific to cgroup v1, -// and must not be used from any cgroup v2 code. - -const ( - CgroupNamePrefix = "name=" - defaultPrefix = "/sys/fs/cgroup" -) - -var ( - errUnified = errors.New("not implemented for cgroup v2 unified hierarchy") - ErrV1NoUnified = errors.New("invalid configuration: cannot use unified on cgroup v1") - - readMountinfoOnce sync.Once - readMountinfoErr error - cgroupMountinfo []*mountinfo.Info -) - -type NotFoundError struct { - Subsystem string -} - -func (e *NotFoundError) Error() string { - return fmt.Sprintf("mountpoint for %s not found", e.Subsystem) -} - -func NewNotFoundError(sub string) error { - return &NotFoundError{ - Subsystem: sub, - } -} - -func IsNotFound(err error) bool { - var nfErr *NotFoundError - return errors.As(err, &nfErr) -} - -func tryDefaultPath(cgroupPath, subsystem string) string { - if !strings.HasPrefix(defaultPrefix, cgroupPath) { - return "" - } - - // remove possible prefix - subsystem = strings.TrimPrefix(subsystem, CgroupNamePrefix) - - // Make sure we're still under defaultPrefix, and resolve - // a possible symlink (like cpu -> cpu,cpuacct). - path, err := securejoin.SecureJoin(defaultPrefix, subsystem) - if err != nil { - return "" - } - - // (1) path should be a directory. - st, err := os.Lstat(path) - if err != nil || !st.IsDir() { - return "" - } - - // (2) path should be a mount point. - pst, err := os.Lstat(filepath.Dir(path)) - if err != nil { - return "" - } - - if st.Sys().(*syscall.Stat_t).Dev == pst.Sys().(*syscall.Stat_t).Dev { - // parent dir has the same dev -- path is not a mount point - return "" - } - - // (3) path should have 'cgroup' fs type. - fst := unix.Statfs_t{} - err = unix.Statfs(path, &fst) - if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC { - return "" - } - - return path -} - -// readCgroupMountinfo returns a list of cgroup v1 mounts (i.e. the ones -// with fstype of "cgroup") for the current running process. -// -// The results are cached (to avoid re-reading mountinfo which is relatively -// expensive), so it is assumed that cgroup mounts are not being changed. -func readCgroupMountinfo() ([]*mountinfo.Info, error) { - readMountinfoOnce.Do(func() { - // mountinfo.GetMounts uses /proc/thread-self, so we can use it without - // issues. - cgroupMountinfo, readMountinfoErr = mountinfo.GetMounts( - mountinfo.FSTypeFilter("cgroup"), - ) - }) - return cgroupMountinfo, readMountinfoErr -} - -// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt -func FindCgroupMountpoint(cgroupPath, subsystem string) (string, error) { - if IsCgroup2UnifiedMode() { - return "", errUnified - } - - // If subsystem is empty, we look for the cgroupv2 hybrid path. - if len(subsystem) == 0 { - return hybridMountpoint, nil - } - - // Avoid parsing mountinfo by trying the default path first, if possible. - if path := tryDefaultPath(cgroupPath, subsystem); path != "" { - return path, nil - } - - mnt, _, err := FindCgroupMountpointAndRoot(cgroupPath, subsystem) - return mnt, err -} - -func FindCgroupMountpointAndRoot(cgroupPath, subsystem string) (string, string, error) { - if IsCgroup2UnifiedMode() { - return "", "", errUnified - } - - mi, err := readCgroupMountinfo() - if err != nil { - return "", "", err - } - - return findCgroupMountpointAndRootFromMI(mi, cgroupPath, subsystem) -} - -func findCgroupMountpointAndRootFromMI(mounts []*mountinfo.Info, cgroupPath, subsystem string) (string, string, error) { - for _, mi := range mounts { - if strings.HasPrefix(mi.Mountpoint, cgroupPath) { - for _, opt := range strings.Split(mi.VFSOptions, ",") { - if opt == subsystem { - return mi.Mountpoint, mi.Root, nil - } - } - } - } - - return "", "", NewNotFoundError(subsystem) -} - -func (m Mount) GetOwnCgroup(cgroups map[string]string) (string, error) { - if len(m.Subsystems) == 0 { - return "", errors.New("no subsystem for mount") - } - - return getControllerPath(m.Subsystems[0], cgroups) -} - -func getCgroupMountsHelper(ss map[string]bool, mounts []*mountinfo.Info, all bool) ([]Mount, error) { - res := make([]Mount, 0, len(ss)) - numFound := 0 - for _, mi := range mounts { - m := Mount{ - Mountpoint: mi.Mountpoint, - Root: mi.Root, - } - for _, opt := range strings.Split(mi.VFSOptions, ",") { - seen, known := ss[opt] - if !known || (!all && seen) { - continue - } - ss[opt] = true - opt = strings.TrimPrefix(opt, CgroupNamePrefix) - m.Subsystems = append(m.Subsystems, opt) - numFound++ - } - if len(m.Subsystems) > 0 || all { - res = append(res, m) - } - if !all && numFound >= len(ss) { - break - } - } - return res, nil -} - -func getCgroupMountsV1(all bool) ([]Mount, error) { - mi, err := readCgroupMountinfo() - if err != nil { - return nil, err - } - - // We don't need to use /proc/thread-self here because runc always runs - // with every thread in the same cgroup. This lets us avoid having to do - // runtime.LockOSThread. - allSubsystems, err := ParseCgroupFile("/proc/self/cgroup") - if err != nil { - return nil, err - } - - allMap := make(map[string]bool) - for s := range allSubsystems { - allMap[s] = false - } - - return getCgroupMountsHelper(allMap, mi, all) -} - -// GetOwnCgroup returns the relative path to the cgroup docker is running in. -func GetOwnCgroup(subsystem string) (string, error) { - if IsCgroup2UnifiedMode() { - return "", errUnified - } - - // We don't need to use /proc/thread-self here because runc always runs - // with every thread in the same cgroup. This lets us avoid having to do - // runtime.LockOSThread. - cgroups, err := ParseCgroupFile("/proc/self/cgroup") - if err != nil { - return "", err - } - - return getControllerPath(subsystem, cgroups) -} - -func GetOwnCgroupPath(subsystem string) (string, error) { - cgroup, err := GetOwnCgroup(subsystem) - if err != nil { - return "", err - } - - // If subsystem is empty, we look for the cgroupv2 hybrid path. - if len(subsystem) == 0 { - return hybridMountpoint, nil - } - - return getCgroupPathHelper(subsystem, cgroup) -} - -func getCgroupPathHelper(subsystem, cgroup string) (string, error) { - mnt, root, err := FindCgroupMountpointAndRoot("", subsystem) - if err != nil { - return "", err - } - - // This is needed for nested containers, because in /proc/self/cgroup we - // see paths from host, which don't exist in container. - relCgroup, err := filepath.Rel(root, cgroup) - if err != nil { - return "", err - } - - return filepath.Join(mnt, relCgroup), nil -} - -func getControllerPath(subsystem string, cgroups map[string]string) (string, error) { - if IsCgroup2UnifiedMode() { - return "", errUnified - } - - if p, ok := cgroups[subsystem]; ok { - return p, nil - } - - if p, ok := cgroups[CgroupNamePrefix+subsystem]; ok { - return p, nil - } - - return "", NewNotFoundError(subsystem) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go deleted file mode 100644 index 865344f99..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go +++ /dev/null @@ -1,66 +0,0 @@ -package configs - -import "fmt" - -// BlockIODevice holds major:minor format supported in blkio cgroup. -type BlockIODevice struct { - // Major is the device's major number - Major int64 `json:"major"` - // Minor is the device's minor number - Minor int64 `json:"minor"` -} - -// WeightDevice struct holds a `major:minor weight`|`major:minor leaf_weight` pair -type WeightDevice struct { - BlockIODevice - // Weight is the bandwidth rate for the device, range is from 10 to 1000 - Weight uint16 `json:"weight"` - // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only - LeafWeight uint16 `json:"leafWeight"` -} - -// NewWeightDevice returns a configured WeightDevice pointer -func NewWeightDevice(major, minor int64, weight, leafWeight uint16) *WeightDevice { - wd := &WeightDevice{} - wd.Major = major - wd.Minor = minor - wd.Weight = weight - wd.LeafWeight = leafWeight - return wd -} - -// WeightString formats the struct to be writable to the cgroup specific file -func (wd *WeightDevice) WeightString() string { - return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight) -} - -// LeafWeightString formats the struct to be writable to the cgroup specific file -func (wd *WeightDevice) LeafWeightString() string { - return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight) -} - -// ThrottleDevice struct holds a `major:minor rate_per_second` pair -type ThrottleDevice struct { - BlockIODevice - // Rate is the IO rate limit per cgroup per device - Rate uint64 `json:"rate"` -} - -// NewThrottleDevice returns a configured ThrottleDevice pointer -func NewThrottleDevice(major, minor int64, rate uint64) *ThrottleDevice { - td := &ThrottleDevice{} - td.Major = major - td.Minor = minor - td.Rate = rate - return td -} - -// String formats the struct to be writable to the cgroup specific file -func (td *ThrottleDevice) String() string { - return fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate) -} - -// StringName formats the struct to be writable to the cgroup specific file -func (td *ThrottleDevice) StringName(name string) string { - return fmt.Sprintf("%d:%d %s=%d", td.Major, td.Minor, name, td.Rate) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go deleted file mode 100644 index 4a34cf76f..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go +++ /dev/null @@ -1,169 +0,0 @@ -package configs - -import ( - systemdDbus "github.com/coreos/go-systemd/v22/dbus" - "github.com/opencontainers/runc/libcontainer/devices" -) - -type FreezerState string - -const ( - Undefined FreezerState = "" - Frozen FreezerState = "FROZEN" - Thawed FreezerState = "THAWED" -) - -// Cgroup holds properties of a cgroup on Linux. -type Cgroup struct { - // Name specifies the name of the cgroup - Name string `json:"name,omitempty"` - - // Parent specifies the name of parent of cgroup or slice - Parent string `json:"parent,omitempty"` - - // Path specifies the path to cgroups that are created and/or joined by the container. - // The path is assumed to be relative to the host system cgroup mountpoint. - Path string `json:"path"` - - // ScopePrefix describes prefix for the scope name - ScopePrefix string `json:"scope_prefix"` - - // Resources contains various cgroups settings to apply - *Resources - - // Systemd tells if systemd should be used to manage cgroups. - Systemd bool - - // SystemdProps are any additional properties for systemd, - // derived from org.systemd.property.xxx annotations. - // Ignored unless systemd is used for managing cgroups. - SystemdProps []systemdDbus.Property `json:"-"` - - // Rootless tells if rootless cgroups should be used. - Rootless bool - - // The host UID that should own the cgroup, or nil to accept - // the default ownership. This should only be set when the - // cgroupfs is to be mounted read/write. - // Not all cgroup manager implementations support changing - // the ownership. - OwnerUID *int `json:"owner_uid,omitempty"` -} - -type Resources struct { - // Devices is the set of access rules for devices in the container. - Devices []*devices.Rule `json:"devices"` - - // Memory limit (in bytes) - Memory int64 `json:"memory"` - - // Memory reservation or soft_limit (in bytes) - MemoryReservation int64 `json:"memory_reservation"` - - // Total memory usage (memory + swap); set `-1` to enable unlimited swap - MemorySwap int64 `json:"memory_swap"` - - // CPU shares (relative weight vs. other containers) - CpuShares uint64 `json:"cpu_shares"` - - // CPU hardcap limit (in usecs). Allowed cpu time in a given period. - CpuQuota int64 `json:"cpu_quota"` - - // CPU hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst in a given period. - CpuBurst *uint64 `json:"cpu_burst"` //nolint:revive - - // CPU period to be used for hardcapping (in usecs). 0 to use system default. - CpuPeriod uint64 `json:"cpu_period"` - - // How many time CPU will use in realtime scheduling (in usecs). - CpuRtRuntime int64 `json:"cpu_rt_quota"` - - // CPU period to be used for realtime scheduling (in usecs). - CpuRtPeriod uint64 `json:"cpu_rt_period"` - - // CPU to use - CpusetCpus string `json:"cpuset_cpus"` - - // MEM to use - CpusetMems string `json:"cpuset_mems"` - - // cgroup SCHED_IDLE - CPUIdle *int64 `json:"cpu_idle,omitempty"` - - // Process limit; set <= `0' to disable limit. - PidsLimit int64 `json:"pids_limit"` - - // Specifies per cgroup weight, range is from 10 to 1000. - BlkioWeight uint16 `json:"blkio_weight"` - - // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only - BlkioLeafWeight uint16 `json:"blkio_leaf_weight"` - - // Weight per cgroup per device, can override BlkioWeight. - BlkioWeightDevice []*WeightDevice `json:"blkio_weight_device"` - - // IO read rate limit per cgroup per device, bytes per second. - BlkioThrottleReadBpsDevice []*ThrottleDevice `json:"blkio_throttle_read_bps_device"` - - // IO write rate limit per cgroup per device, bytes per second. - BlkioThrottleWriteBpsDevice []*ThrottleDevice `json:"blkio_throttle_write_bps_device"` - - // IO read rate limit per cgroup per device, IO per second. - BlkioThrottleReadIOPSDevice []*ThrottleDevice `json:"blkio_throttle_read_iops_device"` - - // IO write rate limit per cgroup per device, IO per second. - BlkioThrottleWriteIOPSDevice []*ThrottleDevice `json:"blkio_throttle_write_iops_device"` - - // set the freeze value for the process - Freezer FreezerState `json:"freezer"` - - // Hugetlb limit (in bytes) - HugetlbLimit []*HugepageLimit `json:"hugetlb_limit"` - - // Whether to disable OOM Killer - OomKillDisable bool `json:"oom_kill_disable"` - - // Tuning swappiness behaviour per cgroup - MemorySwappiness *uint64 `json:"memory_swappiness"` - - // Set priority of network traffic for container - NetPrioIfpriomap []*IfPrioMap `json:"net_prio_ifpriomap"` - - // Set class identifier for container's network packets - NetClsClassid uint32 `json:"net_cls_classid_u"` - - // Rdma resource restriction configuration - Rdma map[string]LinuxRdma `json:"rdma"` - - // Used on cgroups v2: - - // CpuWeight sets a proportional bandwidth limit. - CpuWeight uint64 `json:"cpu_weight"` - - // Unified is cgroupv2-only key-value map. - Unified map[string]string `json:"unified"` - - // SkipDevices allows to skip configuring device permissions. - // Used by e.g. kubelet while creating a parent cgroup (kubepods) - // common for many containers, and by runc update. - // - // NOTE it is impossible to start a container which has this flag set. - SkipDevices bool `json:"-"` - - // SkipFreezeOnSet is a flag for cgroup manager to skip the cgroup - // freeze when setting resources. Only applicable to systemd legacy - // (i.e. cgroup v1) manager (which uses freeze by default to avoid - // spurious permission errors caused by systemd inability to update - // device rules in a non-disruptive manner). - // - // If not set, a few methods (such as looking into cgroup's - // devices.list and querying the systemd unit properties) are used - // during Set() to figure out whether the freeze is required. Those - // methods may be relatively slow, thus this flag. - SkipFreezeOnSet bool `json:"-"` - - // MemoryCheckBeforeUpdate is a flag for cgroup v2 managers to check - // if the new memory limits (Memory and MemorySwap) being set are lower - // than the current memory usage, and reject if so. - MemoryCheckBeforeUpdate bool `json:"memory_check_before_update"` -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go deleted file mode 100644 index 53f5ec5a0..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build !linux - -package configs - -// Cgroup holds properties of a cgroup on Linux -// TODO Windows: This can ultimately be entirely factored out on Windows as -// cgroups are a Unix-specific construct. -type Cgroup struct{} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go deleted file mode 100644 index 22fe0f9b4..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go +++ /dev/null @@ -1,508 +0,0 @@ -package configs - -import ( - "bytes" - "encoding/json" - "fmt" - "os/exec" - "time" - - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" - - "github.com/opencontainers/runc/libcontainer/devices" - "github.com/opencontainers/runtime-spec/specs-go" -) - -type Rlimit struct { - Type int `json:"type"` - Hard uint64 `json:"hard"` - Soft uint64 `json:"soft"` -} - -// IDMap represents UID/GID Mappings for User Namespaces. -type IDMap struct { - ContainerID int64 `json:"container_id"` - HostID int64 `json:"host_id"` - Size int64 `json:"size"` -} - -// Seccomp represents syscall restrictions -// By default, only the native architecture of the kernel is allowed to be used -// for syscalls. Additional architectures can be added by specifying them in -// Architectures. -type Seccomp struct { - DefaultAction Action `json:"default_action"` - Architectures []string `json:"architectures"` - Flags []specs.LinuxSeccompFlag `json:"flags"` - Syscalls []*Syscall `json:"syscalls"` - DefaultErrnoRet *uint `json:"default_errno_ret"` - ListenerPath string `json:"listener_path,omitempty"` - ListenerMetadata string `json:"listener_metadata,omitempty"` -} - -// Action is taken upon rule match in Seccomp -type Action int - -const ( - Kill Action = iota + 1 - Errno - Trap - Allow - Trace - Log - Notify - KillThread - KillProcess -) - -// Operator is a comparison operator to be used when matching syscall arguments in Seccomp -type Operator int - -const ( - EqualTo Operator = iota + 1 - NotEqualTo - GreaterThan - GreaterThanOrEqualTo - LessThan - LessThanOrEqualTo - MaskEqualTo -) - -// Arg is a rule to match a specific syscall argument in Seccomp -type Arg struct { - Index uint `json:"index"` - Value uint64 `json:"value"` - ValueTwo uint64 `json:"value_two"` - Op Operator `json:"op"` -} - -// Syscall is a rule to match a syscall in Seccomp -type Syscall struct { - Name string `json:"name"` - Action Action `json:"action"` - ErrnoRet *uint `json:"errnoRet"` - Args []*Arg `json:"args"` -} - -// Config defines configuration options for executing a process inside a contained environment. -type Config struct { - // NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs - // This is a common option when the container is running in ramdisk - NoPivotRoot bool `json:"no_pivot_root"` - - // ParentDeathSignal specifies the signal that is sent to the container's process in the case - // that the parent process dies. - ParentDeathSignal int `json:"parent_death_signal"` - - // Path to a directory containing the container's root filesystem. - Rootfs string `json:"rootfs"` - - // Umask is the umask to use inside of the container. - Umask *uint32 `json:"umask"` - - // Readonlyfs will remount the container's rootfs as readonly where only externally mounted - // bind mounts are writtable. - Readonlyfs bool `json:"readonlyfs"` - - // Specifies the mount propagation flags to be applied to /. - RootPropagation int `json:"rootPropagation"` - - // Mounts specify additional source and destination paths that will be mounted inside the container's - // rootfs and mount namespace if specified - Mounts []*Mount `json:"mounts"` - - // The device nodes that should be automatically created within the container upon container start. Note, make sure that the node is marked as allowed in the cgroup as well! - Devices []*devices.Device `json:"devices"` - - MountLabel string `json:"mount_label"` - - // Hostname optionally sets the container's hostname if provided - Hostname string `json:"hostname"` - - // Domainname optionally sets the container's domainname if provided - Domainname string `json:"domainname"` - - // Namespaces specifies the container's namespaces that it should setup when cloning the init process - // If a namespace is not provided that namespace is shared from the container's parent process - Namespaces Namespaces `json:"namespaces"` - - // Capabilities specify the capabilities to keep when executing the process inside the container - // All capabilities not specified will be dropped from the processes capability mask - Capabilities *Capabilities `json:"capabilities"` - - // Networks specifies the container's network setup to be created - Networks []*Network `json:"networks"` - - // Routes can be specified to create entries in the route table as the container is started - Routes []*Route `json:"routes"` - - // Cgroups specifies specific cgroup settings for the various subsystems that the container is - // placed into to limit the resources the container has available - Cgroups *Cgroup `json:"cgroups"` - - // AppArmorProfile specifies the profile to apply to the process running in the container and is - // change at the time the process is execed - AppArmorProfile string `json:"apparmor_profile,omitempty"` - - // ProcessLabel specifies the label to apply to the process running in the container. It is - // commonly used by selinux - ProcessLabel string `json:"process_label,omitempty"` - - // Rlimits specifies the resource limits, such as max open files, to set in the container - // If Rlimits are not set, the container will inherit rlimits from the parent process - Rlimits []Rlimit `json:"rlimits,omitempty"` - - // OomScoreAdj specifies the adjustment to be made by the kernel when calculating oom scores - // for a process. Valid values are between the range [-1000, '1000'], where processes with - // higher scores are preferred for being killed. If it is unset then we don't touch the current - // value. - // More information about kernel oom score calculation here: https://lwn.net/Articles/317814/ - OomScoreAdj *int `json:"oom_score_adj,omitempty"` - - // UIDMappings is an array of User ID mappings for User Namespaces - UIDMappings []IDMap `json:"uid_mappings"` - - // GIDMappings is an array of Group ID mappings for User Namespaces - GIDMappings []IDMap `json:"gid_mappings"` - - // MaskPaths specifies paths within the container's rootfs to mask over with a bind - // mount pointing to /dev/null as to prevent reads of the file. - MaskPaths []string `json:"mask_paths"` - - // ReadonlyPaths specifies paths within the container's rootfs to remount as read-only - // so that these files prevent any writes. - ReadonlyPaths []string `json:"readonly_paths"` - - // Sysctl is a map of properties and their values. It is the equivalent of using - // sysctl -w my.property.name value in Linux. - Sysctl map[string]string `json:"sysctl"` - - // Seccomp allows actions to be taken whenever a syscall is made within the container. - // A number of rules are given, each having an action to be taken if a syscall matches it. - // A default action to be taken if no rules match is also given. - Seccomp *Seccomp `json:"seccomp"` - - // NoNewPrivileges controls whether processes in the container can gain additional privileges. - NoNewPrivileges bool `json:"no_new_privileges,omitempty"` - - // Hooks are a collection of actions to perform at various container lifecycle events. - // CommandHooks are serialized to JSON, but other hooks are not. - Hooks Hooks - - // Version is the version of opencontainer specification that is supported. - Version string `json:"version"` - - // Labels are user defined metadata that is stored in the config and populated on the state - Labels []string `json:"labels"` - - // NoNewKeyring will not allocated a new session keyring for the container. It will use the - // callers keyring in this case. - NoNewKeyring bool `json:"no_new_keyring"` - - // IntelRdt specifies settings for Intel RDT group that the container is placed into - // to limit the resources (e.g., L3 cache, memory bandwidth) the container has available - IntelRdt *IntelRdt `json:"intel_rdt,omitempty"` - - // RootlessEUID is set when the runc was launched with non-zero EUID. - // Note that RootlessEUID is set to false when launched with EUID=0 in userns. - // When RootlessEUID is set, runc creates a new userns for the container. - // (config.json needs to contain userns settings) - RootlessEUID bool `json:"rootless_euid,omitempty"` - - // RootlessCgroups is set when unlikely to have the full access to cgroups. - // When RootlessCgroups is set, cgroups errors are ignored. - RootlessCgroups bool `json:"rootless_cgroups,omitempty"` - - // TimeOffsets specifies the offset for supporting time namespaces. - TimeOffsets map[string]specs.LinuxTimeOffset `json:"time_offsets,omitempty"` - - // Scheduler represents the scheduling attributes for a process. - Scheduler *Scheduler `json:"scheduler,omitempty"` - - // Personality contains configuration for the Linux personality syscall. - Personality *LinuxPersonality `json:"personality,omitempty"` - - // IOPriority is the container's I/O priority. - IOPriority *IOPriority `json:"io_priority,omitempty"` -} - -// Scheduler is based on the Linux sched_setattr(2) syscall. -type Scheduler = specs.Scheduler - -// ToSchedAttr is to convert *configs.Scheduler to *unix.SchedAttr. -func ToSchedAttr(scheduler *Scheduler) (*unix.SchedAttr, error) { - var policy uint32 - switch scheduler.Policy { - case specs.SchedOther: - policy = 0 - case specs.SchedFIFO: - policy = 1 - case specs.SchedRR: - policy = 2 - case specs.SchedBatch: - policy = 3 - case specs.SchedISO: - policy = 4 - case specs.SchedIdle: - policy = 5 - case specs.SchedDeadline: - policy = 6 - default: - return nil, fmt.Errorf("invalid scheduler policy: %s", scheduler.Policy) - } - - var flags uint64 - for _, flag := range scheduler.Flags { - switch flag { - case specs.SchedFlagResetOnFork: - flags |= 0x01 - case specs.SchedFlagReclaim: - flags |= 0x02 - case specs.SchedFlagDLOverrun: - flags |= 0x04 - case specs.SchedFlagKeepPolicy: - flags |= 0x08 - case specs.SchedFlagKeepParams: - flags |= 0x10 - case specs.SchedFlagUtilClampMin: - flags |= 0x20 - case specs.SchedFlagUtilClampMax: - flags |= 0x40 - default: - return nil, fmt.Errorf("invalid scheduler flag: %s", flag) - } - } - - return &unix.SchedAttr{ - Size: unix.SizeofSchedAttr, - Policy: policy, - Flags: flags, - Nice: scheduler.Nice, - Priority: uint32(scheduler.Priority), - Runtime: scheduler.Runtime, - Deadline: scheduler.Deadline, - Period: scheduler.Period, - }, nil -} - -var IOPrioClassMapping = map[specs.IOPriorityClass]int{ - specs.IOPRIO_CLASS_RT: 1, - specs.IOPRIO_CLASS_BE: 2, - specs.IOPRIO_CLASS_IDLE: 3, -} - -type IOPriority = specs.LinuxIOPriority - -type ( - HookName string - HookList []Hook - Hooks map[HookName]HookList -) - -const ( - // Prestart commands are executed after the container namespaces are created, - // but before the user supplied command is executed from init. - // Note: This hook is now deprecated - // Prestart commands are called in the Runtime namespace. - Prestart HookName = "prestart" - - // CreateRuntime commands MUST be called as part of the create operation after - // the runtime environment has been created but before the pivot_root has been executed. - // CreateRuntime is called immediately after the deprecated Prestart hook. - // CreateRuntime commands are called in the Runtime Namespace. - CreateRuntime HookName = "createRuntime" - - // CreateContainer commands MUST be called as part of the create operation after - // the runtime environment has been created but before the pivot_root has been executed. - // CreateContainer commands are called in the Container namespace. - CreateContainer HookName = "createContainer" - - // StartContainer commands MUST be called as part of the start operation and before - // the container process is started. - // StartContainer commands are called in the Container namespace. - StartContainer HookName = "startContainer" - - // Poststart commands are executed after the container init process starts. - // Poststart commands are called in the Runtime Namespace. - Poststart HookName = "poststart" - - // Poststop commands are executed after the container init process exits. - // Poststop commands are called in the Runtime Namespace. - Poststop HookName = "poststop" -) - -// KnownHookNames returns the known hook names. -// Used by `runc features`. -func KnownHookNames() []string { - return []string{ - string(Prestart), // deprecated - string(CreateRuntime), - string(CreateContainer), - string(StartContainer), - string(Poststart), - string(Poststop), - } -} - -type Capabilities struct { - // Bounding is the set of capabilities checked by the kernel. - Bounding []string - // Effective is the set of capabilities checked by the kernel. - Effective []string - // Inheritable is the capabilities preserved across execve. - Inheritable []string - // Permitted is the limiting superset for effective capabilities. - Permitted []string - // Ambient is the ambient set of capabilities that are kept. - Ambient []string -} - -// Deprecated: use (Hooks).Run instead. -func (hooks HookList) RunHooks(state *specs.State) error { - for i, h := range hooks { - if err := h.Run(state); err != nil { - return fmt.Errorf("error running hook #%d: %w", i, err) - } - } - - return nil -} - -func (hooks *Hooks) UnmarshalJSON(b []byte) error { - var state map[HookName][]CommandHook - - if err := json.Unmarshal(b, &state); err != nil { - return err - } - - *hooks = Hooks{} - for n, commandHooks := range state { - if len(commandHooks) == 0 { - continue - } - - (*hooks)[n] = HookList{} - for _, h := range commandHooks { - (*hooks)[n] = append((*hooks)[n], h) - } - } - - return nil -} - -func (hooks *Hooks) MarshalJSON() ([]byte, error) { - serialize := func(hooks []Hook) (serializableHooks []CommandHook) { - for _, hook := range hooks { - switch chook := hook.(type) { - case CommandHook: - serializableHooks = append(serializableHooks, chook) - default: - logrus.Warnf("cannot serialize hook of type %T, skipping", hook) - } - } - - return serializableHooks - } - - return json.Marshal(map[string]interface{}{ - "prestart": serialize((*hooks)[Prestart]), - "createRuntime": serialize((*hooks)[CreateRuntime]), - "createContainer": serialize((*hooks)[CreateContainer]), - "startContainer": serialize((*hooks)[StartContainer]), - "poststart": serialize((*hooks)[Poststart]), - "poststop": serialize((*hooks)[Poststop]), - }) -} - -// Run executes all hooks for the given hook name. -func (hooks Hooks) Run(name HookName, state *specs.State) error { - list := hooks[name] - for i, h := range list { - if err := h.Run(state); err != nil { - return fmt.Errorf("error running %s hook #%d: %w", name, i, err) - } - } - - return nil -} - -type Hook interface { - // Run executes the hook with the provided state. - Run(*specs.State) error -} - -// NewFunctionHook will call the provided function when the hook is run. -func NewFunctionHook(f func(*specs.State) error) FuncHook { - return FuncHook{ - run: f, - } -} - -type FuncHook struct { - run func(*specs.State) error -} - -func (f FuncHook) Run(s *specs.State) error { - return f.run(s) -} - -type Command struct { - Path string `json:"path"` - Args []string `json:"args"` - Env []string `json:"env"` - Dir string `json:"dir"` - Timeout *time.Duration `json:"timeout"` -} - -// NewCommandHook will execute the provided command when the hook is run. -func NewCommandHook(cmd Command) CommandHook { - return CommandHook{ - Command: cmd, - } -} - -type CommandHook struct { - Command -} - -func (c Command) Run(s *specs.State) error { - b, err := json.Marshal(s) - if err != nil { - return err - } - var stdout, stderr bytes.Buffer - cmd := exec.Cmd{ - Path: c.Path, - Args: c.Args, - Env: c.Env, - Stdin: bytes.NewReader(b), - Stdout: &stdout, - Stderr: &stderr, - } - if err := cmd.Start(); err != nil { - return err - } - errC := make(chan error, 1) - go func() { - err := cmd.Wait() - if err != nil { - err = fmt.Errorf("%w, stdout: %s, stderr: %s", err, stdout.String(), stderr.String()) - } - errC <- err - }() - var timerCh <-chan time.Time - if c.Timeout != nil { - timer := time.NewTimer(*c.Timeout) - defer timer.Stop() - timerCh = timer.C - } - select { - case err := <-errC: - return err - case <-timerCh: - _ = cmd.Process.Kill() - <-errC - return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds()) - } -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/config_linux.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/config_linux.go deleted file mode 100644 index e401f5331..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/config_linux.go +++ /dev/null @@ -1,97 +0,0 @@ -package configs - -import ( - "errors" - "fmt" - "math" -) - -var ( - errNoUIDMap = errors.New("user namespaces enabled, but no uid mappings found") - errNoGIDMap = errors.New("user namespaces enabled, but no gid mappings found") -) - -// Please check https://man7.org/linux/man-pages/man2/personality.2.html for const details. -// https://raw.githubusercontent.com/torvalds/linux/master/include/uapi/linux/personality.h -const ( - PerLinux = 0x0000 - PerLinux32 = 0x0008 -) - -type LinuxPersonality struct { - // Domain for the personality - // can only contain values "LINUX" and "LINUX32" - Domain int `json:"domain"` -} - -// HostUID gets the translated uid for the process on host which could be -// different when user namespaces are enabled. -func (c Config) HostUID(containerId int) (int, error) { - if c.Namespaces.Contains(NEWUSER) { - if len(c.UIDMappings) == 0 { - return -1, errNoUIDMap - } - id, found := c.hostIDFromMapping(int64(containerId), c.UIDMappings) - if !found { - return -1, fmt.Errorf("user namespaces enabled, but no mapping found for uid %d", containerId) - } - // If we are a 32-bit binary running on a 64-bit system, it's possible - // the mapped user is too large to store in an int, which means we - // cannot do the mapping. We can't just return an int64, because - // os.Setuid() takes an int. - if id > math.MaxInt { - return -1, fmt.Errorf("mapping for uid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt) - } - return int(id), nil - } - // Return unchanged id. - return containerId, nil -} - -// HostRootUID gets the root uid for the process on host which could be non-zero -// when user namespaces are enabled. -func (c Config) HostRootUID() (int, error) { - return c.HostUID(0) -} - -// HostGID gets the translated gid for the process on host which could be -// different when user namespaces are enabled. -func (c Config) HostGID(containerId int) (int, error) { - if c.Namespaces.Contains(NEWUSER) { - if len(c.GIDMappings) == 0 { - return -1, errNoGIDMap - } - id, found := c.hostIDFromMapping(int64(containerId), c.GIDMappings) - if !found { - return -1, fmt.Errorf("user namespaces enabled, but no mapping found for gid %d", containerId) - } - // If we are a 32-bit binary running on a 64-bit system, it's possible - // the mapped user is too large to store in an int, which means we - // cannot do the mapping. We can't just return an int64, because - // os.Setgid() takes an int. - if id > math.MaxInt { - return -1, fmt.Errorf("mapping for gid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt) - } - return int(id), nil - } - // Return unchanged id. - return containerId, nil -} - -// HostRootGID gets the root gid for the process on host which could be non-zero -// when user namespaces are enabled. -func (c Config) HostRootGID() (int, error) { - return c.HostGID(0) -} - -// Utility function that gets a host ID for a container ID from user namespace map -// if that ID is present in the map. -func (c Config) hostIDFromMapping(containerID int64, uMap []IDMap) (int64, bool) { - for _, m := range uMap { - if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) { - hostID := m.HostID + (containerID - m.ContainerID) - return hostID, true - } - } - return -1, false -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/configs_fuzzer.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/configs_fuzzer.go deleted file mode 100644 index 1fd87ce6a..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/configs_fuzzer.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build gofuzz - -package configs - -func FuzzUnmarshalJSON(data []byte) int { - hooks := Hooks{} - _ = hooks.UnmarshalJSON(data) - return 1 -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go deleted file mode 100644 index d30216380..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go +++ /dev/null @@ -1,9 +0,0 @@ -package configs - -type HugepageLimit struct { - // which type of hugepage to limit. - Pagesize string `json:"page_size"` - - // usage limit for hugepage. - Limit uint64 `json:"limit"` -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go deleted file mode 100644 index f8d951ab8..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/intelrdt.go +++ /dev/null @@ -1,16 +0,0 @@ -package configs - -type IntelRdt struct { - // The identity for RDT Class of Service - ClosID string `json:"closID,omitempty"` - - // The schema for L3 cache id and capacity bitmask (CBM) - // Format: "L3:=;=;..." - L3CacheSchema string `json:"l3_cache_schema,omitempty"` - - // The schema of memory bandwidth per L3 cache id - // Format: "MB:=bandwidth0;=bandwidth1;..." - // The unit of memory bandwidth is specified in "percentages" by - // default, and in "MBps" if MBA Software Controller is enabled. - MemBwSchema string `json:"memBwSchema,omitempty"` -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go deleted file mode 100644 index 9a0395eaf..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go +++ /dev/null @@ -1,14 +0,0 @@ -package configs - -import ( - "fmt" -) - -type IfPrioMap struct { - Interface string `json:"interface"` - Priority int64 `json:"priority"` -} - -func (i *IfPrioMap) CgroupString() string { - return fmt.Sprintf("%s %d", i.Interface, i.Priority) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go deleted file mode 100644 index bfd356e49..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go +++ /dev/null @@ -1,7 +0,0 @@ -package configs - -const ( - // EXT_COPYUP is a directive to copy up the contents of a directory when - // a tmpfs is mounted over it. - EXT_COPYUP = 1 << iota //nolint:golint,revive // ignore "don't use ALL_CAPS" warning -) diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount_linux.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/mount_linux.go deleted file mode 100644 index b69e9ab23..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount_linux.go +++ /dev/null @@ -1,66 +0,0 @@ -package configs - -import "golang.org/x/sys/unix" - -type MountIDMapping struct { - // Recursive indicates if the mapping needs to be recursive. - Recursive bool `json:"recursive"` - - // UserNSPath is a path to a user namespace that indicates the necessary - // id-mappings for MOUNT_ATTR_IDMAP. If set to non-"", UIDMappings and - // GIDMappings must be set to nil. - UserNSPath string `json:"userns_path,omitempty"` - - // UIDMappings is the uid mapping set for this mount, to be used with - // MOUNT_ATTR_IDMAP. - UIDMappings []IDMap `json:"uid_mappings,omitempty"` - - // GIDMappings is the gid mapping set for this mount, to be used with - // MOUNT_ATTR_IDMAP. - GIDMappings []IDMap `json:"gid_mappings,omitempty"` -} - -type Mount struct { - // Source path for the mount. - Source string `json:"source"` - - // Destination path for the mount inside the container. - Destination string `json:"destination"` - - // Device the mount is for. - Device string `json:"device"` - - // Mount flags. - Flags int `json:"flags"` - - // Mount flags that were explicitly cleared in the configuration (meaning - // the user explicitly requested that these flags *not* be set). - ClearedFlags int `json:"cleared_flags"` - - // Propagation Flags - PropagationFlags []int `json:"propagation_flags"` - - // Mount data applied to the mount. - Data string `json:"data"` - - // Relabel source if set, "z" indicates shared, "Z" indicates unshared. - Relabel string `json:"relabel"` - - // RecAttr represents mount properties to be applied recursively (AT_RECURSIVE), see mount_setattr(2). - RecAttr *unix.MountAttr `json:"rec_attr"` - - // Extensions are additional flags that are specific to runc. - Extensions int `json:"extensions"` - - // Mapping is the MOUNT_ATTR_IDMAP configuration for the mount. If non-nil, - // the mount is configured to use MOUNT_ATTR_IDMAP-style id mappings. - IDMapping *MountIDMapping `json:"id_mapping,omitempty"` -} - -func (m *Mount) IsBind() bool { - return m.Flags&unix.MS_BIND != 0 -} - -func (m *Mount) IsIDMapped() bool { - return m.IDMapping != nil -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount_unsupported.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/mount_unsupported.go deleted file mode 100644 index 1d4d9fe52..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/mount_unsupported.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !linux - -package configs - -type Mount struct{} - -func (m *Mount) IsBind() bool { - return false -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go deleted file mode 100644 index a3329a31a..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go +++ /dev/null @@ -1,5 +0,0 @@ -package configs - -type NamespaceType string - -type Namespaces []Namespace diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_linux.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_linux.go deleted file mode 100644 index 898f96fd0..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_linux.go +++ /dev/null @@ -1,133 +0,0 @@ -package configs - -import ( - "fmt" - "os" - "sync" -) - -const ( - NEWNET NamespaceType = "NEWNET" - NEWPID NamespaceType = "NEWPID" - NEWNS NamespaceType = "NEWNS" - NEWUTS NamespaceType = "NEWUTS" - NEWIPC NamespaceType = "NEWIPC" - NEWUSER NamespaceType = "NEWUSER" - NEWCGROUP NamespaceType = "NEWCGROUP" - NEWTIME NamespaceType = "NEWTIME" -) - -var ( - nsLock sync.Mutex - supportedNamespaces = make(map[NamespaceType]bool) -) - -// NsName converts the namespace type to its filename -func NsName(ns NamespaceType) string { - switch ns { - case NEWNET: - return "net" - case NEWNS: - return "mnt" - case NEWPID: - return "pid" - case NEWIPC: - return "ipc" - case NEWUSER: - return "user" - case NEWUTS: - return "uts" - case NEWCGROUP: - return "cgroup" - case NEWTIME: - return "time" - } - return "" -} - -// IsNamespaceSupported returns whether a namespace is available or -// not -func IsNamespaceSupported(ns NamespaceType) bool { - nsLock.Lock() - defer nsLock.Unlock() - supported, ok := supportedNamespaces[ns] - if ok { - return supported - } - nsFile := NsName(ns) - // if the namespace type is unknown, just return false - if nsFile == "" { - return false - } - // We don't need to use /proc/thread-self here because the list of - // namespace types is unrelated to the thread. This lets us avoid having to - // do runtime.LockOSThread. - _, err := os.Stat("/proc/self/ns/" + nsFile) - // a namespace is supported if it exists and we have permissions to read it - supported = err == nil - supportedNamespaces[ns] = supported - return supported -} - -func NamespaceTypes() []NamespaceType { - return []NamespaceType{ - NEWUSER, // Keep user NS always first, don't move it. - NEWIPC, - NEWUTS, - NEWNET, - NEWPID, - NEWNS, - NEWCGROUP, - NEWTIME, - } -} - -// Namespace defines configuration for each namespace. It specifies an -// alternate path that is able to be joined via setns. -type Namespace struct { - Type NamespaceType `json:"type"` - Path string `json:"path"` -} - -func (n *Namespace) GetPath(pid int) string { - return fmt.Sprintf("/proc/%d/ns/%s", pid, NsName(n.Type)) -} - -func (n *Namespaces) Remove(t NamespaceType) bool { - i := n.index(t) - if i == -1 { - return false - } - *n = append((*n)[:i], (*n)[i+1:]...) - return true -} - -func (n *Namespaces) Add(t NamespaceType, path string) { - i := n.index(t) - if i == -1 { - *n = append(*n, Namespace{Type: t, Path: path}) - return - } - (*n)[i].Path = path -} - -func (n *Namespaces) index(t NamespaceType) int { - for i, ns := range *n { - if ns.Type == t { - return i - } - } - return -1 -} - -func (n *Namespaces) Contains(t NamespaceType) bool { - return n.index(t) != -1 -} - -func (n *Namespaces) PathOf(t NamespaceType) string { - i := n.index(t) - if i == -1 { - return "" - } - return (*n)[i].Path -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go deleted file mode 100644 index 26b70b26f..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go +++ /dev/null @@ -1,45 +0,0 @@ -//go:build linux - -package configs - -import "golang.org/x/sys/unix" - -func (n *Namespace) Syscall() int { - return namespaceInfo[n.Type] -} - -var namespaceInfo = map[NamespaceType]int{ - NEWNET: unix.CLONE_NEWNET, - NEWNS: unix.CLONE_NEWNS, - NEWUSER: unix.CLONE_NEWUSER, - NEWIPC: unix.CLONE_NEWIPC, - NEWUTS: unix.CLONE_NEWUTS, - NEWPID: unix.CLONE_NEWPID, - NEWCGROUP: unix.CLONE_NEWCGROUP, - NEWTIME: unix.CLONE_NEWTIME, -} - -// CloneFlags parses the container's Namespaces options to set the correct -// flags on clone, unshare. This function returns flags only for new namespaces. -func (n *Namespaces) CloneFlags() uintptr { - var flag int - for _, v := range *n { - if v.Path != "" { - continue - } - flag |= namespaceInfo[v.Type] - } - return uintptr(flag) -} - -// IsPrivate tells whether the namespace of type t is configured as private -// (i.e. it exists and is not shared). -func (n Namespaces) IsPrivate(t NamespaceType) bool { - for _, v := range n { - if v.Type == t { - return v.Path == "" - } - } - // Not found, so implicitly sharing a parent namespace. - return false -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go deleted file mode 100644 index 10bf24365..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !linux && !windows - -package configs - -func (n *Namespace) Syscall() int { - panic("No namespace syscall support") -} - -// CloneFlags parses the container's Namespaces options to set the correct -// flags on clone, unshare. This function returns flags only for new namespaces. -func (n *Namespaces) CloneFlags() uintptr { - panic("No namespace syscall support") -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go deleted file mode 100644 index 914684993..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !linux - -package configs - -// Namespace defines configuration for each namespace. It specifies an -// alternate path that is able to be joined via setns. -type Namespace struct{} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/network.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/network.go deleted file mode 100644 index c44c3ea71..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/network.go +++ /dev/null @@ -1,75 +0,0 @@ -package configs - -// Network defines configuration for a container's networking stack -// -// The network configuration can be omitted from a container causing the -// container to be setup with the host's networking stack -type Network struct { - // Type sets the networks type, commonly veth and loopback - Type string `json:"type"` - - // Name of the network interface - Name string `json:"name"` - - // The bridge to use. - Bridge string `json:"bridge"` - - // MacAddress contains the MAC address to set on the network interface - MacAddress string `json:"mac_address"` - - // Address contains the IPv4 and mask to set on the network interface - Address string `json:"address"` - - // Gateway sets the gateway address that is used as the default for the interface - Gateway string `json:"gateway"` - - // IPv6Address contains the IPv6 and mask to set on the network interface - IPv6Address string `json:"ipv6_address"` - - // IPv6Gateway sets the ipv6 gateway address that is used as the default for the interface - IPv6Gateway string `json:"ipv6_gateway"` - - // Mtu sets the mtu value for the interface and will be mirrored on both the host and - // container's interfaces if a pair is created, specifically in the case of type veth - // Note: This does not apply to loopback interfaces. - Mtu int `json:"mtu"` - - // TxQueueLen sets the tx_queuelen value for the interface and will be mirrored on both the host and - // container's interfaces if a pair is created, specifically in the case of type veth - // Note: This does not apply to loopback interfaces. - TxQueueLen int `json:"txqueuelen"` - - // HostInterfaceName is a unique name of a veth pair that resides on in the host interface of the - // container. - HostInterfaceName string `json:"host_interface_name"` - - // HairpinMode specifies if hairpin NAT should be enabled on the virtual interface - // bridge port in the case of type veth - // Note: This is unsupported on some systems. - // Note: This does not apply to loopback interfaces. - HairpinMode bool `json:"hairpin_mode"` -} - -// Route defines a routing table entry. -// -// Routes can be specified to create entries in the routing table as the container -// is started. -// -// All of destination, source, and gateway should be either IPv4 or IPv6. -// One of the three options must be present, and omitted entries will use their -// IP family default for the route table. For IPv4 for example, setting the -// gateway to 1.2.3.4 and the interface to eth0 will set up a standard -// destination of 0.0.0.0(or *) when viewed in the route table. -type Route struct { - // Destination specifies the destination IP address and mask in the CIDR form. - Destination string `json:"destination"` - - // Source specifies the source IP address and mask in the CIDR form. - Source string `json:"source"` - - // Gateway specifies the gateway IP address. - Gateway string `json:"gateway"` - - // InterfaceName specifies the device to set this route up for, for example eth0. - InterfaceName string `json:"interface_name"` -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/rdma.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/rdma.go deleted file mode 100644 index c69f2c802..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/rdma.go +++ /dev/null @@ -1,9 +0,0 @@ -package configs - -// LinuxRdma for Linux cgroup 'rdma' resource management (Linux 4.11) -type LinuxRdma struct { - // Maximum number of HCA handles that can be opened. Default is "no limit". - HcaHandles *uint32 `json:"hca_handles,omitempty"` - // Maximum number of HCA objects that can be created. Default is "no limit". - HcaObjects *uint32 `json:"hca_objects,omitempty"` -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/devices/device.go b/vendor/github.com/opencontainers/runc/libcontainer/devices/device.go deleted file mode 100644 index c2c2b3bb7..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/devices/device.go +++ /dev/null @@ -1,174 +0,0 @@ -package devices - -import ( - "fmt" - "os" - "strconv" -) - -const ( - Wildcard = -1 -) - -type Device struct { - Rule - - // Path to the device. - Path string `json:"path"` - - // FileMode permission bits for the device. - FileMode os.FileMode `json:"file_mode"` - - // Uid of the device. - Uid uint32 `json:"uid"` - - // Gid of the device. - Gid uint32 `json:"gid"` -} - -// Permissions is a cgroupv1-style string to represent device access. It -// has to be a string for backward compatibility reasons, hence why it has -// methods to do set operations. -type Permissions string - -const ( - deviceRead uint = (1 << iota) - deviceWrite - deviceMknod -) - -func (p Permissions) toSet() uint { - var set uint - for _, perm := range p { - switch perm { - case 'r': - set |= deviceRead - case 'w': - set |= deviceWrite - case 'm': - set |= deviceMknod - } - } - return set -} - -func fromSet(set uint) Permissions { - var perm string - if set&deviceRead == deviceRead { - perm += "r" - } - if set&deviceWrite == deviceWrite { - perm += "w" - } - if set&deviceMknod == deviceMknod { - perm += "m" - } - return Permissions(perm) -} - -// Union returns the union of the two sets of Permissions. -func (p Permissions) Union(o Permissions) Permissions { - lhs := p.toSet() - rhs := o.toSet() - return fromSet(lhs | rhs) -} - -// Difference returns the set difference of the two sets of Permissions. -// In set notation, A.Difference(B) gives you A\B. -func (p Permissions) Difference(o Permissions) Permissions { - lhs := p.toSet() - rhs := o.toSet() - return fromSet(lhs &^ rhs) -} - -// Intersection computes the intersection of the two sets of Permissions. -func (p Permissions) Intersection(o Permissions) Permissions { - lhs := p.toSet() - rhs := o.toSet() - return fromSet(lhs & rhs) -} - -// IsEmpty returns whether the set of permissions in a Permissions is -// empty. -func (p Permissions) IsEmpty() bool { - return p == Permissions("") -} - -// IsValid returns whether the set of permissions is a subset of valid -// permissions (namely, {r,w,m}). -func (p Permissions) IsValid() bool { - return p == fromSet(p.toSet()) -} - -type Type rune - -const ( - WildcardDevice Type = 'a' - BlockDevice Type = 'b' - CharDevice Type = 'c' // or 'u' - FifoDevice Type = 'p' -) - -func (t Type) IsValid() bool { - switch t { - case WildcardDevice, BlockDevice, CharDevice, FifoDevice: - return true - default: - return false - } -} - -func (t Type) CanMknod() bool { - switch t { - case BlockDevice, CharDevice, FifoDevice: - return true - default: - return false - } -} - -func (t Type) CanCgroup() bool { - switch t { - case WildcardDevice, BlockDevice, CharDevice: - return true - default: - return false - } -} - -type Rule struct { - // Type of device ('c' for char, 'b' for block). If set to 'a', this rule - // acts as a wildcard and all fields other than Allow are ignored. - Type Type `json:"type"` - - // Major is the device's major number. - Major int64 `json:"major"` - - // Minor is the device's minor number. - Minor int64 `json:"minor"` - - // Permissions is the set of permissions that this rule applies to (in the - // cgroupv1 format -- any combination of "rwm"). - Permissions Permissions `json:"permissions"` - - // Allow specifies whether this rule is allowed. - Allow bool `json:"allow"` -} - -func (d *Rule) CgroupString() string { - var ( - major = strconv.FormatInt(d.Major, 10) - minor = strconv.FormatInt(d.Minor, 10) - ) - if d.Major == Wildcard { - major = "*" - } - if d.Minor == Wildcard { - minor = "*" - } - return fmt.Sprintf("%c %s:%s %s", d.Type, major, minor, d.Permissions) -} - -func (d *Rule) Mkdev() (uint64, error) { - return mkDev(d) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/devices/device_unix.go b/vendor/github.com/opencontainers/runc/libcontainer/devices/device_unix.go deleted file mode 100644 index d00775f51..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/devices/device_unix.go +++ /dev/null @@ -1,119 +0,0 @@ -//go:build !windows - -package devices - -import ( - "errors" - "os" - "path/filepath" - - "golang.org/x/sys/unix" -) - -// ErrNotADevice denotes that a file is not a valid linux device. -var ErrNotADevice = errors.New("not a device node") - -// Testing dependencies -var ( - unixLstat = unix.Lstat - osReadDir = os.ReadDir -) - -func mkDev(d *Rule) (uint64, error) { - if d.Major == Wildcard || d.Minor == Wildcard { - return 0, errors.New("cannot mkdev() device with wildcards") - } - return unix.Mkdev(uint32(d.Major), uint32(d.Minor)), nil -} - -// DeviceFromPath takes the path to a device and its cgroup_permissions (which -// cannot be easily queried) to look up the information about a linux device -// and returns that information as a Device struct. -func DeviceFromPath(path, permissions string) (*Device, error) { - var stat unix.Stat_t - err := unixLstat(path, &stat) - if err != nil { - return nil, err - } - - var ( - devType Type - mode = stat.Mode - devNumber = uint64(stat.Rdev) //nolint:unconvert // Rdev is uint32 on e.g. MIPS. - major = unix.Major(devNumber) - minor = unix.Minor(devNumber) - ) - switch mode & unix.S_IFMT { - case unix.S_IFBLK: - devType = BlockDevice - case unix.S_IFCHR: - devType = CharDevice - case unix.S_IFIFO: - devType = FifoDevice - default: - return nil, ErrNotADevice - } - return &Device{ - Rule: Rule{ - Type: devType, - Major: int64(major), - Minor: int64(minor), - Permissions: Permissions(permissions), - }, - Path: path, - FileMode: os.FileMode(mode &^ unix.S_IFMT), - Uid: stat.Uid, - Gid: stat.Gid, - }, nil -} - -// HostDevices returns all devices that can be found under /dev directory. -func HostDevices() ([]*Device, error) { - return GetDevices("/dev") -} - -// GetDevices recursively traverses a directory specified by path -// and returns all devices found there. -func GetDevices(path string) ([]*Device, error) { - files, err := osReadDir(path) - if err != nil { - return nil, err - } - var out []*Device - for _, f := range files { - switch { - case f.IsDir(): - switch f.Name() { - // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 - // ".udev" added to address https://github.com/opencontainers/runc/issues/2093 - case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev": - continue - default: - sub, err := GetDevices(filepath.Join(path, f.Name())) - if err != nil { - return nil, err - } - - out = append(out, sub...) - continue - } - case f.Name() == "console": - continue - } - device, err := DeviceFromPath(filepath.Join(path, f.Name()), "rwm") - if err != nil { - if errors.Is(err, ErrNotADevice) { - continue - } - if os.IsNotExist(err) { - continue - } - return nil, err - } - if device.Type == FifoDevice { - continue - } - out = append(out, device) - } - return out, nil -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go deleted file mode 100644 index 2edd1417a..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go +++ /dev/null @@ -1,119 +0,0 @@ -package utils - -/* - * Copyright 2016, 2017 SUSE LLC - * - * 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. - */ - -import ( - "fmt" - "os" - "runtime" - - "golang.org/x/sys/unix" -) - -// MaxNameLen is the maximum length of the name of a file descriptor being sent -// using SendFile. The name of the file handle returned by RecvFile will never be -// larger than this value. -const MaxNameLen = 4096 - -// oobSpace is the size of the oob slice required to store a single FD. Note -// that unix.UnixRights appears to make the assumption that fd is always int32, -// so sizeof(fd) = 4. -var oobSpace = unix.CmsgSpace(4) - -// RecvFile waits for a file descriptor to be sent over the given AF_UNIX -// socket. The file name of the remote file descriptor will be recreated -// locally (it is sent as non-auxiliary data in the same payload). -func RecvFile(socket *os.File) (_ *os.File, Err error) { - name := make([]byte, MaxNameLen) - oob := make([]byte, oobSpace) - - sockfd := socket.Fd() - n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, unix.MSG_CMSG_CLOEXEC) - if err != nil { - return nil, err - } - if n >= MaxNameLen || oobn != oobSpace { - return nil, fmt.Errorf("recvfile: incorrect number of bytes read (n=%d oobn=%d)", n, oobn) - } - // Truncate. - name = name[:n] - oob = oob[:oobn] - - scms, err := unix.ParseSocketControlMessage(oob) - if err != nil { - return nil, err - } - - // We cannot control how many SCM_RIGHTS we receive, and upon receiving - // them all of the descriptors are installed in our fd table, so we need to - // parse all of the SCM_RIGHTS we received in order to close all of the - // descriptors on error. - var fds []int - defer func() { - for i, fd := range fds { - if i == 0 && Err == nil { - // Only close the first one on error. - continue - } - // Always close extra ones. - _ = unix.Close(fd) - } - }() - var lastErr error - for _, scm := range scms { - if scm.Header.Type == unix.SCM_RIGHTS { - scmFds, err := unix.ParseUnixRights(&scm) - if err != nil { - lastErr = err - } else { - fds = append(fds, scmFds...) - } - } - } - if lastErr != nil { - return nil, lastErr - } - - // We do this after collecting the fds to make sure we close them all when - // returning an error here. - if len(scms) != 1 { - return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms)) - } - if len(fds) != 1 { - return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds)) - } - return os.NewFile(uintptr(fds[0]), string(name)), nil -} - -// SendFile sends a file over the given AF_UNIX socket. file.Name() is also -// included so that if the other end uses RecvFile, the file will have the same -// name information. -func SendFile(socket *os.File, file *os.File) error { - name := file.Name() - if len(name) >= MaxNameLen { - return fmt.Errorf("sendfd: filename too long: %s", name) - } - err := SendRawFd(socket, name, file.Fd()) - runtime.KeepAlive(file) - return err -} - -// SendRawFd sends a specific file descriptor over the given AF_UNIX socket. -func SendRawFd(socket *os.File, msg string, fd uintptr) error { - oob := unix.UnixRights(int(fd)) - return unix.Sendmsg(int(socket.Fd()), []byte(msg), oob, nil, 0) -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go deleted file mode 100644 index db420ea68..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go +++ /dev/null @@ -1,115 +0,0 @@ -package utils - -import ( - "encoding/json" - "io" - "os" - "path/filepath" - "strings" - - "golang.org/x/sys/unix" -) - -const ( - exitSignalOffset = 128 -) - -// ExitStatus returns the correct exit status for a process based on if it -// was signaled or exited cleanly -func ExitStatus(status unix.WaitStatus) int { - if status.Signaled() { - return exitSignalOffset + int(status.Signal()) - } - return status.ExitStatus() -} - -// WriteJSON writes the provided struct v to w using standard json marshaling -// without a trailing newline. This is used instead of json.Encoder because -// there might be a problem in json decoder in some cases, see: -// https://github.com/docker/docker/issues/14203#issuecomment-174177790 -func WriteJSON(w io.Writer, v interface{}) error { - data, err := json.Marshal(v) - if err != nil { - return err - } - _, err = w.Write(data) - return err -} - -// CleanPath makes a path safe for use with filepath.Join. This is done by not -// only cleaning the path, but also (if the path is relative) adding a leading -// '/' and cleaning it (then removing the leading '/'). This ensures that a -// path resulting from prepending another path will always resolve to lexically -// be a subdirectory of the prefixed path. This is all done lexically, so paths -// that include symlinks won't be safe as a result of using CleanPath. -func CleanPath(path string) string { - // Deal with empty strings nicely. - if path == "" { - return "" - } - - // Ensure that all paths are cleaned (especially problematic ones like - // "/../../../../../" which can cause lots of issues). - path = filepath.Clean(path) - - // If the path isn't absolute, we need to do more processing to fix paths - // such as "../../../..//some/path". We also shouldn't convert absolute - // paths to relative ones. - if !filepath.IsAbs(path) { - path = filepath.Clean(string(os.PathSeparator) + path) - // This can't fail, as (by definition) all paths are relative to root. - path, _ = filepath.Rel(string(os.PathSeparator), path) - } - - // Clean the path again for good measure. - return filepath.Clean(path) -} - -// stripRoot returns the passed path, stripping the root path if it was -// (lexicially) inside it. Note that both passed paths will always be treated -// as absolute, and the returned path will also always be absolute. In -// addition, the paths are cleaned before stripping the root. -func stripRoot(root, path string) string { - // Make the paths clean and absolute. - root, path = CleanPath("/"+root), CleanPath("/"+path) - switch { - case path == root: - path = "/" - case root == "/": - // do nothing - case strings.HasPrefix(path, root+"/"): - path = strings.TrimPrefix(path, root+"/") - } - return CleanPath("/" + path) -} - -// SearchLabels searches through a list of key=value pairs for a given key, -// returning its value, and the binary flag telling whether the key exist. -func SearchLabels(labels []string, key string) (string, bool) { - key += "=" - for _, s := range labels { - if strings.HasPrefix(s, key) { - return s[len(key):], true - } - } - return "", false -} - -// Annotations returns the bundle path and user defined annotations from the -// libcontainer state. We need to remove the bundle because that is a label -// added by libcontainer. -func Annotations(labels []string) (bundle string, userAnnotations map[string]string) { - userAnnotations = make(map[string]string) - for _, l := range labels { - name, value, ok := strings.Cut(l, "=") - if !ok { - continue - } - if name == "bundle" { - bundle = value - } else { - userAnnotations[name] = value - } - } - return -} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go deleted file mode 100644 index 8f179b6aa..000000000 --- a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go +++ /dev/null @@ -1,360 +0,0 @@ -//go:build !windows - -package utils - -import ( - "fmt" - "math" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "sync" - _ "unsafe" // for go:linkname - - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -// EnsureProcHandle returns whether or not the given file handle is on procfs. -func EnsureProcHandle(fh *os.File) error { - var buf unix.Statfs_t - if err := unix.Fstatfs(int(fh.Fd()), &buf); err != nil { - return fmt.Errorf("ensure %s is on procfs: %w", fh.Name(), err) - } - if buf.Type != unix.PROC_SUPER_MAGIC { - return fmt.Errorf("%s is not on procfs", fh.Name()) - } - return nil -} - -var ( - haveCloseRangeCloexecBool bool - haveCloseRangeCloexecOnce sync.Once -) - -func haveCloseRangeCloexec() bool { - haveCloseRangeCloexecOnce.Do(func() { - // Make sure we're not closing a random file descriptor. - tmpFd, err := unix.FcntlInt(0, unix.F_DUPFD_CLOEXEC, 0) - if err != nil { - return - } - defer unix.Close(tmpFd) - - err = unix.CloseRange(uint(tmpFd), uint(tmpFd), unix.CLOSE_RANGE_CLOEXEC) - // Any error means we cannot use close_range(CLOSE_RANGE_CLOEXEC). - // -ENOSYS and -EINVAL ultimately mean we don't have support, but any - // other potential error would imply that even the most basic close - // operation wouldn't work. - haveCloseRangeCloexecBool = err == nil - }) - return haveCloseRangeCloexecBool -} - -type fdFunc func(fd int) - -// fdRangeFrom calls the passed fdFunc for each file descriptor that is open in -// the current process. -func fdRangeFrom(minFd int, fn fdFunc) error { - procSelfFd, closer := ProcThreadSelf("fd") - defer closer() - - fdDir, err := os.Open(procSelfFd) - if err != nil { - return err - } - defer fdDir.Close() - - if err := EnsureProcHandle(fdDir); err != nil { - return err - } - - fdList, err := fdDir.Readdirnames(-1) - if err != nil { - return err - } - for _, fdStr := range fdList { - fd, err := strconv.Atoi(fdStr) - // Ignore non-numeric file names. - if err != nil { - continue - } - // Ignore descriptors lower than our specified minimum. - if fd < minFd { - continue - } - // Ignore the file descriptor we used for readdir, as it will be closed - // when we return. - if uintptr(fd) == fdDir.Fd() { - continue - } - // Run the closure. - fn(fd) - } - return nil -} - -// CloseExecFrom sets the O_CLOEXEC flag on all file descriptors greater or -// equal to minFd in the current process. -func CloseExecFrom(minFd int) error { - // Use close_range(CLOSE_RANGE_CLOEXEC) if possible. - if haveCloseRangeCloexec() { - err := unix.CloseRange(uint(minFd), math.MaxUint, unix.CLOSE_RANGE_CLOEXEC) - return os.NewSyscallError("close_range", err) - } - // Otherwise, fall back to the standard loop. - return fdRangeFrom(minFd, unix.CloseOnExec) -} - -//go:linkname runtime_IsPollDescriptor internal/poll.IsPollDescriptor - -// In order to make sure we do not close the internal epoll descriptors the Go -// runtime uses, we need to ensure that we skip descriptors that match -// "internal/poll".IsPollDescriptor. Yes, this is a Go runtime internal thing, -// unfortunately there's no other way to be sure we're only keeping the file -// descriptors the Go runtime needs. Hopefully nothing blows up doing this... -func runtime_IsPollDescriptor(fd uintptr) bool //nolint:revive - -// UnsafeCloseFrom closes all file descriptors greater or equal to minFd in the -// current process, except for those critical to Go's runtime (such as the -// netpoll management descriptors). -// -// NOTE: That this function is incredibly dangerous to use in most Go code, as -// closing file descriptors from underneath *os.File handles can lead to very -// bad behaviour (the closed file descriptor can be re-used and then any -// *os.File operations would apply to the wrong file). This function is only -// intended to be called from the last stage of runc init. -func UnsafeCloseFrom(minFd int) error { - // We cannot use close_range(2) even if it is available, because we must - // not close some file descriptors. - return fdRangeFrom(minFd, func(fd int) { - if runtime_IsPollDescriptor(uintptr(fd)) { - // These are the Go runtimes internal netpoll file descriptors. - // These file descriptors are operated on deep in the Go scheduler, - // and closing those files from underneath Go can result in panics. - // There is no issue with keeping them because they are not - // executable and are not useful to an attacker anyway. Also we - // don't have any choice. - return - } - // There's nothing we can do about errors from close(2), and the - // only likely error to be seen is EBADF which indicates the fd was - // already closed (in which case, we got what we wanted). - _ = unix.Close(fd) - }) -} - -// NewSockPair returns a new SOCK_STREAM unix socket pair. -func NewSockPair(name string) (parent, child *os.File, err error) { - fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) - if err != nil { - return nil, nil, err - } - return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil -} - -// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...) -// corresponding to the unsafePath resolved within the root. Before passing the -// fd, this path is verified to have been inside the root -- so operating on it -// through the passed fdpath should be safe. Do not access this path through -// the original path strings, and do not attempt to use the pathname outside of -// the passed closure (the file handle will be freed once the closure returns). -func WithProcfd(root, unsafePath string, fn func(procfd string) error) error { - // Remove the root then forcefully resolve inside the root. - unsafePath = stripRoot(root, unsafePath) - path, err := securejoin.SecureJoin(root, unsafePath) - if err != nil { - return fmt.Errorf("resolving path inside rootfs failed: %w", err) - } - - procSelfFd, closer := ProcThreadSelf("fd/") - defer closer() - - // Open the target path. - fh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0) - if err != nil { - return fmt.Errorf("open o_path procfd: %w", err) - } - defer fh.Close() - - procfd := filepath.Join(procSelfFd, strconv.Itoa(int(fh.Fd()))) - // Double-check the path is the one we expected. - if realpath, err := os.Readlink(procfd); err != nil { - return fmt.Errorf("procfd verification failed: %w", err) - } else if realpath != path { - return fmt.Errorf("possibly malicious path detected -- refusing to operate on %s", realpath) - } - - return fn(procfd) -} - -type ProcThreadSelfCloser func() - -var ( - haveProcThreadSelf bool - haveProcThreadSelfOnce sync.Once -) - -// ProcThreadSelf returns a string that is equivalent to -// /proc/thread-self/, with a graceful fallback on older kernels where -// /proc/thread-self doesn't exist. This method DOES NOT use SecureJoin, -// meaning that the passed string needs to be trusted. The caller _must_ call -// the returned procThreadSelfCloser function (which is runtime.UnlockOSThread) -// *only once* after it has finished using the returned path string. -func ProcThreadSelf(subpath string) (string, ProcThreadSelfCloser) { - haveProcThreadSelfOnce.Do(func() { - if _, err := os.Stat("/proc/thread-self/"); err == nil { - haveProcThreadSelf = true - } else { - logrus.Debugf("cannot stat /proc/thread-self (%v), falling back to /proc/self/task/", err) - } - }) - - // We need to lock our thread until the caller is done with the path string - // because any non-atomic operation on the path (such as opening a file, - // then reading it) could be interrupted by the Go runtime where the - // underlying thread is swapped out and the original thread is killed, - // resulting in pull-your-hair-out-hard-to-debug issues in the caller. In - // addition, the pre-3.17 fallback makes everything non-atomic because the - // same thing could happen between unix.Gettid() and the path operations. - // - // In theory, we don't need to lock in the atomic user case when using - // /proc/thread-self/, but it's better to be safe than sorry (and there are - // only one or two truly atomic users of /proc/thread-self/). - runtime.LockOSThread() - - threadSelf := "/proc/thread-self/" - if !haveProcThreadSelf { - // Pre-3.17 kernels did not have /proc/thread-self, so do it manually. - threadSelf = "/proc/self/task/" + strconv.Itoa(unix.Gettid()) + "/" - if _, err := os.Stat(threadSelf); err != nil { - // Unfortunately, this code is called from rootfs_linux.go where we - // are running inside the pid namespace of the container but /proc - // is the host's procfs. Unfortunately there is no real way to get - // the correct tid to use here (the kernel age means we cannot do - // things like set up a private fsopen("proc") -- even scanning - // NSpid in all of the tasks in /proc/self/task/*/status requires - // Linux 4.1). - // - // So, we just have to assume that /proc/self is acceptable in this - // one specific case. - if os.Getpid() == 1 { - logrus.Debugf("/proc/thread-self (tid=%d) cannot be emulated inside the initial container setup -- using /proc/self instead: %v", unix.Gettid(), err) - } else { - // This should never happen, but the fallback should work in most cases... - logrus.Warnf("/proc/thread-self could not be emulated for pid=%d (tid=%d) -- using more buggy /proc/self fallback instead: %v", os.Getpid(), unix.Gettid(), err) - } - threadSelf = "/proc/self/" - } - } - return threadSelf + subpath, runtime.UnlockOSThread -} - -// ProcThreadSelfFd is small wrapper around ProcThreadSelf to make it easier to -// create a /proc/thread-self handle for given file descriptor. -// -// It is basically equivalent to ProcThreadSelf(fmt.Sprintf("fd/%d", fd)), but -// without using fmt.Sprintf to avoid unneeded overhead. -func ProcThreadSelfFd(fd uintptr) (string, ProcThreadSelfCloser) { - return ProcThreadSelf("fd/" + strconv.FormatUint(uint64(fd), 10)) -} - -// IsLexicallyInRoot is shorthand for strings.HasPrefix(path+"/", root+"/"), -// but properly handling the case where path or root are "/". -// -// NOTE: The return value only make sense if the path doesn't contain "..". -func IsLexicallyInRoot(root, path string) bool { - if root != "/" { - root += "/" - } - if path != "/" { - path += "/" - } - return strings.HasPrefix(path, root) -} - -// MkdirAllInRootOpen attempts to make -// -// path, _ := securejoin.SecureJoin(root, unsafePath) -// os.MkdirAll(path, mode) -// os.Open(path) -// -// safer against attacks where components in the path are changed between -// SecureJoin returning and MkdirAll (or Open) being called. In particular, we -// try to detect any symlink components in the path while we are doing the -// MkdirAll. -// -// NOTE: If unsafePath is a subpath of root, we assume that you have already -// called SecureJoin and so we use the provided path verbatim without resolving -// any symlinks (this is done in a way that avoids symlink-exchange races). -// This means that the path also must not contain ".." elements, otherwise an -// error will occur. -// -// This uses securejoin.MkdirAllHandle under the hood, but it has special -// handling if unsafePath has already been scoped within the rootfs (this is -// needed for a lot of runc callers and fixing this would require reworking a -// lot of path logic). -func MkdirAllInRootOpen(root, unsafePath string, mode os.FileMode) (_ *os.File, Err error) { - // If the path is already "within" the root, get the path relative to the - // root and use that as the unsafe path. This is necessary because a lot of - // MkdirAllInRootOpen callers have already done SecureJoin, and refactoring - // all of them to stop using these SecureJoin'd paths would require a fair - // amount of work. - // TODO(cyphar): Do the refactor to libpathrs once it's ready. - if IsLexicallyInRoot(root, unsafePath) { - subPath, err := filepath.Rel(root, unsafePath) - if err != nil { - return nil, err - } - unsafePath = subPath - } - - // Check for any silly mode bits. - if mode&^0o7777 != 0 { - return nil, fmt.Errorf("tried to include non-mode bits in MkdirAll mode: 0o%.3o", mode) - } - // Linux (and thus os.MkdirAll) silently ignores the suid and sgid bits if - // passed. While it would make sense to return an error in that case (since - // the user has asked for a mode that won't be applied), for compatibility - // reasons we have to ignore these bits. - if ignoredBits := mode &^ 0o1777; ignoredBits != 0 { - logrus.Warnf("MkdirAll called with no-op mode bits that are ignored by Linux: 0o%.3o", ignoredBits) - mode &= 0o1777 - } - - rootDir, err := os.OpenFile(root, unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - if err != nil { - return nil, fmt.Errorf("open root handle: %w", err) - } - defer rootDir.Close() - - return securejoin.MkdirAllHandle(rootDir, unsafePath, mode) -} - -// MkdirAllInRoot is a wrapper around MkdirAllInRootOpen which closes the -// returned handle, for callers that don't need to use it. -func MkdirAllInRoot(root, unsafePath string, mode os.FileMode) error { - f, err := MkdirAllInRootOpen(root, unsafePath, mode) - if err == nil { - _ = f.Close() - } - return err -} - -// Openat is a Go-friendly openat(2) wrapper. -func Openat(dir *os.File, path string, flags int, mode uint32) (*os.File, error) { - dirFd := unix.AT_FDCWD - if dir != nil { - dirFd = int(dir.Fd()) - } - flags |= unix.O_CLOEXEC - - fd, err := unix.Openat(dirFd, path, flags, mode) - if err != nil { - return nil, &os.PathError{Op: "openat", Path: path, Err: err} - } - return os.NewFile(uintptr(fd), dir.Name()+"/"+path), nil -} diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index d1236ba72..3ef333387 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -31,6 +31,8 @@ type Spec struct { VM *VM `json:"vm,omitempty" platform:"vm"` // ZOS is platform-specific configuration for z/OS based containers. ZOS *ZOS `json:"zos,omitempty" platform:"zos"` + // FreeBSD is platform-specific configuration for FreeBSD based containers. + FreeBSD *FreeBSD `json:"freebsd,omitempty" platform:"freebsd"` } // Scheduler represents the scheduling attributes for a process. It is based on @@ -83,7 +85,7 @@ type Process struct { // Rlimits specifies rlimit options to apply to the process. Rlimits []POSIXRlimit `json:"rlimits,omitempty" platform:"linux,solaris,zos"` // NoNewPrivileges controls whether additional privileges could be gained by processes in the container. - NoNewPrivileges bool `json:"noNewPrivileges,omitempty" platform:"linux"` + NoNewPrivileges bool `json:"noNewPrivileges,omitempty" platform:"linux,zos"` // ApparmorProfile specifies the apparmor profile for the container. ApparmorProfile string `json:"apparmorProfile,omitempty" platform:"linux"` // Specify an oom_score_adj for the container. @@ -94,10 +96,12 @@ type Process struct { SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"` // IOPriority contains the I/O priority settings for the cgroup. IOPriority *LinuxIOPriority `json:"ioPriority,omitempty" platform:"linux"` + // ExecCPUAffinity specifies CPU affinity for exec processes. + ExecCPUAffinity *CPUAffinity `json:"execCPUAffinity,omitempty" platform:"linux"` } // LinuxCapabilities specifies the list of allowed capabilities that are kept for a process. -// http://man7.org/linux/man-pages/man7/capabilities.7.html +// https://man7.org/linux/man-pages/man7/capabilities.7.html type LinuxCapabilities struct { // Bounding is the set of capabilities checked by the kernel. Bounding []string `json:"bounding,omitempty" platform:"linux"` @@ -127,6 +131,12 @@ const ( IOPRIO_CLASS_IDLE IOPriorityClass = "IOPRIO_CLASS_IDLE" ) +// CPUAffinity specifies process' CPU affinity. +type CPUAffinity struct { + Initial string `json:"initial,omitempty"` + Final string `json:"final,omitempty"` +} + // Box specifies dimensions of a rectangle. Used for specifying the size of a console. type Box struct { // Height is the vertical dimension of a box. @@ -162,7 +172,7 @@ type Mount struct { // Destination is the absolute path where the mount will be placed in the container. Destination string `json:"destination"` // Type specifies the mount kind. - Type string `json:"type,omitempty" platform:"linux,solaris,zos"` + Type string `json:"type,omitempty" platform:"linux,solaris,zos,freebsd"` // Source specifies the source path of the mount. Source string `json:"source,omitempty"` // Options are fstab style mount options. @@ -228,6 +238,8 @@ type Linux struct { Namespaces []LinuxNamespace `json:"namespaces,omitempty"` // Devices are a list of device nodes that are created for the container Devices []LinuxDevice `json:"devices,omitempty"` + // NetDevices are key-value pairs, keyed by network device name on the host, moved to the container's network namespace. + NetDevices map[string]LinuxNetDevice `json:"netDevices,omitempty"` // Seccomp specifies the seccomp security settings for the container. Seccomp *LinuxSeccomp `json:"seccomp,omitempty"` // RootfsPropagation is the rootfs mount propagation mode for the container. @@ -241,6 +253,8 @@ type Linux struct { // IntelRdt contains Intel Resource Director Technology (RDT) information for // handling resource constraints and monitoring metrics (e.g., L3 cache, memory bandwidth) for the container IntelRdt *LinuxIntelRdt `json:"intelRdt,omitempty"` + // MemoryPolicy contains NUMA memory policy for the container. + MemoryPolicy *LinuxMemoryPolicy `json:"memoryPolicy,omitempty"` // Personality contains configuration for the Linux personality syscall Personality *LinuxPersonality `json:"personality,omitempty"` // TimeOffsets specifies the offset for supporting time namespaces. @@ -422,7 +436,7 @@ type LinuxCPU struct { // LinuxPids for Linux cgroup 'pids' resource management (Linux 4.3) type LinuxPids struct { // Maximum number of PIDs. Default is "no limit". - Limit int64 `json:"limit"` + Limit *int64 `json:"limit,omitempty"` } // LinuxNetwork identification and priority configuration @@ -483,6 +497,12 @@ type LinuxDevice struct { GID *uint32 `json:"gid,omitempty"` } +// LinuxNetDevice represents a single network device to be added to the container's network namespace +type LinuxNetDevice struct { + // Name of the device in the container namespace + Name string `json:"name,omitempty"` +} + // LinuxDeviceCgroup represents a device rule for the devices specified to // the device controller type LinuxDeviceCgroup struct { @@ -627,6 +647,17 @@ type WindowsCPUResources struct { // cycles per 10,000 cycles. Set processor `maximum` to a percentage times // 100. Maximum *uint16 `json:"maximum,omitempty"` + // Set of CPUs to affinitize for this container. + Affinity []WindowsCPUGroupAffinity `json:"affinity,omitempty"` +} + +// Similar to _GROUP_AFFINITY struct defined in +// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/miniport/ns-miniport-_group_affinity +type WindowsCPUGroupAffinity struct { + // CPU mask relative to this CPU group. + Mask uint64 `json:"mask,omitempty"` + // Processor group the mask refers to, as returned by GetLogicalProcessorInformationEx. + Group uint32 `json:"group,omitempty"` } // WindowsStorageResources contains storage resource management settings. @@ -659,6 +690,32 @@ type WindowsHyperV struct { UtilityVMPath string `json:"utilityVMPath,omitempty"` } +// IOMems contains information about iomem addresses that should be passed to the VM. +type IOMems struct { + // Guest Frame Number to map the iomem range. If GFN is not specified, the mapping will be done to the same Frame Number as was provided in FirstMFN. + FirstGFN *uint64 `json:"firstGFN,omitempty"` + // Physical page number of iomem regions. + FirstMFN *uint64 `json:"firstMFN"` + // Number of pages to be mapped. + NrMFNs *uint64 `json:"nrMFNs"` +} + +// Hardware configuration for the VM image +type HWConfig struct { + // Path to the container device-tree file that should be passed to the VM configuration. + DeviceTree string `json:"deviceTree,omitempty"` + // Number of virtual cpus for the VM. + VCPUs *uint32 `json:"vcpus,omitempty"` + // Maximum memory in bytes allocated to the VM. + Memory *uint64 `json:"memory,omitempty"` + // Host device tree nodes to passthrough to the VM. + DtDevs []string `json:"dtdevs,omitempty"` + // Allow auto-translated domains to access specific hardware I/O memory pages. + IOMems []IOMems `json:"iomems,omitempty"` + // Allows VM to access specific physical IRQs. + Irqs []uint32 `json:"irqs,omitempty"` +} + // VM contains information for virtual-machine-based containers. type VM struct { // Hypervisor specifies hypervisor-related configuration for virtual-machine-based containers. @@ -667,6 +724,8 @@ type VM struct { Kernel VMKernel `json:"kernel"` // Image specifies guest image related configuration for virtual-machine-based containers. Image VMImage `json:"image,omitempty"` + // Hardware configuration that should be passed to the VM. + HwConfig *HWConfig `json:"hwconfig,omitempty"` } // VMHypervisor contains information about the hypervisor to use for a virtual machine. @@ -751,6 +810,10 @@ const ( ArchPARISC Arch = "SCMP_ARCH_PARISC" ArchPARISC64 Arch = "SCMP_ARCH_PARISC64" ArchRISCV64 Arch = "SCMP_ARCH_RISCV64" + ArchLOONGARCH64 Arch = "SCMP_ARCH_LOONGARCH64" + ArchM68K Arch = "SCMP_ARCH_M68K" + ArchSH Arch = "SCMP_ARCH_SH" + ArchSHEB Arch = "SCMP_ARCH_SHEB" ) // LinuxSeccompAction taken upon Seccomp rule match @@ -805,49 +868,92 @@ type LinuxSyscall struct { type LinuxIntelRdt struct { // The identity for RDT Class of Service ClosID string `json:"closID,omitempty"` + + // Schemata specifies the complete schemata to be written as is to the + // schemata file in resctrl fs. Each element represents a single line in the schemata file. + // NOTE: This will overwrite schemas specified in the L3CacheSchema and/or + // MemBwSchema fields. + Schemata []string `json:"schemata,omitempty"` + // The schema for L3 cache id and capacity bitmask (CBM) // Format: "L3:=;=;..." + // NOTE: Should not be specified if Schemata is non-empty. L3CacheSchema string `json:"l3CacheSchema,omitempty"` // The schema of memory bandwidth per L3 cache id // Format: "MB:=bandwidth0;=bandwidth1;..." // The unit of memory bandwidth is specified in "percentages" by // default, and in "MBps" if MBA Software Controller is enabled. + // NOTE: Should not be specified if Schemata is non-empty. MemBwSchema string `json:"memBwSchema,omitempty"` - // EnableCMT is the flag to indicate if the Intel RDT CMT is enabled. CMT (Cache Monitoring Technology) supports monitoring of - // the last-level cache (LLC) occupancy for the container. - EnableCMT bool `json:"enableCMT,omitempty"` + // EnableMonitoring enables resctrl monitoring for the container. This will + // create a dedicated resctrl monitoring group for the container. + EnableMonitoring bool `json:"enableMonitoring,omitempty"` +} + +// LinuxMemoryPolicy represents input for the set_mempolicy syscall. +type LinuxMemoryPolicy struct { + // Mode for the set_mempolicy syscall. + Mode MemoryPolicyModeType `json:"mode"` - // EnableMBM is the flag to indicate if the Intel RDT MBM is enabled. MBM (Memory Bandwidth Monitoring) supports monitoring of - // total and local memory bandwidth for the container. - EnableMBM bool `json:"enableMBM,omitempty"` + // Nodes representing the nodemask for the set_mempolicy syscall in comma separated ranges format. + // Format: "-,,-,..." + Nodes string `json:"nodes"` + + // Flags for the set_mempolicy syscall. + Flags []MemoryPolicyFlagType `json:"flags,omitempty"` } // ZOS contains platform-specific configuration for z/OS based containers. type ZOS struct { - // Devices are a list of device nodes that are created for the container - Devices []ZOSDevice `json:"devices,omitempty"` + // Namespaces contains the namespaces that are created and/or joined by the container + Namespaces []ZOSNamespace `json:"namespaces,omitempty"` } -// ZOSDevice represents the mknod information for a z/OS special device file -type ZOSDevice struct { - // Path to the device. - Path string `json:"path"` - // Device type, block, char, etc. - Type string `json:"type"` - // Major is the device's major number. - Major int64 `json:"major"` - // Minor is the device's minor number. - Minor int64 `json:"minor"` - // FileMode permission bits for the device. - FileMode *os.FileMode `json:"fileMode,omitempty"` - // UID of the device. - UID *uint32 `json:"uid,omitempty"` - // Gid of the device. - GID *uint32 `json:"gid,omitempty"` +// ZOSNamespace is the configuration for a z/OS namespace +type ZOSNamespace struct { + // Type is the type of namespace + Type ZOSNamespaceType `json:"type"` + // Path is a path to an existing namespace persisted on disk that can be joined + // and is of the same type + Path string `json:"path,omitempty"` } +// ZOSNamespaceType is one of the z/OS namespaces +type ZOSNamespaceType string + +const ( + // PIDNamespace for isolating process IDs + ZOSPIDNamespace ZOSNamespaceType = "pid" + // MountNamespace for isolating mount points + ZOSMountNamespace ZOSNamespaceType = "mount" + // IPCNamespace for isolating System V IPC, POSIX message queues + ZOSIPCNamespace ZOSNamespaceType = "ipc" + // UTSNamespace for isolating hostname and NIS domain name + ZOSUTSNamespace ZOSNamespaceType = "uts" +) + +type MemoryPolicyModeType string + +const ( + MpolDefault MemoryPolicyModeType = "MPOL_DEFAULT" + MpolBind MemoryPolicyModeType = "MPOL_BIND" + MpolInterleave MemoryPolicyModeType = "MPOL_INTERLEAVE" + MpolWeightedInterleave MemoryPolicyModeType = "MPOL_WEIGHTED_INTERLEAVE" + MpolPreferred MemoryPolicyModeType = "MPOL_PREFERRED" + MpolPreferredMany MemoryPolicyModeType = "MPOL_PREFERRED_MANY" + MpolLocal MemoryPolicyModeType = "MPOL_LOCAL" +) + +type MemoryPolicyFlagType string + +const ( + MpolFNumaBalancing MemoryPolicyFlagType = "MPOL_F_NUMA_BALANCING" + MpolFRelativeNodes MemoryPolicyFlagType = "MPOL_F_RELATIVE_NODES" + MpolFStaticNodes MemoryPolicyFlagType = "MPOL_F_STATIC_NODES" +) + // LinuxSchedulerPolicy represents different scheduling policies used with the Linux Scheduler type LinuxSchedulerPolicy string @@ -887,3 +993,75 @@ const ( // SchedFlagUtilClampMin represents the utilization clamp maximum scheduling flag SchedFlagUtilClampMax LinuxSchedulerFlag = "SCHED_FLAG_UTIL_CLAMP_MAX" ) + +// FreeBSD contains platform-specific configuration for FreeBSD based containers. +type FreeBSD struct { + // Devices which are accessible in the container + Devices []FreeBSDDevice `json:"devices,omitempty"` + // Jail definition for this container + Jail *FreeBSDJail `json:"jail,omitempty"` +} + +type FreeBSDDevice struct { + // Path to the device, relative to /dev. + Path string `json:"path"` + // FileMode permission bits for the device. + Mode *os.FileMode `json:"mode,omitempty"` +} + +// FreeBSDJail describes how to configure the container's jail +type FreeBSDJail struct { + // Parent jail name - this can be used to share a single vnet + // across several containers + Parent string `json:"parent,omitempty"` + // Whether to use parent UTS names or override in the container + Host FreeBSDSharing `json:"host,omitempty"` + // IPv4 address sharing for the container + Ip4 FreeBSDSharing `json:"ip4,omitempty"` + // IPv4 addresses for the container + Ip4Addr []string `json:"ip4Addr,omitempty"` + // IPv6 address sharing for the container + Ip6 FreeBSDSharing `json:"ip6,omitempty"` + // IPv6 addresses for the container + Ip6Addr []string `json:"ip6Addr,omitempty"` + // Which network stack to use for the container + Vnet FreeBSDSharing `json:"vnet,omitempty"` + // If set, Ip4Addr and Ip6Addr addresses will be added to this interface + Interface string `json:"interface,omitempty"` + // List interfaces to be moved to the container's vnet + VnetInterfaces []string `json:"vnetInterfaces,omitempty"` + // SystemV IPC message sharing for the container + SysVMsg FreeBSDSharing `json:"sysvmsg,omitempty"` + // SystemV semaphore message sharing for the container + SysVSem FreeBSDSharing `json:"sysvsem,omitempty"` + // SystemV memory sharing for the container + SysVShm FreeBSDSharing `json:"sysvshm,omitempty"` + // Mount visibility (see jail(8) for details) + EnforceStatfs *int `json:"enforceStatfs,omitempty"` + // Jail capabilities + Allow *FreeBSDJailAllow `json:"allow,omitempty"` +} + +// These values are used to control access to features in the container, either +// disabling the feature, sharing state with the parent or creating new private +// state in the container. +type FreeBSDSharing string + +const ( + FreeBSDShareDisable FreeBSDSharing = "disable" + FreeBSDShareNew FreeBSDSharing = "new" + FreeBSDShareInherit FreeBSDSharing = "inherit" +) + +// FreeBSDJailAllow describes jail capabilities +type FreeBSDJailAllow struct { + SetHostname bool `json:"setHostname,omitempty"` + RawSockets bool `json:"rawSockets,omitempty"` + Chflags bool `json:"chflags,omitempty"` + Mount []string `json:"mount,omitempty"` + Quotas bool `json:"quotas,omitempty"` + SocketAf bool `json:"socketAf,omitempty"` + Mlock bool `json:"mlock,omitempty"` + ReservedPorts bool `json:"reservedPorts,omitempty"` + Suser bool `json:"suser,omitempty"` +} diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go index 503971e05..0257dba3e 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go @@ -6,7 +6,7 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 1 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 2 + VersionMinor = 3 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 diff --git a/vendor/k8s.io/kubernetes/openshift-hack/e2e/include.go b/vendor/k8s.io/kubernetes/openshift-hack/e2e/include.go index e0b481a17..465631dc9 100644 --- a/vendor/k8s.io/kubernetes/openshift-hack/e2e/include.go +++ b/vendor/k8s.io/kubernetes/openshift-hack/e2e/include.go @@ -5,7 +5,6 @@ package e2e // k8s.io/kubernetes/test/e2e/e2e_test.go. It is intended to affect: // // - what is included in the k8s-e2e.test binary built from this package -// - the annotations generated by the annotate package import ( // define and freeze constants diff --git a/vendor/k8s.io/kubernetes/pkg/api/service/warnings.go b/vendor/k8s.io/kubernetes/pkg/api/service/warnings.go index 41e69704b..92fef3afa 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/service/warnings.go +++ b/vendor/k8s.io/kubernetes/pkg/api/service/warnings.go @@ -48,7 +48,7 @@ func GetWarningsForService(service, oldService *api.Service) []string { if len(service.Spec.ExternalIPs) > 0 { warnings = append(warnings, "spec.externalIPs is ignored for headless services") } - if service.Spec.SessionAffinity != "" { + if service.Spec.SessionAffinity != api.ServiceAffinityNone { warnings = append(warnings, "spec.SessionAffinity is ignored for headless services") } } diff --git a/vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go b/vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go index c847ad4f2..c6c3b31f0 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go @@ -89,12 +89,9 @@ const ( // PodNodeNameKeyIndex is the name of the index used by PodInformer to index pods by their node name. PodNodeNameKeyIndex = "spec.nodeName" - // OrphanPodIndexKey is used to index all Orphan pods to this key - OrphanPodIndexKey = "_ORPHAN_POD" - - // podControllerUIDIndex is the name for the Pod store's index function, - // which is to index by pods's controllerUID. - PodControllerUIDIndex = "podControllerUID" + // PodControllerIndex is the name for the Pod store's index function, + // which indexes by the key returned from PodControllerIndexKey. + PodControllerIndex = "podController" ) var UpdateTaintBackoff = wait.Backoff{ @@ -1139,43 +1136,59 @@ func AddPodNodeNameIndexer(podInformer cache.SharedIndexInformer) error { }) } -// OrphanPodIndexKeyForNamespace returns the orphan pod index key for a specific namespace. -func OrphanPodIndexKeyForNamespace(namespace string) string { - return OrphanPodIndexKey + "/" + namespace +// PodControllerIndexKey returns the index key to locate pods with the specified controller ownerReference. +// If ownerReference is nil, the returned key locates pods in the namespace without a controller ownerReference. +func PodControllerIndexKey(namespace string, ownerReference *metav1.OwnerReference) string { + if ownerReference == nil { + return namespace + } + return namespace + "/" + ownerReference.Kind + "/" + ownerReference.Name + "/" + string(ownerReference.UID) } -// AddPodControllerUIDIndexer adds an indexer for Pod's controllerRef.UID to the given PodInformer. +// AddPodControllerIndexer adds an indexer for Pod's controllerRef.UID to the given PodInformer. // This indexer is used to efficiently look up pods by their ControllerRef.UID -func AddPodControllerUIDIndexer(podInformer cache.SharedIndexInformer) error { - if _, exists := podInformer.GetIndexer().GetIndexers()[PodControllerUIDIndex]; exists { +func AddPodControllerIndexer(podInformer cache.SharedIndexInformer) error { + if _, exists := podInformer.GetIndexer().GetIndexers()[PodControllerIndex]; exists { // indexer already exists, do nothing return nil } return podInformer.AddIndexers(cache.Indexers{ - PodControllerUIDIndex: func(obj interface{}) ([]string, error) { + PodControllerIndex: func(obj interface{}) ([]string, error) { pod, ok := obj.(*v1.Pod) if !ok { return nil, nil } - // Get the ControllerRef of the Pod to check if it's managed by a controller - if ref := metav1.GetControllerOf(pod); ref != nil { - return []string{string(ref.UID)}, nil - } - // If the Pod has no controller (i.e., it's orphaned), index it with the OrphanPodIndexKeyForNamespace - // This helps identify orphan pods for reconciliation and adoption by controllers - return []string{OrphanPodIndexKeyForNamespace(pod.Namespace)}, nil + // Get the ControllerRef of the Pod to check if it's managed by a controller. + // Index with a non-nil controller (indicating an owned pod) or a nil controller (indicating an orphan pod). + return []string{PodControllerIndexKey(pod.Namespace, metav1.GetControllerOf(pod))}, nil }, }) } // FilterPodsByOwner gets the Pods managed by an owner or orphan Pods in the owner's namespace -func FilterPodsByOwner(podIndexer cache.Indexer, owner *metav1.ObjectMeta) ([]*v1.Pod, error) { +func FilterPodsByOwner(podIndexer cache.Indexer, owner *metav1.ObjectMeta, ownerKind string, includeOrphanedPods bool) ([]*v1.Pod, error) { result := []*v1.Pod{} - // Iterate over two keys: - // - the UID of the owner, which identifies Pods that are controlled by the owner - // - the OrphanPodIndexKey, which identifies orphaned Pods in the owner's namespace and might be adopted by the owner later - for _, key := range []string{string(owner.UID), OrphanPodIndexKeyForNamespace(owner.Namespace)} { - pods, err := podIndexer.ByIndex(PodControllerUIDIndex, key) + + if len(owner.Namespace) == 0 { + return nil, fmt.Errorf("no owner namespace provided") + } + if len(owner.Name) == 0 { + return nil, fmt.Errorf("no owner name provided") + } + if len(owner.UID) == 0 { + return nil, fmt.Errorf("no owner uid provided") + } + if len(ownerKind) == 0 { + return nil, fmt.Errorf("no owner kind provided") + } + // Always include the owner key, which identifies Pods that are controlled by the owner + keys := []string{PodControllerIndexKey(owner.Namespace, &metav1.OwnerReference{Name: owner.Name, Kind: ownerKind, UID: owner.UID})} + if includeOrphanedPods { + // Optionally include the unowned key, which identifies orphaned Pods in the owner's namespace and might be adopted by the owner later + keys = append(keys, PodControllerIndexKey(owner.Namespace, nil)) + } + for _, key := range keys { + pods, err := podIndexer.ByIndex(PodControllerIndex, key) if err != nil { return nil, err } diff --git a/vendor/k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go index 4965cb75c..0ab4290ae 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go @@ -223,7 +223,7 @@ func NewDaemonSetsController( dsc.podLister = podInformer.Lister() dsc.podStoreSynced = podInformer.Informer().HasSynced controller.AddPodNodeNameIndexer(podInformer.Informer()) - controller.AddPodControllerUIDIndexer(podInformer.Informer()) + controller.AddPodControllerIndexer(podInformer.Informer()) dsc.podIndexer = podInformer.Informer().GetIndexer() nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -710,7 +710,7 @@ func (dsc *DaemonSetsController) getDaemonPods(ctx context.Context, ds *apps.Dae return nil, err } // List all pods indexed to DS UID and Orphan pods - pods, err := controller.FilterPodsByOwner(dsc.podIndexer, &ds.ObjectMeta) + pods, err := controller.FilterPodsByOwner(dsc.podIndexer, &ds.ObjectMeta, "DaemonSet", true) if err != nil { return nil, err } diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go index 8934bd460..204f4c46a 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go @@ -220,7 +220,7 @@ func NewBaseController(logger klog.Logger, rsInformer appsinformers.ReplicaSetIn }) rsc.podLister = podInformer.Lister() rsc.podListerSynced = podInformer.Informer().HasSynced - controller.AddPodControllerUIDIndexer(podInformer.Informer()) //nolint:errcheck + controller.AddPodControllerIndexer(podInformer.Informer()) //nolint:errcheck rsc.podIndexer = podInformer.Informer().GetIndexer() rsc.syncHandler = rsc.syncReplicaSet @@ -728,7 +728,7 @@ func (rsc *ReplicaSetController) syncReplicaSet(ctx context.Context, key string) } // List all pods indexed to RS UID and Orphan pods - allRSPods, err := controller.FilterPodsByOwner(rsc.podIndexer, &rs.ObjectMeta) + allRSPods, err := controller.FilterPodsByOwner(rsc.podIndexer, &rs.ObjectMeta, rsc.Kind, true) if err != nil { return err } diff --git a/vendor/k8s.io/kubernetes/pkg/features/kube_features.go b/vendor/k8s.io/kubernetes/pkg/features/kube_features.go index a3b55db58..5b7794431 100644 --- a/vendor/k8s.io/kubernetes/pkg/features/kube_features.go +++ b/vendor/k8s.io/kubernetes/pkg/features/kube_features.go @@ -941,6 +941,12 @@ const ( // Enables policies controlling deletion of PVCs created by a StatefulSet. StatefulSetAutoDeletePVC featuregate.Feature = "StatefulSetAutoDeletePVC" + // owner: @liggitt + // + // Mitigates spurious statefulset rollouts due to controller revision comparison mismatches + // which are not semantically significant (e.g. serialization differences or missing defaulted fields). + StatefulSetSemanticRevisionComparison = "StatefulSetSemanticRevisionComparison" + // owner: @cupnes // kep: https://kep.k8s.io/4049 // @@ -1679,7 +1685,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate }, SchedulerAsyncAPICalls: { - {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Beta}, }, SchedulerAsyncPreemption: { @@ -1755,6 +1761,12 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.32, remove in 1.35 }, + StatefulSetSemanticRevisionComparison: { + // This is a mitigation for a 1.34 regression due to serialization differences that cannot be feature-gated, + // so this mitigation should not auto-disable even if emulating versions prior to 1.34 with --emulation-version. + {Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.Beta}, + }, + StorageCapacityScoring: { {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, }, diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/cgroup_manager_linux.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/cgroup_manager_linux.go index 8bec4833a..5251502f2 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/cgroup_manager_linux.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/cgroup_manager_linux.go @@ -25,11 +25,11 @@ import ( "sync" "time" + "github.com/opencontainers/cgroups" libcontainercgroups "github.com/opencontainers/cgroups" "github.com/opencontainers/cgroups/fscommon" libcontainercgroupmanager "github.com/opencontainers/cgroups/manager" cgroupsystemd "github.com/opencontainers/cgroups/systemd" - "github.com/opencontainers/runc/libcontainer/cgroups" "k8s.io/klog/v2" v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" @@ -308,7 +308,7 @@ func (m *cgroupCommon) toResources(resourceConfig *ResourceConfig) *libcontainer resources.CpuPeriod = *resourceConfig.CPUPeriod } if resourceConfig.PidsLimit != nil { - resources.PidsLimit = *resourceConfig.PidsLimit + resources.PidsLimit = resourceConfig.PidsLimit } if !resourceConfig.CPUSet.IsEmpty() { resources.CpusetCpus = resourceConfig.CPUSet.String() diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go index 2b76d3bd3..5305546cc 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin/dra_plugin_manager.go @@ -62,6 +62,9 @@ type DRAPluginManager struct { wipingDelay time.Duration streamHandler StreamHandler + // withIdleTimeout is only for unit testing, ignore if <= 0. + withIdleTimeout time.Duration + wg sync.WaitGroup mutex sync.RWMutex @@ -115,7 +118,13 @@ func (m *monitoredPlugin) HandleConn(_ context.Context, stats grpcstats.ConnStat case *grpcstats.ConnEnd: // We have to ask for a reconnect, otherwise gRPC wouldn't try and // thus we wouldn't be notified about a restart of the plugin. - m.conn.Connect() + // + // This must be done in a goroutine because gRPC deadlocks + // when called directly from inside HandleConn when a connection + // goes idle (and only then). It looks like cc.idlenessMgr.ExitIdleMode + // in Connect tries to lock a mutex that is already locked by + // the caller of HandleConn. + go m.conn.Connect() default: return } @@ -361,12 +370,15 @@ func (pm *DRAPluginManager) add(driverName string, endpoint string, chosenServic // The gRPC connection gets created once. gRPC then connects to the gRPC server on demand. target := "unix:" + endpoint logger.V(4).Info("Creating new gRPC connection", "target", target) - conn, err := grpc.NewClient( - target, + options := []grpc.DialOption{ grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithChainUnaryInterceptor(newMetricsInterceptor(driverName)), grpc.WithStatsHandler(mp), - ) + } + if pm.withIdleTimeout > 0 { + options = append(options, grpc.WithIdleTimeout(pm.withIdleTimeout)) + } + conn, err := grpc.NewClient(target, options...) if err != nil { return fmt.Errorf("create gRPC connection to DRA driver %s plugin at endpoint %s: %w", driverName, endpoint, err) } diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker.go index 8578c415d..60461165b 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker.go @@ -83,6 +83,16 @@ type worker struct { proberDurationUnknownMetricLabels metrics.Labels } +// isInitContainer checks if the worker's container is in the pod's init containers +func (w *worker) isInitContainer() bool { + for _, initContainer := range w.pod.Spec.InitContainers { + if initContainer.Name == w.container.Name { + return true + } + } + return false +} + // Creates and starts a new probe worker. func newWorker( m *manager, @@ -253,12 +263,17 @@ func (w *worker) doProbe(ctx context.Context) (keepGoing bool) { if !w.containerID.IsEmpty() { w.resultsManager.Set(w.containerID, results.Failure, w.pod) } + + isRestartableInitContainer := w.isInitContainer() && + w.container.RestartPolicy != nil && *w.container.RestartPolicy == v1.ContainerRestartPolicyAlways + // Abort if the container will not be restarted. if utilfeature.DefaultFeatureGate.Enabled(features.ContainerRestartRules) { return c.State.Terminated != nil || podutil.IsContainerRestartable(w.pod.Spec, w.container) } return c.State.Terminated == nil || - w.pod.Spec.RestartPolicy != v1.RestartPolicyNever + w.pod.Spec.RestartPolicy != v1.RestartPolicyNever || + isRestartableInitContainer } // Graceful shutdown of the pod. diff --git a/vendor/k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go b/vendor/k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go index 9e75df58d..48b9f2fc8 100644 --- a/vendor/k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go +++ b/vendor/k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go @@ -116,8 +116,8 @@ func (pl *TaintToleration) Filter(ctx context.Context, state fwk.CycleState, pod return nil } - errReason := fmt.Sprintf("node(s) had untolerated taint {%s: %s}", taint.Key, taint.Value) - return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, errReason) + klog.FromContext(ctx).V(4).Info("node had untolerated taints", "node", klog.KObj(node), "pod", klog.KObj(pod), "untoleratedTaint", taint) + return fwk.NewStatus(fwk.UnschedulableAndUnresolvable, "node(s) had untolerated taint(s)") } // preScoreState computed at PreScore and used at Score. diff --git a/vendor/k8s.io/kubernetes/pkg/scheduler/framework/preemption/preemption.go b/vendor/k8s.io/kubernetes/pkg/scheduler/framework/preemption/preemption.go index 9bcd91592..ee98910ef 100644 --- a/vendor/k8s.io/kubernetes/pkg/scheduler/framework/preemption/preemption.go +++ b/vendor/k8s.io/kubernetes/pkg/scheduler/framework/preemption/preemption.go @@ -189,12 +189,12 @@ func NewEvaluator(pluginName string, fh framework.Handle, i Interface, enableAsy } } if err := util.DeletePod(ctx, ev.Handler.ClientSet(), victim); err != nil { - if apierrors.IsNotFound(err) { - logger.V(2).Info("Victim Pod is already deleted", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) - } else { + if !apierrors.IsNotFound(err) { logger.Error(err, "Tried to preempted pod", "pod", klog.KObj(victim), "preemptor", klog.KObj(preemptor)) + return err } - return err + logger.V(2).Info("Victim Pod is already deleted", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) + return nil } logger.V(2).Info("Preemptor Pod preempted victim Pod", "preemptor", klog.KObj(preemptor), "victim", klog.KObj(victim), "node", c.Name()) } @@ -436,14 +436,7 @@ func (ev *Evaluator) prepareCandidate(ctx context.Context, c Candidate, pod *v1. logger := klog.FromContext(ctx) errCh := parallelize.NewErrorChannel() fh.Parallelizer().Until(ctx, len(c.Victims().Pods), func(index int) { - victimPod := c.Victims().Pods[index] - if victimPod.DeletionTimestamp != nil { - // If the victim Pod is already being deleted, we don't have to make another deletion api call. - logger.V(2).Info("Victim Pod is already deleted, skipping the API call for it", "preemptor", klog.KObj(pod), "node", c.Name(), "victim", klog.KObj(victimPod)) - return - } - - if err := ev.PreemptPod(ctx, c, pod, victimPod, pluginName); err != nil && !apierrors.IsNotFound(err) { + if err := ev.PreemptPod(ctx, c, pod, c.Victims().Pods[index], pluginName); err != nil { errCh.SendErrorWithCancel(err, cancel) } }, ev.PluginName) @@ -504,34 +497,11 @@ func (ev *Evaluator) prepareCandidateAsync(c Candidate, pod *v1.Pod, pluginName // Intentionally create a new context, not using a ctx from the scheduling cycle, to create ctx, // because this process could continue even after this scheduling cycle finishes. ctx, cancel := context.WithCancel(context.Background()) - logger := klog.FromContext(ctx) - victimPods := make([]*v1.Pod, 0, len(c.Victims().Pods)) - for _, victim := range c.Victims().Pods { - if victim.DeletionTimestamp != nil { - // If the victim Pod is already being deleted, we don't have to make another deletion api call. - logger.V(2).Info("Victim Pod is already deleted, skipping the API call for it", "preemptor", klog.KObj(pod), "node", c.Name(), "victim", klog.KObj(victim)) - continue - } - victimPods = append(victimPods, victim) - } - if len(victimPods) == 0 { - cancel() - return - } - errCh := parallelize.NewErrorChannel() - // Whether all victim pods are already deleted before making API call. - var allPodsAlreadyDeleted atomic.Bool - allPodsAlreadyDeleted.Store(true) preemptPod := func(index int) { - victim := victimPods[index] - err := ev.PreemptPod(ctx, c, pod, victim, pluginName) - switch { - case err != nil && !apierrors.IsNotFound(err): - // We don't have to handle NotFound error here, because it means the victim Pod is already deleted, and the preemption didn't have to remove it. + victim := c.Victims().Pods[index] + if err := ev.PreemptPod(ctx, c, pod, victim, pluginName); err != nil { errCh.SendErrorWithCancel(err, cancel) - case err == nil: - allPodsAlreadyDeleted.Store(false) } } @@ -539,24 +509,21 @@ func (ev *Evaluator) prepareCandidateAsync(c Candidate, pod *v1.Pod, pluginName ev.preempting.Insert(pod.UID) ev.mu.Unlock() + logger := klog.FromContext(ctx) go func() { startTime := time.Now() result := metrics.GoroutineResultSuccess - defer metrics.PreemptionGoroutinesDuration.WithLabelValues(result).Observe(metrics.SinceInSeconds(startTime)) defer metrics.PreemptionGoroutinesExecutionTotal.WithLabelValues(result).Inc() defer func() { - // When API call isn't successful, the Pod may get stuck in the unschedulable pod pool in the worst case. - // So, we should move the Pod to the activeQ. - if result == metrics.GoroutineResultError || - // When all pods are already deleted (which is very rare, but could happen in theory), - // it's safe to activate the preemptor Pod because it might miss Pod/delete event that requeues the pod. - allPodsAlreadyDeleted.Load() { + if result == metrics.GoroutineResultError { + // When API call isn't successful, the Pod may get stuck in the unschedulable pod pool in the worst case. + // So, we should move the Pod to the activeQ. ev.Handler.Activate(logger, map[string]*v1.Pod{pod.Name: pod}) } }() defer cancel() - logger.V(2).Info("Start the preemption asynchronously", "preemptor", klog.KObj(pod), "node", c.Name(), "numVictims", len(c.Victims().Pods), "numVictimsToDelete", len(victimPods)) + logger.V(2).Info("Start the preemption asynchronously", "preemptor", klog.KObj(pod), "node", c.Name(), "numVictims", len(c.Victims().Pods)) // Lower priority pods nominated to run on this node, may no longer fit on // this node. So, we should remove their nomination. Removing their @@ -569,32 +536,33 @@ func (ev *Evaluator) prepareCandidateAsync(c Candidate, pod *v1.Pod, pluginName // We do not return as this error is not critical. } - if len(victimPods) > 1 { - // We can evict all victims in parallel, but the last one. - // We have to remove the pod from the preempting map before the last one is evicted - // because, otherwise, the pod removal might be notified to the scheduling queue before - // we remove this pod from the preempting map, - // and the pod could end up stucking at the unschedulable pod pool - // by all the pod removal events being ignored. - ev.Handler.Parallelizer().Until(ctx, len(victimPods)-1, preemptPod, ev.PluginName) - if err := errCh.ReceiveError(); err != nil { - utilruntime.HandleErrorWithContext(ctx, err, "Error occurred during async preemption") - result = metrics.GoroutineResultError - } + if len(c.Victims().Pods) == 0 { + ev.mu.Lock() + delete(ev.preempting, pod.UID) + ev.mu.Unlock() + + return + } + + // We can evict all victims in parallel, but the last one. + // We have to remove the pod from the preempting map before the last one is evicted + // because, otherwise, the pod removal might be notified to the scheduling queue before + // we remove this pod from the preempting map, + // and the pod could end up stucking at the unschedulable pod pool + // by all the pod removal events being ignored. + ev.Handler.Parallelizer().Until(ctx, len(c.Victims().Pods)-1, preemptPod, ev.PluginName) + if err := errCh.ReceiveError(); err != nil { + utilruntime.HandleErrorWithContext(ctx, err, "Error occurred during async preemption") + result = metrics.GoroutineResultError } ev.mu.Lock() delete(ev.preempting, pod.UID) ev.mu.Unlock() - err := ev.PreemptPod(ctx, c, pod, victimPods[len(victimPods)-1], pluginName) - switch { - case err != nil && !apierrors.IsNotFound(err): - // We don't have to handle NotFound error here, because it means the victim Pod is already deleted, and the preemption didn't have to remove it. + if err := ev.PreemptPod(ctx, c, pod, c.Victims().Pods[len(c.Victims().Pods)-1], pluginName); err != nil { utilruntime.HandleErrorWithContext(ctx, err, "Error occurred during async preemption") result = metrics.GoroutineResultError - case err == nil: - allPodsAlreadyDeleted.Store(false) } logger.V(2).Info("Async Preemption finished completely", "preemptor", klog.KObj(pod), "node", c.Name(), "result", result) diff --git a/vendor/k8s.io/kubernetes/pkg/volume/csi/csi_attacher.go b/vendor/k8s.io/kubernetes/pkg/volume/csi/csi_attacher.go index 314fe68a8..c4fd43506 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/csi/csi_attacher.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/csi/csi_attacher.go @@ -252,7 +252,7 @@ func (c *csiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.No } func (c *csiAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) { - klog.V(4).Info(log("attacher.GetDeviceMountPath(%v)", spec)) + klog.V(4).Info(log("attacher.GetDeviceMountPath for volume(%s)", spec.Name())) deviceMountPath, err := makeDeviceMountPath(c.plugin, spec) if err != nil { return "", errors.New(log("attacher.GetDeviceMountPath failed to make device mount path: %v", err)) diff --git a/vendor/k8s.io/kubernetes/pkg/volume/plugins.go b/vendor/k8s.io/kubernetes/pkg/volume/plugins.go index a6426cbd9..24b661493 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/plugins.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/plugins.go @@ -997,7 +997,7 @@ func NewPersistentVolumeRecyclerPodTemplate() *v1.Pod { Containers: []v1.Container{ { Name: "pv-recycler", - Image: "registry.k8s.io/build-image/debian-base:bookworm-v1.0.4", + Image: "registry.k8s.io/build-image/debian-base:bookworm-v1.0.6", Command: []string{"/bin/sh"}, Args: []string{"-c", "test -e /scrub && find /scrub -mindepth 1 -delete && test -z \"$(ls -A /scrub)\" || exit 1"}, VolumeMounts: []v1.VolumeMount{ diff --git a/vendor/k8s.io/kubernetes/pkg/volume/portworx/portworx.go b/vendor/k8s.io/kubernetes/pkg/volume/portworx/portworx.go index c41f06b3a..f8d4c348c 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/portworx/portworx.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/portworx/portworx.go @@ -308,8 +308,9 @@ func (b *portworxVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterAr notMnt, err := b.mounter.IsLikelyNotMountPoint(dir) klog.Infof("Portworx Volume set up. Dir: %s %v %v", dir, !notMnt, err) if err != nil && !os.IsNotExist(err) { - klog.Errorf("Cannot validate mountpoint: %s", dir) - return err + // don't log error details from client calls in events + klog.V(4).Infof("Cannot validate mountpoint %s: %v", dir, err) + return fmt.Errorf("failed to validate mountpoint: see kube-controller-manager.log for details") } if !notMnt { return nil @@ -319,7 +320,9 @@ func (b *portworxVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterAr attachOptions[attachContextKey] = dir attachOptions[attachHostKey] = b.plugin.host.GetHostName() if _, err := b.manager.AttachVolume(b, attachOptions); err != nil { - return err + // don't log error details from client calls in events + klog.V(4).Infof("Failed to attach volume %s: %v", b.volumeID, err) + return fmt.Errorf("failed to attach volume: see kube-controller-manager.log for details") } klog.V(4).Infof("Portworx Volume %s attached", b.volumeID) @@ -329,7 +332,9 @@ func (b *portworxVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterAr } if err := b.manager.MountVolume(b, dir); err != nil { - return err + // don't log error details from client calls in events + klog.V(4).Infof("Failed to mount volume %s: %v", b.volumeID, err) + return fmt.Errorf("failed to mount volume: see kube-controller-manager.log for details") } if !b.readOnly { // Since portworxVolume is in process of being removed from in-tree, we avoid larger refactor to add progress tracking for ownership operation @@ -362,12 +367,16 @@ func (c *portworxVolumeUnmounter) TearDownAt(dir string) error { klog.Infof("Portworx Volume TearDown of %s", dir) if err := c.manager.UnmountVolume(c, dir); err != nil { - return err + // don't log error details from client calls in events + klog.V(4).Infof("Failed to unmount volume %s: %v", c.volumeID, err) + return fmt.Errorf("failed to unmount volume: see kube-controller-manager.log for details") } // Call Portworx Detach Volume. if err := c.manager.DetachVolume(c); err != nil { - return err + // don't log error details from client calls in events + klog.V(4).Infof("Failed to detach volume %s: %v", c.volumeID, err) + return fmt.Errorf("failed to detach volume: see kube-controller-manager.log for details") } return nil @@ -384,7 +393,13 @@ func (d *portworxVolumeDeleter) GetPath() string { } func (d *portworxVolumeDeleter) Delete() error { - return d.manager.DeleteVolume(d) + err := d.manager.DeleteVolume(d) + if err != nil { + // don't log error details from client calls in events + klog.V(4).Infof("Failed to delete volume %s: %v", d.volumeID, err) + return fmt.Errorf("failed to delete volume: see kube-controller-manager.log for details") + } + return nil } type portworxVolumeProvisioner struct { @@ -405,7 +420,9 @@ func (c *portworxVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopo volumeID, sizeGiB, labels, err := c.manager.CreateVolume(c) if err != nil { - return nil, err + // don't log error details from client calls in events + klog.V(4).Infof("Failed to create volume: %v", err) + return nil, fmt.Errorf("failed to create volume: see kube-controller-manager.log for details") } pv := &v1.PersistentVolume{ diff --git a/vendor/k8s.io/kubernetes/test/e2e/common/node/framework/cgroups/cgroups_linux.go b/vendor/k8s.io/kubernetes/test/e2e/common/node/framework/cgroups/cgroups_linux.go index c77c560d5..c7f3064b5 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/common/node/framework/cgroups/cgroups_linux.go +++ b/vendor/k8s.io/kubernetes/test/e2e/common/node/framework/cgroups/cgroups_linux.go @@ -17,13 +17,33 @@ limitations under the License. package cgroups import ( + "math" "strconv" - libcontainercgroups "github.com/opencontainers/cgroups" v1 "k8s.io/api/core/v1" kubecm "k8s.io/kubernetes/pkg/kubelet/cm" ) +// convertCPUSharesToCgroupV2Value is copied from ConvertCPUSharesToCgroupV2Value in opencontainers/cgroups. +// https://github.com/opencontainers/cgroups/pull/20/files +func convertCPUSharesToCgroupV2Value(cpuShares uint64) uint64 { + // The value of 0 means "unset". + if cpuShares == 0 { + return 0 + } + if cpuShares <= 2 { + return 1 + } + if cpuShares >= 262144 { + return 10000 + } + l := math.Log2(float64(cpuShares)) + // Quadratic function which fits min, max, and default. + exponent := (l*l+125*l)/612.0 - 7.0/34.0 + + return uint64(math.Ceil(math.Pow(10, exponent))) +} + func getExpectedCPUShares(rr *v1.ResourceRequirements, podOnCgroupv2 bool) []string { // This function is moved out from cgroups.go because opencontainers/cgroups can only be compiled in linux platforms. cpuRequest := rr.Requests.Cpu() @@ -42,7 +62,7 @@ func getExpectedCPUShares(rr *v1.ResourceRequirements, podOnCgroupv2 bool) []str // container runtimes, we check if either the old or the new conversion matches the actual value for now. // TODO: Remove the old conversion once container runtimes are updated. oldConverted := 1 + ((shares-2)*9999)/262142 - converted := libcontainercgroups.ConvertCPUSharesToCgroupV2Value(uint64(shares)) + converted := convertCPUSharesToCgroupV2Value(uint64(shares)) return []string{strconv.FormatInt(oldConverted, 10), strconv.FormatInt(int64(converted), 10)} } else { return []string{strconv.FormatInt(shares, 10)} diff --git a/vendor/k8s.io/kubernetes/test/e2e/common/node/pod_resize.go b/vendor/k8s.io/kubernetes/test/e2e/common/node/pod_resize.go index 8fa7af2e5..fbd7dfbb3 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/common/node/pod_resize.go +++ b/vendor/k8s.io/kubernetes/test/e2e/common/node/pod_resize.go @@ -49,12 +49,12 @@ const ( increasedCPU = "25m" increasedCPULimit = "35m" - originalMem = "20Mi" - originalMemLimit = "30Mi" - reducedMem = "15Mi" - reducedMemLimit = "25Mi" - increasedMem = "25Mi" - increasedMemLimit = "35Mi" + originalMem = "35Mi" + originalMemLimit = "45Mi" + reducedMem = "30Mi" + reducedMemLimit = "40Mi" + increasedMem = "40Mi" + increasedMemLimit = "50Mi" ) func offsetCPU(index int, value string) string { diff --git a/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/kubelet_metrics.go b/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/kubelet_metrics.go index fd0776d1b..ae7a7e211 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/kubelet_metrics.go +++ b/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/kubelet_metrics.go @@ -33,7 +33,6 @@ import ( ) const ( - proxyTimeout = 2 * time.Minute // dockerOperationsLatencyKey is the key for the operation latency metrics. // Taken from k8s.io/kubernetes/pkg/kubelet/dockershim/metrics dockerOperationsLatencyKey = "docker_operations_duration_seconds" diff --git a/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/metrics_grabber.go b/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/metrics_grabber.go index 9919cccd9..b49889aa2 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/metrics_grabber.go +++ b/vendor/k8s.io/kubernetes/test/e2e/framework/metrics/metrics_grabber.go @@ -29,6 +29,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + k8snet "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -94,7 +95,6 @@ type Grabber struct { // support it. If disabled for a component, the corresponding Grab function // will immediately return an error derived from MetricsGrabbingDisabledError. func NewMetricsGrabber(ctx context.Context, c clientset.Interface, ec clientset.Interface, config *rest.Config, kubelets bool, scheduler bool, controllers bool, apiServer bool, clusterAutoscaler bool, snapshotController bool) (*Grabber, error) { - kubeScheduler := "" kubeControllerManager := "" snapshotControllerManager := "" @@ -213,28 +213,29 @@ func (g *Grabber) grabFromKubeletInternal(ctx context.Context, nodeName string, } func (g *Grabber) getMetricsFromNode(ctx context.Context, nodeName string, kubeletPort int, pathSuffix string) (string, error) { - // There's a problem with timing out during proxy. Wrapping this in a goroutine to prevent deadlock. - finished := make(chan struct{}, 1) + // There's a problem with timing out during proxy. We are going to set a 45 second client timeout, and issue a retry. var err error - var rawOutput []byte - go func() { - rawOutput, err = g.client.CoreV1().RESTClient().Get(). + var output []byte + err = wait.PollUntilContextTimeout(ctx, 15*time.Second, 2*time.Minute, true, func(ctx context.Context) (done bool, retErr error) { + rawOutput, err := g.client.CoreV1().RESTClient().Get(). Resource("nodes"). SubResource("proxy"). Name(fmt.Sprintf("%v:%v", nodeName, kubeletPort)). Suffix(pathSuffix). + Timeout(45 * time.Second). Do(ctx).Raw() - finished <- struct{}{} - }() - select { - case <-time.After(proxyTimeout): - return "", fmt.Errorf("Timed out when waiting for proxy to gather metrics from %v", nodeName) - case <-finished: if err != nil { - return "", err + if k8snet.IsTimeout(err) { + klog.Warningf("Metrics rest call timed out") + return false, nil + } + klog.Warningf("Metrics rest call errored: %v", err) + return false, nil } - return string(rawOutput), nil - } + output = rawOutput + return true, nil + }) + return string(output), err } // GrabFromKubeProxy returns metrics from kube-proxy diff --git a/vendor/k8s.io/kubernetes/test/e2e/framework/pod/wait.go b/vendor/k8s.io/kubernetes/test/e2e/framework/pod/wait.go index d7067be28..0a8f31532 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/framework/pod/wait.go +++ b/vendor/k8s.io/kubernetes/test/e2e/framework/pod/wait.go @@ -44,10 +44,10 @@ import ( const ( // defaultPodDeletionTimeout is the default timeout for deleting pod. - defaultPodDeletionTimeout = 3 * time.Minute + defaultPodDeletionTimeout = 10 * time.Minute // podListTimeout is how long to wait for the pod to be listable. - podListTimeout = time.Minute + podListTimeout = 5 * time.Minute podRespondingTimeout = 15 * time.Minute @@ -55,7 +55,7 @@ const ( podScheduledBeforeTimeout = podListTimeout + (20 * time.Second) // podStartTimeout is how long to wait for the pod to be started. - podStartTimeout = 5 * time.Minute + podStartTimeout = 10 * time.Minute // singleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent // transient failures from failing tests. diff --git a/vendor/k8s.io/kubernetes/test/e2e/framework/pv/wait.go b/vendor/k8s.io/kubernetes/test/e2e/framework/pv/wait.go index ebfe227af..aa94b1708 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/framework/pv/wait.go +++ b/vendor/k8s.io/kubernetes/test/e2e/framework/pv/wait.go @@ -76,11 +76,16 @@ func WaitForPersistentVolumeClaimModificationFailure(ctx context.Context, c clie desiredClass := ptr.Deref(claim.Spec.VolumeAttributesClassName, "") var match = func(claim *v1.PersistentVolumeClaim) bool { + foundErrorCondition := false for _, condition := range claim.Status.Conditions { - if condition.Type != v1.PersistentVolumeClaimVolumeModifyVolumeError { - return false + if condition.Type == v1.PersistentVolumeClaimVolumeModifyVolumeError { + foundErrorCondition = true } } + // if no error found it must be an error + if !foundErrorCondition { + return false + } // check if claim's current volume attributes class is NOT desired one, and has appropriate ModifyVolumeStatus currentClass := ptr.Deref(claim.Status.CurrentVolumeAttributesClassName, "") diff --git a/vendor/k8s.io/kubernetes/test/e2e/kubectl/kubectl.go b/vendor/k8s.io/kubernetes/test/e2e/kubectl/kubectl.go index 2e27e3f7a..a73583d49 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/kubectl/kubectl.go +++ b/vendor/k8s.io/kubernetes/test/e2e/kubectl/kubectl.go @@ -2138,7 +2138,9 @@ metadata: }) ginkgo.It("GET on status subresource of built-in type (node) returns identical info as GET on the built-in type", func(ctx context.Context) { ginkgo.By("first listing nodes in the cluster, and using first node of the list") - nodes, err := c.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + nodes, err := c.CoreV1().Nodes().List(ctx, metav1.ListOptions{ + FieldSelector: "spec.unschedulable=false", + }) framework.ExpectNoError(err) gomega.Expect(nodes.Items).ToNot(gomega.BeEmpty()) node := nodes.Items[0] diff --git a/vendor/k8s.io/kubernetes/test/e2e/scheduling/preemption.go b/vendor/k8s.io/kubernetes/test/e2e/scheduling/preemption.go index a4e3129c3..04b608226 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/scheduling/preemption.go +++ b/vendor/k8s.io/kubernetes/test/e2e/scheduling/preemption.go @@ -373,7 +373,7 @@ var _ = SIGDescribe("SchedulerPreemption", framework.WithSerial(), func() { highPriorityPods := make([]*v1.Pod, 0, 5*nodeListLen) mediumPriorityPods := make([]*v1.Pod, 0, 10*nodeListLen) - ginkgo.By("Run high/medium priority pods that have same requirements as that of lower priority pod") + ginkgo.By("Run medium priority pods that have same requirements as that of lower priority pod") for i := range nodeList.Items { // Create medium priority pods first // to confirm the scheduler finally prioritize the high priority pods, ignoring the medium priority pods. @@ -391,19 +391,6 @@ var _ = SIGDescribe("SchedulerPreemption", framework.WithSerial(), func() { }) mediumPriorityPods = append(mediumPriorityPods, p) } - - for j := 0; j < 5; j++ { - p := createPausePod(ctx, f, pausePodConfig{ - Name: fmt.Sprintf("pod%d-%d-%v", i, j, highPriorityClassName), - PriorityClassName: highPriorityClassName, - Resources: &v1.ResourceRequirements{ - // Set the pod request to the low priority pod's resources - Requests: lowPriorityPods[0].Spec.Containers[0].Resources.Requests, - Limits: lowPriorityPods[0].Spec.Containers[0].Resources.Requests, - }, - }) - highPriorityPods = append(highPriorityPods, p) - } } // All low priority Pods should be the target of preemption. @@ -419,6 +406,22 @@ var _ = SIGDescribe("SchedulerPreemption", framework.WithSerial(), func() { })) } + ginkgo.By("Run high priority pods that have same requirements as that of lower priority pod") + for i := range nodeList.Items { + for j := 0; j < 5; j++ { + p := createPausePod(ctx, f, pausePodConfig{ + Name: fmt.Sprintf("pod%d-%d-%v", i, j, highPriorityClassName), + PriorityClassName: highPriorityClassName, + Resources: &v1.ResourceRequirements{ + // Set the pod request to the low priority pod's resources + Requests: lowPriorityPods[0].Spec.Containers[0].Resources.Requests, + Limits: lowPriorityPods[0].Spec.Containers[0].Resources.Requests, + }, + }) + highPriorityPods = append(highPriorityPods, p) + } + } + // All high priority Pods should be schedulable by removing the low priority Pods. ginkgo.By("Wait for high priority pods to be ready for the preemption.") for _, pod := range highPriorityPods { diff --git a/vendor/k8s.io/kubernetes/test/e2e/storage/csimock/mutable_csinode_allocatable.go b/vendor/k8s.io/kubernetes/test/e2e/storage/csimock/mutable_csinode_allocatable.go index eccdcdb41..179d2eb2c 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/storage/csimock/mutable_csinode_allocatable.go +++ b/vendor/k8s.io/kubernetes/test/e2e/storage/csimock/mutable_csinode_allocatable.go @@ -18,7 +18,6 @@ package csimock import ( "context" - "encoding/json" "fmt" "strings" "sync/atomic" @@ -30,17 +29,12 @@ import ( "github.com/onsi/ginkgo/v2" - jsonpatch "gopkg.in/evanphx/json-patch.v4" - storagev1 "k8s.io/api/storage/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/test/e2e/framework" e2epod "k8s.io/kubernetes/test/e2e/framework/pod" - e2evolume "k8s.io/kubernetes/test/e2e/framework/volume" "k8s.io/kubernetes/test/e2e/storage/drivers" storageframework "k8s.io/kubernetes/test/e2e/storage/framework" "k8s.io/kubernetes/test/e2e/storage/utils" @@ -145,9 +139,10 @@ var _ = utils.SIGDescribe("MutableCSINodeAllocatableCount", framework.WithFeatur } opts := drivers.CSIMockDriverOpts{ - Embedded: true, - RegisterDriver: true, - Hooks: hook, + Embedded: true, + EnableMutableCSINodeAllocatableCount: true, + RegisterDriver: true, + Hooks: hook, } driver = drivers.InitMockCSIDriver(opts) cfg = driver.PrepareTest(ctx, f) @@ -173,45 +168,13 @@ var _ = utils.SIGDescribe("MutableCSINodeAllocatableCount", framework.WithFeatur }) f.It("should transition pod to failed state when attachment limit exceeded", func(ctx context.Context) { - _, claim, pod := m.createPod(ctx, pvcReference) + _, _, pod := m.createPod(ctx, pvcReference) if pod == nil { return } - ginkgo.By("Checking if VolumeAttachment was created for the pod") - testConfig := storageframework.ConvertTestConfig(m.config) - attachmentName := e2evolume.GetVolumeAttachmentName(ctx, m.cs, testConfig, m.provisioner, claim.Name, claim.Namespace) - - var va *storagev1.VolumeAttachment - err := wait.PollUntilContextTimeout(ctx, framework.Poll, 30*time.Second, true, func(ctx context.Context) (bool, error) { - var getErr error - va, getErr = m.cs.StorageV1().VolumeAttachments().Get(ctx, attachmentName, metav1.GetOptions{}) - if getErr != nil { - if apierrors.IsNotFound(getErr) { - return false, nil - } - return false, getErr - } - return true, nil - }) - framework.ExpectNoError(err, "VolumeAttachment not created") - - // Patch VolumeAttachment.Status.AttachError.ErrorCode with ResourceExhausted - clone := va.DeepCopy() - clone.Status.Attached = false - errorCode := int32(codes.ResourceExhausted) - clone.Status.AttachError = &storagev1.VolumeError{ - Message: "ResourceExhausted: attachment limit exceeded", - Time: metav1.Now(), - ErrorCode: &errorCode, - } - patch, err := createMergePatch(va, clone) - framework.ExpectNoError(err, "Failed to create merge patch") - _, err = m.cs.StorageV1().VolumeAttachments().Patch(ctx, attachmentName, types.MergePatchType, patch, metav1.PatchOptions{}, "status") - framework.ExpectNoError(err, "Failed to patch VolumeAttachment status") - ginkgo.By("Waiting for Pod to fail with VolumeAttachmentLimitExceeded") - err = e2epod.WaitForPodFailedReason(ctx, m.cs, pod, "VolumeAttachmentLimitExceeded", 4*time.Minute) + err := e2epod.WaitForPodFailedReason(ctx, m.cs, pod, "VolumeAttachmentLimitExceeded", 4*time.Minute) framework.ExpectNoError(err, "Pod did not fail with VolumeAttachmentLimitExceeded") }) }) @@ -245,19 +208,3 @@ func readCSINodeLimit(ctx context.Context, cs clientset.Interface, node, drv str } return 0, fmt.Errorf("driver %q not present on CSINode", drv) } - -func createMergePatch(original, new interface{}) ([]byte, error) { - pvByte, err := json.Marshal(original) - if err != nil { - return nil, err - } - cloneByte, err := json.Marshal(new) - if err != nil { - return nil, err - } - patch, err := jsonpatch.CreateMergePatch(pvByte, cloneByte) - if err != nil { - return nil, err - } - return patch, nil -} diff --git a/vendor/k8s.io/kubernetes/test/e2e/storage/drivers/csi.go b/vendor/k8s.io/kubernetes/test/e2e/storage/drivers/csi.go index 6ef88f955..d1253ddb7 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/storage/drivers/csi.go +++ b/vendor/k8s.io/kubernetes/test/e2e/storage/drivers/csi.go @@ -337,25 +337,27 @@ func (h *hostpathCSIDriver) PrepareTest(ctx context.Context, f *framework.Framew // mockCSI type mockCSIDriver struct { - driverInfo storageframework.DriverInfo - manifests []string - podInfo *bool - storageCapacity *bool - attachable bool - attachLimit int - enableTopology bool - enableNodeExpansion bool - hooks Hooks - tokenRequests []storagev1.TokenRequest - requiresRepublish *bool - fsGroupPolicy *storagev1.FSGroupPolicy - enableVolumeMountGroup bool - enableNodeVolumeCondition bool - embedded bool - calls MockCSICalls - embeddedCSIDriver *mockdriver.CSIDriver - enableSELinuxMount *bool - disableControllerExpansion bool + driverInfo storageframework.DriverInfo + manifests []string + podInfo *bool + storageCapacity *bool + attachable bool + attachLimit int + enableTopology bool + enableNodeExpansion bool + hooks Hooks + tokenRequests []storagev1.TokenRequest + requiresRepublish *bool + serviceAccountTokenInSecrets *bool + fsGroupPolicy *storagev1.FSGroupPolicy + enableVolumeMountGroup bool + enableNodeVolumeCondition bool + embedded bool + calls MockCSICalls + embeddedCSIDriver *mockdriver.CSIDriver + enableSELinuxMount *bool + disableControllerExpansion bool + enableMutableCSINodeAllocatableCount bool // Additional values set during PrepareTest clientSet clientset.Interface @@ -389,22 +391,24 @@ type MockCSITestDriver interface { // CSIMockDriverOpts defines options used for csi driver type CSIMockDriverOpts struct { - RegisterDriver bool - DisableAttach bool - PodInfo *bool - StorageCapacity *bool - AttachLimit int - EnableTopology bool - EnableResizing bool - EnableNodeExpansion bool - DisableControllerExpansion bool - EnableSnapshot bool - EnableVolumeMountGroup bool - EnableNodeVolumeCondition bool - TokenRequests []storagev1.TokenRequest - RequiresRepublish *bool - FSGroupPolicy *storagev1.FSGroupPolicy - EnableSELinuxMount *bool + RegisterDriver bool + DisableAttach bool + PodInfo *bool + StorageCapacity *bool + AttachLimit int + EnableTopology bool + EnableResizing bool + EnableNodeExpansion bool + DisableControllerExpansion bool + EnableSnapshot bool + EnableVolumeMountGroup bool + EnableNodeVolumeCondition bool + TokenRequests []storagev1.TokenRequest + ServiceAccountTokenInSecrets *bool + RequiresRepublish *bool + FSGroupPolicy *storagev1.FSGroupPolicy + EnableSELinuxMount *bool + EnableMutableCSINodeAllocatableCount bool // Embedded defines whether the CSI mock driver runs // inside the cluster (false, the default) or just a proxy @@ -546,22 +550,24 @@ func InitMockCSIDriver(driverOpts CSIMockDriverOpts) MockCSITestDriver { storageframework.CapMultiplePVsSameID: true, }, }, - manifests: driverManifests, - podInfo: driverOpts.PodInfo, - storageCapacity: driverOpts.StorageCapacity, - enableTopology: driverOpts.EnableTopology, - attachable: !driverOpts.DisableAttach, - attachLimit: driverOpts.AttachLimit, - enableNodeExpansion: driverOpts.EnableNodeExpansion, - enableNodeVolumeCondition: driverOpts.EnableNodeVolumeCondition, - disableControllerExpansion: driverOpts.DisableControllerExpansion, - tokenRequests: driverOpts.TokenRequests, - requiresRepublish: driverOpts.RequiresRepublish, - fsGroupPolicy: driverOpts.FSGroupPolicy, - enableVolumeMountGroup: driverOpts.EnableVolumeMountGroup, - enableSELinuxMount: driverOpts.EnableSELinuxMount, - embedded: driverOpts.Embedded, - hooks: driverOpts.Hooks, + manifests: driverManifests, + podInfo: driverOpts.PodInfo, + storageCapacity: driverOpts.StorageCapacity, + enableTopology: driverOpts.EnableTopology, + attachable: !driverOpts.DisableAttach, + attachLimit: driverOpts.AttachLimit, + enableNodeExpansion: driverOpts.EnableNodeExpansion, + enableNodeVolumeCondition: driverOpts.EnableNodeVolumeCondition, + disableControllerExpansion: driverOpts.DisableControllerExpansion, + tokenRequests: driverOpts.TokenRequests, + requiresRepublish: driverOpts.RequiresRepublish, + serviceAccountTokenInSecrets: driverOpts.ServiceAccountTokenInSecrets, + fsGroupPolicy: driverOpts.FSGroupPolicy, + enableVolumeMountGroup: driverOpts.EnableVolumeMountGroup, + enableSELinuxMount: driverOpts.EnableSELinuxMount, + enableMutableCSINodeAllocatableCount: driverOpts.EnableMutableCSINodeAllocatableCount, + embedded: driverOpts.Embedded, + hooks: driverOpts.Hooks, } } @@ -717,6 +723,9 @@ func (m *mockCSIDriver) PrepareTest(ctx context.Context, f *framework.Framework) SELinuxMount: m.enableSELinuxMount, Features: map[string][]string{}, } + if m.enableMutableCSINodeAllocatableCount { + o.Features["csi-attacher"] = []string{"MutableCSINodeAllocatableCount=true"} + } err = utils.CreateFromManifests(ctx, f, m.driverNamespace, func(item interface{}) error { if err := utils.PatchCSIDeployment(config.Framework, o, item); err != nil { diff --git a/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/base.go b/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/base.go index 04b4ac163..e04c567bd 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/base.go +++ b/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/base.go @@ -80,7 +80,6 @@ var CSISuites = append(BaseSuites, return InitCustomEphemeralTestSuite(CSIEphemeralTestPatterns()) }, InitSnapshottableTestSuite, - InitVolumeGroupSnapshottableTestSuite, InitSnapshottableStressTestSuite, InitVolumePerformanceTestSuite, InitPvcDeletionPerformanceTestSuite, diff --git a/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/readwriteoncepod.go b/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/readwriteoncepod.go index 1926ca3d7..a149084b3 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/readwriteoncepod.go +++ b/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/readwriteoncepod.go @@ -130,7 +130,7 @@ func (t *readWriteOncePodTestSuite) DefineTests(driver storageframework.TestDriv ginkgo.DeferCleanup(cleanup) }) - ginkgo.It("should preempt lower priority pods using ReadWriteOncePod volumes", func(ctx context.Context) { + f.It("should preempt lower priority pods using ReadWriteOncePod volumes", f.WithSerial(), func(ctx context.Context) { // Create the ReadWriteOncePod PVC. accessModes := []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod} l.volume = storageframework.CreateVolumeResourceWithAccessModes(ctx, driver, l.config, pattern, t.GetTestSuiteInfo().SupportedSizeRange, accessModes, nil) diff --git a/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/volume_group_snapshottable.go b/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/volume_group_snapshottable.go index 0707f557b..e823442a5 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/volume_group_snapshottable.go +++ b/vendor/k8s.io/kubernetes/test/e2e/storage/testsuites/volume_group_snapshottable.go @@ -185,17 +185,18 @@ func (s *VolumeGroupSnapshottableTestSuite) DefineTests(driver storageframework. volumeListMap := snapshot.VGSContent.Object["status"].(map[string]interface{}) err = framework.Gomega().Expect(volumeListMap).NotTo(gomega.BeNil()) framework.ExpectNoError(err, "failed to get volume snapshot list") - volumeSnapshotHandlePairList := volumeListMap["volumeSnapshotHandlePairList"].([]interface{}) - err = framework.Gomega().Expect(volumeSnapshotHandlePairList).NotTo(gomega.BeNil()) + volumeSnapshotInfoList := volumeListMap["volumeSnapshotInfoList"].([]interface{}) + err = framework.Gomega().Expect(volumeSnapshotInfoList).NotTo(gomega.BeNil()) framework.ExpectNoError(err, "failed to get volume snapshot list") - err = framework.Gomega().Expect(len(volumeSnapshotHandlePairList)).To(gomega.Equal(groupTest.numVolumes)) + err = framework.Gomega().Expect(len(volumeSnapshotInfoList)).To(gomega.Equal(groupTest.numVolumes)) framework.ExpectNoError(err, "failed to get volume snapshot list") claimSize := groupTest.volumeGroup[0][0].Pvc.Spec.Resources.Requests.Storage().String() - for _, volume := range volumeSnapshotHandlePairList { + for _, info := range volumeSnapshotInfoList { // Create a PVC from the snapshot - volumeHandle := volume.(map[string]interface{})["volumeHandle"].(string) + volumeHandle := info.(map[string]interface{})["volumeHandle"].(string) err = framework.Gomega().Expect(volumeHandle).NotTo(gomega.BeNil()) framework.ExpectNoError(err, "failed to get volume handle from volume") + uid := snapshot.VGS.Object["metadata"].(map[string]interface{})["uid"].(string) err = framework.Gomega().Expect(uid).NotTo(gomega.BeNil()) framework.ExpectNoError(err, "failed to get uuid from content") diff --git a/vendor/k8s.io/kubernetes/test/e2e/storage/utils/volume_group_snapshot.go b/vendor/k8s.io/kubernetes/test/e2e/storage/utils/volume_group_snapshot.go index b5515e879..a3ed60203 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/storage/utils/volume_group_snapshot.go +++ b/vendor/k8s.io/kubernetes/test/e2e/storage/utils/volume_group_snapshot.go @@ -33,16 +33,16 @@ const ( // VolumeGroupSnapshot is the group snapshot api VolumeGroupSnapshotAPIGroup = "groupsnapshot.storage.k8s.io" // VolumeGroupSnapshotAPIVersion is the group snapshot api version - VolumeGroupSnapshotAPIVersion = "groupsnapshot.storage.k8s.io/v1beta1" + VolumeGroupSnapshotAPIVersion = "groupsnapshot.storage.k8s.io/v1beta2" ) var ( // VolumeGroupSnapshotGVR is GroupVersionResource for volumegroupsnapshots - VolumeGroupSnapshotGVR = schema.GroupVersionResource{Group: VolumeGroupSnapshotAPIGroup, Version: "v1beta1", Resource: "volumegroupsnapshots"} + VolumeGroupSnapshotGVR = schema.GroupVersionResource{Group: VolumeGroupSnapshotAPIGroup, Version: "v1beta2", Resource: "volumegroupsnapshots"} // VolumeGroupSnapshotClassGVR is GroupVersionResource for volumegroupsnapshotsclasses - VolumeGroupSnapshotClassGVR = schema.GroupVersionResource{Group: VolumeGroupSnapshotAPIGroup, Version: "v1beta1", Resource: "volumegroupsnapshotclasses"} - VolumeGroupSnapshotContentGVR = schema.GroupVersionResource{Group: VolumeGroupSnapshotAPIGroup, Version: "v1beta1", Resource: "volumegroupsnapshotcontents"} + VolumeGroupSnapshotClassGVR = schema.GroupVersionResource{Group: VolumeGroupSnapshotAPIGroup, Version: "v1beta2", Resource: "volumegroupsnapshotclasses"} + VolumeGroupSnapshotContentGVR = schema.GroupVersionResource{Group: VolumeGroupSnapshotAPIGroup, Version: "v1beta2", Resource: "volumegroupsnapshotcontents"} ) // WaitForVolumeGroupSnapshotReady waits for a VolumeGroupSnapshot to be ready to use or until timeout occurs, whichever comes first. diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml index a06c328d4..7b996dd9c 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml @@ -1,5 +1,5 @@ -# Do not edit, downloaded from https://github.com/kubernetes-csi/external-attacher/raw/v4.6.1/deploy/kubernetes//rbac.yaml -# for csi-driver-host-path release-1.14 +# Do not edit, downloaded from https://github.com/kubernetes-csi/external-attacher/raw/v4.10.0/deploy/kubernetes//rbac.yaml +# for csi-driver-host-path master # by ./update-hostpath.sh # # This YAML file contains all RBAC objects that are necessary to run external diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-health-monitor/external-health-monitor-controller/rbac.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-health-monitor/external-health-monitor-controller/rbac.yaml index 9154a0e0a..8d91a016f 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-health-monitor/external-health-monitor-controller/rbac.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-health-monitor/external-health-monitor-controller/rbac.yaml @@ -1,5 +1,5 @@ -# Do not edit, downloaded from https://github.com/kubernetes-csi/external-health-monitor/raw/v0.12.1/deploy/kubernetes/external-health-monitor-controller/rbac.yaml -# for csi-driver-host-path release-1.14 +# Do not edit, downloaded from https://github.com/kubernetes-csi/external-health-monitor/raw/v0.16.0/deploy/kubernetes/external-health-monitor-controller/rbac.yaml +# for csi-driver-host-path master # by ./update-hostpath.sh # # This YAML file contains all RBAC objects that are necessary to run external diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml index d0811b00a..06bc4a3c7 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml @@ -1,5 +1,5 @@ -# Do not edit, downloaded from https://github.com/kubernetes-csi/external-provisioner/raw/v5.0.1/deploy/kubernetes//rbac.yaml -# for csi-driver-host-path release-1.14 +# Do not edit, downloaded from https://github.com/kubernetes-csi/external-provisioner/raw/v6.0.0/deploy/kubernetes//rbac.yaml +# for csi-driver-host-path master # by ./update-hostpath.sh # # This YAML file contains all RBAC objects that are necessary to run external diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-resizer/rbac.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-resizer/rbac.yaml index afefe5c90..0d051eb76 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-resizer/rbac.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-resizer/rbac.yaml @@ -1,5 +1,5 @@ -# Do not edit, downloaded from https://github.com/kubernetes-csi/external-resizer/raw/v1.11.1/deploy/kubernetes//rbac.yaml -# for csi-driver-host-path release-1.14 +# Do not edit, downloaded from https://github.com/kubernetes-csi/external-resizer/raw/v2.0.0/deploy/kubernetes//rbac.yaml +# for csi-driver-host-path master # by ./update-hostpath.sh # # This YAML file contains all RBAC objects that are necessary to run external @@ -46,7 +46,6 @@ rules: - apiGroups: [""] resources: ["events"] verbs: ["list", "watch", "create", "update", "patch"] - # only required if enabling the alpha volume modify feature - apiGroups: ["storage.k8s.io"] resources: ["volumeattributesclasses"] verbs: ["get", "list", "watch"] diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/csi-snapshotter/rbac-csi-snapshotter.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/csi-snapshotter/rbac-csi-snapshotter.yaml index b02de9fdf..3fcb9f541 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/csi-snapshotter/rbac-csi-snapshotter.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/csi-snapshotter/rbac-csi-snapshotter.yaml @@ -1,5 +1,5 @@ -# Do not edit, downloaded from https://github.com/kubernetes-csi/external-snapshotter/raw/v8.0.1/deploy/kubernetes/csi-snapshotter/rbac-csi-snapshotter.yaml -# for csi-driver-host-path release-1.14 +# Do not edit, downloaded from https://github.com/kubernetes-csi/external-snapshotter/raw/v8.4.0/deploy/kubernetes/csi-snapshotter/rbac-csi-snapshotter.yaml +# for csi-driver-host-path master # by ./update-hostpath.sh # # Together with the RBAC file for external-provisioner, this YAML file @@ -38,12 +38,9 @@ rules: - apiGroups: ["snapshot.storage.k8s.io"] resources: ["volumesnapshotclasses"] verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update", "patch", "create"] - apiGroups: ["snapshot.storage.k8s.io"] resources: ["volumesnapshotcontents"] - verbs: ["get", "list", "watch", "update", "patch", "create"] + verbs: ["get", "list", "watch", "update", "patch"] - apiGroups: ["snapshot.storage.k8s.io"] resources: ["volumesnapshotcontents/status"] verbs: ["update", "patch"] diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotclasses.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotclasses.yaml index e552f81b5..81299d5ec 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotclasses.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotclasses.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/1150" + api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/1337" controller-gen.kubebuilder.io/version: v0.15.0 name: volumegroupsnapshotclasses.groupsnapshot.storage.k8s.io spec: @@ -31,6 +31,7 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true name: v1beta1 schema: openAPIV3Schema: @@ -90,5 +91,88 @@ spec: - driver type: object served: true + storage: false + subresources: {} + - additionalPrinterColumns: + - jsonPath: .driver + name: Driver + type: string + - description: Determines whether a VolumeGroupSnapshotContent created through + the VolumeGroupSnapshotClass should be deleted when its bound VolumeGroupSnapshot + is deleted. + jsonPath: .deletionPolicy + name: DeletionPolicy + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: |- + VolumeGroupSnapshotClass specifies parameters that a underlying storage system + uses when creating a volume group snapshot. A specific VolumeGroupSnapshotClass + is used by specifying its name in a VolumeGroupSnapshot object. + VolumeGroupSnapshotClasses are non-namespaced. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + deletionPolicy: + description: |- + DeletionPolicy determines whether a VolumeGroupSnapshotContent created + through the VolumeGroupSnapshotClass should be deleted when its bound + VolumeGroupSnapshot is deleted. + Supported values are "Retain" and "Delete". + "Retain" means that the VolumeGroupSnapshotContent and its physical group + snapshot on underlying storage system are kept. + "Delete" means that the VolumeGroupSnapshotContent and its physical group + snapshot on underlying storage system are deleted. + Required. + enum: + - Delete + - Retain + type: string + x-kubernetes-validations: + - message: deletionPolicy is immutable once set + rule: self == oldSelf + driver: + description: |- + Driver is the name of the storage driver expected to handle this VolumeGroupSnapshotClass. + Required. + type: string + x-kubernetes-validations: + - message: driver is immutable once set + rule: self == oldSelf + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + parameters: + additionalProperties: + type: string + description: |- + Parameters is a key-value map with storage driver specific parameters for + creating group snapshots. + These values are opaque to Kubernetes and are passed directly to the driver. + type: object + x-kubernetes-validations: + - message: parameters are immutable once set + rule: self == oldSelf + required: + - deletionPolicy + - driver + type: object + served: true storage: true subresources: {} diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotcontents.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotcontents.yaml index a6d15d8ad..235198ac4 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotcontents.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshotcontents.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/1150" + api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/1337" controller-gen.kubebuilder.io/version: v0.15.0 name: volumegroupsnapshotcontents.groupsnapshot.storage.k8s.io spec: @@ -17,6 +17,15 @@ spec: - vgscs singular: volumegroupsnapshotcontent scope: Cluster + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: ["v1"] + clientConfig: + service: + namespace: default + name: snapshot-conversion-webhook-service + path: /convert versions: - additionalPrinterColumns: - description: Indicates if all the individual snapshots in the group are ready @@ -53,6 +62,7 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true name: v1beta1 schema: openAPIV3Schema: @@ -319,6 +329,333 @@ spec: - spec type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Indicates if all the individual snapshots in the group are ready + to be used to restore a group of volumes. + jsonPath: .status.readyToUse + name: ReadyToUse + type: boolean + - description: Determines whether this VolumeGroupSnapshotContent and its physical + group snapshot on the underlying storage system should be deleted when its + bound VolumeGroupSnapshot is deleted. + jsonPath: .spec.deletionPolicy + name: DeletionPolicy + type: string + - description: Name of the CSI driver used to create the physical group snapshot + on the underlying storage system. + jsonPath: .spec.driver + name: Driver + type: string + - description: Name of the VolumeGroupSnapshotClass from which this group snapshot + was (or will be) created. + jsonPath: .spec.volumeGroupSnapshotClassName + name: VolumeGroupSnapshotClass + type: string + - description: Namespace of the VolumeGroupSnapshot object to which this VolumeGroupSnapshotContent + object is bound. + jsonPath: .spec.volumeGroupSnapshotRef.namespace + name: VolumeGroupSnapshotNamespace + type: string + - description: Name of the VolumeGroupSnapshot object to which this VolumeGroupSnapshotContent + object is bound. + jsonPath: .spec.volumeGroupSnapshotRef.name + name: VolumeGroupSnapshot + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: |- + VolumeGroupSnapshotContent represents the actual "on-disk" group snapshot object + in the underlying storage system + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec defines properties of a VolumeGroupSnapshotContent created by the underlying storage system. + Required. + properties: + deletionPolicy: + description: |- + DeletionPolicy determines whether this VolumeGroupSnapshotContent and the + physical group snapshot on the underlying storage system should be deleted + when the bound VolumeGroupSnapshot is deleted. + Supported values are "Retain" and "Delete". + "Retain" means that the VolumeGroupSnapshotContent and its physical group + snapshot on underlying storage system are kept. + "Delete" means that the VolumeGroupSnapshotContent and its physical group + snapshot on underlying storage system are deleted. + For dynamically provisioned group snapshots, this field will automatically + be filled in by the CSI snapshotter sidecar with the "DeletionPolicy" field + defined in the corresponding VolumeGroupSnapshotClass. + For pre-existing snapshots, users MUST specify this field when creating the + VolumeGroupSnapshotContent object. + Required. + enum: + - Delete + - Retain + type: string + driver: + description: |- + Driver is the name of the CSI driver used to create the physical group snapshot on + the underlying storage system. + This MUST be the same as the name returned by the CSI GetPluginName() call for + that driver. + Required. + type: string + x-kubernetes-validations: + - message: driver is immutable once set + rule: self == oldSelf + source: + description: |- + Source specifies whether the snapshot is (or should be) dynamically provisioned + or already exists, and just requires a Kubernetes object representation. + This field is immutable after creation. + Required. + properties: + groupSnapshotHandles: + description: |- + GroupSnapshotHandles specifies the CSI "group_snapshot_id" of a pre-existing + group snapshot and a list of CSI "snapshot_id" of pre-existing snapshots + on the underlying storage system for which a Kubernetes object + representation was (or should be) created. + This field is immutable. + properties: + volumeGroupSnapshotHandle: + description: |- + VolumeGroupSnapshotHandle specifies the CSI "group_snapshot_id" of a pre-existing + group snapshot on the underlying storage system for which a Kubernetes object + representation was (or should be) created. + This field is immutable. + Required. + type: string + volumeSnapshotHandles: + description: |- + VolumeSnapshotHandles is a list of CSI "snapshot_id" of pre-existing + snapshots on the underlying storage system for which Kubernetes objects + representation were (or should be) created. + This field is immutable. + Required. + items: + type: string + type: array + required: + - volumeGroupSnapshotHandle + - volumeSnapshotHandles + type: object + x-kubernetes-validations: + - message: groupSnapshotHandles is immutable + rule: self == oldSelf + volumeHandles: + description: |- + VolumeHandles is a list of volume handles on the backend to be snapshotted + together. It is specified for dynamic provisioning of the VolumeGroupSnapshot. + This field is immutable. + items: + type: string + type: array + x-kubernetes-validations: + - message: volumeHandles is immutable + rule: self == oldSelf + type: object + x-kubernetes-validations: + - message: volumeHandles is required once set + rule: '!has(oldSelf.volumeHandles) || has(self.volumeHandles)' + - message: groupSnapshotHandles is required once set + rule: '!has(oldSelf.groupSnapshotHandles) || has(self.groupSnapshotHandles)' + - message: exactly one of volumeHandles and groupSnapshotHandles must + be set + rule: (has(self.volumeHandles) && !has(self.groupSnapshotHandles)) + || (!has(self.volumeHandles) && has(self.groupSnapshotHandles)) + volumeGroupSnapshotClassName: + description: |- + VolumeGroupSnapshotClassName is the name of the VolumeGroupSnapshotClass from + which this group snapshot was (or will be) created. + Note that after provisioning, the VolumeGroupSnapshotClass may be deleted or + recreated with different set of values, and as such, should not be referenced + post-snapshot creation. + For dynamic provisioning, this field must be set. + This field may be unset for pre-provisioned snapshots. + type: string + x-kubernetes-validations: + - message: volumeGroupSnapshotClassName is immutable once set + rule: self == oldSelf + volumeGroupSnapshotRef: + description: |- + VolumeGroupSnapshotRef specifies the VolumeGroupSnapshot object to which this + VolumeGroupSnapshotContent object is bound. + VolumeGroupSnapshot.Spec.VolumeGroupSnapshotContentName field must reference to + this VolumeGroupSnapshotContent's name for the bidirectional binding to be valid. + For a pre-existing VolumeGroupSnapshotContent object, name and namespace of the + VolumeGroupSnapshot object MUST be provided for binding to happen. + This field is immutable after creation. + Required. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: both volumeGroupSnapshotRef.name and volumeGroupSnapshotRef.namespace + must be set + rule: has(self.name) && has(self.__namespace__) + - message: volumeGroupSnapshotRef.name and volumeGroupSnapshotRef.namespace + are immutable + rule: self.name == oldSelf.name && self.__namespace__ == oldSelf.__namespace__ + - message: volumeGroupSnapshotRef.uid is immutable once set + rule: '!has(oldSelf.uid) || (has(self.uid) && self.uid == oldSelf.uid)' + required: + - deletionPolicy + - driver + - source + - volumeGroupSnapshotRef + type: object + status: + description: status represents the current information of a group snapshot. + properties: + creationTime: + description: |- + CreationTime is the timestamp when the point-in-time group snapshot is taken + by the underlying storage system. + If not specified, it indicates the creation time is unknown. + If not specified, it means the readiness of a group snapshot is unknown. + This field is the source for the CreationTime field in VolumeGroupSnapshotStatus + format: date-time + type: string + error: + description: |- + Error is the last observed error during group snapshot creation, if any. + Upon success after retry, this error field will be cleared. + properties: + message: + description: |- + message is a string detailing the encountered error during snapshot + creation if specified. + NOTE: message may be logged, and it should not contain sensitive + information. + type: string + time: + description: time is the timestamp when the error was encountered. + format: date-time + type: string + type: object + readyToUse: + description: |- + ReadyToUse indicates if all the individual snapshots in the group are ready to be + used to restore a group of volumes. + ReadyToUse becomes true when ReadyToUse of all individual snapshots become true. + type: boolean + volumeGroupSnapshotHandle: + description: |- + VolumeGroupSnapshotHandle is a unique id returned by the CSI driver + to identify the VolumeGroupSnapshot on the storage system. + If a storage system does not provide such an id, the + CSI driver can choose to return the VolumeGroupSnapshot name. + type: string + x-kubernetes-validations: + - message: volumeGroupSnapshotHandle is immutable once set + rule: self == oldSelf + volumeSnapshotInfoList: + description: |- + This field is introduced in v1beta2 + It is replacing VolumeSnapshotHandlePairList + VolumeSnapshotInfoList is a list of snapshot information returned by + by the CSI driver to identify snapshots on the storage system. + items: + description: |- + The VolumeSnapshotInfo struct is added in v1beta2 + VolumeSnapshotInfo contains information for a snapshot + properties: + creationTime: + description: |- + creationTime is the timestamp when the point-in-time snapshot is taken + by the underlying storage system. + format: int64 + type: integer + readyToUse: + description: ReadyToUse indicates if the snapshot is ready to + be used to restore a volume. + type: boolean + restoreSize: + description: |- + RestoreSize represents the minimum size of volume required to create a volume + from this snapshot. + format: int64 + type: integer + snapshotHandle: + description: SnapshotHandle is the CSI "snapshot_id" of this + snapshot on the underlying storage system. + type: string + volumeHandle: + description: |- + VolumeHandle specifies the CSI "volume_id" of the volume from which this snapshot + was taken from. + type: string + type: object + type: array + type: object + required: + - spec + type: object + served: true storage: true subresources: status: {} diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshots.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshots.yaml index 145d1211d..3f8c4909a 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshots.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/groupsnapshot.storage.k8s.io_volumegroupsnapshots.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/1150" + api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/1337" controller-gen.kubebuilder.io/version: v0.15.0 name: volumegroupsnapshots.groupsnapshot.storage.k8s.io spec: @@ -43,6 +43,7 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true name: v1beta1 schema: openAPIV3Schema: @@ -234,6 +235,226 @@ spec: - spec type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Indicates if all the individual snapshots in the group are ready + to be used to restore a group of volumes. + jsonPath: .status.readyToUse + name: ReadyToUse + type: boolean + - description: The name of the VolumeGroupSnapshotClass requested by the VolumeGroupSnapshot. + jsonPath: .spec.volumeGroupSnapshotClassName + name: VolumeGroupSnapshotClass + type: string + - description: Name of the VolumeGroupSnapshotContent object to which the VolumeGroupSnapshot + object intends to bind to. Please note that verification of binding actually + requires checking both VolumeGroupSnapshot and VolumeGroupSnapshotContent + to ensure both are pointing at each other. Binding MUST be verified prior + to usage of this object. + jsonPath: .status.boundVolumeGroupSnapshotContentName + name: VolumeGroupSnapshotContent + type: string + - description: Timestamp when the point-in-time group snapshot was taken by the + underlying storage system. + jsonPath: .status.creationTime + name: CreationTime + type: date + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: |- + VolumeGroupSnapshot is a user's request for creating either a point-in-time + group snapshot or binding to a pre-existing group snapshot. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec defines the desired characteristics of a group snapshot requested by a user. + Required. + properties: + source: + description: |- + Source specifies where a group snapshot will be created from. + This field is immutable after creation. + Required. + properties: + selector: + description: |- + Selector is a label query over persistent volume claims that are to be + grouped together for snapshotting. + This labelSelector will be used to match the label added to a PVC. + If the label is added or removed to a volume after a group snapshot + is created, the existing group snapshots won't be modified. + Once a VolumeGroupSnapshotContent is created and the sidecar starts to process + it, the volume list will not change with retries. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: selector is immutable + rule: self == oldSelf + volumeGroupSnapshotContentName: + description: |- + VolumeGroupSnapshotContentName specifies the name of a pre-existing VolumeGroupSnapshotContent + object representing an existing volume group snapshot. + This field should be set if the volume group snapshot already exists and + only needs a representation in Kubernetes. + This field is immutable. + type: string + x-kubernetes-validations: + - message: volumeGroupSnapshotContentName is immutable + rule: self == oldSelf + type: object + x-kubernetes-validations: + - message: selector is required once set + rule: '!has(oldSelf.selector) || has(self.selector)' + - message: volumeGroupSnapshotContentName is required once set + rule: '!has(oldSelf.volumeGroupSnapshotContentName) || has(self.volumeGroupSnapshotContentName)' + - message: exactly one of selector and volumeGroupSnapshotContentName + must be set + rule: (has(self.selector) && !has(self.volumeGroupSnapshotContentName)) + || (!has(self.selector) && has(self.volumeGroupSnapshotContentName)) + volumeGroupSnapshotClassName: + description: |- + VolumeGroupSnapshotClassName is the name of the VolumeGroupSnapshotClass + requested by the VolumeGroupSnapshot. + VolumeGroupSnapshotClassName may be left nil to indicate that the default + class will be used. + Empty string is not allowed for this field. + type: string + x-kubernetes-validations: + - message: volumeGroupSnapshotClassName must not be the empty string + when set + rule: size(self) > 0 + required: + - source + type: object + status: + description: |- + Status represents the current information of a group snapshot. + Consumers must verify binding between VolumeGroupSnapshot and + VolumeGroupSnapshotContent objects is successful (by validating that both + VolumeGroupSnapshot and VolumeGroupSnapshotContent point to each other) before + using this object. + properties: + boundVolumeGroupSnapshotContentName: + description: |- + BoundVolumeGroupSnapshotContentName is the name of the VolumeGroupSnapshotContent + object to which this VolumeGroupSnapshot object intends to bind to. + If not specified, it indicates that the VolumeGroupSnapshot object has not + been successfully bound to a VolumeGroupSnapshotContent object yet. + NOTE: To avoid possible security issues, consumers must verify binding between + VolumeGroupSnapshot and VolumeGroupSnapshotContent objects is successful + (by validating that both VolumeGroupSnapshot and VolumeGroupSnapshotContent + point at each other) before using this object. + type: string + x-kubernetes-validations: + - message: boundVolumeGroupSnapshotContentName is immutable once set + rule: self == oldSelf + creationTime: + description: |- + CreationTime is the timestamp when the point-in-time group snapshot is taken + by the underlying storage system. + If not specified, it may indicate that the creation time of the group snapshot + is unknown. + This field is updated based on the CreationTime field in VolumeGroupSnapshotContentStatus + format: date-time + type: string + error: + description: |- + Error is the last observed error during group snapshot creation, if any. + This field could be helpful to upper level controllers (i.e., application + controller) to decide whether they should continue on waiting for the group + snapshot to be created based on the type of error reported. + The snapshot controller will keep retrying when an error occurs during the + group snapshot creation. Upon success, this error field will be cleared. + properties: + message: + description: |- + message is a string detailing the encountered error during snapshot + creation if specified. + NOTE: message may be logged, and it should not contain sensitive + information. + type: string + time: + description: time is the timestamp when the error was encountered. + format: date-time + type: string + type: object + readyToUse: + description: |- + ReadyToUse indicates if all the individual snapshots in the group are ready + to be used to restore a group of volumes. + ReadyToUse becomes true when ReadyToUse of all individual snapshots become true. + If not specified, it means the readiness of a group snapshot is unknown. + type: boolean + type: object + required: + - spec + type: object + served: true storage: true subresources: status: {} diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/csi-hostpath-plugin.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/csi-hostpath-plugin.yaml index 35dc8c4b2..4bedcf6e3 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/csi-hostpath-plugin.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/csi-hostpath-plugin.yaml @@ -354,7 +354,7 @@ spec: name: socket-dir - name: csi-snapshotter - image: registry.k8s.io/sig-storage/csi-snapshotter:v8.3.0 + image: registry.k8s.io/sig-storage/csi-snapshotter:v8.4.0 args: - -v=5 - --csi-address=/csi/csi.sock diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/run_group_snapshot_e2e.sh b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/run_group_snapshot_e2e.sh index f62b40d84..9791f9b07 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/run_group_snapshot_e2e.sh +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/external-snapshotter/volume-group-snapshots/run_group_snapshot_e2e.sh @@ -266,13 +266,12 @@ run_tests() { export KUBE_CONTAINER_RUNTIME=remote export KUBE_CONTAINER_RUNTIME_ENDPOINT=unix:///run/containerd/containerd.sock export KUBE_CONTAINER_RUNTIME_NAME=containerd - export SNAPSHOTTER_VERSION="${SNAPSHOTTER_VERSION:-v8.3.0}" + export SNAPSHOTTER_VERSION="${SNAPSHOTTER_VERSION:-v8.4.0}" echo "SNAPSHOTTER_VERSION is $SNAPSHOTTER_VERSION" - # ginkgo can take forever to exit, so we run it in the background and save the - # PID, bash will not run traps while waiting on a process, but it will while - # running a builtin like `wait`, saving the PID also allows us to forward the - # interrupt - + + # Enable VolumeGroupSnapshot tests in csi-driver-hostpath + export CSI_PROW_ENABLE_GROUP_SNAPSHOT=true + kubectl apply -f ./cluster/addons/volumesnapshots/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml || exit 1 kubectl apply -f ./cluster/addons/volumesnapshots/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml || exit 1 kubectl apply -f ./cluster/addons/volumesnapshots/crd/snapshot.storage.k8s.io_volumesnapshots.yaml || exit 1 @@ -287,6 +286,10 @@ sed "s|image: registry.k8s.io/sig-storage/snapshot-controller:.*|image: registry kubectl apply -f - || exit 1 + # ginkgo can take forever to exit, so we run it in the background and save the + # PID, bash will not run traps while waiting on a process, but it will while + # running a builtin like `wait`, saving the PID also allows us to forward the + # interrupt ./hack/ginkgo-e2e.sh \ '--provider=skeleton' "--num-nodes=${NUM_NODES}" \ "--ginkgo.focus=${FOCUS}" "--ginkgo.skip=${SKIP}" "--ginkgo.label-filter=${LABEL_FILTER}" \ diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/README.md b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/README.md index 2a53facea..6b662f547 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/README.md +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/README.md @@ -1,4 +1,4 @@ The files in this directory are exact copies of "kubernetes-latest" in -https://github.com/kubernetes-csi/csi-driver-host-path/tree/release-1.14/deploy/ +https://github.com/kubernetes-csi/csi-driver-host-path/tree/master/deploy/ Do not edit manually. Run ./update-hostpath.sh to refresh the content. diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-plugin.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-plugin.yaml index a5af9814a..d2e97779b 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-plugin.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-plugin.yaml @@ -1,4 +1,4 @@ -# All of the individual sidecar RBAC roles get bound + # All of the individual sidecar RBAC roles get bound # to this account. kind: ServiceAccount apiVersion: v1 @@ -102,6 +102,24 @@ subjects: namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/instance: hostpath.csi.k8s.io + app.kubernetes.io/part-of: csi-driver-host-path + app.kubernetes.io/name: csi-hostpathplugin + app.kubernetes.io/component: snapshot-metadata-cluster-role + name: csi-hostpathplugin-snapshot-metadata-cluster-role +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-snapshot-metadata-runner +subjects: +- kind: ServiceAccount + name: csi-hostpathplugin-sa + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: @@ -219,12 +237,13 @@ spec: serviceAccountName: csi-hostpathplugin-sa containers: - name: hostpath - image: registry.k8s.io/sig-storage/hostpathplugin:v1.16.1 + image: registry.k8s.io/sig-storage/hostpathplugin:v1.17.0 args: - "--drivername=hostpath.csi.k8s.io" - "--v=5" - "--endpoint=$(CSI_ENDPOINT)" - "--nodeid=$(KUBE_NODE_NAME)" + # end hostpath args env: - name: CSI_ENDPOINT value: unix:///csi/csi.sock @@ -262,7 +281,7 @@ spec: name: dev-dir - name: csi-external-health-monitor-controller - image: registry.k8s.io/sig-storage/csi-external-health-monitor-controller:v0.12.1 + image: registry.k8s.io/sig-storage/csi-external-health-monitor-controller:v0.16.0 args: - "--v=5" - "--csi-address=$(ADDRESS)" @@ -324,7 +343,7 @@ spec: name: socket-dir - name: csi-provisioner - image: registry.k8s.io/sig-storage/csi-provisioner:v5.1.0 + image: registry.k8s.io/sig-storage/csi-provisioner:v6.0.0 args: - -v=5 - --csi-address=/csi/csi.sock @@ -340,7 +359,7 @@ spec: name: socket-dir - name: csi-resizer - image: registry.k8s.io/sig-storage/csi-resizer:v1.13.1 + image: registry.k8s.io/sig-storage/csi-resizer:v2.0.0 args: - -v=5 - -csi-address=/csi/csi.sock @@ -354,7 +373,7 @@ spec: name: socket-dir - name: csi-snapshotter - image: registry.k8s.io/sig-storage/csi-snapshotter:v8.3.0 + image: registry.k8s.io/sig-storage/csi-snapshotter:v8.4.0 args: - -v=5 - --csi-address=/csi/csi.sock @@ -367,6 +386,8 @@ spec: - mountPath: /csi name: socket-dir + # end csi containers + volumes: - hostPath: path: /var/lib/kubelet/plugins/csi-hostpath @@ -394,3 +415,4 @@ spec: path: /dev type: Directory name: dev-dir + # end csi volumes diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-testing.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-testing.yaml index 494f2aacc..19c9888ee 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-testing.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/hostpath/hostpath/csi-hostpath-testing.yaml @@ -66,7 +66,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: socat - image: registry.k8s.io/sig-storage/hostpathplugin:v1.16.1 + image: registry.k8s.io/sig-storage/hostpathplugin:v1.15.0 command: - socat args: diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-attacher.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-attacher.yaml index 9716caaad..f2f7850f0 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-attacher.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-attacher.yaml @@ -15,7 +15,7 @@ spec: serviceAccountName: csi-mock containers: - name: csi-attacher - image: registry.k8s.io/sig-storage/csi-attacher:v4.8.0 + image: registry.k8s.io/sig-storage/csi-attacher:v4.10.0 args: - --v=5 - --csi-address=$(ADDRESS) diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-resizer.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-resizer.yaml index c2f7d0777..0133796e7 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-resizer.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-resizer.yaml @@ -15,7 +15,7 @@ spec: serviceAccountName: csi-mock containers: - name: csi-resizer - image: registry.k8s.io/sig-storage/csi-resizer:v1.13.1 + image: registry.k8s.io/sig-storage/csi-resizer:v2.0.0 args: - "--v=5" - "--csi-address=$(ADDRESS)" diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-snapshotter.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-snapshotter.yaml index cf5bbba29..15c4bb523 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-snapshotter.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-snapshotter.yaml @@ -15,7 +15,7 @@ spec: serviceAccountName: csi-mock containers: - name: csi-snapshotter - image: registry.k8s.io/sig-storage/csi-snapshotter:v8.3.0 + image: registry.k8s.io/sig-storage/csi-snapshotter:v8.4.0 args: - "--v=5" - "--csi-address=$(ADDRESS)" diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver.yaml index 21b3d7339..fbac7b5fb 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver.yaml @@ -15,7 +15,7 @@ spec: serviceAccountName: csi-mock containers: - name: csi-provisioner - image: registry.k8s.io/sig-storage/csi-provisioner:v5.1.0 + image: registry.k8s.io/sig-storage/csi-provisioner:v6.0.0 args: - "--csi-address=$(ADDRESS)" # Topology support is needed for the pod rescheduling test @@ -34,7 +34,7 @@ spec: - mountPath: /csi name: socket-dir - name: driver-registrar - image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.13.0 + image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.15.0 args: - --v=5 - --csi-address=/csi/csi.sock @@ -53,7 +53,7 @@ spec: - mountPath: /registration name: registration-dir - name: mock - image: registry.k8s.io/sig-storage/hostpathplugin:v1.16.1 + image: registry.k8s.io/sig-storage/hostpathplugin:v1.17.0 args: - "--drivername=mock.storage.k8s.io" - "--nodeid=$(KUBE_NODE_NAME)" diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-proxy.yaml b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-proxy.yaml index a267f4f59..0530c9a47 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-proxy.yaml +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/mock/csi-mock-proxy.yaml @@ -15,7 +15,7 @@ spec: serviceAccountName: csi-mock containers: - name: csi-provisioner - image: registry.k8s.io/sig-storage/csi-provisioner:v5.1.0 + image: registry.k8s.io/sig-storage/csi-provisioner:v6.0.0 args: - "--csi-address=$(ADDRESS)" # Topology support is needed for the pod rescheduling test @@ -35,7 +35,7 @@ spec: - mountPath: /csi name: socket-dir - name: driver-registrar - image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.13.0 + image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.15.0 args: - --v=5 - --csi-address=/csi/csi.sock @@ -53,7 +53,7 @@ spec: - mountPath: /registration name: registration-dir - name: mock - image: registry.k8s.io/sig-storage/hostpathplugin:v1.16.1 + image: registry.k8s.io/sig-storage/hostpathplugin:v1.17.0 args: - -v=5 - -nodeid=$(KUBE_NODE_NAME) diff --git a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/update-hostpath.sh b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/update-hostpath.sh index 122de0d18..cf9ec057b 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/update-hostpath.sh +++ b/vendor/k8s.io/kubernetes/test/e2e/testing-manifests/storage-csi/update-hostpath.sh @@ -37,7 +37,7 @@ set -xe cd "$(dirname "$0")" # Remove stale files. -rm -rf external-attacher external-provisioner external-resizer external-snapshotter external-health-monitor hostpath csi-driver-host-path +rm -rf external-attacher external-provisioner external-resizer external-snapshotter/csi-snapshotter external-health-monitor hostpath csi-driver-host-path # Check out desired release. git clone https://github.com/kubernetes-csi/csi-driver-host-path.git @@ -127,6 +127,10 @@ for image in $images; do path="csi-snapshotter" rbac="rbac-csi-snapshotter.yaml" ;; + external-snapshot-metadata) + # Another special case. There is no e2e test that needs the RBAC manifest + it's on an unusual place. + continue + ;; esac ;; esac @@ -137,5 +141,12 @@ done grep -r image: hostpath/hostpath/csi-hostpath-plugin.yaml | while read -r image; do version=$(echo "$image" | sed -e 's/.*:\(.*\)/\1/') image=$(echo "$image" | sed -e 's/.*image: \([^:]*\).*/\1/') - sed -i '' -e "s;$image:.*;$image:$version;" mock/*.yaml + + if sed --version >/dev/null 2>&1; then + # GNU sed, `-i ''` fails + sed -i -e "s;$image:.*;$image:$version;" mock/*.yaml + else + # BSD sed (macOS) + sed -i '' -e "s;$image:.*;$image:$version;" mock/*.yaml + fi done diff --git a/vendor/k8s.io/kubernetes/test/utils/image/manifest.go b/vendor/k8s.io/kubernetes/test/utils/image/manifest.go index 2498c3c73..242b286f9 100644 --- a/vendor/k8s.io/kubernetes/test/utils/image/manifest.go +++ b/vendor/k8s.io/kubernetes/test/utils/image/manifest.go @@ -221,7 +221,7 @@ func initImageConfigs(list RegistryList) (map[ImageID]Config, map[ImageID]Config configs[APIServer] = Config{list.PromoterE2eRegistry, "sample-apiserver", "1.29.2"} configs[AppArmorLoader] = Config{list.PromoterE2eRegistry, "apparmor-loader", "1.4"} configs[BusyBox] = Config{list.PromoterE2eRegistry, "busybox", "1.37.0-1"} - configs[DistrolessIptables] = Config{list.BuildImageRegistry, "distroless-iptables", "v0.7.8"} + configs[DistrolessIptables] = Config{list.BuildImageRegistry, "distroless-iptables", "v0.7.11"} configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.6.4-0"} configs[Httpd] = Config{list.PromoterE2eRegistry, "httpd", "2.4.38-4"} configs[HttpdNew] = Config{list.PromoterE2eRegistry, "httpd", "2.4.39-4"} diff --git a/vendor/modules.txt b/vendor/modules.txt index db6fc8c9f..b1969936c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -207,8 +207,8 @@ github.com/containerd/typeurl/v2 # github.com/coreos/go-semver v0.3.1 ## explicit; go 1.8 github.com/coreos/go-semver/semver -# github.com/coreos/go-systemd/v22 v22.5.0 -## explicit; go 1.12 +# github.com/coreos/go-systemd/v22 v22.6.0 +## explicit; go 1.23 github.com/coreos/go-systemd/v22/daemon github.com/coreos/go-systemd/v22/dbus github.com/coreos/go-systemd/v22/journal @@ -216,7 +216,7 @@ github.com/coreos/go-systemd/v22/journal ## explicit; go 1.21 github.com/curioswitch/go-reassign github.com/curioswitch/go-reassign/internal/analyzer -# github.com/cyphar/filepath-securejoin v0.6.0 +# github.com/cyphar/filepath-securejoin v0.6.1 ## explicit; go 1.18 github.com/cyphar/filepath-securejoin github.com/cyphar/filepath-securejoin/internal/consts @@ -946,7 +946,7 @@ github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/types -# github.com/opencontainers/cgroups v0.0.3 +# github.com/opencontainers/cgroups v0.0.6 ## explicit; go 1.23.0 github.com/opencontainers/cgroups github.com/opencontainers/cgroups/devices/config @@ -963,13 +963,7 @@ github.com/opencontainers/go-digest ## explicit; go 1.18 github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/opencontainers/runc v1.2.5 -## explicit; go 1.22 -github.com/opencontainers/runc/libcontainer/cgroups -github.com/opencontainers/runc/libcontainer/configs -github.com/opencontainers/runc/libcontainer/devices -github.com/opencontainers/runc/libcontainer/utils -# github.com/opencontainers/runtime-spec v1.2.0 +# github.com/opencontainers/runtime-spec v1.3.0 ## explicit github.com/opencontainers/runtime-spec/specs-go # github.com/opencontainers/selinux v1.13.0 @@ -3067,7 +3061,7 @@ k8s.io/kubelet/pkg/cri/streaming k8s.io/kubelet/pkg/cri/streaming/portforward k8s.io/kubelet/pkg/cri/streaming/remotecommand k8s.io/kubelet/pkg/types -# k8s.io/kubernetes v1.34.1 => github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd +# k8s.io/kubernetes v1.34.1 => /home/rmanak/work-git/kubernetes ## explicit; go 1.24.0 k8s.io/kubernetes/cmd/kubelet/app k8s.io/kubernetes/cmd/kubelet/app/options @@ -3688,5 +3682,5 @@ sigs.k8s.io/yaml/kyaml # github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1 # k8s.io/apiserver => github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292 # k8s.io/kubelet => github.com/openshift/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20251015171918-61114aa5a292 -# k8s.io/kubernetes => github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd +# k8s.io/kubernetes => /home/rmanak/work-git/kubernetes # github.com/openshift/library-go => github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38 From 31f58ed09c3986e461fdec75baa58008cf656ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Tue, 27 Jan 2026 15:54:10 +0100 Subject: [PATCH 6/9] Make tls watcher failure fatal --- cmd/machine-api-operator/start.go | 5 +++- pkg/operator/config.go | 2 +- pkg/operator/operator.go | 14 ++++------ pkg/operator/operator_test.go | 24 ++++++++--------- pkg/operator/sync.go | 30 +++------------------ pkg/operator/sync_test.go | 43 ------------------------------- 6 files changed, 26 insertions(+), 92 deletions(-) diff --git a/cmd/machine-api-operator/start.go b/cmd/machine-api-operator/start.go index 0f2fe1c65..be0637a0f 100644 --- a/cmd/machine-api-operator/start.go +++ b/cmd/machine-api-operator/start.go @@ -265,7 +265,7 @@ func setupTLSProfileWatcher(ctx *ControllerContext, shutdown func()) error { } apiServerInformer := ctx.ConfigInformerFactory.Config().V1().APIServers().Informer() - apiServerInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + _, err = apiServerInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { handleTLSProfileEvent(obj, initialProfile, shutdown) }, @@ -273,6 +273,9 @@ func setupTLSProfileWatcher(ctx *ControllerContext, shutdown func()) error { handleTLSProfileEvent(newObj, initialProfile, shutdown) }, }) + if err != nil { + return fmt.Errorf("failed to add APIServer event handler: %w", err) + } return nil } diff --git a/pkg/operator/config.go b/pkg/operator/config.go index 451c6518d..b8faa2506 100644 --- a/pkg/operator/config.go +++ b/pkg/operator/config.go @@ -25,7 +25,7 @@ type OperatorConfig struct { Proxy *configv1.Proxy PlatformType configv1.PlatformType Features map[string]bool - TLSProfile *configv1.TLSProfileSpec + TLSProfile configv1.TLSProfileSpec } type Controllers struct { diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 8979d80e3..124341166 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -484,17 +484,13 @@ func (optr *Operator) maoConfigFromInfrastructure() (*OperatorConfig, error) { } // Fetch TLS security profile from APIServer - var tlsProfile *osconfigv1.TLSProfileSpec apiServer, err := optr.osClient.ConfigV1().APIServers().Get(context.Background(), "cluster", metav1.GetOptions{}) if err != nil { - klog.Warningf("Failed to fetch APIServer, using default TLS profile: %v", err) - } else { - profile, err := utiltls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile) - if err != nil { - klog.Warningf("Failed to get TLS profile spec, using defaults: %v", err) - } else { - tlsProfile = &profile - } + return nil, fmt.Errorf("failed to fetch APIServer for TLS profile: %w", err) + } + tlsProfile, err := utiltls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile) + if err != nil { + return nil, fmt.Errorf("failed to get TLS profile spec: %w", err) } return &OperatorConfig{ diff --git a/pkg/operator/operator_test.go b/pkg/operator/operator_test.go index ff4c95da7..40d07d299 100644 --- a/pkg/operator/operator_test.go +++ b/pkg/operator/operator_test.go @@ -405,7 +405,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.AWSPlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -441,7 +441,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.LibvirtPlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -477,7 +477,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.OpenStackPlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -513,7 +513,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.AzurePlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -549,7 +549,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.BareMetalPlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -585,7 +585,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.GCPPlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -621,7 +621,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: kubemarkPlatform, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -657,7 +657,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.VSpherePlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -693,7 +693,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.NonePlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -731,7 +731,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.BareMetalPlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -767,7 +767,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: openshiftv1.BareMetalPlatformType, Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { @@ -803,7 +803,7 @@ func TestMAOConfigFromInfrastructure(t *testing.T) { }, PlatformType: "bad-platform", Features: enabledFeatureMap, - TLSProfile: openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], + TLSProfile: *openshiftv1.TLSProfiles[openshiftv1.TLSProfileIntermediateType], }, }, { diff --git a/pkg/operator/sync.go b/pkg/operator/sync.go index 2be84294e..f24294dea 100644 --- a/pkg/operator/sync.go +++ b/pkg/operator/sync.go @@ -22,7 +22,6 @@ import ( configv1 "github.com/openshift/api/config/v1" machinev1beta1 "github.com/openshift/api/machine/v1beta1" - utiltls "github.com/openshift/library-go/pkg/controllerruntime/tls" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/library-go/pkg/operator/resource/resourceapply" "github.com/openshift/library-go/pkg/operator/resource/resourcehash" @@ -854,7 +853,7 @@ func newContainers(config *OperatorConfig, features map[string]bool) []corev1.Co return containers } -func newKubeProxyContainers(image string, withMHCProxy bool, tlsProfile *configv1.TLSProfileSpec) []corev1.Container { +func newKubeProxyContainers(image string, withMHCProxy bool, tlsProfile configv1.TLSProfileSpec) []corev1.Container { proxyContainers := []corev1.Container{ newKubeProxyContainer(image, "machineset-mtrc", metrics.DefaultMachineSetMetricsAddress, machineSetExposeMetricsPort, tlsProfile), newKubeProxyContainer(image, "machine-mtrc", metrics.DefaultMachineMetricsAddress, machineExposeMetricsPort, tlsProfile), @@ -867,29 +866,7 @@ func newKubeProxyContainers(image string, withMHCProxy bool, tlsProfile *configv return proxyContainers } -// buildKubeRBACProxyTLSArgs constructs TLS arguments for kube-rbac-proxy -// based on the cluster's TLS security profile. -func buildKubeRBACProxyTLSArgs(tlsProfile *configv1.TLSProfileSpec) []string { - // Use defaults if no profile provided - ciphers := utiltls.DefaultTLSCiphers - minVersion := utiltls.DefaultMinTLSVersion - - if tlsProfile != nil { - if len(tlsProfile.Ciphers) > 0 { - ciphers = tlsProfile.Ciphers - } - if tlsProfile.MinTLSVersion != "" { - minVersion = tlsProfile.MinTLSVersion - } - } - - return []string{ - fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(ciphers, ",")), - fmt.Sprintf("--tls-min-version=%s", minVersion), - } -} - -func newKubeProxyContainer(image, portName, upstreamPort string, exposePort int32, tlsProfile *configv1.TLSProfileSpec) corev1.Container { +func newKubeProxyContainer(image, portName, upstreamPort string, exposePort int32, tlsProfile configv1.TLSProfileSpec) corev1.Container { configMountPath := "/etc/kube-rbac-proxy" tlsCertMountPath := "/etc/tls/private" resources := corev1.ResourceRequirements{ @@ -904,10 +881,11 @@ func newKubeProxyContainer(image, portName, upstreamPort string, exposePort int3 fmt.Sprintf("--config-file=%s/config-file.yaml", configMountPath), fmt.Sprintf("--tls-cert-file=%s/tls.crt", tlsCertMountPath), fmt.Sprintf("--tls-private-key-file=%s/tls.key", tlsCertMountPath), + fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(tlsProfile.Ciphers, ",")), + fmt.Sprintf("--tls-min-version=%s", tlsProfile.MinTLSVersion), "--logtostderr=true", "--v=3", } - args = append(args, buildKubeRBACProxyTLSArgs(tlsProfile)...) ports := []corev1.ContainerPort{{ Name: portName, ContainerPort: exposePort, diff --git a/pkg/operator/sync_test.go b/pkg/operator/sync_test.go index 1324c88c2..a9a7fd524 100644 --- a/pkg/operator/sync_test.go +++ b/pkg/operator/sync_test.go @@ -504,46 +504,3 @@ func TestSyncWebhookConfiguration(t *testing.T) { }) } } - -func TestBuildKubeRBACProxyTLSArgs(t *testing.T) { - testCases := []struct { - name string - tlsProfile *configv1.TLSProfileSpec - expectedCiphers string - expectedMin configv1.TLSProtocolVersion - }{ - { - name: "nil profile uses defaults", - tlsProfile: nil, - expectedMin: configv1.VersionTLS12, - }, - { - name: "custom profile with specific ciphers", - tlsProfile: &configv1.TLSProfileSpec{ - Ciphers: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"}, - MinTLSVersion: configv1.VersionTLS12, - }, - expectedCiphers: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - expectedMin: configv1.VersionTLS12, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - g := NewWithT(t) - - args := buildKubeRBACProxyTLSArgs(tc.tlsProfile) - - g.Expect(args).To(HaveLen(2), "should return exactly 2 args (cipher-suites and min-version)") - g.Expect(args[0]).To(HavePrefix("--tls-cipher-suites=")) - g.Expect(args[1]).To(HavePrefix("--tls-min-version=")) - - if tc.expectedCiphers != "" { - g.Expect(args[0]).To(Equal("--tls-cipher-suites=" + tc.expectedCiphers)) - } - if tc.expectedMin != "" { - g.Expect(args[1]).To(Equal("--tls-min-version=" + string(tc.expectedMin))) - } - }) - } -} From da4ae017f008ed468c4c454c0c22b119d01c6f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Wed, 28 Jan 2026 11:34:58 +0100 Subject: [PATCH 7/9] Fix unit --- pkg/controller/machine/machine_controller_test.go | 3 ++- pkg/operator/operator_test.go | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/controller/machine/machine_controller_test.go b/pkg/controller/machine/machine_controller_test.go index 35bbd6bda..d77c5c4e3 100644 --- a/pkg/controller/machine/machine_controller_test.go +++ b/pkg/controller/machine/machine_controller_test.go @@ -22,8 +22,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "context" + machinev1 "github.com/openshift/api/machine/v1beta1" - "golang.org/x/net/context" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" diff --git a/pkg/operator/operator_test.go b/pkg/operator/operator_test.go index 40d07d299..7e371f23e 100644 --- a/pkg/operator/operator_test.go +++ b/pkg/operator/operator_test.go @@ -217,9 +217,15 @@ func TestOperatorSync_NoOp(t *testing.T) { }, } + apiServer := &openshiftv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster", + }, + } + stopCh := make(chan struct{}) defer close(stopCh) - optr, err := newFakeOperator(nil, []runtime.Object{infra, proxy}, nil, imagesJSONFile, nil, stopCh) + optr, err := newFakeOperator(nil, []runtime.Object{infra, proxy, apiServer}, nil, imagesJSONFile, nil, stopCh) if err != nil { t.Fatal(err) } From 43d31613124a866d55ddbd356b664a674184756f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Wed, 28 Jan 2026 14:38:36 +0100 Subject: [PATCH 8/9] Use IANA cipher names for kube-rbac-proxy TLS configuration Use library-go's TLS utilities to validate the TLS profile and convert cipher suite codes to IANA names. Skip setting cipher suites when the list is empty. --- pkg/operator/sync.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/pkg/operator/sync.go b/pkg/operator/sync.go index f24294dea..9a915ecf6 100644 --- a/pkg/operator/sync.go +++ b/pkg/operator/sync.go @@ -2,6 +2,7 @@ package operator import ( "context" + "crypto/tls" "fmt" "os" "slices" @@ -22,6 +23,8 @@ import ( configv1 "github.com/openshift/api/config/v1" machinev1beta1 "github.com/openshift/api/machine/v1beta1" + utiltls "github.com/openshift/library-go/pkg/controllerruntime/tls" + libgocrypto "github.com/openshift/library-go/pkg/crypto" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/library-go/pkg/operator/resource/resourceapply" "github.com/openshift/library-go/pkg/operator/resource/resourcehash" @@ -875,17 +878,35 @@ func newKubeProxyContainer(image, portName, upstreamPort string, exposePort int3 corev1.ResourceCPU: resource.MustParse("10m"), }, } + + tlsConfigFn, unsupportedCiphers := utiltls.NewTLSConfigFromProfile(tlsProfile) + if len(unsupportedCiphers) > 0 { + klog.V(3).Infof("kube-rbac-proxy-%s: unsupported ciphers ignored: %v", portName, unsupportedCiphers) + } + + // Apply the config function to get the validated cipher codes. + tlsConf := &tls.Config{} + tlsConfigFn(tlsConf) + args := []string{ fmt.Sprintf("--secure-listen-address=0.0.0.0:%d", exposePort), fmt.Sprintf("--upstream=http://localhost%s", upstreamPort), fmt.Sprintf("--config-file=%s/config-file.yaml", configMountPath), fmt.Sprintf("--tls-cert-file=%s/tls.crt", tlsCertMountPath), fmt.Sprintf("--tls-private-key-file=%s/tls.key", tlsCertMountPath), - fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(tlsProfile.Ciphers, ",")), + } + + // Ciphers are empty when using TLS 1.3, so we don't need to set them. + if len(tlsConf.CipherSuites) > 0 { + ianaCiphers := libgocrypto.CipherSuitesToNamesOrDie(tlsConf.CipherSuites) + args = append(args, fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(ianaCiphers, ","))) + } + + args = append(args, fmt.Sprintf("--tls-min-version=%s", tlsProfile.MinTLSVersion), "--logtostderr=true", "--v=3", - } + ) ports := []corev1.ContainerPort{{ Name: portName, ContainerPort: exposePort, From 4e665c062c4603a930e8dd6f5c5e801af37ed1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Wed, 28 Jan 2026 14:46:46 +0100 Subject: [PATCH 9/9] Add tests for newKubeProxyContainer Add unit tests to verify TLS configuration handling in newKubeProxyContainer, including tests for TLS 1.2 with cipher suites and TLS 1.3 without cipher suites. --- pkg/operator/sync_test.go | 100 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/pkg/operator/sync_test.go b/pkg/operator/sync_test.go index a9a7fd524..7030b743f 100644 --- a/pkg/operator/sync_test.go +++ b/pkg/operator/sync_test.go @@ -504,3 +504,103 @@ func TestSyncWebhookConfiguration(t *testing.T) { }) } } + +func TestNewKubeProxyContainer(t *testing.T) { + testCases := []struct { + name string + image string + portName string + upstreamPort string + exposePort int32 + tlsProfile configv1.TLSProfileSpec + expectedCipherSuitesInArgs bool + }{ + { + name: "TLS 1.2 Intermediate profile with cipher suites", + image: "test-image:latest", + portName: "test-mtrc", + upstreamPort: ":8080", + exposePort: 8443, + tlsProfile: configv1.TLSProfileSpec{ + Ciphers: []string{ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + }, + MinTLSVersion: configv1.VersionTLS12, + }, + expectedCipherSuitesInArgs: true, + }, + { + name: "TLS 1.3 Modern profile without cipher suites", + image: "test-image:latest", + portName: "test-mtrc", + upstreamPort: ":8080", + exposePort: 8443, + tlsProfile: configv1.TLSProfileSpec{ + Ciphers: []string{ + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + }, + MinTLSVersion: configv1.VersionTLS13, + }, + expectedCipherSuitesInArgs: false, + }, + { + name: "Empty cipher list", + image: "test-image:latest", + portName: "test-mtrc", + upstreamPort: ":8080", + exposePort: 8443, + tlsProfile: configv1.TLSProfileSpec{ + Ciphers: []string{}, + MinTLSVersion: configv1.VersionTLS13, + }, + expectedCipherSuitesInArgs: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + container := newKubeProxyContainer(tc.image, tc.portName, tc.upstreamPort, tc.exposePort, tc.tlsProfile) + + // Verify basic container properties + g.Expect(container.Name).To(Equal("kube-rbac-proxy-" + tc.portName)) + g.Expect(container.Image).To(Equal(tc.image)) + + // Verify ports + g.Expect(container.Ports).To(HaveLen(1)) + g.Expect(container.Ports[0].Name).To(Equal(tc.portName)) + g.Expect(container.Ports[0].ContainerPort).To(Equal(tc.exposePort)) + + // Verify resource requests + g.Expect(container.Resources.Requests).To(HaveKey(corev1.ResourceMemory)) + g.Expect(container.Resources.Requests).To(HaveKey(corev1.ResourceCPU)) + + // Verify volume mounts + g.Expect(container.VolumeMounts).To(HaveLen(2)) + + // Verify args + hasCipherSuitesArg := false + hasTLSMinVersionArg := false + for _, arg := range container.Args { + if len(arg) >= len("--tls-cipher-suites=") && arg[:len("--tls-cipher-suites=")] == "--tls-cipher-suites=" { + hasCipherSuitesArg = true + } + if len(arg) >= len("--tls-min-version=") && arg[:len("--tls-min-version=")] == "--tls-min-version=" { + hasTLSMinVersionArg = true + g.Expect(arg).To(ContainSubstring(string(tc.tlsProfile.MinTLSVersion))) + } + } + + g.Expect(hasCipherSuitesArg).To(Equal(tc.expectedCipherSuitesInArgs), + "cipher suites arg presence mismatch") + g.Expect(hasTLSMinVersionArg).To(BeTrue(), "TLS min version arg should be present") + }) + } +}