File 0009-CVE-2025-52881-backport-subset-of-patch-from-runc.patch of Package buildah.41503

From daa91a1d3774ce353eaee1efad1bf7e471821a71 Mon Sep 17 00:00:00 2001
From: Danish Prakash <contact@danishpraka.sh>
Date: Thu, 6 Nov 2025 13:10:31 +0530
Subject: [PATCH 9/9] CVE-2025-52881: backport subset of patch from runc

buildah imports libcontainer/apparmor for the chroot isolation engine,
so we need to patch it directly. None of the other vulnerable codepaths
from CVE-2025-52881 are relevant.

Bugs: https://bugzilla.suse.com/show_bug.cgi?id=1252376

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Signed-off-by: Danish Prakash <contact@danishpraka.sh>
---
 go.mod                                        |   2 +-
 go.sum                                        |   4 +-
 .../cyphar/filepath-securejoin/.golangci.yml  |  56 ++
 .../cyphar/filepath-securejoin/CHANGELOG.md   | 403 +++++++++++++
 .../cyphar/filepath-securejoin/COPYING.md     | 447 ++++++++++++++
 .../{LICENSE => LICENSE.BSD}                  |   0
 .../filepath-securejoin/LICENSE.MPL-2.0       | 373 ++++++++++++
 .../cyphar/filepath-securejoin/README.md      |  24 +-
 .../cyphar/filepath-securejoin/VERSION        |   2 +-
 .../cyphar/filepath-securejoin/codecov.yml    |  29 +
 .../filepath-securejoin/deprecated_linux.go   |  48 ++
 .../cyphar/filepath-securejoin/doc.go         |  47 ++
 .../internal/consts/consts.go                 |  15 +
 .../cyphar/filepath-securejoin/join.go        |  87 ++-
 .../cyphar/filepath-securejoin/mkdir_linux.go | 228 --------
 .../filepath-securejoin/openat2_linux.go      | 128 -----
 .../filepath-securejoin/openat_linux.go       |  59 --
 .../filepath-securejoin/pathrs-lite/README.md |  33 ++
 .../filepath-securejoin/pathrs-lite/doc.go    |  14 +
 .../pathrs-lite/internal/assert/assert.go     |  30 +
 .../pathrs-lite/internal/errors_linux.go      |  41 ++
 .../pathrs-lite/internal/fd/at_linux.go       | 148 +++++
 .../pathrs-lite/internal/fd/fd.go             |  55 ++
 .../pathrs-lite/internal/fd/fd_linux.go       |  78 +++
 .../pathrs-lite/internal/fd/mount_linux.go    |  54 ++
 .../pathrs-lite/internal/fd/openat2_linux.go  |  62 ++
 .../pathrs-lite/internal/gocompat/README.md   |  10 +
 .../pathrs-lite/internal/gocompat/doc.go      |  13 +
 .../gocompat/gocompat_errors_go120.go         |  19 +
 .../gocompat/gocompat_errors_unsupported.go   |  40 ++
 .../gocompat/gocompat_generics_go121.go       |  53 ++
 .../gocompat/gocompat_generics_unsupported.go | 187 ++++++
 .../internal/kernelversion/kernel_linux.go    | 123 ++++
 .../pathrs-lite/internal/linux/doc.go         |  12 +
 .../pathrs-lite/internal/linux/mount_linux.go |  47 ++
 .../internal/linux/openat2_linux.go           |  31 +
 .../internal/procfs/procfs_linux.go           | 544 ++++++++++++++++++
 .../internal/procfs/procfs_lookup_linux.go    | 222 +++++++
 .../{ => pathrs-lite}/lookup_linux.go         | 261 +++++----
 .../pathrs-lite/mkdir_linux.go                | 246 ++++++++
 .../{ => pathrs-lite}/open_linux.go           |  59 +-
 .../pathrs-lite/openat2_linux.go              | 101 ++++
 .../pathrs-lite/procfs/procfs_linux.go        | 157 +++++
 .../filepath-securejoin/procfs_linux.go       | 481 ----------------
 .../testing_mocks_linux.go                    |  68 ---
 .../cyphar/filepath-securejoin/vfs.go         |  26 +-
 .../libcontainer/apparmor/apparmor_linux.go   |  36 +-
 vendor/modules.txt                            |  14 +-
 48 files changed, 4039 insertions(+), 1178 deletions(-)
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/COPYING.md
 rename vendor/github.com/cyphar/filepath-securejoin/{LICENSE => LICENSE.BSD} (100%)
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/codecov.yml
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/doc.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
 delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go
 delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
 delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
 rename vendor/github.com/cyphar/filepath-securejoin/{ => pathrs-lite}/lookup_linux.go (67%)
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go
 rename vendor/github.com/cyphar/filepath-securejoin/{ => pathrs-lite}/open_linux.go (57%)
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
 create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
 delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
 delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/testing_mocks_linux.go

diff --git a/go.mod b/go.mod
index 146e206d926c..b0d2f7d40f7f 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/containers/luksy v0.0.0-20240212203526-ceb12d4fd50c
 	github.com/containers/ocicrypt v1.1.10
 	github.com/containers/storage v1.53.0
-	github.com/cyphar/filepath-securejoin v0.3.0
+	github.com/cyphar/filepath-securejoin v0.5.1
 	github.com/docker/distribution v2.8.3+incompatible
 	github.com/docker/docker v25.0.3+incompatible
 	github.com/docker/go-units v0.5.0
diff --git a/go.sum b/go.sum
index 2fb6eccc7ab6..ee7bfd618b19 100644
--- a/go.sum
+++ b/go.sum
@@ -77,8 +77,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f h1:eHnXnuK47UlSTOQexbzxAZfekVz6i+LKRdj1CU5DPaM=
 github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
-github.com/cyphar/filepath-securejoin v0.3.0 h1:tXpmbiaeBrS/K2US8nhgwdKYnfAOnVfkcLPKFgFHeA0=
-github.com/cyphar/filepath-securejoin v0.3.0/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
+github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
+github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
 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
new file mode 100644
index 000000000000..e965034ed36d
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: MPL-2.0
+
+# Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
+# Copyright (C) 2025 SUSE LLC
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+version: "2"
+
+linters:
+  enable:
+    - asasalint
+    - asciicheck
+    - containedctx
+    - contextcheck
+    - errcheck
+    - errorlint
+    - exhaustive
+    - forcetypeassert
+    - godot
+    - goprintffuncname
+    - govet
+    - importas
+    - ineffassign
+    - makezero
+    - misspell
+    - musttag
+    - nilerr
+    - nilnesserr
+    - nilnil
+    - noctx
+    - prealloc
+    - revive
+    - staticcheck
+    - testifylint
+    - unconvert
+    - unparam
+    - unused
+    - usetesting
+  settings:
+    govet:
+      enable:
+        - nilness
+    testifylint:
+      enable-all: true
+
+formatters:
+  enable:
+    - gofumpt
+    - goimports
+  settings:
+    goimports:
+      local-prefixes:
+        - github.com/cyphar/filepath-securejoin
diff --git a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
new file mode 100644
index 000000000000..3faee0bc55b8
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
@@ -0,0 +1,403 @@
+# Changelog #
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
+and this project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased 0.5.z] ##
+
+## [0.5.1] - 2025-10-31 ##
+
+> Spooky scary skeletons send shivers down your spine!
+
+### Changed ###
+- `openat2` can return `-EAGAIN` if it detects a possible attack in certain
+  scenarios (namely if there was a rename or mount while walking a path with a
+  `..` component). While this is necessary to avoid a denial-of-service in the
+  kernel, it does require retry loops in userspace.
+
+  In previous versions, `pathrs-lite` would retry `openat2` 32 times before
+  returning an error, but we've received user reports that this limit can be
+  hit on systems with very heavy load. In some synthetic benchmarks (testing
+  the worst-case of an attacker doing renames in a tight loop on every core of
+  a 16-core machine) we managed to get a ~3% failure rate in runc. We have
+  improved this situation in two ways:
+
+  * We have now increased this limit to 128, which should be good enough for
+    most use-cases without becoming a denial-of-service vector (the number of
+    syscalls called by the `O_PATH` resolver in a typical case is within the
+    same ballpark). The same benchmarks show a failure rate of ~0.12% which
+    (while not zero) is probably sufficient for most users.
+
+  * In addition, we now return a `unix.EAGAIN` error that is bubbled up and can
+    be detected by callers. This means that callers with stricter requirements
+    to avoid spurious errors can choose to do their own infinite `EAGAIN` retry
+    loop (though we would strongly recommend users use time-based deadlines in
+    such retry loops to avoid potentially unbounded denials-of-service).
+
+## [0.5.0] - 2025-09-26 ##
+
+> Let the past die. Kill it if you have to.
+
+> **NOTE**: With this release, some parts of
+> `github.com/cyphar/filepath-securejoin` are now licensed under the Mozilla
+> Public License (version 2). Please see [COPYING.md][] as well as the the
+> license header in each file for more details.
+
+[COPYING.md]: ./COPYING.md
+
+### Breaking ###
+- The new API introduced in the [0.3.0][] release has been moved to a new
+  subpackage called `pathrs-lite`. This was primarily done to better indicate
+  the split between the new and old APIs, as well as indicate to users the
+  purpose of this subpackage (it is a less complete version of [libpathrs][]).
+
+  We have added some wrappers to the top-level package to ease the transition,
+  but those are deprecated and will be removed in the next minor release of
+  filepath-securejoin. Users should update their import paths.
+
+  This new subpackage has also been relicensed under the Mozilla Public License
+  (version 2), please see [COPYING.md][] for more details.
+
+### Added ###
+- Most of the key bits the safe `procfs` API have now been exported and are
+  available in `github.com/cyphar/filepath-securejoin/pathrs-lite/procfs`. At
+  the moment this primarily consists of a new `procfs.Handle` API:
+
+   * `OpenProcRoot` returns a new handle to `/proc`, endeavouring to make it
+     safe if possible (`subset=pid` to protect against mistaken write attacks
+     and leaks, as well as using `fsopen(2)` to avoid racing mount attacks).
+
+     `OpenUnsafeProcRoot` returns a handle without attempting to create one
+     with `subset=pid`, which makes it more dangerous to leak. Most users
+     should use `OpenProcRoot` (even if you need to use `ProcRoot` as the base
+     of an operation, as filepath-securejoin will internally open a handle when
+     necessary).
+
+   * The `(*procfs.Handle).Open*` family of methods lets you get a safe
+     `O_PATH` handle to subpaths within `/proc` for certain subpaths.
+
+     For `OpenThreadSelf`, the returned `ProcThreadSelfCloser` needs to be
+     called after you completely finish using the handle (this is necessary
+     because Go is multi-threaded and `ProcThreadSelf` references
+     `/proc/thread-self` which may disappear if we do not
+     `runtime.LockOSThread` -- `ProcThreadSelfCloser` is currently equivalent
+     to `runtime.UnlockOSThread`).
+
+     Note that you cannot open any `procfs` symlinks (most notably magic-links)
+     using this API. At the moment, filepath-securejoin does not support this
+     feature (but [libpathrs][] does).
+
+   * `ProcSelfFdReadlink` lets you get the in-kernel path representation of a
+     file descriptor (think `readlink("/proc/self/fd/...")`), except that we
+     verify that there aren't any tricky overmounts that could fool the
+     process.
+
+     Please be aware that the returned string is simply a snapshot at that
+     particular moment, and an attacker could move the file being pointed to.
+     In addition, complex namespace configurations could result in non-sensical
+     or confusing paths to be returned. The value received from this function
+     should only be used as secondary verification of some security property,
+     not as proof that a particular handle has a particular path.
+
+  The procfs handle used internally by the API is the same as the rest of
+  `filepath-securejoin` (for privileged programs this is usually a private
+  in-process `procfs` instance created with `fsopen(2)`).
+
+  As before, this is intended as a stop-gap before users migrate to
+  [libpathrs][], which provides a far more extensive safe `procfs` API and is
+  generally more robust.
+
+- Previously, the hardened procfs implementation (used internally within
+  `Reopen` and `Open(at)InRoot`) only protected against overmount attacks on
+  systems with `openat2(2)` (Linux 5.6) or systems with `fsopen(2)` or
+  `open_tree(2)` (Linux 5.2) and programs with privileges to use them (with
+  some caveats about locked mounts that probably affect very few users). For
+  other users, an attacker with the ability to create malicious mounts (on most
+  systems, a sysadmin) could trick you into operating on files you didn't
+  expect. This attack only really makes sense in the context of container
+  runtime implementations.
+
+  This was considered a reasonable trade-off, as the long-term intention was to
+  get all users to just switch to [libpathrs][] if they wanted to use the safe
+  `procfs` API (which had more extensive protections, and is what these new
+  protections in `filepath-securejoin` are based on). However, as the API
+  is now being exported it seems unwise to advertise the API as "safe" if we do
+  not protect against known attacks.
+
+  The procfs API is now more protected against attackers on systems lacking the
+  aforementioned protections. However, the most comprehensive of these
+  protections effectively rely on [`statx(STATX_MNT_ID)`][statx.2] (Linux 5.8).
+  On older kernel versions, there is no effective protection (there is some
+  minimal protection against non-`procfs` filesystem components but a
+  sufficiently clever attacker can work around those). In addition,
+  `STATX_MNT_ID` is vulnerable to mount ID reuse attacks by sufficiently
+  motivated and privileged attackers -- this problem is mitigated with
+  `STATX_MNT_ID_UNIQUE` (Linux 6.8) but that raises the minimum kernel version
+  for more protection.
+
+  The fact that these protections are quite limited despite needing a fair bit
+  of extra code to handle was one of the primary reasons we did not initially
+  implement this in `filepath-securejoin` ([libpathrs][] supports all of this,
+  of course).
+
+### Fixed ###
+- RHEL 8 kernels have backports of `fsopen(2)` but in some testing we've found
+  that it has very bad (and very difficult to debug) performance issues, and so
+  we will explicitly refuse to use `fsopen(2)` if the running kernel version is
+  pre-5.2 and will instead fallback to `open("/proc")`.
+
+[CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
+[libpathrs]: https://github.com/cyphar/libpathrs
+[statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html
+
+## [0.4.1] - 2025-01-28 ##
+
+### Fixed ###
+- The restrictions added for `root` paths passed to `SecureJoin` in 0.4.0 was
+  found to be too strict and caused some regressions when folks tried to
+  update, so this restriction has been relaxed to only return an error if the
+  path contains a `..` component. We still recommend users use `filepath.Clean`
+  (and even `filepath.EvalSymlinks`) on the `root` path they are using, but at
+  least you will no longer be punished for "trivial" unclean paths.
+
+## [0.4.0] - 2025-01-13 ##
+
+### Breaking ####
+- `SecureJoin(VFS)` will now return an error if the provided `root` is not a
+  `filepath.Clean`'d path.
+
+  While it is ultimately the responsibility of the caller to ensure the root is
+  a safe path to use, passing a path like `/symlink/..` as a root would result
+  in the `SecureJoin`'d path being placed in `/` even though `/symlink/..`
+  might be a different directory, and so we should more strongly discourage
+  such usage.
+
+  All major users of `securejoin.SecureJoin` already ensure that the paths they
+  provide are safe (and this is ultimately a question of user error), but
+  removing this foot-gun is probably a good idea. Of course, this is
+  necessarily a breaking API change (though we expect no real users to be
+  affected by it).
+
+  Thanks to [Erik Sjölund](https://github.com/eriksjolund), who initially
+  reported this issue as a possible security issue.
+
+- `MkdirAll` and `MkdirHandle` now take an `os.FileMode`-style mode argument
+  instead of a raw `unix.S_*`-style mode argument, which may cause compile-time
+  type errors depending on how you use `filepath-securejoin`. For most users,
+  there will be no change in behaviour aside from the type change (as the
+  bottom `0o777` bits are the same in both formats, and most users are probably
+  only using those bits).
+
+  However, if you were using `unix.S_ISVTX` to set the sticky bit with
+  `MkdirAll(Handle)` you will need to switch to `os.ModeSticky` otherwise you
+  will get a runtime error with this update. In addition, the error message you
+  will get from passing `unix.S_ISUID` and `unix.S_ISGID` will be different as
+  they are treated as invalid bits now (note that previously passing said bits
+  was also an error).
+
+## [0.3.6] - 2024-12-17 ##
+
+### Compatibility ###
+- The minimum Go version requirement for `filepath-securejoin` is now Go 1.18
+  (we use generics internally).
+
+  For reference, `filepath-securejoin@v0.3.0` somewhat-arbitrarily bumped the
+  Go version requirement to 1.21.
+
+  While we did make some use of Go 1.21 stdlib features (and in principle Go
+  versions <= 1.21 are no longer even supported by upstream anymore), some
+  downstreams have complained that the version bump has meant that they have to
+  do workarounds when backporting fixes that use the new `filepath-securejoin`
+  API onto old branches. This is not an ideal situation, but since using this
+  library is probably better for most downstreams than a hand-rolled
+  workaround, we now have compatibility shims that allow us to build on older
+  Go versions.
+- Lower minimum version requirement for `golang.org/x/sys` to `v0.18.0` (we
+  need the wrappers for `fsconfig(2)`), which should also make backporting
+  patches to older branches easier.
+
+## [0.3.5] - 2024-12-06 ##
+
+### Fixed ###
+- `MkdirAll` will now no longer return an `EEXIST` error if two racing
+  processes are creating the same directory. We will still verify that the path
+  is a directory, but this will avoid spurious errors when multiple threads or
+  programs are trying to `MkdirAll` the same path. opencontainers/runc#4543
+
+## [0.3.4] - 2024-10-09 ##
+
+### Fixed ###
+- Previously, some testing mocks we had resulted in us doing `import "testing"`
+  in non-`_test.go` code, which made some downstreams like Kubernetes unhappy.
+  This has been fixed. (#32)
+
+## [0.3.3] - 2024-09-30 ##
+
+### Fixed ###
+- The mode and owner verification logic in `MkdirAll` has been removed. This
+  was originally intended to protect against some theoretical attacks but upon
+  further consideration these protections don't actually buy us anything and
+  they were causing spurious errors with more complicated filesystem setups.
+- The "is the created directory empty" logic in `MkdirAll` has also been
+  removed. This was not causing us issues yet, but some pseudofilesystems (such
+  as `cgroup`) create non-empty directories and so this logic would've been
+  wrong for such cases.
+
+## [0.3.2] - 2024-09-13 ##
+
+### Changed ###
+- Passing the `S_ISUID` or `S_ISGID` modes to `MkdirAllInRoot` will now return
+  an explicit error saying that those bits are ignored by `mkdirat(2)`. In the
+  past a different error was returned, but since the silent ignoring behaviour
+  is codified in the man pages a more explicit error seems apt. While silently
+  ignoring these bits would be the most compatible option, it could lead to
+  users thinking their code sets these bits when it doesn't. Programs that need
+  to deal with compatibility can mask the bits themselves. (#23, #25)
+
+### Fixed ###
+- If a directory has `S_ISGID` set, then all child directories will have
+  `S_ISGID` set when created and a different gid will be used for any inode
+  created under the directory. Previously, the "expected owner and mode"
+  validation in `securejoin.MkdirAll` did not correctly handle this. We now
+  correctly handle this case. (#24, #25)
+
+## [0.3.1] - 2024-07-23 ##
+
+### Changed ###
+- By allowing `Open(at)InRoot` to opt-out of the extra work done by `MkdirAll`
+  to do the necessary "partial lookups", `Open(at)InRoot` now does less work
+  for both implementations (resulting in a many-fold decrease in the number of
+  operations for `openat2`, and a modest improvement for non-`openat2`) and is
+  far more guaranteed to match the correct `openat2(RESOLVE_IN_ROOT)`
+  behaviour.
+- We now use `readlinkat(fd, "")` where possible. For `Open(at)InRoot` this
+  effectively just means that we no longer risk getting spurious errors during
+  rename races. However, for our hardened procfs handler, this in theory should
+  prevent mount attacks from tricking us when doing magic-link readlinks (even
+  when using the unsafe host `/proc` handle). Unfortunately `Reopen` is still
+  potentially vulnerable to those kinds of somewhat-esoteric attacks.
+
+  Technically this [will only work on post-2.6.39 kernels][linux-readlinkat-emptypath]
+  but it seems incredibly unlikely anyone is using `filepath-securejoin` on a
+  pre-2011 kernel.
+
+### Fixed ###
+- Several improvements were made to the errors returned by `Open(at)InRoot` and
+  `MkdirAll` when dealing with invalid paths under the emulated (ie.
+  non-`openat2`) implementation. Previously, some paths would return the wrong
+  error (`ENOENT` when the last component was a non-directory), and other paths
+  would be returned as though they were acceptable (trailing-slash components
+  after a non-directory would be ignored by `Open(at)InRoot`).
+
+  These changes were done to match `openat2`'s behaviour and purely is a
+  consistency fix (most users are going to be using `openat2` anyway).
+
+[linux-readlinkat-emptypath]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=65cfc6722361570bfe255698d9cd4dccaf47570d
+
+## [0.3.0] - 2024-07-11 ##
+
+### Added ###
+- A new set of `*os.File`-based APIs have been added. These are adapted from
+  [libpathrs][] and we strongly suggest using them if possible (as they provide
+  far more protection against attacks than `SecureJoin`):
+
+   - `Open(at)InRoot` resolves a path inside a rootfs and returns an `*os.File`
+     handle to the path. Note that the handle returned is an `O_PATH` handle,
+     which cannot be used for reading or writing (as well as some other
+     operations -- [see open(2) for more details][open.2])
+
+   - `Reopen` takes an `O_PATH` file handle and safely re-opens it to upgrade
+     it to a regular handle. This can also be used with non-`O_PATH` handles,
+     but `O_PATH` is the most obvious application.
+
+   - `MkdirAll` is an implementation of `os.MkdirAll` that is safe to use to
+     create a directory tree within a rootfs.
+
+  As these are new APIs, they may change in the future. However, they should be
+  safe to start migrating to as we have extensive tests ensuring they behave
+  correctly and are safe against various races and other attacks.
+
+[libpathrs]: https://github.com/cyphar/libpathrs
+[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html
+
+## [0.2.5] - 2024-05-03 ##
+
+### Changed ###
+- Some minor changes were made to how lexical components (like `..` and `.`)
+  are handled during path generation in `SecureJoin`. There is no behaviour
+  change as a result of this fix (the resulting paths are the same).
+
+### Fixed ###
+- The error returned when we hit a symlink loop now references the correct
+  path. (#10)
+
+## [0.2.4] - 2023-09-06 ##
+
+### Security ###
+- This release fixes a potential security issue in filepath-securejoin when
+  used on Windows ([GHSA-6xv5-86q9-7xr8][], which could be used to generate
+  paths outside of the provided rootfs in certain cases), as well as improving
+  the overall behaviour of filepath-securejoin when dealing with Windows paths
+  that contain volume names. Thanks to Paulo Gomes for discovering and fixing
+  these issues.
+
+### Fixed ###
+- Switch to GitHub Actions for CI so we can test on Windows as well as Linux
+  and MacOS.
+
+[GHSA-6xv5-86q9-7xr8]: https://github.com/advisories/GHSA-6xv5-86q9-7xr8
+
+## [0.2.3] - 2021-06-04 ##
+
+### Changed ###
+- Switch to Go 1.13-style `%w` error wrapping, letting us drop the dependency
+  on `github.com/pkg/errors`.
+
+## [0.2.2] - 2018-09-05 ##
+
+### Changed ###
+- Use `syscall.ELOOP` as the base error for symlink loops, rather than our own
+  (internal) error. This allows callers to more easily use `errors.Is` to check
+  for this case.
+
+## [0.2.1] - 2018-09-05 ##
+
+### Fixed ###
+- Use our own `IsNotExist` implementation, which lets us handle `ENOTDIR`
+  properly within `SecureJoin`.
+
+## [0.2.0] - 2017-07-19 ##
+
+We now have 100% test coverage!
+
+### Added ###
+- Add a `SecureJoinVFS` API that can be used for mocking (as we do in our new
+  tests) or for implementing custom handling of lookup operations (such as for
+  rootless containers, where work is necessary to access directories with weird
+  modes because we don't have `CAP_DAC_READ_SEARCH` or `CAP_DAC_OVERRIDE`).
+
+## 0.1.0 - 2017-07-19
+
+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 0.5.z]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...release-0.5
+[0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1
+[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0
+[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1
+[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0
+[0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6
+[0.3.5]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.4...v0.3.5
+[0.3.4]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.3...v0.3.4
+[0.3.3]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.2...v0.3.3
+[0.3.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.1...v0.3.2
+[0.3.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.0...v0.3.1
+[0.3.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.5...v0.3.0
+[0.2.5]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.4...v0.2.5
+[0.2.4]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.3...v0.2.4
+[0.2.3]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.2...v0.2.3
+[0.2.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.1...v0.2.2
+[0.2.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.0...v0.2.1
+[0.2.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.1.0...v0.2.0
diff --git a/vendor/github.com/cyphar/filepath-securejoin/COPYING.md b/vendor/github.com/cyphar/filepath-securejoin/COPYING.md
new file mode 100644
index 000000000000..520e822b1849
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/COPYING.md
@@ -0,0 +1,447 @@
+## COPYING ##
+
+`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`
+
+This project is made up of code licensed under different licenses. Which code
+you use will have an impact on whether only one or both licenses apply to your
+usage of this library.
+
+Note that **each file** in this project individually has a code comment at the
+start describing the license of that particular file -- this is the most
+accurate license information of this project; in case there is any conflict
+between this document and the comment at the start of a file, the comment shall
+take precedence. The only purpose of this document is to work around [a known
+technical limitation of pkg.go.dev's license checking tool when dealing with
+non-trivial project licenses][go75067].
+
+[go75067]: https://go.dev/issue/75067
+
+### `BSD-3-Clause` ###
+
+At time of writing, the following files and directories are licensed under the
+BSD-3-Clause license:
+
+ * `doc.go`
+ * `join*.go`
+ * `vfs.go`
+ * `internal/consts/*.go`
+ * `pathrs-lite/internal/gocompat/*.go`
+ * `pathrs-lite/internal/kernelversion/*.go`
+
+The text of the BSD-3-Clause license used by this project is the following (the
+text is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file):
+
+```
+Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
+Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+```
+
+### `MPL-2.0` ###
+
+All other files (unless otherwise marked) are licensed under the Mozilla Public
+License (version 2.0).
+
+The text of the Mozilla Public License (version 2.0) is the following (the text
+is also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file):
+
+```
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
+```
diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD
similarity index 100%
rename from vendor/github.com/cyphar/filepath-securejoin/LICENSE
rename to vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD
diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
new file mode 100644
index 000000000000..d0a1fa1482ee
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/vendor/github.com/cyphar/filepath-securejoin/README.md b/vendor/github.com/cyphar/filepath-securejoin/README.md
index 253956f86576..6673abfc842a 100644
--- a/vendor/github.com/cyphar/filepath-securejoin/README.md
+++ b/vendor/github.com/cyphar/filepath-securejoin/README.md
@@ -1,5 +1,6 @@
 ## `filepath-securejoin` ##
 
+[![Go Documentation](https://pkg.go.dev/badge/github.com/cyphar/filepath-securejoin.svg)](https://pkg.go.dev/github.com/cyphar/filepath-securejoin)
 [![Build Status](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml/badge.svg)](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml)
 
 ### Old API ###
@@ -66,7 +67,8 @@ func SecureJoin(root, unsafePath string) (string, error) {
 [libpathrs]: https://github.com/openSUSE/libpathrs
 [go#20126]: https://github.com/golang/go/issues/20126
 
-### New API ###
+### <a name="new-api" /> New API ###
+[#new-api]: #new-api
 
 While we recommend users switch to [libpathrs][libpathrs] as soon as it has a
 stable release, some methods implemented by libpathrs have been ported to this
@@ -85,7 +87,7 @@ more secure. In particular:
   or avoid being tricked by a `/proc` that is not legitimate. This is done
   using [`openat2`][openat2.2] for all users, and privileged users will also be
   further protected by using [`fsopen`][fsopen.2] and [`open_tree`][open_tree.2]
-  (Linux 4.18 or later).
+  (Linux 5.2 or later).
 
 [openat2.2]: https://www.man7.org/linux/man-pages/man2/openat2.2.html
 [fsopen.2]: https://github.com/brauner/man-pages-md/blob/main/fsopen.md
@@ -164,5 +166,19 @@ after `MkdirAll`).
 
 ### License ###
 
-The license of this project is the same as Go, which is a BSD 3-clause license
-available in the `LICENSE` file.
+`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`
+
+Some of the code in this project is derived from Go, and is licensed under a
+BSD 3-clause license (available in `LICENSE.BSD`). Other files (many of which
+are derived from [libpathrs][libpathrs]) are licensed under the Mozilla Public
+License version 2.0 (available in `LICENSE.MPL-2.0`). If you are using the
+["New API" described above][#new-api], you are probably using code from files
+released under this license.
+
+Every source file in this project has a copyright header describing its
+license. Please check the license headers of each file to see what license
+applies to it.
+
+See [COPYING.md](./COPYING.md) for some more details.
+
+[umoci]: https://github.com/opencontainers/umoci
diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION
index 0d91a54c7d43..4b9fcbec101a 100644
--- a/vendor/github.com/cyphar/filepath-securejoin/VERSION
+++ b/vendor/github.com/cyphar/filepath-securejoin/VERSION
@@ -1 +1 @@
-0.3.0
+0.5.1
diff --git a/vendor/github.com/cyphar/filepath-securejoin/codecov.yml b/vendor/github.com/cyphar/filepath-securejoin/codecov.yml
new file mode 100644
index 000000000000..ff284dbfaf95
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/codecov.yml
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: MPL-2.0
+
+# Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
+# Copyright (C) 2025 SUSE LLC
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+comment:
+  layout: "condensed_header, reach, diff, components, condensed_files, condensed_footer"
+  require_changes: true
+  branches:
+    - main
+
+coverage:
+  range: 60..100
+  status:
+    project:
+      default:
+        target: 85%
+        threshold: 0%
+    patch:
+      default:
+        target: auto
+        informational: true
+
+github_checks:
+  annotations: false
diff --git a/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go b/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
new file mode 100644
index 000000000000..3e427b164099
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
@@ -0,0 +1,48 @@
+// 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 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/doc.go b/vendor/github.com/cyphar/filepath-securejoin/doc.go
new file mode 100644
index 000000000000..1438fc9c09c9
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/doc.go
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
+// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package securejoin implements a set of helpers to make it easier to write Go
+// code that is safe against symlink-related escape attacks. The primary idea
+// is to let you resolve a path within a rootfs directory as if the rootfs was
+// a chroot.
+//
+// securejoin has two APIs, a "legacy" API and a "modern" API.
+//
+// The legacy API is [SecureJoin] and [SecureJoinVFS]. These methods are
+// **not** safe against race conditions where an attacker changes the
+// filesystem after (or during) the [SecureJoin] operation.
+//
+// The new API is available in the [pathrs-lite] subpackage, and provide
+// protections against racing attackers as well as several other key
+// protections against attacks often seen by container runtimes. As the name
+// suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of
+// [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and
+// [procfs.Handle] -- other APIs are not planned to be ported. The long-term
+// goal is for users to migrate to [libpathrs] which is more fully-featured.
+//
+// securejoin has been used by several container runtimes (Docker, runc,
+// Kubernetes, etc) for quite a few years as a de-facto standard for operating
+// on container filesystem paths "safely". However, most users still use the
+// legacy API which is unsafe against various attacks (there is a fairly long
+// history of CVEs in dependent as a result). Users should switch to the modern
+// API as soon as possible (or even better, switch to libpathrs).
+//
+// This project was initially intended to be included in the Go standard
+// library, but it was rejected (see https://go.dev/issue/20126). Much later,
+// [os.Root] was added to the Go stdlib that shares some of the goals of
+// filepath-securejoin. However, its design is intended to work like
+// openat2(RESOLVE_BENEATH) which does not fit the usecase of container
+// runtimes and most system tools.
+//
+// [pathrs-lite]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite
+// [libpathrs]: https://github.com/openSUSE/libpathrs
+// [OpenInRoot]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#OpenInRoot
+// [MkdirAll]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#MkdirAll
+// [procfs.Handle]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle
+// [os.Root]: https:///pkg.go.dev/os#Root
+package securejoin
diff --git a/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go b/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
new file mode 100644
index 000000000000..c69c4da91eea
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
+// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package consts contains the definitions of internal constants used
+// throughout filepath-securejoin.
+package consts
+
+// MaxSymlinkLimit is the maximum number of symlinks that can be encountered
+// during a single lookup before returning -ELOOP. At time of writing, Linux
+// has an internal limit of 40.
+const MaxSymlinkLimit = 255
diff --git a/vendor/github.com/cyphar/filepath-securejoin/join.go b/vendor/github.com/cyphar/filepath-securejoin/join.go
index bd86a48b0cc1..199c1d839247 100644
--- a/vendor/github.com/cyphar/filepath-securejoin/join.go
+++ b/vendor/github.com/cyphar/filepath-securejoin/join.go
@@ -1,13 +1,10 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
 // Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
-// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
+// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package securejoin is an implementation of the hopefully-soon-to-be-included
-// SecureJoin helper that is meant to be part of the "path/filepath" package.
-// The purpose of this project is to provide a PoC implementation to make the
-// SecureJoin proposal (https://github.com/golang/go/issues/20126) more
-// tangible.
 package securejoin
 
 import (
@@ -16,33 +13,59 @@ import (
 	"path/filepath"
 	"strings"
 	"syscall"
-)
 
-const maxSymlinkLimit = 255
+	"github.com/cyphar/filepath-securejoin/internal/consts"
+)
 
 // IsNotExist tells you if err is an error that implies that either the path
 // accessed does not exist (or path components don't exist). This is
-// effectively a more broad version of os.IsNotExist.
+// effectively a more broad version of [os.IsNotExist].
 func IsNotExist(err error) bool {
 	// Check that it's not actually an ENOTDIR, which in some cases is a more
 	// convoluted case of ENOENT (usually involving weird paths).
 	return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT)
 }
 
-// SecureJoinVFS joins the two given path components (similar to Join) except
-// that the returned path is guaranteed to be scoped inside the provided root
-// path (when evaluated). Any symbolic links in the path are evaluated with the
-// given root treated as the root of the filesystem, similar to a chroot. The
-// filesystem state is evaluated through the given VFS interface (if nil, the
-// standard os.* family of functions are used).
+// errUnsafeRoot is returned if the user provides SecureJoinVFS with a path
+// that contains ".." components.
+var errUnsafeRoot = errors.New("root path provided to SecureJoin contains '..' components")
+
+// stripVolume just gets rid of the Windows volume included in a path. Based on
+// some godbolt tests, the Go compiler is smart enough to make this a no-op on
+// Linux.
+func stripVolume(path string) string {
+	return path[len(filepath.VolumeName(path)):]
+}
+
+// hasDotDot checks if the path contains ".." components in a platform-agnostic
+// way.
+func hasDotDot(path string) bool {
+	// If we are on Windows, strip any volume letters. It turns out that
+	// C:..\foo may (or may not) be a valid pathname and we need to handle that
+	// leading "..".
+	path = stripVolume(path)
+	// Look for "/../" in the path, but we need to handle leading and trailing
+	// ".."s by adding separators. Doing this with filepath.Separator is ugly
+	// so just convert to Unix-style "/" first.
+	path = filepath.ToSlash(path)
+	return strings.Contains("/"+path+"/", "/../")
+}
+
+// SecureJoinVFS joins the two given path components (similar to
+// [filepath.Join]) except that the returned path is guaranteed to be scoped
+// inside the provided root path (when evaluated). Any symbolic links in the
+// path are evaluated with the given root treated as the root of the
+// filesystem, similar to a chroot. The filesystem state is evaluated through
+// the given [VFS] interface (if nil, the standard [os].* family of functions
+// are used).
 //
 // Note that the guarantees provided by this function only apply if the path
 // components in the returned string are not modified (in other words are not
 // replaced with symlinks on the filesystem) after this function has returned.
-// Such a symlink race is necessarily out-of-scope of SecureJoin.
+// Such a symlink race is necessarily out-of-scope of SecureJoinVFS.
 //
 // NOTE: Due to the above limitation, Linux users are strongly encouraged to
-// use OpenInRoot instead, which does safely protect against these kinds of
+// use [OpenInRoot] instead, which does safely protect against these kinds of
 // attacks. There is no way to solve this problem with SecureJoinVFS because
 // the API is fundamentally wrong (you cannot return a "safe" path string and
 // guarantee it won't be modified afterwards).
@@ -51,7 +74,22 @@ func IsNotExist(err error) bool {
 // provided via direct input or when evaluating symlinks. Therefore:
 //
 // "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt"
-func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
+//
+// If the provided root is not [filepath.Clean] then an error will be returned,
+// as such root paths are bordering on somewhat unsafe and using such paths is
+// not best practice. We also strongly suggest that any root path is first
+// fully resolved using [filepath.EvalSymlinks] or otherwise constructed to
+// avoid containing symlink components. Of course, the root also *must not* be
+// attacker-controlled.
+func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:revive // name is part of public API
+	// The root path must not contain ".." components, otherwise when we join
+	// the subpath we will end up with a weird path. We could work around this
+	// in other ways but users shouldn't be giving us non-lexical root paths in
+	// the first place.
+	if hasDotDot(root) {
+		return "", errUnsafeRoot
+	}
+
 	// Use the os.* VFS implementation if none was specified.
 	if vfs == nil {
 		vfs = osVFS{}
@@ -64,9 +102,10 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
 		linksWalked   int
 	)
 	for remainingPath != "" {
-		if v := filepath.VolumeName(remainingPath); v != "" {
-			remainingPath = remainingPath[len(v):]
-		}
+		// On Windows, if we managed to end up at a path referencing a volume,
+		// drop the volume to make sure we don't end up with broken paths or
+		// escaping the root volume.
+		remainingPath = stripVolume(remainingPath)
 
 		// Get the next path component.
 		var part string
@@ -102,7 +141,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
 		// It's a symlink, so get its contents and expand it by prepending it
 		// to the yet-unparsed path.
 		linksWalked++
-		if linksWalked > maxSymlinkLimit {
+		if linksWalked > consts.MaxSymlinkLimit {
 			return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP}
 		}
 
@@ -123,8 +162,8 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
 	return filepath.Join(root, finalPath), nil
 }
 
-// SecureJoin is a wrapper around SecureJoinVFS that just uses the os.* library
-// of functions as the VFS. If in doubt, use this function over SecureJoinVFS.
+// SecureJoin is a wrapper around [SecureJoinVFS] that just uses the [os].* library
+// of functions as the [VFS]. If in doubt, use this function over [SecureJoinVFS].
 func SecureJoin(root, unsafePath string) (string, error) {
 	return SecureJoinVFS(root, unsafePath, nil)
 }
diff --git a/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go b/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go
deleted file mode 100644
index 05e0bde9af4b..000000000000
--- a/vendor/github.com/cyphar/filepath-securejoin/mkdir_linux.go
+++ /dev/null
@@ -1,228 +0,0 @@
-//go:build linux
-
-// Copyright (C) 2024 SUSE LLC. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package securejoin
-
-import (
-	"errors"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"slices"
-	"strings"
-
-	"golang.org/x/sys/unix"
-)
-
-var (
-	errInvalidMode    = errors.New("invalid permission mode")
-	errPossibleAttack = errors.New("possible attack detected")
-)
-
-// 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 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.
-func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err error) {
-	// Make sure there are no os.FileMode bits set.
-	if mode&^0o7777 != 0 {
-		return nil, fmt.Errorf("%w for mkdir 0o%.3o", errInvalidMode, mode)
-	}
-
-	// Try to open as much of the path as possible.
-	currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath)
-	if err != nil {
-		return nil, fmt.Errorf("find existing subpath of %q: %w", unsafePath, err)
-	}
-	defer func() {
-		if Err != nil {
-			_ = currentDir.Close()
-		}
-	}()
-
-	// 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 := 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 {
-		currentDir = reopenDir
-	}
-
-	remainingParts := strings.Split(remainingPath, string(filepath.Separator))
-	if slices.Contains(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)
-	}
-
-	// Make sure the mode doesn't have any type bits.
-	mode &^= unix.S_IFMT
-	// What properties do we expect any newly created directories to have?
-	var (
-		// While umask(2) is a per-thread property, and thus this value could
-		// vary between threads, a functioning Go program would LockOSThread
-		// threads with different umasks and so we don't need to LockOSThread
-		// for this entire mkdirat loop (if we are in the locked thread with a
-		// different umask, we are already locked and there's nothing for us to
-		// do -- and if not then it doesn't matter which thread we run on and
-		// there's nothing for us to do).
-		expectedMode = uint32(unix.S_IFDIR | (mode &^ getUmask()))
-
-		// We would want to get the fs[ug]id here, but we can't access those
-		// from userspace. In practice, nobody uses setfs[ug]id() anymore, so
-		// just use the effective [ug]id (which is equivalent to the fs[ug]id
-		// for programs that don't use setfs[ug]id).
-		expectedUid = uint32(unix.Geteuid())
-		expectedGid = uint32(unix.Getegid())
-	)
-
-	// 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 finaly component without worrying about symlink-exchange
-		// attacks.
-		if err := unix.Mkdirat(int(currentDir.Fd()), part, uint32(mode)); err != nil {
-			err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err}
-			// Make the error a bit nicer if the directory is dead.
-			if err2 := isDeadInode(currentDir); err2 != nil {
-				err = fmt.Errorf("%w (%w)", err, err2)
-			}
-			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 hasOpenat2() {
-			nextDir, err = openat2File(currentDir, part, &unix.OpenHow{
-				Flags:   unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC,
-				Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
-			})
-		} else {
-			nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
-		}
-		if err != nil {
-			return nil, err
-		}
-		_ = currentDir.Close()
-		currentDir = nextDir
-
-		// Make sure that the directory matches what we expect. An attacker
-		// could have swapped the directory between us making it and opening
-		// it. There's no way for us to be sure that the directory is
-		// _precisely_ the same as the directory we created, but if we are in
-		// an empty directory with the same owner and mode as the one we
-		// created then there is nothing the attacker could do with this new
-		// directory that they couldn't do with the old one.
-		if stat, err := fstat(currentDir); err != nil {
-			return nil, fmt.Errorf("check newly created directory: %w", err)
-		} else {
-			if stat.Mode != expectedMode {
-				return nil, fmt.Errorf("%w: newly created directory %q has incorrect mode 0o%.3o (expected 0o%.3o)", errPossibleAttack, currentDir.Name(), stat.Mode, expectedMode)
-			}
-			if stat.Uid != expectedUid || stat.Gid != expectedGid {
-				return nil, fmt.Errorf("%w: newly created directory %q has incorrect owner %d:%d (expected %d:%d)", errPossibleAttack, currentDir.Name(), stat.Uid, stat.Gid, expectedUid, expectedGid)
-			}
-			// Check that the directory is empty. We only need to check for
-			// a single entry, and we should get EOF if the directory is
-			// empty.
-			_, err := currentDir.Readdirnames(1)
-			if !errors.Is(err, io.EOF) {
-				if err == nil {
-					err = fmt.Errorf("%w: newly created directory %q is non-empty", errPossibleAttack, currentDir.Name())
-				}
-				return nil, fmt.Errorf("check if newly created directory %q is empty: %w", currentDir.Name(), err)
-			}
-			// Reset the offset.
-			_, _ = currentDir.Seek(0, unix.SEEK_SET)
-		}
-	}
-	return currentDir, nil
-}
-
-// MkdirAll is a race-safe alternative to the Go stdlib's 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 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.
-//
-// NOTE: The mode argument must be set the unix mode bits (unix.S_I...), not
-// the Go generic mode bits (os.Mode...).
-func MkdirAll(root, unsafePath string, mode int) error {
-	rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
-	if err != nil {
-		return err
-	}
-	defer rootDir.Close()
-
-	f, err := MkdirAllHandle(rootDir, unsafePath, mode)
-	if err != nil {
-		return err
-	}
-	_ = f.Close()
-	return nil
-}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
deleted file mode 100644
index fc93db8644eb..000000000000
--- a/vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
+++ /dev/null
@@ -1,128 +0,0 @@
-//go:build linux
-
-// Copyright (C) 2024 SUSE LLC. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package securejoin
-
-import (
-	"errors"
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
-	"sync"
-	"testing"
-
-	"golang.org/x/sys/unix"
-)
-
-var (
-	hasOpenat2Bool bool
-	hasOpenat2Once sync.Once
-
-	testingForceHasOpenat2 *bool
-)
-
-func hasOpenat2() bool {
-	if testing.Testing() && testingForceHasOpenat2 != nil {
-		return *testingForceHasOpenat2
-	}
-	hasOpenat2Once.Do(func() {
-		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 {
-			hasOpenat2Bool = true
-			_ = unix.Close(fd)
-		}
-	})
-	return hasOpenat2Bool
-}
-
-func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
-	// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
-	// ".." while a mount or rename occurs anywhere on the system. This could
-	// happen spuriously, or as the result of an attacker trying to mess with
-	// us during lookup.
-	//
-	// In addition, scoped lookups have a "safety check" at the end of
-	// complete_walk which will return -EXDEV if the final path is not in the
-	// root.
-	return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
-		(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
-}
-
-const scopedLookupMaxRetries = 10
-
-func openat2File(dir *os.File, path string, how *unix.OpenHow) (*os.File, error) {
-	fullPath := dir.Name() + "/" + path
-	// Make sure we always set O_CLOEXEC.
-	how.Flags |= unix.O_CLOEXEC
-	var tries int
-	for tries < scopedLookupMaxRetries {
-		fd, err := unix.Openat2(int(dir.Fd()), path, how)
-		if err != nil {
-			if scopedLookupShouldRetry(how, err) {
-				// We retry a couple of times to avoid the spurious errors, and
-				// if we are being attacked then returning -EAGAIN is the best
-				// we can do.
-				tries++
-				continue
-			}
-			return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
-		}
-		// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong.
-		// NOTE: The procRoot code MUST NOT use RESOLVE_IN_ROOT, otherwise
-		//       you'll get infinite recursion here.
-		if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT {
-			if actualPath, err := rawProcSelfFdReadlink(fd); err == nil {
-				fullPath = actualPath
-			}
-		}
-		return os.NewFile(uintptr(fd), fullPath), nil
-	}
-	return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: errPossibleAttack}
-}
-
-// partialLookupOpenat2 is an alternative implementation of
-// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a
-// handle to the deepest existing child of the requested path within the root.
-func partialLookupOpenat2(root *os.File, unsafePath string) (*os.File, string, error) {
-	// TODO: Implement this as a git-bisect-like binary search.
-
-	unsafePath = filepath.ToSlash(unsafePath) // noop
-	endIdx := len(unsafePath)
-	for endIdx > 0 {
-		subpath := unsafePath[:endIdx]
-
-		handle, err := openat2File(root, subpath, &unix.OpenHow{
-			Flags:   unix.O_PATH | unix.O_CLOEXEC,
-			Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
-		})
-		if err == nil {
-			// Jump over the slash if we have a non-"" remainingPath.
-			if endIdx < len(unsafePath) {
-				endIdx += 1
-			}
-			// We found a subpath!
-			return handle, unsafePath[endIdx:], nil
-		}
-		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, '/')
-			continue
-		}
-		return nil, "", fmt.Errorf("open subpath: %w", err)
-	}
-	// If we couldn't open anything, the whole subpath is missing. Return a
-	// copy of the root fd so that the caller doesn't close this one by
-	// accident.
-	rootClone, err := dupFile(root)
-	if err != nil {
-		return nil, "", err
-	}
-	return rootClone, unsafePath, nil
-}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go b/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
deleted file mode 100644
index 949fb5f2d82d..000000000000
--- a/vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
+++ /dev/null
@@ -1,59 +0,0 @@
-//go:build linux
-
-// Copyright (C) 2024 SUSE LLC. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package securejoin
-
-import (
-	"os"
-	"path/filepath"
-
-	"golang.org/x/sys/unix"
-)
-
-func dupFile(f *os.File) (*os.File, error) {
-	fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0)
-	if err != nil {
-		return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
-	}
-	return os.NewFile(uintptr(fd), f.Name()), nil
-}
-
-func openatFile(dir *os.File, path string, flags int, mode int) (*os.File, error) {
-	// Make sure we always set O_CLOEXEC.
-	flags |= unix.O_CLOEXEC
-	fd, err := unix.Openat(int(dir.Fd()), path, flags, uint32(mode))
-	if err != nil {
-		return nil, &os.PathError{Op: "openat", Path: dir.Name() + "/" + path, Err: err}
-	}
-	// All of the paths we use with openatFile(2) are guaranteed to be
-	// lexically safe, so we can use path.Join here.
-	fullPath := filepath.Join(dir.Name(), path)
-	return os.NewFile(uintptr(fd), fullPath), nil
-}
-
-func fstatatFile(dir *os.File, path string, flags int) (unix.Stat_t, error) {
-	var stat unix.Stat_t
-	if err := unix.Fstatat(int(dir.Fd()), path, &stat, flags); err != nil {
-		return stat, &os.PathError{Op: "fstatat", Path: dir.Name() + "/" + path, Err: err}
-	}
-	return stat, nil
-}
-
-func readlinkatFile(dir *os.File, path string) (string, error) {
-	size := 4096
-	for {
-		linkBuf := make([]byte, size)
-		n, err := unix.Readlinkat(int(dir.Fd()), path, linkBuf)
-		if err != nil {
-			return "", &os.PathError{Op: "readlinkat", Path: dir.Name() + "/" + path, Err: err}
-		}
-		if n != size {
-			return string(linkBuf[:n]), nil
-		}
-		// Possible truncation, resize the buffer.
-		size *= 2
-	}
-}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
new file mode 100644
index 000000000000..1be727e75b3a
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
@@ -0,0 +1,33 @@
+## `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 <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/.
+```
+
+[COPYING.md]: ../COPYING.md
+[LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
new file mode 100644
index 000000000000..d3d745175009
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
@@ -0,0 +1,14 @@
+// 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 (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
new file mode 100644
index 000000000000..595dfbf1acf6
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: MPL-2.0
+
+// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
+// Copyright (C) 2025 SUSE LLC
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// Package assert provides some basic assertion helpers for Go.
+package assert
+
+import (
+	"fmt"
+)
+
+// Assert panics if the predicate is false with the provided argument.
+func Assert(predicate bool, msg any) {
+	if !predicate {
+		panic(msg)
+	}
+}
+
+// Assertf panics if the predicate is false and formats the message using the
+// same formatting as [fmt.Printf].
+//
+// [fmt.Printf]: https://pkg.go.dev/fmt#Printf
+func Assertf(predicate bool, fmtMsg string, args ...any) {
+	Assert(predicate, fmt.Sprintf(fmtMsg, args...))
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go
new file mode 100644
index 000000000000..d0b200f4f9a1
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 internal contains unexported common code for filepath-securejoin.
+package internal
+
+import (
+	"errors"
+
+	"golang.org/x/sys/unix"
+)
+
+type xdevErrorish struct {
+	description string
+}
+
+func (err xdevErrorish) Error() string        { return err.description }
+func (err xdevErrorish) Is(target error) bool { return target == unix.EXDEV }
+
+var (
+	// ErrPossibleAttack indicates that some attack was detected.
+	ErrPossibleAttack error = xdevErrorish{"possible attack detected"}
+
+	// ErrPossibleBreakout indicates that during an operation we ended up in a
+	// state that could be a breakout but we detected it.
+	ErrPossibleBreakout error = xdevErrorish{"possible breakout detected"}
+
+	// ErrInvalidDirectory indicates an unlinked directory.
+	ErrInvalidDirectory = errors.New("wandered into deleted directory")
+
+	// ErrDeletedInode indicates an unlinked file (non-directory).
+	ErrDeletedInode = errors.New("cannot verify path of deleted inode")
+)
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
new file mode 100644
index 000000000000..091054913046
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 fd
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+
+	"golang.org/x/sys/unix"
+
+	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
+)
+
+// prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using
+// the dir.Fd(). We use -EBADF because in filepath-securejoin we generally
+// don't want to allow relative-to-cwd paths. The returned path is an
+// *informational* string that describes a reasonable pathname for the given
+// *at(2) arguments. You must not use the full path for any actual filesystem
+// operations.
+func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) {
+	dirFd, dirPath := -int(unix.EBADF), "."
+	if dir != nil {
+		dirFd, dirPath = int(dir.Fd()), dir.Name()
+	}
+	if !filepath.IsAbs(path) {
+		// only prepend the dirfd path for relative paths
+		path = dirPath + "/" + path
+	}
+	// NOTE: If path is "." or "", the returned path won't be filepath.Clean,
+	// but that's okay since this path is either used for errors (in which case
+	// a trailing "/" or "/." is important information) or will be
+	// filepath.Clean'd later (in the case of fd.Openat).
+	return dirFd, path
+}
+
+// Openat is an [Fd]-based wrapper around unix.Openat.
+func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func
+	dirFd, fullPath := prepareAt(dir, path)
+	// Make sure we always set O_CLOEXEC.
+	flags |= unix.O_CLOEXEC
+	fd, err := unix.Openat(dirFd, path, flags, uint32(mode))
+	if err != nil {
+		return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
+	}
+	runtime.KeepAlive(dir)
+	// openat is only used with lexically-safe paths so we can use
+	// filepath.Clean here, and also the path itself is not going to be used
+	// for actual path operations.
+	fullPath = filepath.Clean(fullPath)
+	return os.NewFile(uintptr(fd), fullPath), nil
+}
+
+// Fstatat is an [Fd]-based wrapper around unix.Fstatat.
+func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) {
+	dirFd, fullPath := prepareAt(dir, path)
+	var stat unix.Stat_t
+	if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil {
+		return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err}
+	}
+	runtime.KeepAlive(dir)
+	return stat, nil
+}
+
+// Faccessat is an [Fd]-based wrapper around unix.Faccessat.
+func Faccessat(dir Fd, path string, mode uint32, flags int) error {
+	dirFd, fullPath := prepareAt(dir, path)
+	err := unix.Faccessat(dirFd, path, mode, flags)
+	if err != nil {
+		err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err}
+	}
+	runtime.KeepAlive(dir)
+	return err
+}
+
+// Readlinkat is an [Fd]-based wrapper around unix.Readlinkat.
+func Readlinkat(dir Fd, path string) (string, error) {
+	dirFd, fullPath := prepareAt(dir, path)
+	size := 4096
+	for {
+		linkBuf := make([]byte, size)
+		n, err := unix.Readlinkat(dirFd, path, linkBuf)
+		if err != nil {
+			return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err}
+		}
+		runtime.KeepAlive(dir)
+		if n != size {
+			return string(linkBuf[:n]), nil
+		}
+		// Possible truncation, resize the buffer.
+		size *= 2
+	}
+}
+
+const (
+	// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to
+	// avoid bumping the requirement for a single constant we can just define it
+	// ourselves.
+	_STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name
+
+	// We don't care which mount ID we get. The kernel will give us the unique
+	// one if it is supported. If the kernel doesn't support
+	// STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask
+	// will only contain STATX_MNT_ID (if supported).
+	wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
+)
+
+var hasStatxMountID = gocompat.SyncOnceValue(func() bool {
+	var stx unix.Statx_t
+	err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx)
+	return err == nil && stx.Mask&wantStatxMntMask != 0
+})
+
+// GetMountID gets the mount identifier associated with the fd and path
+// combination. It is effectively a wrapper around fetching
+// STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the
+// kernel doesn't support the feature.
+func GetMountID(dir Fd, path string) (uint64, error) {
+	// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
+	if !hasStatxMountID() {
+		return 0, nil
+	}
+
+	dirFd, fullPath := prepareAt(dir, path)
+
+	var stx unix.Statx_t
+	err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx)
+	if stx.Mask&wantStatxMntMask == 0 {
+		// It's not a kernel limitation, for some reason we couldn't get a
+		// mount ID. Assume it's some kind of attack.
+		err = fmt.Errorf("could not get mount id: %w", err)
+	}
+	if err != nil {
+		return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err}
+	}
+	runtime.KeepAlive(dir)
+	return stx.Mnt_id, nil
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
new file mode 100644
index 000000000000..d2206a386f91
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: MPL-2.0
+
+// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
+// Copyright (C) 2025 SUSE LLC
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// Package fd provides a drop-in interface-based replacement of [*os.File] that
+// allows for things like noop-Close wrappers to be used.
+//
+// [*os.File]: https://pkg.go.dev/os#File
+package fd
+
+import (
+	"io"
+	"os"
+)
+
+// Fd is an interface that mirrors most of the API of [*os.File], allowing you
+// to create wrappers that can be used in place of [*os.File].
+//
+// [*os.File]: https://pkg.go.dev/os#File
+type Fd interface {
+	io.Closer
+	Name() string
+	Fd() uintptr
+}
+
+// Compile-time interface checks.
+var (
+	_ Fd = (*os.File)(nil)
+	_ Fd = noClose{}
+)
+
+type noClose struct{ inner Fd }
+
+func (f noClose) Name() string { return f.inner.Name() }
+func (f noClose) Fd() uintptr  { return f.inner.Fd() }
+
+func (f noClose) Close() error { return nil }
+
+// NopCloser returns an [*os.File]-like object where the [Close] method is now
+// a no-op.
+//
+// Note that for [*os.File] and similar objects, the Go garbage collector will
+// still call [Close] on the underlying file unless you use
+// [runtime.SetFinalizer] to disable this behaviour. This is up to the caller
+// to do (if necessary).
+//
+// [*os.File]: https://pkg.go.dev/os#File
+// [Close]: https://pkg.go.dev/io#Closer
+// [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer
+func NopCloser(f Fd) Fd { return noClose{inner: f} }
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
new file mode 100644
index 000000000000..e1ec3c0b8e42
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 fd
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+
+	"golang.org/x/sys/unix"
+
+	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
+)
+
+// DupWithName creates a new file descriptor referencing the same underlying
+// file, but with the provided name instead of fd.Name().
+func DupWithName(fd Fd, name string) (*os.File, error) {
+	fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0)
+	if err != nil {
+		return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
+	}
+	runtime.KeepAlive(fd)
+	return os.NewFile(uintptr(fd2), name), nil
+}
+
+// Dup creates a new file description referencing the same underlying file.
+func Dup(fd Fd) (*os.File, error) {
+	return DupWithName(fd, fd.Name())
+}
+
+// Fstat is an [Fd]-based wrapper around unix.Fstat.
+func Fstat(fd Fd) (unix.Stat_t, error) {
+	var stat unix.Stat_t
+	if err := unix.Fstat(int(fd.Fd()), &stat); err != nil {
+		return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err}
+	}
+	runtime.KeepAlive(fd)
+	return stat, nil
+}
+
+// Fstatfs is an [Fd]-based wrapper around unix.Fstatfs.
+func Fstatfs(fd Fd) (unix.Statfs_t, error) {
+	var statfs unix.Statfs_t
+	if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil {
+		return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err}
+	}
+	runtime.KeepAlive(fd)
+	return statfs, nil
+}
+
+// IsDeadInode detects whether the file has been unlinked from a filesystem and
+// is thus a "dead inode" from the kernel's perspective.
+func IsDeadInode(file Fd) error {
+	// If the nlink of a file drops to 0, there is an attacker deleting
+	// directories during our walk, which could result in weird /proc values.
+	// It's better to error out in this case.
+	stat, err := Fstat(file)
+	if err != nil {
+		return fmt.Errorf("check for dead inode: %w", err)
+	}
+	if stat.Nlink == 0 {
+		err := internal.ErrDeletedInode
+		if stat.Mode&unix.S_IFMT == unix.S_IFDIR {
+			err = internal.ErrInvalidDirectory
+		}
+		return fmt.Errorf("%w %q", err, file.Name())
+	}
+	return nil
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
new file mode 100644
index 000000000000..77549c7a993c
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 fd
+
+import (
+	"os"
+	"runtime"
+
+	"golang.org/x/sys/unix"
+)
+
+// Fsopen is an [Fd]-based wrapper around unix.Fsopen.
+func Fsopen(fsName string, flags int) (*os.File, error) {
+	// Make sure we always set O_CLOEXEC.
+	flags |= unix.FSOPEN_CLOEXEC
+	fd, err := unix.Fsopen(fsName, flags)
+	if err != nil {
+		return nil, os.NewSyscallError("fsopen "+fsName, err)
+	}
+	return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil
+}
+
+// Fsmount is an [Fd]-based wrapper around unix.Fsmount.
+func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) {
+	// Make sure we always set O_CLOEXEC.
+	flags |= unix.FSMOUNT_CLOEXEC
+	fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs)
+	if err != nil {
+		return nil, os.NewSyscallError("fsmount "+ctx.Name(), err)
+	}
+	return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil
+}
+
+// OpenTree is an [Fd]-based wrapper around unix.OpenTree.
+func OpenTree(dir Fd, path string, flags uint) (*os.File, error) {
+	dirFd, fullPath := prepareAt(dir, path)
+	// Make sure we always set O_CLOEXEC.
+	flags |= unix.OPEN_TREE_CLOEXEC
+	fd, err := unix.OpenTree(dirFd, path, flags)
+	if err != nil {
+		return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err}
+	}
+	runtime.KeepAlive(dir)
+	return os.NewFile(uintptr(fd), fullPath), nil
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
new file mode 100644
index 000000000000..3e937fe3c161
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 fd
+
+import (
+	"errors"
+	"os"
+	"runtime"
+
+	"golang.org/x/sys/unix"
+)
+
+func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
+	// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
+	// ".." while a mount or rename occurs anywhere on the system. This could
+	// happen spuriously, or as the result of an attacker trying to mess with
+	// us during lookup.
+	//
+	// In addition, scoped lookups have a "safety check" at the end of
+	// complete_walk which will return -EXDEV if the final path is not in the
+	// root.
+	return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
+		(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
+}
+
+// This is a fairly arbitrary limit we have just to avoid an attacker being
+// able to make us spin in an infinite retry loop -- callers can choose to
+// retry on EAGAIN if they prefer.
+const scopedLookupMaxRetries = 128
+
+// Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry
+// logic in case of EAGAIN errors.
+func Openat2(dir Fd, path string, how *unix.OpenHow) (*os.File, error) {
+	dirFd, fullPath := prepareAt(dir, path)
+	// Make sure we always set O_CLOEXEC.
+	how.Flags |= unix.O_CLOEXEC
+	var tries int
+	for {
+		fd, err := unix.Openat2(dirFd, path, how)
+		if err != nil {
+			if scopedLookupShouldRetry(how, err) && tries < scopedLookupMaxRetries {
+				// We retry a couple of times to avoid the spurious errors, and
+				// if we are being attacked then returning -EAGAIN is the best
+				// we can do.
+				tries++
+				continue
+			}
+			return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
+		}
+		runtime.KeepAlive(dir)
+		return os.NewFile(uintptr(fd), fullPath), nil
+	}
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
new file mode 100644
index 000000000000..5dcb6ae00702
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
@@ -0,0 +1,10 @@
+## gocompat ##
+
+This directory contains backports of stdlib functions from later Go versions so
+the filepath-securejoin can continue to be used by projects that are stuck with
+Go 1.18 support. Note that often filepath-securejoin is added in security
+patches for old releases, so avoiding the need to bump Go compiler requirements
+is a huge plus to downstreams.
+
+The source code is licensed under the same license as the Go stdlib. See the
+source files for the precise license information.
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
new file mode 100644
index 000000000000..4b1803f580ad
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//go:build linux && go1.20
+
+// Copyright (C) 2025 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package gocompat includes compatibility shims (backported from future Go
+// stdlib versions) to permit filepath-securejoin to be used with older Go
+// versions (often filepath-securejoin is added in security patches for old
+// releases, so avoiding the need to bump Go compiler requirements is a huge
+// plus to downstreams).
+package gocompat
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go
new file mode 100644
index 000000000000..4a114bd3da97
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//go:build linux && go1.20
+
+// Copyright (C) 2024 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package 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
new file mode 100644
index 000000000000..3061016a6a69
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go
@@ -0,0 +1,40 @@
+// 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
new file mode 100644
index 000000000000..d4a938186e48
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build linux && go1.21
+
+// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocompat
+
+import (
+	"cmp"
+	"slices"
+	"sync"
+)
+
+// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
+func SlicesDeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S {
+	return slices.DeleteFunc(slice, delFn)
+}
+
+// SlicesContains is equivalent to Go 1.21's slices.Contains.
+func SlicesContains[S ~[]E, E comparable](slice S, val E) bool {
+	return slices.Contains(slice, val)
+}
+
+// SlicesClone is equivalent to Go 1.21's slices.Clone.
+func SlicesClone[S ~[]E, E any](slice S) S {
+	return slices.Clone(slice)
+}
+
+// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
+func SyncOnceValue[T any](f func() T) func() T {
+	return sync.OnceValue(f)
+}
+
+// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
+func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
+	return sync.OnceValues(f)
+}
+
+// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
+type CmpOrdered = cmp.Ordered
+
+// CmpCompare is equivalent to Go 1.21's cmp.Compare.
+func CmpCompare[T CmpOrdered](x, y T) int {
+	return cmp.Compare(x, y)
+}
+
+// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters).
+func Max2[T CmpOrdered](x, y T) T {
+	return max(x, y)
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
new file mode 100644
index 000000000000..0ea6218aa6c8
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build linux && !go1.21
+
+// Copyright (C) 2021, 2022 The Go Authors. All rights reserved.
+// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.BSD file.
+
+package gocompat
+
+import (
+	"sync"
+)
+
+// These are very minimal implementations of functions that appear in Go 1.21's
+// stdlib, included so that we can build on older Go versions. Most are
+// borrowed directly from the stdlib, and a few are modified to be "obviously
+// correct" without needing to copy too many other helpers.
+
+// clearSlice is equivalent to Go 1.21's builtin clear.
+// Copied from the Go 1.24 stdlib implementation.
+func clearSlice[S ~[]E, E any](slice S) {
+	var zero E
+	for i := range slice {
+		slice[i] = zero
+	}
+}
+
+// slicesIndexFunc is equivalent to Go 1.21's slices.IndexFunc.
+// Copied from the Go 1.24 stdlib implementation.
+func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
+	for i := range s {
+		if f(s[i]) {
+			return i
+		}
+	}
+	return -1
+}
+
+// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
+// Copied from the Go 1.24 stdlib implementation.
+func SlicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
+	i := slicesIndexFunc(s, del)
+	if i == -1 {
+		return s
+	}
+	// Don't start copying elements until we find one to delete.
+	for j := i + 1; j < len(s); j++ {
+		if v := s[j]; !del(v) {
+			s[i] = v
+			i++
+		}
+	}
+	clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
+	return s[:i]
+}
+
+// SlicesContains is equivalent to Go 1.21's slices.Contains.
+// Similar to the stdlib slices.Contains, except that we don't have
+// slices.Index so we need to use slices.IndexFunc for this non-Func helper.
+func SlicesContains[S ~[]E, E comparable](s S, v E) bool {
+	return slicesIndexFunc(s, func(e E) bool { return e == v }) >= 0
+}
+
+// SlicesClone is equivalent to Go 1.21's slices.Clone.
+// Copied from the Go 1.24 stdlib implementation.
+func SlicesClone[S ~[]E, E any](s S) S {
+	// Preserve nil in case it matters.
+	if s == nil {
+		return nil
+	}
+	return append(S([]E{}), s...)
+}
+
+// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
+// Copied from the Go 1.25 stdlib implementation.
+func SyncOnceValue[T any](f func() T) func() T {
+	// Use a struct so that there's a single heap allocation.
+	d := struct {
+		f      func() T
+		once   sync.Once
+		valid  bool
+		p      any
+		result T
+	}{
+		f: f,
+	}
+	return func() T {
+		d.once.Do(func() {
+			defer func() {
+				d.f = nil
+				d.p = recover()
+				if !d.valid {
+					panic(d.p)
+				}
+			}()
+			d.result = d.f()
+			d.valid = true
+		})
+		if !d.valid {
+			panic(d.p)
+		}
+		return d.result
+	}
+}
+
+// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
+// Copied from the Go 1.25 stdlib implementation.
+func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
+	// Use a struct so that there's a single heap allocation.
+	d := struct {
+		f     func() (T1, T2)
+		once  sync.Once
+		valid bool
+		p     any
+		r1    T1
+		r2    T2
+	}{
+		f: f,
+	}
+	return func() (T1, T2) {
+		d.once.Do(func() {
+			defer func() {
+				d.f = nil
+				d.p = recover()
+				if !d.valid {
+					panic(d.p)
+				}
+			}()
+			d.r1, d.r2 = d.f()
+			d.valid = true
+		})
+		if !d.valid {
+			panic(d.p)
+		}
+		return d.r1, d.r2
+	}
+}
+
+// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
+// Copied from the Go 1.25 stdlib implementation.
+type CmpOrdered interface {
+	~int | ~int8 | ~int16 | ~int32 | ~int64 |
+		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
+		~float32 | ~float64 |
+		~string
+}
+
+// isNaN reports whether x is a NaN without requiring the math package.
+// This will always return false if T is not floating-point.
+// Copied from the Go 1.25 stdlib implementation.
+func isNaN[T CmpOrdered](x T) bool {
+	return x != x
+}
+
+// CmpCompare is equivalent to Go 1.21's cmp.Compare.
+// Copied from the Go 1.25 stdlib implementation.
+func CmpCompare[T CmpOrdered](x, y T) int {
+	xNaN := isNaN(x)
+	yNaN := isNaN(y)
+	if xNaN {
+		if yNaN {
+			return 0
+		}
+		return -1
+	}
+	if yNaN {
+		return +1
+	}
+	if x < y {
+		return -1
+	}
+	if x > y {
+		return +1
+	}
+	return 0
+}
+
+// Max2 is equivalent to Go 1.21's max builtin for two parameters.
+func Max2[T CmpOrdered](x, y T) T {
+	m := x
+	if y > m {
+		m = y
+	}
+	return m
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
new file mode 100644
index 000000000000..cb6de41861fe
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Copyright (C) 2022 The Go Authors. All rights reserved.
+// Copyright (C) 2025 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.BSD file.
+
+// The parsing logic is very loosely based on the Go stdlib's
+// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks
+// a bit like runc's libcontainer/system/kernelversion.
+//
+// TODO(cyphar): This API has been copied around to a lot of different projects
+// (Docker, containerd, runc, and now filepath-securejoin) -- maybe we should
+// put it in a separate project?
+
+// Package kernelversion provides a simple mechanism for checking whether the
+// running kernel is at least as new as some baseline kernel version. This is
+// often useful when checking for features that would be too complicated to
+// test support for (or in cases where we know that some kernel features in
+// backport-heavy kernels are broken and need to be avoided).
+package kernelversion
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"golang.org/x/sys/unix"
+
+	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
+)
+
+// KernelVersion is a numeric representation of the key numerical elements of a
+// kernel version (for instance, "4.1.2-default-1" would be represented as
+// KernelVersion{4, 1, 2}).
+type KernelVersion []uint64
+
+func (kver KernelVersion) String() string {
+	var str strings.Builder
+	for idx, elem := range kver {
+		if idx != 0 {
+			_, _ = str.WriteRune('.')
+		}
+		_, _ = str.WriteString(strconv.FormatUint(elem, 10))
+	}
+	return str.String()
+}
+
+var errInvalidKernelVersion = errors.New("invalid kernel version")
+
+// parseKernelVersion parses a string and creates a KernelVersion based on it.
+func parseKernelVersion(kverStr string) (KernelVersion, error) {
+	kver := make(KernelVersion, 1, 3)
+	for idx, ch := range kverStr {
+		if '0' <= ch && ch <= '9' {
+			v := &kver[len(kver)-1]
+			*v = (*v * 10) + uint64(ch-'0')
+		} else {
+			if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] {
+				// "." must be preceded by a digit while in version section
+				return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr)
+			}
+			if ch != '.' {
+				break
+			}
+			kver = append(kver, 0)
+		}
+	}
+	if len(kver) < 2 {
+		return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr)
+	}
+	return kver, nil
+}
+
+// getKernelVersion gets the current kernel version.
+var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) {
+	var uts unix.Utsname
+	if err := unix.Uname(&uts); err != nil {
+		return nil, err
+	}
+	// Remove the \x00 from the release.
+	release := uts.Release[:]
+	return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)]))
+})
+
+// GreaterEqualThan returns true if the the host kernel version is greater than
+// or equal to the provided [KernelVersion]. When doing this comparison, any
+// non-numerical suffixes of the host kernel version are ignored.
+//
+// If the number of components provided is not equal to the number of numerical
+// components of the host kernel version, any missing components are treated as
+// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the
+// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the
+// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will
+// return false (because the host version will be treated as "4.0").
+func GreaterEqualThan(wantKver KernelVersion) (bool, error) {
+	hostKver, err := getKernelVersion()
+	if err != nil {
+		return false, err
+	}
+
+	// Pad out the kernel version lengths to match one another.
+	cmpLen := gocompat.Max2(len(hostKver), len(wantKver))
+	hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...)
+	wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...)
+
+	for i := 0; i < cmpLen; i++ {
+		switch gocompat.CmpCompare(hostKver[i], wantKver[i]) {
+		case -1:
+			// host < want
+			return false, nil
+		case +1:
+			// host > want
+			return true, nil
+		case 0:
+			continue
+		}
+	}
+	// equal version values
+	return true, nil
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
new file mode 100644
index 000000000000..4635714f626c
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MPL-2.0
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 linux returns information about what features are supported on the
+// running kernel.
+package linux
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
new file mode 100644
index 000000000000..b29905bff66f
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 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]: <https://lore.kernel.org/lkml/153754740781.17872.7869536526927736855.stgit@warthog.procyon.org.uk/>
+	fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC)
+	if err != nil {
+		return false
+	}
+	_ = unix.Close(fd)
+
+	// RHEL 8 has a backport of fsopen(2) that appears to have some very
+	// difficult to debug performance pathology. As such, it seems prudent to
+	// simply reject pre-5.2 kernels.
+	isNotBackport, _ := kernelversion.GreaterEqualThan(kernelversion.KernelVersion{5, 2})
+	return isNotBackport
+})
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
new file mode 100644
index 000000000000..399609dc3617
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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 linux
+
+import (
+	"golang.org/x/sys/unix"
+
+	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
+)
+
+// HasOpenat2 returns whether openat2(2) is supported on the running kernel.
+var HasOpenat2 = gocompat.SyncOnceValue(func() bool {
+	fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{
+		Flags:   unix.O_PATH | unix.O_CLOEXEC,
+		Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT,
+	})
+	if err != nil {
+		return false
+	}
+	_ = unix.Close(fd)
+	return true
+})
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
new file mode 100644
index 000000000000..21e0a62e8eca
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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. 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/<subpath>").
+	ProcRoot procfsBase = "/proc"
+	// ProcSelf refers to the current process' subdirectory (i.e.,
+	// "/proc/self/<subpath>").
+	ProcSelf procfsBase = "/proc/self"
+	// ProcThreadSelf refers to the current thread's subdirectory (i.e.,
+	// "/proc/thread-self/<subpath>"). 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/<base>/<subpath>". 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/<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.
+func (proc *Handle) OpenThreadSelf(subpath string) (_ *os.File, _ ProcThreadSelfCloser, Err error) {
+	return proc.open(ProcThreadSelf, subpath)
+}
+
+// OpenSelf returns a handle to /proc/self/<subpath>.
+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/<subpath>.
+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/<subpath> (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/<base>/<subpath>" 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/<fd>. This allows for O_PATH file descriptors to be
+// upgraded to regular file descriptors, as well as changing the open mode of a
+// regular file descriptor. Some filesystems have unique handling of open(2)
+// which make this incredibly useful (such as /dev/ptmx).
+func ReopenFd(handle fd.Fd, flags int) (*os.File, error) {
+	procRoot, err := OpenProcRoot() // subset=pid
+	if err != nil {
+		return nil, err
+	}
+	defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
+
+	// We can't operate on /proc/thread-self/fd/$n directly when doing a
+	// re-open, so we need to open /proc/thread-self/fd and then open a single
+	// final component.
+	procFdDir, closer, err := procRoot.OpenThreadSelf("fd/")
+	if err != nil {
+		return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err)
+	}
+	defer procFdDir.Close() //nolint:errcheck // close failures aren't critical here
+	defer closer()
+
+	// Try to detect if there is a mount on top of the magic-link we are about
+	// to open. If we are using unsafeHostProcRoot(), this could change after
+	// we check it (and there's nothing we can do about that) but for
+	// privateProcRoot() this should be guaranteed to be safe (at least since
+	// Linux 5.12[1], when anonymous mount namespaces were completely isolated
+	// from external mounts including mount propagation events).
+	//
+	// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
+	// onto targets that reside on shared mounts").
+	fdStr := strconv.Itoa(int(handle.Fd()))
+	if err := checkSubpathOvermount(procRoot.Inner, procFdDir, fdStr); err != nil {
+		return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err)
+	}
+
+	flags |= unix.O_CLOEXEC
+	// Rather than just wrapping fd.Openat, open-code it so we can copy
+	// handle.Name().
+	reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0)
+	if err != nil {
+		return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err)
+	}
+	return os.NewFile(uintptr(reopenFd), handle.Name()), nil
+}
+
+// Test hooks used in the procfs tests to verify that the fallback logic works.
+// See testing_mocks_linux_test.go and procfs_linux_test.go for more details.
+var (
+	hookForcePrivateProcRootOpenTree            = hookDummyFile
+	hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile
+	hookForceGetProcRootUnsafe                  = hookDummy
+
+	hookForceProcSelfTask = hookDummy
+	hookForceProcSelf     = hookDummy
+)
+
+func hookDummy() bool                { return false }
+func hookDummyFile(_ io.Closer) bool { return false }
diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
new file mode 100644
index 000000000000..1ad1f18eee6a
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//go:build linux
+
+// Copyright (C) 2024-2025 Aleksa Sarai <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/.
+
+// This code is adapted to be a minimal version of the libpathrs proc resolver
+// <https://github.com/opensuse/libpathrs/blob/v0.1.3/src/resolvers/procfs.rs>.
+// 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/lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go
similarity index 67%
rename from vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go
rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go
index 140ac18ff503..f47504e663c7 100644
--- a/vendor/github.com/cyphar/filepath-securejoin/lookup_linux.go
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go
@@ -1,10 +1,15 @@
+// SPDX-License-Identifier: MPL-2.0
+
 //go:build linux
 
-// Copyright (C) 2024 SUSE LLC. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// Copyright (C) 2024-2025 Aleksa Sarai <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 securejoin
+package pathrs
 
 import (
 	"errors"
@@ -12,10 +17,15 @@ import (
 	"os"
 	"path"
 	"path/filepath"
-	"slices"
 	"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 {
@@ -40,16 +50,18 @@ func (se symlinkStackEntry) Close() {
 
 type symlinkStack []*symlinkStackEntry
 
-func (s symlinkStack) IsEmpty() bool {
-	return len(s) == 0
+func (s *symlinkStack) IsEmpty() bool {
+	return s == nil || len(*s) == 0
 }
 
 func (s *symlinkStack) Close() {
-	for _, link := range *s {
-		link.Close()
+	if s != nil {
+		for _, link := range *s {
+			link.Close()
+		}
+		// TODO: Switch to clear once we switch to Go 1.21.
+		*s = nil
 	}
-	// TODO: Switch to clear once we switch to Go 1.21.
-	*s = nil
 }
 
 var (
@@ -58,11 +70,16 @@ var (
 )
 
 func (s *symlinkStack) popPart(part string) error {
-	if s.IsEmpty() {
+	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.
@@ -102,20 +119,16 @@ func (s *symlinkStack) PopPart(part string) error {
 }
 
 func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) error {
-	// Split the link target and clean up any "" parts.
-	linkTargetParts := slices.DeleteFunc(
-		strings.Split(linkTarget, "/"),
-		func(part string) bool { return part == "" })
-
-	// Don't add a no-op link to the stack. You can't create a no-op link
-	// symlink, but if the symlink is /, partialLookupInRoot has already jumped to the
-	// root and so there's nothing more to do.
-	if len(linkTargetParts) == 0 {
+	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 := dupFile(dir)
+	dirCopy, err := fd.Dup(dir)
 	if err != nil {
 		return err
 	}
@@ -145,7 +158,7 @@ func (s *symlinkStack) SwapLink(linkPart string, dir *os.File, remainingPath, li
 }
 
 func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) {
-	if s.IsEmpty() {
+	if s == nil || s.IsEmpty() {
 		return nil, "", false
 	}
 	tailEntry := (*s)[0]
@@ -157,7 +170,22 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) {
 // within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing
 // component of the requested path, returning a file handle to the final
 // existing component and a string containing the remaining path components.
-func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string, Err error) {
+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
@@ -165,25 +193,26 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
 	// managed open, along with the remaining path components not opened.
 
 	// Try to use openat2 if possible.
-	if hasOpenat2() {
-		return partialLookupOpenat2(root, unsafePath)
+	if linux.HasOpenat2() {
+		return lookupOpenat2(root, unsafePath, partial)
 	}
 
 	// Get the "actual" root path from /proc/self/fd. This is necessary if the
 	// root is some magic-link like /proc/$pid/root, in which case we want to
-	// make sure when we do checkProcSelfFdPath that we are using the correct
-	// root path.
-	logicalRootPath, err := procSelfFdReadlink(root)
+	// make sure when we do procfs.CheckProcSelfFdPath that we are using the
+	// correct root path.
+	logicalRootPath, err := procfs.ProcSelfFdReadlink(root)
 	if err != nil {
 		return nil, "", fmt.Errorf("get real root path: %w", err)
 	}
 
-	currentDir, err := dupFile(root)
+	currentDir, err := fd.Dup(root)
 	if err != nil {
 		return nil, "", fmt.Errorf("clone root fd: %w", err)
 	}
 	defer func() {
-		if Err != nil {
+		// If a handle is not returned, close the internal handle.
+		if Handle == nil {
 			_ = currentDir.Close()
 		}
 	}()
@@ -200,8 +229,11 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
 	// 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 symlinkStack symlinkStack
-	defer symlinkStack.Close()
+	var symStack *symlinkStack
+	if partial {
+		symStack = new(symlinkStack)
+		defer symStack.Close()
+	}
 
 	var (
 		linksWalked   int
@@ -220,9 +252,11 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
 		} else {
 			part, remainingPath = remainingPath[:i], remainingPath[i+1:]
 		}
-		// Skip any "//" components.
+		// 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 == "" {
-			continue
+			part = "."
 		}
 
 		// Apply the component lexically to the path we are building.
@@ -233,11 +267,11 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
 		// 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 := symlinkStack.PopPart(part); err != nil {
+			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 := dupFile(root)
+			rootClone, err := fd.Dup(root)
 			if err != nil {
 				return nil, "", fmt.Errorf("clone root fd: %w", err)
 			}
@@ -248,24 +282,59 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
 		}
 
 		// Try to open the next component.
-		nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
-		switch {
-		case err == nil:
+		nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
+		switch err {
+		case nil:
 			st, err := nextDir.Stat()
 			if err != nil {
 				_ = nextDir.Close()
 				return nil, "", fmt.Errorf("stat component %q: %w", part, err)
 			}
 
-			switch st.Mode() & os.ModeType {
-			case os.ModeDir:
+			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 := symlinkStack.PopPart(part); err != nil {
+				if err := symStack.PopPart(part); err != nil {
 					return nil, "", fmt.Errorf("walking into directory %q failed: %w", part, err)
 				}
 
@@ -277,104 +346,54 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
 				// rename or mount on the system.
 				if part == ".." {
 					// Make sure the root hasn't moved.
-					if err := checkProcSelfFdPath(logicalRootPath, root); err != nil {
+					if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil {
 						return nil, "", fmt.Errorf("root path moved during lookup: %w", err)
 					}
 					// Make sure the path is what we expect.
 					fullPath := logicalRootPath + nextPath
-					if err := checkProcSelfFdPath(fullPath, currentDir); err != nil {
+					if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil {
 						return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err)
 					}
 				}
-
-			case os.ModeSymlink:
-				// We don't need the handle anymore.
-				_ = nextDir.Close()
-
-				// Unfortunately, we cannot readlink through our handle and so
-				// we need to do a separate readlinkat (which could race to
-				// give us an error if the attacker swapped the symlink with a
-				// non-symlink).
-				linkDest, err := readlinkatFile(currentDir, part)
-				if err != nil {
-					if errors.Is(err, unix.EINVAL) {
-						// The part was not a symlink, so assume that it's a
-						// regular file. It is possible for it to be a
-						// directory (if an attacker is swapping a directory
-						// and non-directory at this subpath) but erroring out
-						// here is better anyway.
-						err = fmt.Errorf("%w: path component %q is invalid: %w", errPossibleAttack, part, unix.ENOTDIR)
-					}
-					return nil, "", err
-				}
-
-				linksWalked++
-				if linksWalked > maxSymlinkLimit {
-					return nil, "", &os.PathError{Op: "partialLookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP}
-				}
-
-				// Swap out the symlink's component for the link entry itself.
-				if err := symlinkStack.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 := dupFile(root)
-					if err != nil {
-						return nil, "", fmt.Errorf("clone root fd: %w", err)
-					}
-					_ = currentDir.Close()
-					currentDir = rootClone
-					currentPath = "/"
-				}
-			default:
-				// For any other file type, we can't walk further and so we've
-				// hit the end of the lookup. The handling is very similar to
-				// ENOENT from openat(2), except that we return a handle to the
-				// component we just walked into (and we drop the component
-				// from the symlink stack).
-				_ = currentDir.Close()
-
-				// The part existed, so drop it from the symlink stack.
-				if err := symlinkStack.PopPart(part); err != nil {
-					return nil, "", fmt.Errorf("walking into non-directory %q failed: %w", part, 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 := symlinkStack.PopTopSymlink(); ok {
-					_ = nextDir.Close()
-					return oldDir, remainingPath, nil
-				}
-
-				// The current component exists, so return it.
-				return nextDir, remainingPath, nil
 			}
 
-		case errors.Is(err, os.ErrNotExist):
+		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 := symlinkStack.PopTopSymlink(); ok {
+			if oldDir, remainingPath, ok := symStack.PopTopSymlink(); ok {
 				_ = currentDir.Close()
-				return oldDir, remainingPath, nil
+				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, nil
-
-		default:
-			return nil, "", err
+			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
new file mode 100644
index 000000000000..f3c62b0dac65
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go
@@ -0,0 +1,246 @@
+// 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 (
+	"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/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go
similarity index 57%
rename from vendor/github.com/cyphar/filepath-securejoin/open_linux.go
rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go
index 21700612c722..7492d8cfa069 100644
--- a/vendor/github.com/cyphar/filepath-securejoin/open_linux.go
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go
@@ -1,28 +1,30 @@
+// SPDX-License-Identifier: MPL-2.0
+
 //go:build linux
 
-// Copyright (C) 2024 SUSE LLC. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
+// Copyright (C) 2024-2025 Aleksa Sarai <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 securejoin
+package pathrs
 
 import (
-	"fmt"
 	"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.
+// 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, remainingPath, err := partialLookupInRoot(root, unsafePath)
+	handle, err := completeLookupInRoot(root, unsafePath)
 	if err != nil {
-		return nil, err
-	}
-	if remainingPath != "" {
-		_ = handle.Close()
-		return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: unix.ENOENT}
+		return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err}
 	}
 	return handle, nil
 }
@@ -34,7 +36,7 @@ func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
 //	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 OpenFile, it is
+// 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
@@ -42,17 +44,19 @@ func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
 // 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.
+// [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()
+	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 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())
@@ -62,22 +66,9 @@ func OpenInRoot(root, unsafePath string) (*os.File, error) {
 // 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.
+// operations. See [CVE-2019-19921] for more details.
+//
+// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
 func Reopen(handle *os.File, flags int) (*os.File, error) {
-	procRoot, err := getProcRoot()
-	if err != nil {
-		return nil, err
-	}
-
-	flags |= unix.O_CLOEXEC
-	fdPath := fmt.Sprintf("fd/%d", handle.Fd())
-	return doProcSelfMagiclink(procRoot, fdPath, func(procDirHandle *os.File, base string) (*os.File, error) {
-		// Rather than just wrapping openatFile, open-code it so we can copy
-		// handle.Name().
-		reopenFd, err := unix.Openat(int(procDirHandle.Fd()), base, flags, 0)
-		if err != nil {
-			return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err)
-		}
-		return os.NewFile(uintptr(reopenFd), handle.Name()), nil
-	})
+	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
new file mode 100644
index 000000000000..937bc435f2b8
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
@@ -0,0 +1,101 @@
+// 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 (
+	"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
new file mode 100644
index 000000000000..ec187a414c55
--- /dev/null
+++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
@@ -0,0 +1,157 @@
+// 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 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/<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)
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// 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) {
+	return procfs.ProcSelfFdReadlink(f)
+}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
deleted file mode 100644
index daac3f061712..000000000000
--- a/vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
+++ /dev/null
@@ -1,481 +0,0 @@
-//go:build linux
-
-// Copyright (C) 2024 SUSE LLC. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package securejoin
-
-import (
-	"errors"
-	"fmt"
-	"os"
-	"path/filepath"
-	"runtime"
-	"strconv"
-	"sync"
-
-	"golang.org/x/sys/unix"
-)
-
-func fstat(f *os.File) (unix.Stat_t, error) {
-	var stat unix.Stat_t
-	if err := unix.Fstat(int(f.Fd()), &stat); err != nil {
-		return stat, &os.PathError{Op: "fstat", Path: f.Name(), Err: err}
-	}
-	return stat, nil
-}
-
-func fstatfs(f *os.File) (unix.Statfs_t, error) {
-	var statfs unix.Statfs_t
-	if err := unix.Fstatfs(int(f.Fd()), &statfs); err != nil {
-		return statfs, &os.PathError{Op: "fstatfs", Path: f.Name(), Err: err}
-	}
-	return statfs, nil
-}
-
-// The kernel guarantees that the root inode of a procfs mount has an
-// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO.
-const (
-	procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC
-	procRootIno    = 1      // PROC_ROOT_INO
-)
-
-func verifyProcRoot(procRoot *os.File) error {
-	if statfs, err := fstatfs(procRoot); err != nil {
-		return err
-	} else if statfs.Type != procSuperMagic {
-		return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type)
-	}
-	if stat, err := fstat(procRoot); err != nil {
-		return err
-	} else if stat.Ino != procRootIno {
-		return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino)
-	}
-	return nil
-}
-
-var (
-	hasNewMountApiBool bool
-	hasNewMountApiOnce sync.Once
-)
-
-func hasNewMountApi() bool {
-	hasNewMountApiOnce.Do(func() {
-		// All of the pieces of the new mount API we use (fsopen, fsconfig,
-		// fsmount, open_tree) were added together in Linux 5.1[1,2], so we can
-		// just check for one of the syscalls and the others should also be
-		// available.
-		//
-		// Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE.
-		// This is equivalent to openat(2), but tells us if open_tree is
-		// available (and thus all of the other basic new mount API syscalls).
-		// open_tree(2) is most light-weight syscall to test here.
-		//
-		// [1]: merge commit 400913252d09
-		// [2]: <https://lore.kernel.org/lkml/153754740781.17872.7869536526927736855.stgit@warthog.procyon.org.uk/>
-		fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC)
-		if err == nil {
-			hasNewMountApiBool = true
-			_ = unix.Close(fd)
-		}
-	})
-	return hasNewMountApiBool
-}
-
-func fsopen(fsName string, flags int) (*os.File, error) {
-	// Make sure we always set O_CLOEXEC.
-	flags |= unix.FSOPEN_CLOEXEC
-	fd, err := unix.Fsopen(fsName, flags)
-	if err != nil {
-		return nil, os.NewSyscallError("fsopen "+fsName, err)
-	}
-	return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil
-}
-
-func fsmount(ctx *os.File, flags, mountAttrs int) (*os.File, error) {
-	// Make sure we always set O_CLOEXEC.
-	flags |= unix.FSMOUNT_CLOEXEC
-	fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs)
-	if err != nil {
-		return nil, os.NewSyscallError("fsmount "+ctx.Name(), err)
-	}
-	return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil
-}
-
-func newPrivateProcMount() (*os.File, error) {
-	procfsCtx, err := fsopen("proc", unix.FSOPEN_CLOEXEC)
-	if err != nil {
-		return nil, err
-	}
-	defer procfsCtx.Close()
-
-	// Try to configure hidepid=ptraceable,subset=pid if possible, but ignore errors.
-	_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable")
-	_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid")
-
-	// Get an actual handle.
-	if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil {
-		return nil, os.NewSyscallError("fsconfig create procfs", err)
-	}
-	return fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_RDONLY|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID)
-}
-
-func openTree(dir *os.File, path string, flags uint) (*os.File, error) {
-	dirFd := -int(unix.EBADF)
-	dirName := "."
-	if dir != nil {
-		dirFd = int(dir.Fd())
-		dirName = dir.Name()
-	}
-	// Make sure we always set O_CLOEXEC.
-	flags |= unix.OPEN_TREE_CLOEXEC
-	fd, err := unix.OpenTree(dirFd, path, flags)
-	if err != nil {
-		return nil, &os.PathError{Op: "open_tree", Path: path, Err: err}
-	}
-	return os.NewFile(uintptr(fd), dirName+"/"+path), nil
-}
-
-func clonePrivateProcMount() (_ *os.File, Err error) {
-	// Try to make a clone without using AT_RECURSIVE if we can. If this works,
-	// we can be sure there are no over-mounts and so if the root is valid then
-	// we're golden. Otherwise, we have to deal with over-mounts.
-	procfsHandle, err := openTree(nil, "/proc", unix.OPEN_TREE_CLONE)
-	if err != nil || testingForcePrivateProcRootOpenTreeAtRecursive(procfsHandle) {
-		procfsHandle, err = openTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE)
-	}
-	if err != nil {
-		return nil, fmt.Errorf("creating a detached procfs clone: %w", err)
-	}
-	defer func() {
-		if Err != nil {
-			_ = procfsHandle.Close()
-		}
-	}()
-	if err := verifyProcRoot(procfsHandle); err != nil {
-		return nil, err
-	}
-	return procfsHandle, nil
-}
-
-func privateProcRoot() (*os.File, error) {
-	if !hasNewMountApi() {
-		return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP)
-	}
-	// Try to create a new procfs mount from scratch if we can. This ensures we
-	// can get a procfs mount even if /proc is fake (for whatever reason).
-	procRoot, err := newPrivateProcMount()
-	if err != nil || testingForcePrivateProcRootOpenTree(procRoot) {
-		// Try to clone /proc then...
-		procRoot, err = clonePrivateProcMount()
-	}
-	return procRoot, err
-}
-
-var (
-	procRootHandle *os.File
-	procRootError  error
-	procRootOnce   sync.Once
-
-	errUnsafeProcfs = errors.New("unsafe procfs detected")
-)
-
-func unsafeHostProcRoot() (_ *os.File, Err error) {
-	procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
-	if err != nil {
-		return nil, err
-	}
-	defer func() {
-		if Err != nil {
-			_ = procRoot.Close()
-		}
-	}()
-	if err := verifyProcRoot(procRoot); err != nil {
-		return nil, err
-	}
-	return procRoot, nil
-}
-
-func doGetProcRoot() (*os.File, error) {
-	procRoot, err := privateProcRoot()
-	if err != nil || testingForceGetProcRootUnsafe(procRoot) {
-		// Fall back to using a /proc handle if making a private mount failed.
-		// If we have openat2, at least we can avoid some kinds of over-mount
-		// attacks, but without openat2 there's not much we can do.
-		procRoot, err = unsafeHostProcRoot()
-	}
-	return procRoot, err
-}
-
-func getProcRoot() (*os.File, error) {
-	procRootOnce.Do(func() {
-		procRootHandle, procRootError = doGetProcRoot()
-	})
-	return procRootHandle, procRootError
-}
-
-var (
-	haveProcThreadSelf     bool
-	haveProcThreadSelfOnce sync.Once
-)
-
-type procThreadSelfCloser func()
-
-// procThreadSelf 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.
-//
-// This is similar to ProcThreadSelf from runc, but with extra hardening
-// applied and using *os.File.
-func procThreadSelf(procRoot *os.File, subpath string) (_ *os.File, _ procThreadSelfCloser, Err error) {
-	haveProcThreadSelfOnce.Do(func() {
-		// If the kernel doesn't support thread-self, it doesn't matter which
-		// /proc handle we use.
-		_, err := fstatatFile(procRoot, "thread-self", unix.AT_SYMLINK_NOFOLLOW)
-		haveProcThreadSelf = (err == nil)
-	})
-
-	// We need to lock our thread until the caller is done with the handle
-	// because between getting the handle and using it we could get interrupted
-	// by the Go runtime and hit the case where the underlying thread is
-	// swapped out and the original thread is killed, resulting in
-	// pull-your-hair-out-hard-to-debug issues in the caller.
-	runtime.LockOSThread()
-	defer func() {
-		if Err != nil {
-			runtime.UnlockOSThread()
-		}
-	}()
-
-	// Figure out what prefix we want to use.
-	threadSelf := "thread-self/"
-	if !haveProcThreadSelf || testingForceProcSelfTask() {
-		/// Pre-3.17 kernels don't have /proc/thread-self, so do it manually.
-		threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) + "/"
-		if _, err := fstatatFile(procRoot, threadSelf, unix.AT_SYMLINK_NOFOLLOW); err != nil || testingForceProcSelf() {
-			// In this case, we running in a pid namespace that doesn't match
-			// the /proc mount we have. This can happen inside runc.
-			//
-			// Unfortunately, there is no nice way to get the correct TID to
-			// use here because of the age of the kernel, so we have to just
-			// use /proc/self and hope that it works.
-			threadSelf = "self/"
-		}
-	}
-
-	// Grab the handle.
-	var (
-		handle *os.File
-		err    error
-	)
-	if hasOpenat2() {
-		// We prefer being able to use RESOLVE_NO_XDEV if we can, to be
-		// absolutely sure we are operating on a clean /proc handle that
-		// doesn't have any cheeky overmounts that could trick us (including
-		// symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't
-		// stricly needed, but just use it since we have it.
-		//
-		// NOTE: /proc/self is technically a magic-link (the contents of the
-		//       symlink are generated dynamically), but it doesn't use
-		//       nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it.
-		//
-		// NOTE: We MUST NOT use RESOLVE_IN_ROOT here, as openat2File uses
-		//       procSelfFdReadlink to clean up the returned f.Name() if we use
-		//       RESOLVE_IN_ROOT (which would lead to an infinite recursion).
-		handle, err = openat2File(procRoot, threadSelf+subpath, &unix.OpenHow{
-			Flags:   unix.O_PATH | unix.O_CLOEXEC,
-			Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS,
-		})
-		if err != nil {
-			return nil, nil, fmt.Errorf("%w: %w", errUnsafeProcfs, err)
-		}
-	} else {
-		handle, err = openatFile(procRoot, threadSelf+subpath, unix.O_PATH|unix.O_CLOEXEC, 0)
-		if err != nil {
-			return nil, nil, fmt.Errorf("%w: %w", errUnsafeProcfs, err)
-		}
-		defer func() {
-			if Err != nil {
-				_ = handle.Close()
-			}
-		}()
-		// We can't detect bind-mounts of different parts of procfs on top of
-		// /proc (a-la RESOLVE_NO_XDEV), but we can at least be sure that we
-		// aren't on the wrong filesystem here.
-		if statfs, err := fstatfs(handle); err != nil {
-			return nil, nil, err
-		} else if statfs.Type != procSuperMagic {
-			return nil, nil, fmt.Errorf("%w: incorrect /proc/self/fd filesystem type 0x%x", errUnsafeProcfs, statfs.Type)
-		}
-	}
-	return handle, runtime.UnlockOSThread, nil
-}
-
-var (
-	hasStatxMountIdBool bool
-	hasStatxMountIdOnce sync.Once
-)
-
-func hasStatxMountId() bool {
-	hasStatxMountIdOnce.Do(func() {
-		var (
-			stx unix.Statx_t
-			// We don't care which mount ID we get. The kernel will give us the
-			// unique one if it is supported.
-			wantStxMask uint32 = unix.STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
-		)
-		err := unix.Statx(-int(unix.EBADF), "/", 0, int(wantStxMask), &stx)
-		hasStatxMountIdBool = (err == nil && (stx.Mask&wantStxMask != 0))
-	})
-	return hasStatxMountIdBool
-}
-
-func checkSymlinkOvermount(dir *os.File, path string) error {
-	// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
-	if !hasStatxMountId() {
-		return nil
-	}
-
-	var (
-		stx unix.Statx_t
-		// We don't care which mount ID we get. The kernel will give us the
-		// unique one if it is supported.
-		wantStxMask uint32 = unix.STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
-	)
-
-	// Get the mntId of our procfs handle.
-	err := unix.Statx(int(dir.Fd()), "", unix.AT_EMPTY_PATH, int(wantStxMask), &stx)
-	if err != nil {
-		return &os.PathError{Op: "statx", Path: dir.Name(), Err: err}
-	}
-	if stx.Mask&wantStxMask == 0 {
-		// It's not a kernel limitation, for some reason we couldn't get a
-		// mount ID. Assume it's some kind of attack.
-		return fmt.Errorf("%w: could not get mnt id of dir %s", errUnsafeProcfs, dir.Name())
-	}
-	expectedMountId := stx.Mnt_id
-
-	// Get the mntId of the target symlink.
-	stx = unix.Statx_t{}
-	err = unix.Statx(int(dir.Fd()), path, unix.AT_SYMLINK_NOFOLLOW, int(wantStxMask), &stx)
-	if err != nil {
-		return &os.PathError{Op: "statx", Path: dir.Name() + "/" + path, Err: err}
-	}
-	if stx.Mask&wantStxMask == 0 {
-		// It's not a kernel limitation, for some reason we couldn't get a
-		// mount ID. Assume it's some kind of attack.
-		return fmt.Errorf("%w: could not get mnt id of symlink %s", errUnsafeProcfs, path)
-	}
-	gotMountId := stx.Mnt_id
-
-	// As long as the directory mount is alive, even with wrapping mount IDs,
-	// we would expect to see a different mount ID here. (Of course, if we're
-	// using unsafeHostProcRoot() then an attaker could change this after we
-	// did this check.)
-	if expectedMountId != gotMountId {
-		return fmt.Errorf("%w: symlink %s/%s has an overmount obscuring the real link (mount ids do not match %d != %d)", errUnsafeProcfs, dir.Name(), path, expectedMountId, gotMountId)
-	}
-	return nil
-}
-
-func doProcSelfMagiclink[T any](procRoot *os.File, subPath string, fn func(procDirHandle *os.File, base string) (T, error)) (T, error) {
-	// We cannot operate on the magic-link directly with a handle, we need to
-	// create a handle to the parent of the magic-link and then do
-	// single-component operations on it.
-	dir, base := filepath.Dir(subPath), filepath.Base(subPath)
-
-	procDirHandle, closer, err := procThreadSelf(procRoot, dir)
-	if err != nil {
-		return *new(T), fmt.Errorf("get safe /proc/thread-self/%s handle: %w", dir, err)
-	}
-	defer procDirHandle.Close()
-	defer closer()
-
-	// Try to detect if there is a mount on top of the symlink we are about to
-	// read. 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").
-	if err := checkSymlinkOvermount(procDirHandle, base); err != nil {
-		return *new(T), fmt.Errorf("check safety of %s proc magiclink: %w", subPath, err)
-	}
-	return fn(procDirHandle, base)
-}
-
-func doRawProcSelfFdReadlink(procRoot *os.File, fd int) (string, error) {
-	fdPath := fmt.Sprintf("fd/%d", fd)
-	return doProcSelfMagiclink(procRoot, fdPath, readlinkatFile)
-}
-
-func rawProcSelfFdReadlink(fd int) (string, error) {
-	procRoot, err := getProcRoot()
-	if err != nil {
-		return "", err
-	}
-	return doRawProcSelfFdReadlink(procRoot, fd)
-}
-
-func procSelfFdReadlink(f *os.File) (string, error) {
-	return rawProcSelfFdReadlink(int(f.Fd()))
-}
-
-var (
-	errPossibleBreakout = errors.New("possible breakout detected")
-	errInvalidDirectory = errors.New("wandered into deleted directory")
-	errDeletedInode     = errors.New("cannot verify path of deleted inode")
-)
-
-func isDeadInode(file *os.File) error {
-	// If the nlink of a file drops to 0, there is an attacker deleting
-	// directories during our walk, which could result in weird /proc values.
-	// It's better to error out in this case.
-	stat, err := fstat(file)
-	if err != nil {
-		return fmt.Errorf("check for dead inode: %w", err)
-	}
-	if stat.Nlink == 0 {
-		err := errDeletedInode
-		if stat.Mode&unix.S_IFMT == unix.S_IFDIR {
-			err = errInvalidDirectory
-		}
-		return fmt.Errorf("%w %q", err, file.Name())
-	}
-	return nil
-}
-
-func getUmask() int {
-	// umask is a per-thread property, but it is inherited by children, so we
-	// need to lock our OS thread to make sure that no other goroutine runs in
-	// this thread and no goroutines are spawned from this thread until we
-	// revert to the old umask.
-	//
-	// We could parse /proc/self/status to avoid this get-set problem, but
-	// /proc/thread-self requires LockOSThread anyway, so there's no real
-	// benefit over just using umask(2).
-	runtime.LockOSThread()
-	umask := unix.Umask(0)
-	unix.Umask(umask)
-	runtime.UnlockOSThread()
-	return umask
-}
-
-func checkProcSelfFdPath(path string, file *os.File) error {
-	if err := isDeadInode(file); err != nil {
-		return err
-	}
-	actualPath, err := procSelfFdReadlink(file)
-	if err != nil {
-		return fmt.Errorf("get path of handle: %w", err)
-	}
-	if actualPath != path {
-		return fmt.Errorf("%w: handle path %q doesn't match expected path %q", errPossibleBreakout, actualPath, path)
-	}
-	return nil
-}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/testing_mocks_linux.go b/vendor/github.com/cyphar/filepath-securejoin/testing_mocks_linux.go
deleted file mode 100644
index 2a25d08e3785..000000000000
--- a/vendor/github.com/cyphar/filepath-securejoin/testing_mocks_linux.go
+++ /dev/null
@@ -1,68 +0,0 @@
-//go:build linux
-
-// Copyright (C) 2024 SUSE LLC. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package securejoin
-
-import (
-	"os"
-	"testing"
-)
-
-type forceGetProcRootLevel int
-
-const (
-	forceGetProcRootDefault             forceGetProcRootLevel = iota
-	forceGetProcRootOpenTree                                  // force open_tree()
-	forceGetProcRootOpenTreeAtRecursive                       // force open_tree(AT_RECURSIVE)
-	forceGetProcRootUnsafe                                    // force open()
-)
-
-var testingForceGetProcRoot *forceGetProcRootLevel
-
-func testingCheckClose(check bool, f *os.File) bool {
-	if check {
-		if f != nil {
-			_ = f.Close()
-		}
-		return true
-	}
-	return false
-}
-
-func testingForcePrivateProcRootOpenTree(f *os.File) bool {
-	return testing.Testing() && testingForceGetProcRoot != nil &&
-		testingCheckClose(*testingForceGetProcRoot >= forceGetProcRootOpenTree, f)
-}
-
-func testingForcePrivateProcRootOpenTreeAtRecursive(f *os.File) bool {
-	return testing.Testing() && testingForceGetProcRoot != nil &&
-		testingCheckClose(*testingForceGetProcRoot >= forceGetProcRootOpenTreeAtRecursive, f)
-}
-
-func testingForceGetProcRootUnsafe(f *os.File) bool {
-	return testing.Testing() && testingForceGetProcRoot != nil &&
-		testingCheckClose(*testingForceGetProcRoot >= forceGetProcRootUnsafe, f)
-}
-
-type forceProcThreadSelfLevel int
-
-const (
-	forceProcThreadSelfDefault forceProcThreadSelfLevel = iota
-	forceProcSelfTask
-	forceProcSelf
-)
-
-var testingForceProcThreadSelf *forceProcThreadSelfLevel
-
-func testingForceProcSelfTask() bool {
-	return testing.Testing() && testingForceProcThreadSelf != nil &&
-		*testingForceProcThreadSelf >= forceProcSelfTask
-}
-
-func testingForceProcSelf() bool {
-	return testing.Testing() && testingForceProcThreadSelf != nil &&
-		*testingForceProcThreadSelf >= forceProcSelf
-}
diff --git a/vendor/github.com/cyphar/filepath-securejoin/vfs.go b/vendor/github.com/cyphar/filepath-securejoin/vfs.go
index 6e27c7dd8e1f..4d89a481ca79 100644
--- a/vendor/github.com/cyphar/filepath-securejoin/vfs.go
+++ b/vendor/github.com/cyphar/filepath-securejoin/vfs.go
@@ -1,3 +1,5 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
 // Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
@@ -10,19 +12,19 @@ import "os"
 // are several projects (umoci and go-mtree) that are using this sort of
 // interface.
 
-// VFS is the minimal interface necessary to use SecureJoinVFS. A nil VFS is
-// equivalent to using the standard os.* family of functions. This is mainly
+// VFS is the minimal interface necessary to use [SecureJoinVFS]. A nil VFS is
+// equivalent to using the standard [os].* family of functions. This is mainly
 // used for the purposes of mock testing, but also can be used to otherwise use
-// SecureJoin with VFS-like system.
+// [SecureJoinVFS] with VFS-like system.
 type VFS interface {
-	// Lstat returns a FileInfo describing the named file. If the file is a
-	// symbolic link, the returned FileInfo describes the symbolic link. Lstat
-	// makes no attempt to follow the link. These semantics are identical to
-	// os.Lstat.
+	// Lstat returns an [os.FileInfo] describing the named file. If the
+	// file is a symbolic link, the returned [os.FileInfo] describes the
+	// symbolic link. Lstat makes no attempt to follow the link.
+	// The semantics are identical to [os.Lstat].
 	Lstat(name string) (os.FileInfo, error)
 
-	// Readlink returns the destination of the named symbolic link. These
-	// semantics are identical to os.Readlink.
+	// Readlink returns the destination of the named symbolic link.
+	// The semantics are identical to [os.Readlink].
 	Readlink(name string) (string, error)
 }
 
@@ -30,12 +32,6 @@ type VFS interface {
 // module.
 type osVFS struct{}
 
-// Lstat returns a FileInfo describing the named file. If the file is a
-// symbolic link, the returned FileInfo describes the symbolic link. Lstat
-// makes no attempt to follow the link. These semantics are identical to
-// os.Lstat.
 func (o osVFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
 
-// Readlink returns the destination of the named symbolic link. These
-// semantics are identical to os.Readlink.
 func (o osVFS) Readlink(name string) (string, error) { return os.Readlink(name) }
diff --git a/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor_linux.go b/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor_linux.go
index 8b1483c7de77..84d8821b8ab5 100644
--- a/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor_linux.go
+++ b/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor_linux.go
@@ -6,6 +6,10 @@ import (
 	"os"
 	"sync"
 
+	"github.com/cyphar/filepath-securejoin/pathrs-lite"
+	"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
+	"golang.org/x/sys/unix"
+
 	"github.com/opencontainers/runc/libcontainer/utils"
 )
 
@@ -26,24 +30,36 @@ func isEnabled() bool {
 }
 
 func setProcAttr(attr, value string) error {
-	// Under AppArmor you can only change your own attr, so use /proc/self/
-	// instead of /proc/<tid>/ like libapparmor does
-	attrPath := "/proc/self/attr/apparmor/" + attr
-	if _, err := os.Stat(attrPath); errors.Is(err, os.ErrNotExist) {
+	attr = utils.CleanPath(attr)
+	attrSubPath := "attr/apparmor/" + attr
+	if _, err := os.Stat("/proc/self/" + attrSubPath); errors.Is(err, os.ErrNotExist) {
 		// fall back to the old convention
-		attrPath = "/proc/self/attr/" + attr
+		attrSubPath = "attr/" + attr
 	}
 
-	f, err := os.OpenFile(attrPath, os.O_WRONLY, 0)
+	proc, err := procfs.OpenProcRoot()
+	if err != nil {
+		return err
+	}
+	defer proc.Close()
+
+	// Under AppArmor you can only change your own attr, so there's no reason
+	// to not use /proc/thread-self/ (instead of /proc/<tid>/, like libapparmor
+	// does).
+	handle, closer, err := proc.OpenThreadSelf(attrSubPath)
+	if err != nil {
+		return err
+	}
+	defer closer()
+	defer handle.Close()
+
+	f, err := pathrs.Reopen(handle, unix.O_WRONLY|unix.O_CLOEXEC)
+
 	if err != nil {
 		return err
 	}
 	defer f.Close()
 
-	if err := utils.EnsureProcHandle(f); err != nil {
-		return err
-	}
-
 	_, err = f.WriteString(value)
 	return err
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f80fa20346be..d27fae224726 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -307,9 +307,19 @@ github.com/coreos/go-systemd/v22/dbus
 # github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f
 ## explicit
 github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer
-# github.com/cyphar/filepath-securejoin v0.3.0
-## explicit; go 1.20
+# github.com/cyphar/filepath-securejoin v0.5.1
+## 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
-- 
2.51.0

openSUSE Build Service is sponsored by