Skip to content

Commit 3c5e239

Browse files
committed
*: update and improve godocs
Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 6df60ca commit 3c5e239

File tree

7 files changed

+76
-84
lines changed

7 files changed

+76
-84
lines changed

doc.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@
2121
// protections that are not provided by the legacy API. There are many more
2222
// operations that most programs expect to be able to do safely, but we do not
2323
// provide explicit support for them because we want to encourage users to
24-
// switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a
25-
// cross-language next-generation library that is entirely designed around
26-
// operating on paths safely.
24+
// switch to [libpathrs] which is a cross-language next-generation library that
25+
// is entirely designed around operating on paths safely.
2726
//
2827
// securejoin has been used by several container runtimes (Docker, runc,
2928
// Kubernetes, etc) for quite a few years as a de-facto standard for operating
@@ -33,9 +32,14 @@
3332
// API as soon as possible (or even better, switch to libpathrs).
3433
//
3534
// This project was initially intended to be included in the Go standard
36-
// library, but [it was rejected](https://go.dev/issue/20126). There is now a
37-
// [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API
38-
// that shares some of the goals of filepath-securejoin. However, that design
39-
// is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the
40-
// usecase of container runtimes and most system tools.
35+
// library, but it was rejected (see https://go.dev/issue/20126). Much later,
36+
// [os.Root] was added to the Go stdlib that shares some of the goals of
37+
// filepath-securejoin. However, its design is intended to work like
38+
// openat2(RESOLVE_BENEATH) which does not fit the usecase of container
39+
// runtimes and most system tools.
40+
//
41+
// [libpathrs]: https://github.com/openSUSE/libpathrs
42+
// [OpenInRoot]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#OpenInRoot
43+
// [MkdirAll]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#MkdirAll
44+
// [os.Root]; https:///pkg.go.dev/os#Root
4145
package securejoin

join.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ func hasDotDot(path string) bool {
5151
return strings.Contains("/"+path+"/", "/../")
5252
}
5353

54-
// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except
55-
// that the returned path is guaranteed to be scoped inside the provided root
56-
// path (when evaluated). Any symbolic links in the path are evaluated with the
57-
// given root treated as the root of the filesystem, similar to a chroot. The
58-
// filesystem state is evaluated through the given [VFS] interface (if nil, the
59-
// standard [os].* family of functions are used).
54+
// SecureJoinVFS joins the two given path components (similar to
55+
// [filepath.Join]) except that the returned path is guaranteed to be scoped
56+
// inside the provided root path (when evaluated). Any symbolic links in the
57+
// path are evaluated with the given root treated as the root of the
58+
// filesystem, similar to a chroot. The filesystem state is evaluated through
59+
// the given [VFS] interface (if nil, the standard [os].* family of functions
60+
// are used).
6061
//
6162
// Note that the guarantees provided by this function only apply if the path
6263
// components in the returned string are not modified (in other words are not

pathrs-lite/internal/fd/at_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) {
4040
// NOTE: If path is "." or "", the returned path won't be filepath.Clean,
4141
// but that's okay since this path is either used for errors (in which case
4242
// a trailing "/" or "/." is important information) or will be
43-
// filepath.Clean'd later (in the case of openatFile).
43+
// filepath.Clean'd later (in the case of fd.Openat).
4444
return dirFd, path
4545
}
4646

pathrs-lite/internal/procfs/procfs_linux.go

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -213,18 +213,7 @@ var getCachedProcRoot = gocompat.SyncOnceValue(func() *Handle {
213213
return procRoot
214214
})
215215

216-
// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the
217-
// "subset=pid" mount option applied, available from Linux 5.8). Unless you
218-
// plan to do many operations with [ProcRoot], users should prefer to use this
219-
// over [OpenUnsafeProcRoot] which is far more dangerous to keep open.
220-
//
221-
// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a
222-
// regular "/proc" handle.
223-
//
224-
// Note that using [ProcRoot] will still work with handles returned by this
225-
// function. If a [ProcRoot] subpath cannot be operated on with a safe "/proc"
226-
// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary
227-
// unsafe handle will be used.
216+
// OpenProcRoot tries to open a "safer" handle to "/proc".
228217
func OpenProcRoot() (*Handle, error) {
229218
if proc := getCachedProcRoot(); proc != nil {
230219
return proc, nil
@@ -233,16 +222,7 @@ func OpenProcRoot() (*Handle, error) {
233222
}
234223

235224
// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or
236-
// masked paths. You must be extremely careful to make sure this handle is
237-
// never leaked to a container and that you program cannot be tricked into
238-
// writing to arbitrary paths within it.
239-
//
240-
// This is not necessary if you just wish to use [ProcRoot], as handles
241-
// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe
242-
// handle in that case. You should only really use this if you need to do many
243-
// operations on [ProcRoot] and the performance overhead of making many procfs
244-
// handles is an issue, and you should make sure to close the handle as soon as
245-
// possible to avoid known-fd-number attacks.
225+
// masked paths (but also without "subset=pid").
246226
func OpenUnsafeProcRoot() (*Handle, error) { return getProcRoot(false) }
247227

248228
func getProcRoot(subset bool) (*Handle, error) {
@@ -328,7 +308,7 @@ func (base procfsBase) prefix(proc *Handle) (string, error) {
328308
// [os.File]: https://pkg.go.dev/os#File
329309
type ProcThreadSelfCloser func()
330310

331-
// Open is the core lookup operation for [Handle]. It returns a handle to
311+
// open is the core lookup operation for [Handle]. It returns a handle to
332312
// "/proc/<base>/<subpath>". If the returned [ProcThreadSelfCloser] is non-nil,
333313
// you should call it after you are done interacting with the returned handle.
334314
//
@@ -395,25 +375,13 @@ func (proc *Handle) OpenThreadSelf(subpath string) (_ *os.File, _ ProcThreadSelf
395375
}
396376

397377
// OpenSelf returns a handle to /proc/self/<subpath>.
398-
//
399-
// Note that in Go programs with non-homogenous threads, this may result in
400-
// spurious errors. If you are monkeying around with APIs that are
401-
// thread-specific, you probably want to use [ProcThreadSelf] instead which
402-
// will guarantee that the handle refers to the same thread as the caller is
403-
// executing on.
404378
func (proc *Handle) OpenSelf(subpath string) (*os.File, error) {
405379
file, closer, err := proc.open(ProcSelf, subpath)
406380
assert.Assert(closer == nil, "closer for ProcSelf must be nil")
407381
return file, err
408382
}
409383

410384
// OpenRoot returns a handle to /proc/<subpath>.
411-
//
412-
// You should only use this when you need to operate on global procfs files
413-
// (such as sysctls in /proc/sys). Unlike [OpenThreadSelf], [OpenSelf], and
414-
// [ProcPid], the procfs handle used internally for this operation will never
415-
// use subset=pids, which makes it a more juicy target for CVE-2024-21626-style
416-
// attacks.
417385
func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
418386
file, closer, err := proc.open(ProcRoot, subpath)
419387
assert.Assert(closer == nil, "closer for ProcRoot must be nil")
@@ -422,16 +390,6 @@ func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
422390

423391
// OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid).
424392
// This is mainly intended for usage when operating on other processes.
425-
//
426-
// You should not use this for the current thread, as special handling is
427-
// needed for /proc/thread-self (or /proc/self/task/<tid>) when dealing with
428-
// goroutine scheduling -- use [OpenThreadSelf] instead.
429-
//
430-
// To refer to the current thread-group, you should use prefer [OpenSelf] to
431-
// passing os.Getpid as the pid argument.
432-
//
433-
// If you want to operate on the top-level /proc filesystem, you should use
434-
// [OpenRoot] instead.
435393
func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) {
436394
return proc.OpenRoot(strconv.Itoa(pid) + "/" + subpath)
437395
}

pathrs-lite/mkdir_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
7272
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
7373
// doing [MkdirAll]. If you intend to open the directory after creating it, you
7474
// should use MkdirAllHandle.
75+
//
76+
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
7577
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) {
7678
unixMode, err := toUnixMode(mode)
7779
if err != nil {
@@ -226,6 +228,8 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
226228
// If you plan to open the directory after you have created it or want to use
227229
// an open directory handle as the root, you should use [MkdirAllHandle] instead.
228230
// This function is a wrapper around [MkdirAllHandle].
231+
//
232+
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
229233
func MkdirAll(root, unsafePath string, mode os.FileMode) error {
230234
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
231235
if err != nil {

pathrs-lite/open_linux.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
4747
// disconnected TTY that could cause a DoS, or some other issue). In order to
4848
// use the returned handle, you can "upgrade" it to a proper handle using
4949
// [Reopen].
50+
//
51+
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
5052
func OpenInRoot(root, unsafePath string) (*os.File, error) {
5153
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
5254
if err != nil {
@@ -101,7 +103,7 @@ func Reopen(handle *os.File, flags int) (*os.File, error) {
101103
}
102104

103105
flags |= unix.O_CLOEXEC
104-
// Rather than just wrapping openatFile, open-code it so we can copy
106+
// Rather than just wrapping fd.Openat, open-code it so we can copy
105107
// handle.Name().
106108
reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0)
107109
if err != nil {

pathrs-lite/procfs/procfs_linux.go

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ type Handle struct {
3737

3838
// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the
3939
// "subset=pid" mount option applied, available from Linux 5.8). Unless you
40-
// plan to do many operations with [ProcRoot], users should prefer to use this
41-
// over [OpenUnsafeProcRoot] which is far more dangerous to keep open.
40+
// plan to do many [Handle.OpenRoot] operations, users should prefer to use
41+
// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open.
4242
//
4343
// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a
4444
// regular "/proc" handle.
4545
//
46-
// Note that using [ProcRoot] will still work with handles returned by this
47-
// function. If a [ProcRoot] subpath cannot be operated on with a safe "/proc"
46+
// Note that using [Handle.OpenRoot] will still work with handles returned by
47+
// this function. If a subpath cannot be operated on with a safe "/proc"
4848
// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary
4949
// unsafe handle will be used.
5050
func OpenProcRoot() (*Handle, error) {
@@ -60,12 +60,13 @@ func OpenProcRoot() (*Handle, error) {
6060
// never leaked to a container and that you program cannot be tricked into
6161
// writing to arbitrary paths within it.
6262
//
63-
// This is not necessary if you just wish to use [ProcRoot], as handles
63+
// This is not necessary if you just wish to use [Handle.OpenRoot], as handles
6464
// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe
6565
// handle in that case. You should only really use this if you need to do many
66-
// operations on [ProcRoot] and the performance overhead of making many procfs
67-
// handles is an issue, and you should make sure to close the handle as soon as
68-
// possible to avoid known-fd-number attacks.
66+
// operations with [Handle.OpenRoot] and the performance overhead of making
67+
// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you
68+
// should make sure to close the handle as soon as possible to avoid
69+
// known-fd-number attacks.
6970
func OpenUnsafeProcRoot() (*Handle, error) {
7071
proc, err := procfs.OpenUnsafeProcRoot()
7172
if err != nil {
@@ -77,8 +78,10 @@ func OpenUnsafeProcRoot() (*Handle, error) {
7778
// OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an
7879
// equivalent handle on older kernels where "/proc/thread-self" doesn't exist).
7980
// Once finished with the handle, you must call the returned closer function
80-
// (runtime.UnlockOSThread). You must not pass the returned *os.File to other
81+
// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other
8182
// Go threads or use the handle after calling the closer.
83+
//
84+
// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread
8285
func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) {
8386
return proc.inner.OpenThreadSelf(subpath)
8487
}
@@ -87,20 +90,24 @@ func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfClos
8790
//
8891
// Note that in Go programs with non-homogenous threads, this may result in
8992
// spurious errors. If you are monkeying around with APIs that are
90-
// thread-specific, you probably want to use [ProcThreadSelf] instead which
91-
// will guarantee that the handle refers to the same thread as the caller is
92-
// executing on.
93+
// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead
94+
// which will guarantee that the handle refers to the same thread as the caller
95+
// is executing on.
9396
func (proc *Handle) OpenSelf(subpath string) (*os.File, error) {
9497
return proc.inner.OpenSelf(subpath)
9598
}
9699

97100
// OpenRoot returns a handle to /proc/<subpath>.
98101
//
99102
// You should only use this when you need to operate on global procfs files
100-
// (such as sysctls in /proc/sys). Unlike [OpenThreadSelf], [OpenSelf], and
101-
// [ProcPid], the procfs handle used internally for this operation will never
102-
// use subset=pids, which makes it a more juicy target for CVE-2024-21626-style
103-
// attacks.
103+
// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf],
104+
// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally
105+
// for this operation will never use "subset=pid", which makes it a more juicy
106+
// target for [CVE-2024-21626]-style attacks (and doing something like opening
107+
// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as
108+
// the file descriptor is open).
109+
//
110+
// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
104111
func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
105112
return proc.inner.OpenRoot(subpath)
106113
}
@@ -110,19 +117,35 @@ func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
110117
//
111118
// You should not use this for the current thread, as special handling is
112119
// needed for /proc/thread-self (or /proc/self/task/<tid>) when dealing with
113-
// goroutine scheduling -- use [OpenThreadSelf] instead.
114-
//
115-
// To refer to the current thread-group, you should use prefer [OpenSelf] to
116-
// passing os.Getpid as the pid argument.
120+
// goroutine scheduling -- use [Handle.OpenThreadSelf] instead.
117121
//
118-
// If you want to operate on the top-level /proc filesystem, you should use
119-
// [OpenRoot] instead.
122+
// To refer to the current thread-group, you should use prefer
123+
// [Handle.OpenSelf] to passing os.Getpid as the pid argument.
120124
func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) {
121125
return proc.inner.OpenPid(pid, subpath)
122126
}
123127

124128
// ProcSelfFdReadlink gets the real path of the given file by looking at
125-
// readlink(/proc/thread-self/fd/$n).
129+
// /proc/self/fd/<fd> with [readlink]. It is effectively just shorthand for
130+
// something along the lines of:
131+
//
132+
// proc, err := procfs.OpenProcRoot()
133+
// if err != nil {
134+
// return err
135+
// }
136+
// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd()))
137+
// if err != nil {
138+
// return err
139+
// }
140+
// defer link.Close()
141+
// var buf [4096]byte
142+
// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:])
143+
// if err != nil {
144+
// return err
145+
// }
146+
// pathname := buf[:n]
147+
//
148+
// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat
126149
func ProcSelfFdReadlink(f *os.File) (string, error) {
127150
return procfs.ProcSelfFdReadlink(f)
128151
}

0 commit comments

Comments
 (0)