|
| 1 | +// SPDX-License-Identifier: MPL-2.0 |
| 2 | + |
| 3 | +//go:build libpathrs |
| 4 | + |
| 5 | +// Copyright (C) 2024-2025 Aleksa Sarai <[email protected]> |
| 6 | +// Copyright (C) 2024-2025 SUSE LLC |
| 7 | +// |
| 8 | +// This Source Code Form is subject to the terms of the Mozilla Public |
| 9 | +// License, v. 2.0. If a copy of the MPL was not distributed with this |
| 10 | +// file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 11 | + |
| 12 | +// Package procfs provides a safe API for operating on /proc on Linux. |
| 13 | +package procfs |
| 14 | + |
| 15 | +import ( |
| 16 | + "os" |
| 17 | + "strconv" |
| 18 | + |
| 19 | + "cyphar.com/go-pathrs/procfs" |
| 20 | + "golang.org/x/sys/unix" |
| 21 | +) |
| 22 | + |
| 23 | +// ProcThreadSelfCloser is a callback that needs to be called when you are done |
| 24 | +// operating on an [os.File] fetched using [Handle.OpenThreadSelf]. |
| 25 | +// |
| 26 | +// [os.File]: https://pkg.go.dev/os#File |
| 27 | +type ProcThreadSelfCloser = procfs.ThreadCloser |
| 28 | + |
| 29 | +// Handle is a wrapper around an *os.File handle to "/proc", which can be used |
| 30 | +// to do further procfs-related operations in a safe way. |
| 31 | +type Handle struct { |
| 32 | + inner *procfs.Handle |
| 33 | +} |
| 34 | + |
| 35 | +// Close close the resources associated with this [Handle]. Note that if this |
| 36 | +// [Handle] was created with [OpenProcRoot], on some kernels the underlying |
| 37 | +// procfs handle is cached and so this Close operation may be a no-op. However, |
| 38 | +// you should always call Close on [Handle]s once you are done with them. |
| 39 | +func (proc *Handle) Close() error { return proc.inner.Close() } |
| 40 | + |
| 41 | +// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the |
| 42 | +// "subset=pid" mount option applied, available from Linux 5.8). Unless you |
| 43 | +// plan to do many [Handle.OpenRoot] operations, users should prefer to use |
| 44 | +// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open. |
| 45 | +// |
| 46 | +// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a |
| 47 | +// regular "/proc" handle. |
| 48 | +// |
| 49 | +// Note that using [Handle.OpenRoot] will still work with handles returned by |
| 50 | +// this function. If a subpath cannot be operated on with a safe "/proc" |
| 51 | +// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary |
| 52 | +// unsafe handle will be used. |
| 53 | +func OpenProcRoot() (*Handle, error) { |
| 54 | + proc, err := procfs.Open() |
| 55 | + if err != nil { |
| 56 | + return nil, err |
| 57 | + } |
| 58 | + return &Handle{inner: proc}, nil |
| 59 | +} |
| 60 | + |
| 61 | +// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or |
| 62 | +// masked paths. You must be extremely careful to make sure this handle is |
| 63 | +// never leaked to a container and that you program cannot be tricked into |
| 64 | +// writing to arbitrary paths within it. |
| 65 | +// |
| 66 | +// This is not necessary if you just wish to use [Handle.OpenRoot], as handles |
| 67 | +// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe |
| 68 | +// handle in that case. You should only really use this if you need to do many |
| 69 | +// operations with [Handle.OpenRoot] and the performance overhead of making |
| 70 | +// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you |
| 71 | +// should make sure to close the handle as soon as possible to avoid |
| 72 | +// known-fd-number attacks. |
| 73 | +func OpenUnsafeProcRoot() (*Handle, error) { |
| 74 | + proc, err := procfs.Open(procfs.UnmaskedProcRoot) |
| 75 | + if err != nil { |
| 76 | + return nil, err |
| 77 | + } |
| 78 | + return &Handle{inner: proc}, nil |
| 79 | +} |
| 80 | + |
| 81 | +// OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an |
| 82 | +// equivalent handle on older kernels where "/proc/thread-self" doesn't exist). |
| 83 | +// Once finished with the handle, you must call the returned closer function |
| 84 | +// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other |
| 85 | +// Go threads or use the handle after calling the closer. |
| 86 | +// |
| 87 | +// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread |
| 88 | +func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) { |
| 89 | + return proc.inner.OpenThreadSelf(subpath, unix.O_PATH|unix.O_NOFOLLOW) |
| 90 | +} |
| 91 | + |
| 92 | +// OpenSelf returns a handle to /proc/self/<subpath>. |
| 93 | +// |
| 94 | +// Note that in Go programs with non-homogenous threads, this may result in |
| 95 | +// spurious errors. If you are monkeying around with APIs that are |
| 96 | +// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead |
| 97 | +// which will guarantee that the handle refers to the same thread as the caller |
| 98 | +// is executing on. |
| 99 | +func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { |
| 100 | + return proc.inner.OpenSelf(subpath, unix.O_PATH|unix.O_NOFOLLOW) |
| 101 | +} |
| 102 | + |
| 103 | +// OpenRoot returns a handle to /proc/<subpath>. |
| 104 | +// |
| 105 | +// You should only use this when you need to operate on global procfs files |
| 106 | +// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf], |
| 107 | +// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally |
| 108 | +// for this operation will never use "subset=pid", which makes it a more juicy |
| 109 | +// target for [CVE-2024-21626]-style attacks (and doing something like opening |
| 110 | +// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as |
| 111 | +// the file descriptor is open). |
| 112 | +// |
| 113 | +// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv |
| 114 | +func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { |
| 115 | + return proc.inner.OpenRoot(subpath, unix.O_PATH|unix.O_NOFOLLOW) |
| 116 | +} |
| 117 | + |
| 118 | +// OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid). |
| 119 | +// This is mainly intended for usage when operating on other processes. |
| 120 | +// |
| 121 | +// You should not use this for the current thread, as special handling is |
| 122 | +// needed for /proc/thread-self (or /proc/self/task/<tid>) when dealing with |
| 123 | +// goroutine scheduling -- use [Handle.OpenThreadSelf] instead. |
| 124 | +// |
| 125 | +// To refer to the current thread-group, you should use prefer |
| 126 | +// [Handle.OpenSelf] to passing os.Getpid as the pid argument. |
| 127 | +func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { |
| 128 | + return proc.inner.OpenPid(pid, subpath, unix.O_PATH|unix.O_NOFOLLOW) |
| 129 | +} |
| 130 | + |
| 131 | +// ProcSelfFdReadlink gets the real path of the given file by looking at |
| 132 | +// /proc/self/fd/<fd> with [readlink]. It is effectively just shorthand for |
| 133 | +// something along the lines of: |
| 134 | +// |
| 135 | +// proc, err := procfs.OpenProcRoot() |
| 136 | +// if err != nil { |
| 137 | +// return err |
| 138 | +// } |
| 139 | +// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd())) |
| 140 | +// if err != nil { |
| 141 | +// return err |
| 142 | +// } |
| 143 | +// defer link.Close() |
| 144 | +// var buf [4096]byte |
| 145 | +// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:]) |
| 146 | +// if err != nil { |
| 147 | +// return err |
| 148 | +// } |
| 149 | +// pathname := buf[:n] |
| 150 | +// |
| 151 | +// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat |
| 152 | +func ProcSelfFdReadlink(f *os.File) (string, error) { |
| 153 | + proc, err := procfs.Open() |
| 154 | + if err != nil { |
| 155 | + return "", err |
| 156 | + } |
| 157 | + defer proc.Close() //nolint:errcheck // close failures aren't critical here |
| 158 | + |
| 159 | + fdPath := "fd/" + strconv.Itoa(int(f.Fd())) |
| 160 | + return proc.Readlink(procfs.ProcThreadSelf, fdPath) |
| 161 | +} |
0 commit comments