Merge pull request #5333 from cyphar/1.4-libpathrs-0.2.5-5291

[1.4] deps: update to libpathrs v0.2.5
This commit is contained in:
Rodrigo Campos Catelin
2026-06-19 12:18:29 +02:00
committed by GitHub
17 changed files with 183 additions and 68 deletions
+12
View File
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased 1.4.z] ## [Unreleased 1.4.z]
### Added ###
- `runc version` and `runc features` now provide version information about
libpathrs when runc is built with the `libpathrs` build tag. (#5291, #5328)
### Changed ###
- For users making use of the `libpathrs` build tag, runc now depends on
[libpathrs v0.2.5] or later, and attempting to build with older versions will
cause compilation errors. (#5291, #5333)
[libpathrs v0.2.5]: https://github.com/cyphar/libpathrs/releases/tag/v0.2.5
## [1.4.3] - 2026-06-13 ## [1.4.3] - 2026-06-13
> The best way to irritate him is to feed his grandmother to the Ravenous > The best way to irritate him is to feed his grandmother to the Ravenous
+4
View File
@@ -93,6 +93,10 @@ var featuresCommand = cli.Command{
feat.Annotations[runcfeatures.AnnotationLibseccompVersion] = fmt.Sprintf("%d.%d.%d", major, minor, patch) feat.Annotations[runcfeatures.AnnotationLibseccompVersion] = fmt.Sprintf("%d.%d.%d", major, minor, patch)
} }
if v := pathrsVersionString(); v != "" {
feat.Annotations[runcfeatures.AnnotationLibpathrsVersion] = v
}
enc := json.NewEncoder(context.App.Writer) enc := json.NewEncoder(context.App.Writer)
enc.SetIndent("", " ") enc.SetIndent("", " ")
return enc.Encode(feat) return enc.Encode(feat)
+15
View File
@@ -0,0 +1,15 @@
//go:build libpathrs
package main
import (
"cyphar.com/go-pathrs"
)
func pathrsVersionString() string {
info, err := pathrs.LibraryVersion()
if err != nil {
panic(err) // should never happen
}
return info.VersionString
}
+7
View File
@@ -0,0 +1,7 @@
//go:build !libpathrs
package main
func pathrsVersionString() string {
return ""
}
+2 -2
View File
@@ -3,10 +3,11 @@ module github.com/opencontainers/runc
go 1.24.0 go 1.24.0
require ( require (
cyphar.com/go-pathrs v0.2.5
github.com/checkpoint-restore/go-criu/v7 v7.2.0 github.com/checkpoint-restore/go-criu/v7 v7.2.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.1.0 github.com/godbus/dbus/v5 v5.1.0
github.com/moby/sys/capability v0.4.0 github.com/moby/sys/capability v0.4.0
@@ -28,7 +29,6 @@ require (
) )
require ( require (
cyphar.com/go-pathrs v0.2.4 // indirect
github.com/cilium/ebpf v0.17.3 // indirect github.com/cilium/ebpf v0.17.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // 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/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo= github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo=
github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo= github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo=
@@ -11,8 +11,8 @@ github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj
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/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 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=
+4
View File
@@ -48,6 +48,10 @@ func printVersion(c *cli.Context) {
if major+minor+micro > 0 { if major+minor+micro > 0 {
fmt.Fprintf(w, "libseccomp: %d.%d.%d\n", major, minor, micro) fmt.Fprintf(w, "libseccomp: %d.%d.%d\n", major, minor, micro)
} }
if v := pathrsVersionString(); v != "" {
fmt.Fprintf(w, "libpathrs: %s\n", v)
}
} }
const ( const (
+3
View File
@@ -22,4 +22,7 @@ const (
// AnnotationLibseccompVersion is the version of libseccomp, e.g., "2.5.1". // AnnotationLibseccompVersion is the version of libseccomp, e.g., "2.5.1".
// Note that the runtime MAY support seccomp even when this annotation is not present. // Note that the runtime MAY support seccomp even when this annotation is not present.
AnnotationLibseccompVersion = "io.github.seccomp.libseccomp.version" AnnotationLibseccompVersion = "io.github.seccomp.libseccomp.version"
// AnnotationLibpathrsVersion is the runtime version of libpathrs.
AnnotationLibpathrsVersion = "com.cyphar.pathrs.libpathrs.version"
) )
+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
@@ -32,7 +32,7 @@ github.com/coreos/go-systemd/v22/dbus
# github.com/cpuguy83/go-md2man/v2 v2.0.7 # github.com/cpuguy83/go-md2man/v2 v2.0.7
## explicit; go 1.12 ## explicit; go 1.12
github.com/cpuguy83/go-md2man/v2/md2man github.com/cpuguy83/go-md2man/v2/md2man
# 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