diff --git a/go.mod b/go.mod index 809367153..9a407362c 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( ) require ( - github.com/cyphar/filepath-securejoin v0.5.0 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/go-cmp v0.6.0 // indirect diff --git a/go.sum b/go.sum index fc81c149e..5da561bba 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/NVIDIA/go-nvml v0.13.0-1/go.mod h1:+KNA7c7gIBH7SKSJ1ntlwkfN80zdx8ovl4 github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= -github.com/cyphar/filepath-securejoin v0.5.0/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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml index e965034ed..3e8dd99bd 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml +++ b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml @@ -9,6 +9,10 @@ version: "2" +run: + build-tags: + - libpathrs + linters: enable: - asasalint diff --git a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md index 6862467c2..734cf61e3 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md +++ b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md @@ -6,6 +6,92 @@ 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. @@ -354,7 +440,9 @@ 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.5.0...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 diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION index 8f0916f76..a918a2aa1 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/VERSION +++ b/vendor/github.com/cyphar/filepath-securejoin/VERSION @@ -1 +1 @@ -0.5.0 +0.6.0 diff --git a/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go b/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go deleted file mode 100644 index 3e427b164..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go +++ /dev/null @@ -1,48 +0,0 @@ -// 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 securejoin - -import ( - "github.com/cyphar/filepath-securejoin/pathrs-lite" -) - -var ( - // MkdirAll is a wrapper around [pathrs.MkdirAll]. - // - // Deprecated: You should use [pathrs.MkdirAll] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - MkdirAll = pathrs.MkdirAll - - // MkdirAllHandle is a wrapper around [pathrs.MkdirAllHandle]. - // - // Deprecated: You should use [pathrs.MkdirAllHandle] directly instead. - // This wrapper will be removed in filepath-securejoin v0.6. - MkdirAllHandle = pathrs.MkdirAllHandle - - // OpenInRoot is a wrapper around [pathrs.OpenInRoot]. - // - // Deprecated: You should use [pathrs.OpenInRoot] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - OpenInRoot = pathrs.OpenInRoot - - // OpenatInRoot is a wrapper around [pathrs.OpenatInRoot]. - // - // Deprecated: You should use [pathrs.OpenatInRoot] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - OpenatInRoot = pathrs.OpenatInRoot - - // Reopen is a wrapper around [pathrs.Reopen]. - // - // Deprecated: You should use [pathrs.Reopen] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - Reopen = pathrs.Reopen -) diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md deleted file mode 100644 index 1be727e75..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md +++ /dev/null @@ -1,33 +0,0 @@ -## `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. - -The long-term plan for `pathrs-lite` is to provide a build tag that will cause -all `pathrs-lite` operations to call into libpathrs directly, thus removing -code duplication for projects that wish to make use of libpathrs (and providing -the ability for software packagers to opt-in to libpathrs support without -needing to patch upstream). - -[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 deleted file mode 100644 index d3d745175..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go +++ /dev/null @@ -1,14 +0,0 @@ -// 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]. -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 deleted file mode 100644 index 595dfbf1a..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go +++ /dev/null @@ -1,30 +0,0 @@ -// 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.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go deleted file mode 100644 index c26e440e9..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go +++ /dev/null @@ -1,30 +0,0 @@ -// 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 internal contains unexported common code for filepath-securejoin. -package internal - -import ( - "errors" -) - -var ( - // ErrPossibleAttack indicates that some attack was detected. - ErrPossibleAttack = errors.New("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 = errors.New("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 deleted file mode 100644 index 091054913..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go +++ /dev/null @@ -1,148 +0,0 @@ -// 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 deleted file mode 100644 index d2206a386..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go +++ /dev/null @@ -1,55 +0,0 @@ -// 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 deleted file mode 100644 index e1ec3c0b8..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go +++ /dev/null @@ -1,78 +0,0 @@ -// 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 deleted file mode 100644 index 77549c7a9..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go +++ /dev/null @@ -1,54 +0,0 @@ -// 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 deleted file mode 100644 index 230530835..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go +++ /dev/null @@ -1,62 +0,0 @@ -// 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" - - "github.com/cyphar/filepath-securejoin/pathrs-lite/internal" -) - -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 = 32 - -// 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 tries < scopedLookupMaxRetries { - fd, err := unix.Openat2(dirFd, 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} - } - runtime.KeepAlive(dir) - return os.NewFile(uintptr(fd), fullPath), nil - } - return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: internal.ErrPossibleAttack} -} 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 deleted file mode 100644 index 5dcb6ae00..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md +++ /dev/null @@ -1,10 +0,0 @@ -## 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 deleted file mode 100644 index 4b1803f58..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go +++ /dev/null @@ -1,13 +0,0 @@ -// 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/pathrs-lite/internal/gocompat/gocompat_errors_go120.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go deleted file mode 100644 index 4a114bd3d..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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 gocompat - -import ( - "fmt" -) - -// 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 { - return fmt.Errorf("%w: %w", extraErr, baseErr) -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go deleted file mode 100644 index 3061016a6..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go +++ /dev/null @@ -1,40 +0,0 @@ -// 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 gocompat - -import ( - "fmt" -) - -type wrappedError struct { - inner error - isError error -} - -func (err wrappedError) Is(target error) bool { - return err.isError == target -} - -func (err wrappedError) Unwrap() error { - return err.inner -} - -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 -// 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 { - 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 deleted file mode 100644 index d4a938186..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go +++ /dev/null @@ -1,53 +0,0 @@ -// 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 deleted file mode 100644 index 0ea6218aa..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go +++ /dev/null @@ -1,187 +0,0 @@ -// 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/kernelversion/kernel_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go deleted file mode 100644 index cb6de4186..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go +++ /dev/null @@ -1,123 +0,0 @@ -// 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 deleted file mode 100644 index 4635714f6..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// 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 deleted file mode 100644 index b29905bff..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go +++ /dev/null @@ -1,47 +0,0 @@ -// 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 deleted file mode 100644 index 399609dc3..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go +++ /dev/null @@ -1,31 +0,0 @@ -// 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 deleted file mode 100644 index 21e0a62e8..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go +++ /dev/null @@ -1,544 +0,0 @@ -// 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 deleted file mode 100644 index 1ad1f18ee..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go +++ /dev/null @@ -1,222 +0,0 @@ -// 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/lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go deleted file mode 100644 index f47504e66..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go +++ /dev/null @@ -1,399 +0,0 @@ -// 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 ( - "errors" - "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/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 { - // (dir, remainingPath) is what we would've returned if the link didn't - // exist. This matches what openat2(RESOLVE_IN_ROOT) would return in - // this case. - dir *os.File - remainingPath string - // linkUnwalked is the remaining path components from the original - // Readlink which we have yet to walk. When this slice is empty, we - // drop the link from the stack. - linkUnwalked []string -} - -func (se symlinkStackEntry) String() string { - return fmt.Sprintf("<%s>/%s [->%s]", se.dir.Name(), se.remainingPath, strings.Join(se.linkUnwalked, "/")) -} - -func (se symlinkStackEntry) Close() { - _ = se.dir.Close() -} - -type symlinkStack []*symlinkStackEntry - -func (s *symlinkStack) IsEmpty() bool { - return s == nil || len(*s) == 0 -} - -func (s *symlinkStack) Close() { - if s != nil { - for _, link := range *s { - link.Close() - } - // TODO: Switch to clear once we switch to Go 1.21. - *s = nil - } -} - -var ( - errEmptyStack = errors.New("[internal] stack is empty") - errBrokenSymlinkStack = errors.New("[internal error] broken symlink stack") -) - -func (s *symlinkStack) popPart(part string) error { - if s == nil || s.IsEmpty() { - // If there is nothing in the symlink stack, then the part was from the - // real path provided by the user, and this is a no-op. - return errEmptyStack - } - if part == "." { - // "." components are no-ops -- we drop them when doing SwapLink. - return nil - } - - tailEntry := (*s)[len(*s)-1] - - // Double-check that we are popping the component we expect. - if len(tailEntry.linkUnwalked) == 0 { - return fmt.Errorf("%w: trying to pop component %q of empty stack entry %s", errBrokenSymlinkStack, part, tailEntry) - } - headPart := tailEntry.linkUnwalked[0] - if headPart != part { - return fmt.Errorf("%w: trying to pop component %q but the last stack entry is %s (%q)", errBrokenSymlinkStack, part, tailEntry, headPart) - } - - // Drop the component, but keep the entry around in case we are dealing - // with a "tail-chained" symlink. - tailEntry.linkUnwalked = tailEntry.linkUnwalked[1:] - return nil -} - -func (s *symlinkStack) PopPart(part string) error { - if err := s.popPart(part); err != nil { - if errors.Is(err, errEmptyStack) { - // Skip empty stacks. - err = nil - } - return err - } - - // Clean up any of the trailing stack entries that are empty. - for lastGood := len(*s) - 1; lastGood >= 0; lastGood-- { - entry := (*s)[lastGood] - if len(entry.linkUnwalked) > 0 { - break - } - entry.Close() - (*s) = (*s)[:lastGood] - } - return nil -} - -func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) error { - if s == nil { - return nil - } - // Split the link target and clean up any "" parts. - 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 := fd.Dup(dir) - if err != nil { - return err - } - - // Add to the stack. - *s = append(*s, &symlinkStackEntry{ - dir: dirCopy, - remainingPath: remainingPath, - linkUnwalked: linkTargetParts, - }) - return nil -} - -func (s *symlinkStack) SwapLink(linkPart string, dir *os.File, remainingPath, linkTarget string) error { - // If we are currently inside a symlink resolution, remove the symlink - // component from the last symlink entry, but don't remove the entry even - // if it's empty. If we are a "tail-chained" symlink (a trailing symlink we - // hit during a symlink resolution) we need to keep the old symlink until - // we finish the resolution. - if err := s.popPart(linkPart); err != nil { - if !errors.Is(err, errEmptyStack) { - return err - } - // Push the component regardless of whether the stack was empty. - } - return s.push(dir, remainingPath, linkTarget) -} - -func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) { - if s == nil || s.IsEmpty() { - return nil, "", false - } - tailEntry := (*s)[0] - *s = (*s)[1:] - return tailEntry.dir, tailEntry.remainingPath, true -} - -// 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 fd.Fd, unsafePath string) (*os.File, string, error) { - return lookupInRoot(root, unsafePath, true) -} - -func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) { - handle, remainingPath, err := lookupInRoot(root, unsafePath, false) - if remainingPath != "" && err == nil { - // should never happen - err = fmt.Errorf("[bug] non-empty remaining path when doing a non-partial lookup: %q", remainingPath) - } - // lookupInRoot(partial=false) will always close the handle if an error is - // returned, so no need to double-check here. - return handle, err -} - -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 - // components using file descriptors. We then return the last component we - // managed open, along with the remaining path components not opened. - - // Try to use openat2 if possible. - 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 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 := fd.Dup(root) - 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() - } - }() - - // symlinkStack is used to emulate how openat2(RESOLVE_IN_ROOT) treats - // dangling symlinks. If we hit a non-existent path while resolving a - // symlink, we need to return the (dir, remainingPath) that we had when we - // hit the symlink (treating the symlink as though it were a regular file). - // The set of (dir, remainingPath) sets is stored within the symlinkStack - // and we add and remove parts when we hit symlink and non-symlink - // components respectively. We need a stack because of recursive symlinks - // (symlinks that contain symlink components in their target). - // - // Note that the stack is ONLY used for book-keeping. All of the actual - // path walking logic is still based on currentPath/remainingPath and - // currentDir (as in SecureJoin). - var symStack *symlinkStack - if partial { - symStack = new(symlinkStack) - defer symStack.Close() - } - - var ( - linksWalked int - currentPath string - remainingPath = unsafePath - ) - for remainingPath != "" { - // Save the current remaining path so if the part is not real we can - // return the path including the component. - oldRemainingPath := 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 we hit an empty component, we need to treat it as though it is - // "." so that trailing "/" and "//" components on a non-directory - // correctly return the right error code. - if part == "" { - part = "." - } - - // 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. - 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 == "/" { - if err := symStack.PopPart(part); err != nil { - return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err) - } - // Jump to root. - rootClone, err := fd.Dup(root) - 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) - 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 { //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 := 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.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP} - } - - // Swap out the symlink's component for the link entry itself. - if err := symStack.SwapLink(part, currentDir, oldRemainingPath, linkDest); err != nil { - return nil, "", fmt.Errorf("walking into symlink %q failed: push symlink: %w", part, err) - } - - // Update our logical remaining path. - remainingPath = linkDest + "/" + remainingPath - // Absolute symlinks reset any work we've already done. - if path.IsAbs(linkDest) { - // Jump to root. - rootClone, err := fd.Dup(root) - if err != nil { - return nil, "", fmt.Errorf("clone root fd: %w", err) - } - _ = currentDir.Close() - currentDir = rootClone - currentPath = "/" - } - - default: - // If we are dealing with a directory, simply walk into it. - _ = currentDir.Close() - currentDir = nextDir - currentPath = nextPath - - // The part was real, so drop it from the symlink stack. - if err := symStack.PopPart(part); err != nil { - return nil, "", fmt.Errorf("walking into directory %q failed: %w", part, err) - } - - // If we are operating on a .., make sure we haven't escaped. - // We only have to check for ".." here because walking down - // into a regular component component cannot cause you to - // escape. This mirrors the logic in RESOLVE_IN_ROOT, except we - // have to check every ".." rather than only checking after a - // rename or mount on the system. - if part == ".." { - // Make sure the root hasn't moved. - 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 := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil { - return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err) - } - } - } - - default: - if !partial { - return nil, "", err - } - // If there are any remaining components in the symlink stack, we - // are still within a symlink resolution and thus we hit a dangling - // symlink. So pretend that the first symlink in the stack we hit - // was an ENOENT (to match openat2). - if oldDir, remainingPath, ok := symStack.PopTopSymlink(); ok { - _ = currentDir.Close() - return oldDir, remainingPath, err - } - // We have hit a final component that doesn't exist, so we have our - // partial open result. Note that we have to use the OLD remaining - // path, since the lookup failed. - return currentDir, oldRemainingPath, err - } - } - - // If the unsafePath had a trailing slash, we need to make sure we try to - // do a relative "." open so that we will correctly return an error when - // the final component is a non-directory (to match openat2). In the - // context of openat2, a trailing slash and a trailing "/." are completely - // equivalent. - if strings.HasSuffix(unsafePath, "/") { - nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - if err != nil { - if !partial { - _ = currentDir.Close() - currentDir = nil - } - return currentDir, "", err - } - _ = currentDir.Close() - currentDir = nextDir - } - - // All of the components existed! - return currentDir, "", nil -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go deleted file mode 100644 index f3c62b0da..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go +++ /dev/null @@ -1,246 +0,0 @@ -// 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 ( - "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/internal/gocompat" - "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" -) - -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 - -//nolint:cyclop // this function needs to handle a lot of cases -func toUnixMode(mode os.FileMode) (uint32, error) { - sysMode := uint32(mode.Perm()) - 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 - } - // 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) - } - // 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 sysMode, nil -} - -// 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, Err error) { - unixMode, err := toUnixMode(mode) - if err != nil { - return nil, err - } - // On Linux, mkdirat(2) (and os.Mkdir) silently ignore the suid and sgid - // bits. We could also silently ignore them but since we have very few - // 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) - } - - // Try to open as much of the path as possible. - currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath) - defer func() { - if Err != nil { - _ = currentDir.Close() - } - }() - if err != nil && !errors.Is(err, unix.ENOENT) { - return nil, fmt.Errorf("find existing subpath of %q: %w", unsafePath, err) - } - - // If there is an attacker deleting directories as we walk into them, - // detect this proactively. Note this is guaranteed to detect if the - // attacker deleted any part of the tree up to currentDir. - // - // Once we walk into a dead directory, partialLookupInRoot would not be - // able to walk further down the tree (directories must be empty before - // they are deleted), and if the attacker has removed the entire tree we - // can be sure that anything that was originally inside a dead directory - // must also be deleted and thus is a dead directory in its own right. - // - // 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 := 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) { - 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 { //nolint:revive // indent-error-flow lint doesn't make sense here - _ = currentDir.Close() - currentDir = reopenDir - } - - remainingParts := strings.Split(remainingPath, string(filepath.Separator)) - 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 - // needs to be supported. So just return an error. - // - // If we do filepath.Clean(remainingPath) then we end up with the - // problem that ".." can erase a trailing dangling symlink and produce - // a path that doesn't quite match what the user asked for. - return nil, fmt.Errorf("%w: yet-to-be-created path %q contains '..' components", unix.ENOENT, remainingPath) - } - - // Create the remaining components. - for _, part := range remainingParts { - switch part { - case "", ".": - // Skip over no-op paths. - continue - } - - // NOTE: mkdir(2) will not follow trailing symlinks, so we can safely - // create the final component without worrying about symlink-exchange - // attacks. - // - // If we get -EEXIST, it's possible that another program created the - // directory at the same time as us. In that case, just continue on as - // if we created it (if the created inode is not a directory, the - // following open call will fail). - 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 := 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 = gocompat.WrapBaseError(err, deadErr) - } - return nil, err - } - - // Get a handle to the next component. O_DIRECTORY means we don't need - // to use O_PATH. - var nextDir *os.File - 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 = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) - } - if err != nil { - return nil, err - } - _ = currentDir.Close() - currentDir = nextDir - - // It's possible that the directory we just opened was swapped by an - // attacker. Unfortunately there isn't much we can do to protect - // against this, and MkdirAll's behaviour is that we will reuse - // existing directories anyway so the need to protect against this is - // incredibly limited (and arguably doesn't even deserve mention here). - // - // Ideally we might want to check that the owner and mode match what we - // would've created -- unfortunately, it is non-trivial to verify that - // the owner and mode of the created directory match. While plain Unix - // DAC rules seem simple enough to emulate, there are a bunch of other - // factors that can change the mode or owner of created directories - // (default POSIX ACLs, mount options like uid=1,gid=2,umask=0 on - // filesystems like vfat, etc etc). We used to try to verify this but - // it just lead to a series of spurious errors. - // - // We could also check that the directory is non-empty, but - // unfortunately some pseduofilesystems (like cgroupfs) create - // non-empty directories, which would result in different spurious - // errors. - } - 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]. -// -// [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/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go deleted file mode 100644 index 7492d8cfa..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go +++ /dev/null @@ -1,74 +0,0 @@ -// 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" - - "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) { - 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]. -// -// [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) -} - -// 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/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go deleted file mode 100644 index 937bc435f..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go +++ /dev/null @@ -1,101 +0,0 @@ -// 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 ( - "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/procfs/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go deleted file mode 100644 index ec187a414..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go +++ /dev/null @@ -1,157 +0,0 @@ -// 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. -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/modules.txt b/vendor/modules.txt index 24a5aa6f3..27a2024af 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -12,19 +12,10 @@ github.com/NVIDIA/go-nvml/pkg/dl github.com/NVIDIA/go-nvml/pkg/nvml github.com/NVIDIA/go-nvml/pkg/nvml/mock github.com/NVIDIA/go-nvml/pkg/nvml/mock/dgxa100 -# github.com/cyphar/filepath-securejoin v0.5.0 +# 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/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/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew