This new version includes the fixes for CVE-2025-52881, so we can remove
the internal/third_party copy of the library we added in commit
ed6b1693b8 ("selinux: use safe procfs API for labels") as well as the
"replace" directive in go.mod (which is problematic for "go get"
installs).

Fixes: ed6b1693b8 ("selinux: use safe procfs API for labels")
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
Aleksa Sarai
2025-11-08 02:10:48 +11:00
parent 846835cce9
commit 96f1962f91
67 changed files with 2240 additions and 4909 deletions
+7 -5
View File
@@ -5,11 +5,13 @@ 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).
`pathrs-lite` also provides a very easy way to switch to `libpathrs` (even for
downstreams where `pathrs-lite` is being used in a third-party package and is
not interested in using CGo). At build time, if you use the `libpathrs` build
tag then `pathrs-lite` will use `libpathrs` directly instead of the pure Go
implementation. The two backends are functionally equivalent (and we have
integration tests to verify this), so this migration should be very easy with
no user-visible impact.
[libpathrs]: https://github.com/cyphar/libpathrs
+2
View File
@@ -11,4 +11,6 @@
// Package pathrs (pathrs-lite) is a less complete pure Go implementation of
// some of the APIs provided by [libpathrs].
//
// [libpathrs]: https://github.com/cyphar/libpathrs
package pathrs
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Package gopathrs is a less complete pure Go implementation of some of the
// APIs provided by [libpathrs].
//
// [libpathrs]: https://github.com/cyphar/libpathrs
package gopathrs
@@ -9,7 +9,7 @@
// 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
package gopathrs
import (
"errors"
@@ -166,11 +166,11 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) {
return tailEntry.dir, tailEntry.remainingPath, true
}
// partialLookupInRoot tries to lookup as much of the request path as possible
// PartialLookupInRoot tries to lookup as much of the request path as possible
// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing
// component of the requested path, returning a file handle to the final
// existing component and a string containing the remaining path components.
func partialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) {
func PartialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) {
return lookupInRoot(root, unsafePath, true)
}
@@ -9,7 +9,7 @@
// 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
package gopathrs
import (
"errors"
@@ -23,9 +23,12 @@ import (
"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"
)
var errInvalidMode = errors.New("invalid permission mode")
// ErrInvalidMode is returned from [MkdirAll] when the requested mode is
// invalid.
var ErrInvalidMode = errors.New("invalid permission mode")
// modePermExt is like os.ModePerm except that it also includes the set[ug]id
// and sticky bits.
@@ -45,11 +48,11 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
}
// We don't allow file type bits.
if mode&os.ModeType != 0 {
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode)
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", ErrInvalidMode, mode, mode)
}
// We don't allow other unknown modes.
if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 {
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode)
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", ErrInvalidMode, mode, mode)
}
return sysMode, nil
}
@@ -84,11 +87,11 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
// users it seems more prudent to return an error so users notice that
// these bits will not be set.
if unixMode&^0o1777 != 0 {
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode)
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", ErrInvalidMode, mode)
}
// Try to open as much of the path as possible.
currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath)
currentDir, remainingPath, err := PartialLookupInRoot(root, unsafePath)
defer func() {
if Err != nil {
_ = currentDir.Close()
@@ -117,7 +120,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
// Re-open the path to match the O_DIRECTORY reopen loop later (so that we
// always return a non-O_PATH handle). We also check that we actually got a
// directory.
if reopenDir, err := Reopen(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) {
if reopenDir, err := procfs.ReopenFd(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) {
return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR)
} else if err != nil {
return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err)
@@ -207,40 +210,3 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
}
return currentDir, nil
}
// MkdirAll is a race-safe alternative to the [os.MkdirAll] function,
// where the new directory is guaranteed to be within the root directory (if an
// attacker can move directories from inside the root to outside the root, the
// created directory tree might be outside of the root but the key constraint
// is that at no point will we walk outside of the directory tree we are
// creating).
//
// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to
//
// path, _ := securejoin.SecureJoin(root, unsafePath)
// err := os.MkdirAll(path, mode)
//
// But is much safer. The above implementation is unsafe because if an attacker
// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is
// possible for MkdirAll to resolve unsafe symlink components and create
// directories outside of the root.
//
// If you plan to open the directory after you have created it or want to use
// an open directory handle as the root, you should use [MkdirAllHandle] instead.
// This function is a wrapper around [MkdirAllHandle].
//
// [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
}
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package gopathrs
import (
"os"
)
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
// using an *[os.File] handle, to ensure that the correct root directory is used.
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
handle, err := completeLookupInRoot(root, unsafePath)
if err != nil {
return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err}
}
return handle, nil
}
@@ -9,7 +9,7 @@
// 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
package gopathrs
import (
"errors"
+55
View File
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package pathrs
import (
"os"
"golang.org/x/sys/unix"
)
// MkdirAll is a race-safe alternative to the [os.MkdirAll] function,
// where the new directory is guaranteed to be within the root directory (if an
// attacker can move directories from inside the root to outside the root, the
// created directory tree might be outside of the root but the key constraint
// is that at no point will we walk outside of the directory tree we are
// creating).
//
// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to
//
// path, _ := securejoin.SecureJoin(root, unsafePath)
// err := os.MkdirAll(path, mode)
//
// But is much safer. The above implementation is unsafe because if an attacker
// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is
// possible for MkdirAll to resolve unsafe symlink components and create
// directories outside of the root.
//
// If you plan to open the directory after you have created it or want to use
// an open directory handle as the root, you should use [MkdirAllHandle] instead.
// This function is a wrapper around [MkdirAllHandle].
//
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
func MkdirAll(root, unsafePath string, mode os.FileMode) error {
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return err
}
defer rootDir.Close() //nolint:errcheck // close failures aren't critical here
f, err := MkdirAllHandle(rootDir, unsafePath, mode)
if err != nil {
return err
}
_ = f.Close()
return nil
}
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MPL-2.0
//go:build libpathrs
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package pathrs
import (
"os"
"cyphar.com/go-pathrs"
)
// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use
// in two respects:
//
// - The caller provides the root directory as an *[os.File] (preferably O_PATH)
// handle. This means that the caller can be sure which root directory is
// being used. Note that this can be emulated by using /proc/self/fd/... as
// the root path with [os.MkdirAll].
//
// - Once all of the directories have been created, an *[os.File] O_PATH handle
// to the directory at unsafePath is returned to the caller. This is done in
// an effectively-race-free way (an attacker would only be able to swap the
// final directory component), which is not possible to emulate with
// [MkdirAll].
//
// In addition, the returned handle is obtained far more efficiently than doing
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
// doing [MkdirAll]. If you intend to open the directory after creating it, you
// should use MkdirAllHandle.
//
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (*os.File, error) {
rootRef, err := pathrs.RootFromFile(root)
if err != nil {
return nil, err
}
defer rootRef.Close() //nolint:errcheck // close failures aren't critical here
handle, err := rootRef.MkdirAll(unsafePath, mode)
if err != nil {
return nil, err
}
return handle.IntoFile(), nil
}
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux && !libpathrs
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package pathrs
import (
"os"
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs"
)
// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use
// in two respects:
//
// - The caller provides the root directory as an *[os.File] (preferably O_PATH)
// handle. This means that the caller can be sure which root directory is
// being used. Note that this can be emulated by using /proc/self/fd/... as
// the root path with [os.MkdirAll].
//
// - Once all of the directories have been created, an *[os.File] O_PATH handle
// to the directory at unsafePath is returned to the caller. This is done in
// an effectively-race-free way (an attacker would only be able to swap the
// final directory component), which is not possible to emulate with
// [MkdirAll].
//
// In addition, the returned handle is obtained far more efficiently than doing
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
// doing [MkdirAll]. If you intend to open the directory after creating it, you
// should use MkdirAllHandle.
//
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (*os.File, error) {
return gopathrs.MkdirAllHandle(root, unsafePath, mode)
}
@@ -15,20 +15,8 @@ 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
//
@@ -55,20 +43,3 @@ func OpenInRoot(root, unsafePath string) (*os.File, error) {
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)
}
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MPL-2.0
//go:build libpathrs
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package pathrs
import (
"os"
"cyphar.com/go-pathrs"
)
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
// using an *[os.File] handle, to ensure that the correct root directory is used.
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
rootRef, err := pathrs.RootFromFile(root)
if err != nil {
return nil, err
}
defer rootRef.Close() //nolint:errcheck // close failures aren't critical here
handle, err := rootRef.Resolve(unsafePath)
if err != nil {
return nil, err
}
return handle.IntoFile(), nil
}
// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd.
// Reopen(file, flags) is effectively equivalent to
//
// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
// os.OpenFile(fdPath, flags|unix.O_CLOEXEC)
//
// But with some extra hardenings to ensure that we are not tricked by a
// maliciously-configured /proc mount. While this attack scenario is not
// common, in container runtimes it is possible for higher-level runtimes to be
// tricked into configuring an unsafe /proc that can be used to attack file
// operations. See [CVE-2019-19921] for more details.
//
// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
func Reopen(file *os.File, flags int) (*os.File, error) {
handle, err := pathrs.HandleFromFile(file)
if err != nil {
return nil, err
}
defer handle.Close() //nolint:errcheck // close failures aren't critical here
return handle.OpenFile(flags)
}
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux && !libpathrs
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package pathrs
import (
"os"
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs"
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
)
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
// using an *[os.File] handle, to ensure that the correct root directory is used.
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
return gopathrs.OpenatInRoot(root, unsafePath)
}
// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd.
// Reopen(file, flags) is effectively equivalent to
//
// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
// os.OpenFile(fdPath, flags|unix.O_CLOEXEC)
//
// But with some extra hardenings to ensure that we are not tricked by a
// maliciously-configured /proc mount. While this attack scenario is not
// common, in container runtimes it is possible for higher-level runtimes to be
// tricked into configuring an unsafe /proc that can be used to attack file
// operations. See [CVE-2019-19921] for more details.
//
// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
func Reopen(handle *os.File, flags int) (*os.File, error) {
return procfs.ReopenFd(handle, flags)
}
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: MPL-2.0
//go:build libpathrs
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Package procfs provides a safe API for operating on /proc on Linux.
package procfs
import (
"os"
"strconv"
"cyphar.com/go-pathrs/procfs"
"golang.org/x/sys/unix"
)
// ProcThreadSelfCloser is a callback that needs to be called when you are done
// operating on an [os.File] fetched using [Handle.OpenThreadSelf].
//
// [os.File]: https://pkg.go.dev/os#File
type ProcThreadSelfCloser = procfs.ThreadCloser
// Handle is a wrapper around an *os.File handle to "/proc", which can be used
// to do further procfs-related operations in a safe way.
type Handle struct {
inner *procfs.Handle
}
// Close close the resources associated with this [Handle]. Note that if this
// [Handle] was created with [OpenProcRoot], on some kernels the underlying
// procfs handle is cached and so this Close operation may be a no-op. However,
// you should always call Close on [Handle]s once you are done with them.
func (proc *Handle) Close() error { return proc.inner.Close() }
// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the
// "subset=pid" mount option applied, available from Linux 5.8). Unless you
// plan to do many [Handle.OpenRoot] operations, users should prefer to use
// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open.
//
// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a
// regular "/proc" handle.
//
// Note that using [Handle.OpenRoot] will still work with handles returned by
// this function. If a subpath cannot be operated on with a safe "/proc"
// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary
// unsafe handle will be used.
func OpenProcRoot() (*Handle, error) {
proc, err := procfs.Open()
if err != nil {
return nil, err
}
return &Handle{inner: proc}, nil
}
// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or
// masked paths. You must be extremely careful to make sure this handle is
// never leaked to a container and that you program cannot be tricked into
// writing to arbitrary paths within it.
//
// This is not necessary if you just wish to use [Handle.OpenRoot], as handles
// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe
// handle in that case. You should only really use this if you need to do many
// operations with [Handle.OpenRoot] and the performance overhead of making
// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you
// should make sure to close the handle as soon as possible to avoid
// known-fd-number attacks.
func OpenUnsafeProcRoot() (*Handle, error) {
proc, err := procfs.Open(procfs.UnmaskedProcRoot)
if err != nil {
return nil, err
}
return &Handle{inner: proc}, nil
}
// OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an
// equivalent handle on older kernels where "/proc/thread-self" doesn't exist).
// Once finished with the handle, you must call the returned closer function
// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other
// Go threads or use the handle after calling the closer.
//
// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread
func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) {
return proc.inner.OpenThreadSelf(subpath, unix.O_PATH|unix.O_NOFOLLOW)
}
// OpenSelf returns a handle to /proc/self/<subpath>.
//
// Note that in Go programs with non-homogenous threads, this may result in
// spurious errors. If you are monkeying around with APIs that are
// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead
// which will guarantee that the handle refers to the same thread as the caller
// is executing on.
func (proc *Handle) OpenSelf(subpath string) (*os.File, error) {
return proc.inner.OpenSelf(subpath, unix.O_PATH|unix.O_NOFOLLOW)
}
// OpenRoot returns a handle to /proc/<subpath>.
//
// You should only use this when you need to operate on global procfs files
// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf],
// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally
// for this operation will never use "subset=pid", which makes it a more juicy
// target for [CVE-2024-21626]-style attacks (and doing something like opening
// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as
// the file descriptor is open).
//
// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
return proc.inner.OpenRoot(subpath, unix.O_PATH|unix.O_NOFOLLOW)
}
// OpenPid returns a handle to /proc/$pid/<subpath> (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/<tid>) when dealing with
// goroutine scheduling -- use [Handle.OpenThreadSelf] instead.
//
// To refer to the current thread-group, you should use prefer
// [Handle.OpenSelf] to passing os.Getpid as the pid argument.
func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) {
return proc.inner.OpenPid(pid, subpath, unix.O_PATH|unix.O_NOFOLLOW)
}
// ProcSelfFdReadlink gets the real path of the given file by looking at
// /proc/self/fd/<fd> with [readlink]. It is effectively just shorthand for
// something along the lines of:
//
// proc, err := procfs.OpenProcRoot()
// if err != nil {
// return err
// }
// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd()))
// if err != nil {
// return err
// }
// defer link.Close()
// var buf [4096]byte
// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:])
// if err != nil {
// return err
// }
// pathname := buf[:n]
//
// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat
func ProcSelfFdReadlink(f *os.File) (string, error) {
proc, err := procfs.Open()
if err != nil {
return "", err
}
defer proc.Close() //nolint:errcheck // close failures aren't critical here
fdPath := "fd/" + strconv.Itoa(int(f.Fd()))
return proc.Readlink(procfs.ProcThreadSelf, fdPath)
}
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
//go:build linux && !libpathrs
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC