deps: update to libpathrs v0.2.5

This update includes a few breaking API changes that I needed to get in
before an actual runc release depends on it, so that we don't need to
deal with compatibility shims for them (or bumping the SOVERSION).

From a Go API perspective, there were no major changes -- though this
bump did also require a bump to github.com/cyphar/filepath-securejoin
because one of the wrapped APIs changed from int to uint64 as a flag
argument type. Again, better to get this done before we really depend on
this in a public way.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
Aleksa Sarai
2026-06-18 10:49:48 +02:00
parent c6f4361b83
commit d47bf88349
19 changed files with 153 additions and 77 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ permissions:
contents: read contents: read
env: env:
LIBPATHRS_VERSION: "0.2.4" LIBPATHRS_VERSION: "0.2.5"
# Don't ignore C warnings. Note that the output of "go env CGO_CFLAGS" by default is "-g -O2", so we keep them. # Don't ignore C warnings. Note that the output of "go env CGO_CFLAGS" by default is "-g -O2", so we keep them.
CGO_CFLAGS: -g -O2 -Werror CGO_CFLAGS: -g -O2 -Werror
# Allow potentially unsafe tests. # Allow potentially unsafe tests.
+1 -1
View File
@@ -14,7 +14,7 @@ permissions:
env: env:
GO_VERSION: 1.25 GO_VERSION: 1.25
LIBPATHRS_VERSION: "0.2.4" LIBPATHRS_VERSION: "0.2.5"
jobs: jobs:
keyring: keyring:
+6
View File
@@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The poststart hooks are now executed after starting the user-specified - The poststart hooks are now executed after starting the user-specified
process, fixing a runtime-spec conformance issue. (#4347, #5186) process, fixing a runtime-spec conformance issue. (#4347, #5186)
### Changed ###
- runc now depends on [libpathrs v0.2.5] or later, and attempting to build with
older versions will cause compilation errors. (#5291)
[libpathrs v0.2.5]: https://github.com/cyphar/libpathrs/releases/tag/v0.2.5
## [1.5.0-rc.3] - 2026-06-13 ## [1.5.0-rc.3] - 2026-06-13
> The best way to get a drink out of a Vogon is to stick your finger down his > The best way to get a drink out of a Vogon is to stick your finger down his
+1 -1
View File
@@ -1,7 +1,7 @@
ARG GO_VERSION=1.25 ARG GO_VERSION=1.25
ARG BATS_VERSION=v1.12.0 ARG BATS_VERSION=v1.12.0
ARG LIBSECCOMP_VERSION=2.6.0 ARG LIBSECCOMP_VERSION=2.6.0
ARG LIBPATHRS_VERSION=0.2.4 ARG LIBPATHRS_VERSION=0.2.5
FROM golang:${GO_VERSION}-trixie FROM golang:${GO_VERSION}-trixie
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
+3 -3
View File
@@ -61,8 +61,8 @@ The following dependencies are optional:
[libpathrs][] is a Rust library runc can optionally use for path safety. As [libpathrs][] is a Rust library runc can optionally use for path safety. As
mentioned in [the build tag section](#build-tags), its use is controlled with mentioned in [the build tag section](#build-tags), its use is controlled with
the `libpathrs` build tag. runc currently requires at least libpathrs 0.2.4 in the `libpathrs` build tag. runc currently requires *at least libpathrs 0.2.5*
order to function properly. in order to function properly.
At time of writing, very few distributions have libpathrs packages and so it is At time of writing, very few distributions have libpathrs packages and so it is
usually necessary to build and install it locally. For detailed installation usually necessary to build and install it locally. For detailed installation
@@ -76,7 +76,7 @@ dependencies like `clang` and `lld`), the following steps are all that are
really necessary to install libpathrs: really necessary to install libpathrs:
```sh ```sh
LIBPATHRS_VERSION=0.2.4 LIBPATHRS_VERSION=0.2.5
curl -o - -sSL https://github.com/cyphar/libpathrs/releases/download/v${LIBPATHRS_VERSION}/libpathrs-${LIBPATHRS_VERSION}.tar.xz | tar xvfJ - curl -o - -sSL https://github.com/cyphar/libpathrs/releases/download/v${LIBPATHRS_VERSION}/libpathrs-${LIBPATHRS_VERSION}.tar.xz | tar xvfJ -
cd libpathrs-${LIBPATHRS_VERSION}/ cd libpathrs-${LIBPATHRS_VERSION}/
make release make release
+2 -2
View File
@@ -6,7 +6,7 @@ require (
github.com/checkpoint-restore/go-criu/v8 v8.3.0 github.com/checkpoint-restore/go-criu/v8 v8.3.0
github.com/containerd/console v1.0.5 github.com/containerd/console v1.0.5
github.com/coreos/go-systemd/v22 v22.7.0 github.com/coreos/go-systemd/v22 v22.7.0
github.com/cyphar/filepath-securejoin v0.6.1 github.com/cyphar/filepath-securejoin v0.7.0
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/godbus/dbus/v5 v5.2.2 github.com/godbus/dbus/v5 v5.2.2
github.com/moby/sys/capability v0.4.0 github.com/moby/sys/capability v0.4.0
@@ -28,7 +28,7 @@ require (
) )
require ( require (
cyphar.com/go-pathrs v0.2.4 // indirect cyphar.com/go-pathrs v0.2.5 // indirect
github.com/aperturerobotics/protobuf-go-lite v0.14.0 // indirect github.com/aperturerobotics/protobuf-go-lite v0.14.0 // indirect
github.com/cilium/ebpf v0.17.3 // indirect github.com/cilium/ebpf v0.17.3 // indirect
) )
+4 -4
View File
@@ -1,5 +1,5 @@
cyphar.com/go-pathrs v0.2.4 h1:iD/mge36swa1UFKdINkr1Frkpp6wZsy3YYEildj9cLY= cyphar.com/go-pathrs v0.2.5 h1:SnX9FBvnoyn3lUs1dkMgZ52bAETpirNu3FTRh5HlRik=
cyphar.com/go-pathrs v0.2.4/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= cyphar.com/go-pathrs v0.2.5/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
github.com/aperturerobotics/protobuf-go-lite v0.14.0 h1:6YhovtoUZtXgXLHZ2VV2GCYUzFfi8UN6172Vl2flNlE= github.com/aperturerobotics/protobuf-go-lite v0.14.0 h1:6YhovtoUZtXgXLHZ2VV2GCYUzFfi8UN6172Vl2flNlE=
github.com/aperturerobotics/protobuf-go-lite v0.14.0/go.mod h1:lGH3s5ArCTXKI4wJdlNpaybUtwSjfAG0vdWjxOfMcF8= github.com/aperturerobotics/protobuf-go-lite v0.14.0/go.mod h1:lGH3s5ArCTXKI4wJdlNpaybUtwSjfAG0vdWjxOfMcF8=
github.com/checkpoint-restore/go-criu/v8 v8.3.0 h1:UC3Ioay1OMN2Wg33U53enRoG2701vJ9Y2Ex4APmC52w= github.com/checkpoint-restore/go-criu/v8 v8.3.0 h1:UC3Ioay1OMN2Wg33U53enRoG2701vJ9Y2Ex4APmC52w=
@@ -10,8 +10,8 @@ github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/q
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+1 -1
View File
@@ -44,7 +44,7 @@ declare -A RUST_TARGET_TO_CC=(
# sha256 checksums for libpathrs release tarballs. # sha256 checksums for libpathrs release tarballs.
declare -A LIBPATHRS_SHA256=( declare -A LIBPATHRS_SHA256=(
["0.2.4"]=45aca68e698b844fae0cf7c459bc441519b0e7b48397caa7d3936cfc68d73f77 ["0.2.5"]=f8f4a9419eb839cd5decbd120b65f0495bf6eac07155477fe39a8c2a23da589d
) )
function generate_cargo_config() { function generate_cargo_config() {
+1 -1
View File
@@ -20,7 +20,7 @@ set -e
# Project-specific options and functions. In *theory* you shouldn't need to # Project-specific options and functions. In *theory* you shouldn't need to
# touch anything else in this script in order to use this elsewhere. # touch anything else in this script in order to use this elsewhere.
: "${LIBSECCOMP_VERSION:=2.6.0}" : "${LIBSECCOMP_VERSION:=2.6.0}"
: "${LIBPATHRS_VERSION:=0.2.4}" : "${LIBPATHRS_VERSION:=0.2.5}"
project="runc" project="runc"
root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")/..")" root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")/..")"
+1 -1
View File
@@ -3,7 +3,7 @@
# Supports Fedora and EL-based distributions. # Supports Fedora and EL-based distributions.
set -eux -o pipefail set -eux -o pipefail
: "${LIBPATHRS_VERSION:=0.2.4}" : "${LIBPATHRS_VERSION:=0.2.5}"
# BATS_VERSION is only consumed for the EL8 platform as its bats package is too old. # BATS_VERSION is only consumed for the EL8 platform as its bats package is too old.
: "${BATS_VERSION:=v1.12.0}" : "${BATS_VERSION:=v1.12.0}"
+5 -3
View File
@@ -17,6 +17,8 @@ import (
"fmt" "fmt"
"os" "os"
"golang.org/x/sys/unix"
"cyphar.com/go-pathrs/internal/fdutils" "cyphar.com/go-pathrs/internal/fdutils"
"cyphar.com/go-pathrs/internal/libpathrs" "cyphar.com/go-pathrs/internal/libpathrs"
) )
@@ -56,11 +58,11 @@ func HandleFromFile(file *os.File) (*Handle, error) {
// and can be opened multiple times. // and can be opened multiple times.
// //
// The handle returned is only usable for reading, and this is method is // The handle returned is only usable for reading, and this is method is
// shorthand for [Handle.OpenFile] with os.O_RDONLY. // shorthand for [Handle.OpenFile] with [unix.O_RDONLY].
// //
// TODO: Rename these to "Reopen" or something. // TODO: Rename these to "Reopen" or something.
func (h *Handle) Open() (*os.File, error) { func (h *Handle) Open() (*os.File, error) {
return h.OpenFile(os.O_RDONLY) return h.OpenFile(unix.O_RDONLY)
} }
// OpenFile creates an "upgraded" file handle to the file referenced by the // OpenFile creates an "upgraded" file handle to the file referenced by the
@@ -71,7 +73,7 @@ func (h *Handle) Open() (*os.File, error) {
// handle. // handle.
// //
// TODO: Rename these to "Reopen" or something. // TODO: Rename these to "Reopen" or something.
func (h *Handle) OpenFile(flags int) (*os.File, error) { func (h *Handle) OpenFile(flags uint64) (*os.File, error) {
return fdutils.WithFileFd(h.inner, func(fd uintptr) (*os.File, error) { return fdutils.WithFileFd(h.inner, func(fd uintptr) (*os.File, error) {
newFd, err := libpathrs.Reopen(fd, flags) newFd, err := libpathrs.Reopen(fd, flags)
if err != nil { if err != nil {
+52 -24
View File
@@ -60,8 +60,8 @@ func OpenRoot(path string) (uintptr, error) {
} }
// Reopen wraps pathrs_reopen. // Reopen wraps pathrs_reopen.
func Reopen(fd uintptr, flags int) (uintptr, error) { func Reopen(fd uintptr, flags uint64) (uintptr, error) {
newFd := C.pathrs_reopen(C.int(fd), C.int(flags)) newFd := C.pathrs_reopen(C.int(fd), C.uint64_t(flags))
return uintptr(newFd), fetchError(newFd) return uintptr(newFd), fetchError(newFd)
} }
@@ -84,11 +84,11 @@ func InRootResolveNoFollow(rootFd uintptr, path string) (uintptr, error) {
} }
// InRootOpen wraps pathrs_inroot_open. // InRootOpen wraps pathrs_inroot_open.
func InRootOpen(rootFd uintptr, path string, flags int) (uintptr, error) { func InRootOpen(rootFd uintptr, path string, flags uint64) (uintptr, error) {
cPath := C.CString(path) cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath)) defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_inroot_open(C.int(rootFd), cPath, C.int(flags)) fd := C.pathrs_inroot_open(C.int(rootFd), cPath, C.uint64_t(flags))
return uintptr(fd), fetchError(fd) return uintptr(fd), fetchError(fd)
} }
@@ -145,23 +145,23 @@ func InRootRemoveAll(rootFd uintptr, path string) error {
} }
// InRootCreat wraps pathrs_inroot_creat. // InRootCreat wraps pathrs_inroot_creat.
func InRootCreat(rootFd uintptr, path string, flags int, mode uint32) (uintptr, error) { func InRootCreat(rootFd uintptr, path string, flags uint64, mode uint32) (uintptr, error) {
cPath := C.CString(path) cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath)) defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_inroot_creat(C.int(rootFd), cPath, C.int(flags), C.uint(mode)) fd := C.pathrs_inroot_creat(C.int(rootFd), cPath, C.uint64_t(flags), C.uint(mode))
return uintptr(fd), fetchError(fd) return uintptr(fd), fetchError(fd)
} }
// InRootRename wraps pathrs_inroot_rename. // InRootRename wraps pathrs_inroot_rename.
func InRootRename(rootFd uintptr, src, dst string, flags uint) error { func InRootRename(oldRootFd uintptr, oldPath string, newRootFd uintptr, newPath string, flags uint64) error {
cSrc := C.CString(src) cOldPath := C.CString(oldPath)
defer C.free(unsafe.Pointer(cSrc)) defer C.free(unsafe.Pointer(cOldPath))
cDst := C.CString(dst) cNewPath := C.CString(newPath)
defer C.free(unsafe.Pointer(cDst)) defer C.free(unsafe.Pointer(cNewPath))
err := C.pathrs_inroot_rename(C.int(rootFd), cSrc, cDst, C.uint(flags)) err := C.pathrs_inroot_rename(C.int(oldRootFd), cOldPath, C.int(newRootFd), cNewPath, C.uint64_t(flags))
return fetchError(err) return fetchError(err)
} }
@@ -193,26 +193,26 @@ func InRootMknod(rootFd uintptr, path string, mode uint32, dev uint64) error {
} }
// InRootSymlink wraps pathrs_inroot_symlink. // InRootSymlink wraps pathrs_inroot_symlink.
func InRootSymlink(rootFd uintptr, path, target string) error { func InRootSymlink(target string, rootFd uintptr, linkpath string) error {
cPath := C.CString(path) cLinkpath := C.CString(linkpath)
defer C.free(unsafe.Pointer(cPath)) defer C.free(unsafe.Pointer(cLinkpath))
cTarget := C.CString(target) cTarget := C.CString(target)
defer C.free(unsafe.Pointer(cTarget)) defer C.free(unsafe.Pointer(cTarget))
err := C.pathrs_inroot_symlink(C.int(rootFd), cPath, cTarget) err := C.pathrs_inroot_symlink(cTarget, C.int(rootFd), cLinkpath)
return fetchError(err) return fetchError(err)
} }
// InRootHardlink wraps pathrs_inroot_hardlink. // InRootHardlink wraps pathrs_inroot_hardlink.
func InRootHardlink(rootFd uintptr, path, target string) error { func InRootHardlink(oldRootFd uintptr, oldPath string, newRootFd uintptr, newPath string, flags uint64) error {
cPath := C.CString(path) cNewPath := C.CString(newPath)
defer C.free(unsafe.Pointer(cPath)) defer C.free(unsafe.Pointer(cNewPath))
cTarget := C.CString(target) cOldPath := C.CString(oldPath)
defer C.free(unsafe.Pointer(cTarget)) defer C.free(unsafe.Pointer(cOldPath))
err := C.pathrs_inroot_hardlink(C.int(rootFd), cPath, cTarget) err := C.pathrs_inroot_hardlink(C.int(oldRootFd), cOldPath, C.int(newRootFd), cNewPath, C.uint64_t(flags))
return fetchError(err) return fetchError(err)
} }
@@ -277,13 +277,13 @@ func init() {
func ProcPid(pid uint32) ProcBase { return ProcBaseTypePid | ProcBase(pid) } func ProcPid(pid uint32) ProcBase { return ProcBaseTypePid | ProcBase(pid) }
// ProcOpenat wraps pathrs_proc_openat. // ProcOpenat wraps pathrs_proc_openat.
func ProcOpenat(procRootFd int, base ProcBase, path string, flags int) (uintptr, error) { func ProcOpenat(procRootFd int, base ProcBase, path string, flags uint64) (uintptr, error) {
cBase := C.pathrs_proc_base_t(base) cBase := C.pathrs_proc_base_t(base)
cPath := C.CString(path) cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath)) defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_proc_openat(C.int(procRootFd), cBase, cPath, C.int(flags)) fd := C.pathrs_proc_openat(C.int(procRootFd), cBase, cPath, C.uint64_t(flags))
return uintptr(fd), fetchError(fd) return uintptr(fd), fetchError(fd)
} }
@@ -335,3 +335,31 @@ func ProcfsOpen(how *ProcfsOpenHow) (uintptr, error) {
fd := C.pathrs_procfs_open((*C.pathrs_procfs_open_how)(how), C.size_t(unsafe.Sizeof(*how))) fd := C.pathrs_procfs_open((*C.pathrs_procfs_open_how)(how), C.size_t(unsafe.Sizeof(*how)))
return uintptr(fd), fetchError(fd) return uintptr(fd), fetchError(fd)
} }
// VersionInfo is a Go-friendly form of pathrs_version_info_t (struct).
type VersionInfo struct {
VersionString string
}
// versionInfo is pathrs_version_info_t (struct).
type versionInfo C.pathrs_version_info_t
// Version is pathrs_version_info_t (sizeof(version) is passed automatically).
func Version() (*VersionInfo, error) {
var rawVersion versionInfo
size := C.pathrs_version((*C.pathrs_version_info_t)(&rawVersion), C.size_t(unsafe.Sizeof(rawVersion)))
switch {
case size < 0:
return nil, fetchError(size)
case size > 0:
// TODO(log): Logging?
fallthrough
default:
// TODO(log): Add a log statement if sizeof(rawVersion) is bigger than
// the number of fields we store in VersionInfo. Otherwise a rebuild
// will mask that Go callers cannot see any new fields.
return &VersionInfo{
VersionString: C.GoString(rawVersion.version_string),
}, nil
}
}
+5 -5
View File
@@ -127,7 +127,7 @@ func (proc *Handle) fd() int {
} }
// TODO: Should we expose open? // TODO: Should we expose open?
func (proc *Handle) open(base ProcBase, path string, flags int) (_ *os.File, Closer ThreadCloser, Err error) { func (proc *Handle) open(base ProcBase, path string, flags uint64) (_ *os.File, Closer ThreadCloser, Err error) {
var closer ThreadCloser var closer ThreadCloser
if base == ProcThreadSelf { if base == ProcThreadSelf {
runtime.LockOSThread() runtime.LockOSThread()
@@ -154,7 +154,7 @@ func (proc *Handle) open(base ProcBase, path string, flags int) (_ *os.File, Clo
// (such as /proc/cpuinfo) or information about other processes (such as // (such as /proc/cpuinfo) or information about other processes (such as
// /proc/1). Accessing your own process information should be done using // /proc/1). Accessing your own process information should be done using
// [Handle.OpenSelf] or [Handle.OpenThreadSelf]. // [Handle.OpenSelf] or [Handle.OpenThreadSelf].
func (proc *Handle) OpenRoot(path string, flags int) (*os.File, error) { func (proc *Handle) OpenRoot(path string, flags uint64) (*os.File, error) {
file, closer, err := proc.open(ProcRoot, path, flags) file, closer, err := proc.open(ProcRoot, path, flags)
if closer != nil { if closer != nil {
// should not happen // should not happen
@@ -180,7 +180,7 @@ func (proc *Handle) OpenRoot(path string, flags int) (*os.File, error) {
// Unlike [Handle.OpenThreadSelf], this method does not involve locking // Unlike [Handle.OpenThreadSelf], this method does not involve locking
// the goroutine to the current OS thread and so is simpler to use and // the goroutine to the current OS thread and so is simpler to use and
// theoretically has slightly less overhead. // theoretically has slightly less overhead.
func (proc *Handle) OpenSelf(path string, flags int) (*os.File, error) { func (proc *Handle) OpenSelf(path string, flags uint64) (*os.File, error) {
file, closer, err := proc.open(ProcSelf, path, flags) file, closer, err := proc.open(ProcSelf, path, flags)
if closer != nil { if closer != nil {
// should not happen // should not happen
@@ -198,7 +198,7 @@ func (proc *Handle) OpenSelf(path string, flags int) (*os.File, error) {
// Be aware that due to PID recycling, using this is generally not safe except // Be aware that due to PID recycling, using this is generally not safe except
// in certain circumstances. See the documentation of [ProcPid] for more // in certain circumstances. See the documentation of [ProcPid] for more
// details. // details.
func (proc *Handle) OpenPid(pid int, path string, flags int) (*os.File, error) { func (proc *Handle) OpenPid(pid int, path string, flags uint64) (*os.File, error) {
file, closer, err := proc.open(ProcPid(pid), path, flags) file, closer, err := proc.open(ProcPid(pid), path, flags)
if closer != nil { if closer != nil {
// should not happen // should not happen
@@ -225,7 +225,7 @@ func (proc *Handle) OpenPid(pid int, path string, flags int) (*os.File, error) {
// callback MUST be called AFTER you have finished using the returned // callback MUST be called AFTER you have finished using the returned
// [os.File]. This callback is completely separate to [os.File.Close], so it // [os.File]. This callback is completely separate to [os.File.Close], so it
// must be called regardless of how you close the handle. // must be called regardless of how you close the handle.
func (proc *Handle) OpenThreadSelf(path string, flags int) (*os.File, ThreadCloser, error) { func (proc *Handle) OpenThreadSelf(path string, flags uint64) (*os.File, ThreadCloser, error) {
return proc.open(ProcThreadSelf, path, flags) return proc.open(ProcThreadSelf, path, flags)
} }
+28 -26
View File
@@ -19,6 +19,8 @@ import (
"os" "os"
"syscall" "syscall"
"golang.org/x/sys/unix"
"cyphar.com/go-pathrs/internal/fdutils" "cyphar.com/go-pathrs/internal/fdutils"
"cyphar.com/go-pathrs/internal/libpathrs" "cyphar.com/go-pathrs/internal/libpathrs"
) )
@@ -27,7 +29,7 @@ import (
// purpose of this "root handle" is to perform operations within the directory // purpose of this "root handle" is to perform operations within the directory
// tree, or to get a [Handle] to inodes within the directory tree. // tree, or to get a [Handle] to inodes within the directory tree.
// //
// At time of writing, it is considered a *VERY BAD IDEA* to open a [Root] // At time of writing, it is considered a *VERY BAD IDEA* to open a Root
// inside a possibly-attacker-controlled directory tree. While we do have // inside a possibly-attacker-controlled directory tree. While we do have
// protections that should defend against it, it's far more dangerous than just // protections that should defend against it, it's far more dangerous than just
// opening a directory tree which is not inside a potentially-untrusted // opening a directory tree which is not inside a potentially-untrusted
@@ -68,7 +70,7 @@ func RootFromFile(file *os.File) (*Root, error) {
// //
// All symlinks (including trailing symlinks) are followed, but they are // All symlinks (including trailing symlinks) are followed, but they are
// resolved within the rootfs. If you wish to open a handle to the symlink // resolved within the rootfs. If you wish to open a handle to the symlink
// itself, use [ResolveNoFollow]. // itself, use [Root.ResolveNoFollow].
func (r *Root) Resolve(path string) (*Handle, error) { func (r *Root) Resolve(path string) (*Handle, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) { return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
handleFd, err := libpathrs.InRootResolve(rootFd, path) handleFd, err := libpathrs.InRootResolve(rootFd, path)
@@ -83,10 +85,10 @@ func (r *Root) Resolve(path string) (*Handle, error) {
}) })
} }
// ResolveNoFollow is effectively an O_NOFOLLOW version of [Resolve]. Their // ResolveNoFollow is effectively an [unix.O_NOFOLLOW] version of
// behaviour is identical, except that *trailing* symlinks will not be // [Root.Resolve]. Their behaviour is identical, except that *trailing*
// followed. If the final component is a trailing symlink, an O_PATH|O_NOFOLLOW // symlinks will not be followed. If the final component is a trailing symlink,
// handle to the symlink itself is returned. // an [unix.O_PATH]|[unix.O_NOFOLLOW] handle to the symlink itself is returned.
func (r *Root) ResolveNoFollow(path string) (*Handle, error) { func (r *Root) ResolveNoFollow(path string) (*Handle, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) { return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
handleFd, err := libpathrs.InRootResolveNoFollow(rootFd, path) handleFd, err := libpathrs.InRootResolveNoFollow(rootFd, path)
@@ -101,29 +103,29 @@ func (r *Root) ResolveNoFollow(path string) (*Handle, error) {
}) })
} }
// Open is effectively shorthand for [Resolve] followed by [Handle.Open], but // Open is effectively shorthand for [Root.Resolve] followed by [Handle.Open],
// can be slightly more efficient (it reduces CGo overhead and the number of // but can be slightly more efficient (it reduces CGo overhead and the number
// syscalls used when using the openat2-based resolver) and is arguably more // of syscalls used when using the openat2-based resolver) and is arguably more
// ergonomic to use. // ergonomic to use.
// //
// This is effectively equivalent to [os.Open]. // This is effectively equivalent to [os.Open].
func (r *Root) Open(path string) (*os.File, error) { func (r *Root) Open(path string) (*os.File, error) {
return r.OpenFile(path, os.O_RDONLY) return r.OpenFile(path, unix.O_RDONLY)
} }
// OpenFile is effectively shorthand for [Resolve] followed by // OpenFile is effectively shorthand for [Root.Resolve] followed by
// [Handle.OpenFile], but can be slightly more efficient (it reduces CGo // [Handle.OpenFile], but can be slightly more efficient (it reduces CGo
// overhead and the number of syscalls used when using the openat2-based // overhead and the number of syscalls used when using the openat2-based
// resolver) and is arguably more ergonomic to use. // resolver) and is arguably more ergonomic to use.
// //
// However, if flags contains os.O_NOFOLLOW and the path is a symlink, then // However, if flags contains [unix.O_NOFOLLOW] and the path is a symlink, then
// OpenFile's behaviour will match that of openat2. In most cases an error will // OpenFile's behaviour will match that of openat2. In most cases an error will
// be returned, but if os.O_PATH is provided along with os.O_NOFOLLOW then a // be returned, but if [unix.O_PATH] is provided along with [unix.O_NOFOLLOW]
// file equivalent to [ResolveNoFollow] will be returned instead. // then a file equivalent to [Root.ResolveNoFollow] will be returned instead.
// //
// This is effectively equivalent to [os.OpenFile], except that os.O_CREAT is // This is effectively equivalent to [os.OpenFile], except that [unix.O_CREAT]
// not supported. // is not supported.
func (r *Root) OpenFile(path string, flags int) (*os.File, error) { func (r *Root) OpenFile(path string, flags uint64) (*os.File, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) { return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
fd, err := libpathrs.InRootOpen(rootFd, path, flags) fd, err := libpathrs.InRootOpen(rootFd, path, flags)
if err != nil { if err != nil {
@@ -139,7 +141,7 @@ func (r *Root) OpenFile(path string, flags int) (*os.File, error) {
// //
// Unlike [os.Create], if the file already exists an error is created rather // Unlike [os.Create], if the file already exists an error is created rather
// than the file being opened and truncated. // than the file being opened and truncated.
func (r *Root) Create(path string, flags int, mode os.FileMode) (*os.File, error) { func (r *Root) Create(path string, flags uint64, mode os.FileMode) (*os.File, error) {
unixMode, err := toUnixMode(mode, false) unixMode, err := toUnixMode(mode, false)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -155,9 +157,9 @@ func (r *Root) Create(path string, flags int, mode os.FileMode) (*os.File, error
// Rename two paths within a [Root]'s directory tree. The flags argument is // Rename two paths within a [Root]'s directory tree. The flags argument is
// identical to the RENAME_* flags to the renameat2(2) system call. // identical to the RENAME_* flags to the renameat2(2) system call.
func (r *Root) Rename(src, dst string, flags uint) error { func (r *Root) Rename(src, dst string, flags uint64) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootRename(rootFd, src, dst, flags) err := libpathrs.InRootRename(rootFd, src, rootFd, dst, flags)
return struct{}{}, err return struct{}{}, err
}) })
return err return err
@@ -277,26 +279,26 @@ func (r *Root) Mknod(path string, mode os.FileMode, dev uint64) error {
} }
// Symlink creates a symlink within a [Root]'s directory tree. The symlink is // Symlink creates a symlink within a [Root]'s directory tree. The symlink is
// created at path and is a link to target. // created at newname and is a link to oldname.
// //
// This is effectively equivalent to [os.Symlink]. // This is effectively equivalent to [os.Symlink].
func (r *Root) Symlink(path, target string) error { func (r *Root) Symlink(oldname, newname string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootSymlink(rootFd, path, target) err := libpathrs.InRootSymlink(oldname, rootFd, newname)
return struct{}{}, err return struct{}{}, err
}) })
return err return err
} }
// Hardlink creates a hardlink within a [Root]'s directory tree. The hardlink // Hardlink creates a hardlink within a [Root]'s directory tree. The hardlink
// is created at path and is a link to target. Both paths are within the // is created at newname and is a link to oldname. Both paths are within the
// [Root]'s directory tree (you cannot hardlink to a different [Root] or the // [Root]'s directory tree (you cannot hardlink to a different [Root] or the
// host). // host).
// //
// This is effectively equivalent to [os.Link]. // This is effectively equivalent to [os.Link].
func (r *Root) Hardlink(path, target string) error { func (r *Root) Hardlink(oldname, newname string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) { _, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootHardlink(rootFd, path, target) err := libpathrs.InRootHardlink(rootFd, oldname, rootFd, newname, 0)
return struct{}{}, err return struct{}{}, err
}) })
return err return err
+27
View File
@@ -0,0 +1,27 @@
//go:build linux
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* 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 (
"cyphar.com/go-pathrs/internal/libpathrs"
)
// LibraryVersionInfo contains information about the version and features
// supported by the underlying libpathrs.so library at runtime.
type LibraryVersionInfo = libpathrs.VersionInfo
// LibraryVersion returns information about the version and features supported
// by the underlying libpathrs.so library at runtime.
func LibraryVersion() (*LibraryVersionInfo, error) {
return libpathrs.Version()
}
+11
View File
@@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] ## ## [Unreleased] ##
## [0.7.0] - 2025-06-17 ##
> You talk of times of peace for all, and then prepare for war.
### Changed ###
- Update to `cyphar.com/go-pathrs@0.2.5`, which included a build-time API
breakage that we needed to work around. The API of this library is unchanged
by this, but users should make sure to update to `v0.7.0` of
`filepath-securejoin` if they use the `libpathrs` built tag and have update
to `libpathrs` `v0.2.5`.
## [0.6.1] - 2025-11-19 ## ## [0.6.1] - 2025-11-19 ##
> At last up jumped the cunning spider, and fiercely held her fast. > At last up jumped the cunning spider, and fiercely held her fast.
+1 -1
View File
@@ -1 +1 @@
0.6.1 0.7.0
@@ -53,5 +53,5 @@ func Reopen(file *os.File, flags int) (*os.File, error) {
} }
defer handle.Close() //nolint:errcheck // close failures aren't critical here defer handle.Close() //nolint:errcheck // close failures aren't critical here
return handle.OpenFile(flags) return handle.OpenFile(uint64(flags))
} }
+2 -2
View File
@@ -1,4 +1,4 @@
# cyphar.com/go-pathrs v0.2.4 # cyphar.com/go-pathrs v0.2.5
## explicit; go 1.18 ## explicit; go 1.18
cyphar.com/go-pathrs cyphar.com/go-pathrs
cyphar.com/go-pathrs/internal/fdutils cyphar.com/go-pathrs/internal/fdutils
@@ -33,7 +33,7 @@ github.com/containerd/console
# github.com/coreos/go-systemd/v22 v22.7.0 # github.com/coreos/go-systemd/v22 v22.7.0
## explicit; go 1.23 ## explicit; go 1.23
github.com/coreos/go-systemd/v22/dbus github.com/coreos/go-systemd/v22/dbus
# github.com/cyphar/filepath-securejoin v0.6.1 # github.com/cyphar/filepath-securejoin v0.7.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/cyphar/filepath-securejoin github.com/cyphar/filepath-securejoin
github.com/cyphar/filepath-securejoin/internal/consts github.com/cyphar/filepath-securejoin/internal/consts