Revert "fix(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0"
This reverts commit e3468665bd.
filepath-securejoin v0.5.0 contains code licensed under MPL-2.0 which is
not approved per CNCF license rules by default. It requires an exception
which has not yet been granted, see
https://github.com/cncf/foundation/issues/1074
Signed-off-by: Paul Holzinger <pholzing@redhat.com>
			
			
This commit is contained in:
		
							parent
							
								
									ae46726d30
								
							
						
					
					
						commit
						82702b4938
					
				
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							|  | @ -9,7 +9,7 @@ require ( | |||
| 	github.com/containernetworking/cni v1.3.0 | ||||
| 	github.com/containers/luksy v0.0.0-20250714213221-8fccf784694e | ||||
| 	github.com/containers/ocicrypt v1.2.1 | ||||
| 	github.com/cyphar/filepath-securejoin v0.5.0 | ||||
| 	github.com/cyphar/filepath-securejoin v0.4.1 | ||||
| 	github.com/docker/distribution v2.8.3+incompatible | ||||
| 	github.com/docker/docker v28.4.0+incompatible | ||||
| 	github.com/docker/go-connections v0.6.0 | ||||
|  |  | |||
							
								
								
									
										4
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										4
									
								
								go.sum
								
								
								
								
							|  | @ -62,8 +62,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= | |||
| github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||||
| github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= | ||||
| github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= | ||||
| github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= | ||||
| github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= | ||||
| github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= | ||||
| github.com/cyphar/filepath-securejoin v0.4.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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= | ||||
|  |  | |||
|  | @ -1,56 +0,0 @@ | |||
| # 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 | ||||
|  | @ -6,122 +6,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). | |||
| 
 | ||||
| ## [Unreleased] ## | ||||
| 
 | ||||
| ## [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 ### | ||||
|  | @ -289,7 +173,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). | |||
|   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 | ||||
| [libpathrs]: https://github.com/openSUSE/libpathrs | ||||
| [open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html | ||||
| 
 | ||||
| ## [0.2.5] - 2024-05-03 ## | ||||
|  | @ -354,8 +238,7 @@ 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]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...HEAD | ||||
| [0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0 | ||||
| [Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD | ||||
| [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 | ||||
|  |  | |||
|  | @ -1,447 +0,0 @@ | |||
| ## 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. | ||||
| ``` | ||||
|  | @ -1,373 +0,0 @@ | |||
| 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. | ||||
|  | @ -67,8 +67,7 @@ func SecureJoin(root, unsafePath string) (string, error) { | |||
| [libpathrs]: https://github.com/openSUSE/libpathrs | ||||
| [go#20126]: https://github.com/golang/go/issues/20126 | ||||
| 
 | ||||
| ### <a name="new-api" /> 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 | ||||
|  | @ -166,19 +165,5 @@ after `MkdirAll`). | |||
| 
 | ||||
| ### License ### | ||||
| 
 | ||||
| `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 | ||||
| The license of this project is the same as Go, which is a BSD 3-clause license | ||||
| available in the `LICENSE` file. | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| 0.5.0 | ||||
| 0.4.1 | ||||
|  |  | |||
|  | @ -1,29 +0,0 @@ | |||
| # 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 | ||||
|  | @ -1,48 +0,0 @@ | |||
| // 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 | ||||
| ) | ||||
|  | @ -1,5 +1,3 @@ | |||
| // 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
 | ||||
|  | @ -16,13 +14,14 @@ | |||
| // **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.
 | ||||
| // The new API is made up of [OpenInRoot] and [MkdirAll] (and derived
 | ||||
| // functions). These are safe against racing attackers and have several other
 | ||||
| // protections that are not provided by the legacy API. There are many more
 | ||||
| // operations that most programs expect to be able to do safely, but we do not
 | ||||
| // provide explicit support for them because we want to encourage users to
 | ||||
| // switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a
 | ||||
| // cross-language next-generation library that is entirely designed around
 | ||||
| // operating on paths safely.
 | ||||
| //
 | ||||
| // securejoin has been used by several container runtimes (Docker, runc,
 | ||||
| // Kubernetes, etc) for quite a few years as a de-facto standard for operating
 | ||||
|  | @ -32,16 +31,9 @@ | |||
| // 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
 | ||||
| // library, but [it was rejected](https://go.dev/issue/20126). There is now a
 | ||||
| // [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API
 | ||||
| // that shares some of the goals of filepath-securejoin. However, that design
 | ||||
| // is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the
 | ||||
| // usecase of container runtimes and most system tools.
 | ||||
| package securejoin | ||||
|  |  | |||
|  | @ -1,19 +1,18 @@ | |||
| // 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 | ||||
| package securejoin | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
 | ||||
| // 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 { | ||||
| func wrapBaseError(baseErr, extraErr error) error { | ||||
| 	return fmt.Errorf("%w: %w", extraErr, baseErr) | ||||
| } | ||||
|  | @ -1,12 +1,10 @@ | |||
| // 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 | ||||
| package securejoin | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | @ -29,10 +27,10 @@ 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
 | ||||
| // 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 { | ||||
| func wrapBaseError(baseErr, extraErr error) error { | ||||
| 	return wrappedError{ | ||||
| 		inner:   baseErr, | ||||
| 		isError: extraErr, | ||||
							
								
								
									
										32
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										32
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,32 @@ | |||
| //go:build linux && go1.21
 | ||||
| 
 | ||||
| // 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 ( | ||||
| 	"slices" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| func slices_DeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S { | ||||
| 	return slices.DeleteFunc(slice, delFn) | ||||
| } | ||||
| 
 | ||||
| func slices_Contains[S ~[]E, E comparable](slice S, val E) bool { | ||||
| 	return slices.Contains(slice, val) | ||||
| } | ||||
| 
 | ||||
| func slices_Clone[S ~[]E, E any](slice S) S { | ||||
| 	return slices.Clone(slice) | ||||
| } | ||||
| 
 | ||||
| func sync_OnceValue[T any](f func() T) func() T { | ||||
| 	return sync.OnceValue(f) | ||||
| } | ||||
| 
 | ||||
| func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { | ||||
| 	return sync.OnceValues(f) | ||||
| } | ||||
							
								
								
									
										124
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										124
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,124 @@ | |||
| //go:build linux && !go1.21
 | ||||
| 
 | ||||
| // 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 ( | ||||
| 	"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 the builtin clear from Go 1.21.
 | ||||
| // 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 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Copied from the Go 1.24 stdlib implementation.
 | ||||
| func slices_IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { | ||||
| 	for i := range s { | ||||
| 		if f(s[i]) { | ||||
| 			return i | ||||
| 		} | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
| 
 | ||||
| // Copied from the Go 1.24 stdlib implementation.
 | ||||
| func slices_DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { | ||||
| 	i := slices_IndexFunc(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] | ||||
| } | ||||
| 
 | ||||
| // 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 slices_Contains[S ~[]E, E comparable](s S, v E) bool { | ||||
| 	return slices_IndexFunc(s, func(e E) bool { return e == v }) >= 0 | ||||
| } | ||||
| 
 | ||||
| // Copied from the Go 1.24 stdlib implementation.
 | ||||
| func slices_Clone[S ~[]E, E any](s S) S { | ||||
| 	// Preserve nil in case it matters.
 | ||||
| 	if s == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return append(S([]E{}), s...) | ||||
| } | ||||
| 
 | ||||
| // Copied from the Go 1.24 stdlib implementation.
 | ||||
| func sync_OnceValue[T any](f func() T) func() T { | ||||
| 	var ( | ||||
| 		once   sync.Once | ||||
| 		valid  bool | ||||
| 		p      any | ||||
| 		result T | ||||
| 	) | ||||
| 	g := func() { | ||||
| 		defer func() { | ||||
| 			p = recover() | ||||
| 			if !valid { | ||||
| 				panic(p) | ||||
| 			} | ||||
| 		}() | ||||
| 		result = f() | ||||
| 		f = nil | ||||
| 		valid = true | ||||
| 	} | ||||
| 	return func() T { | ||||
| 		once.Do(g) | ||||
| 		if !valid { | ||||
| 			panic(p) | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Copied from the Go 1.24 stdlib implementation.
 | ||||
| func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { | ||||
| 	var ( | ||||
| 		once  sync.Once | ||||
| 		valid bool | ||||
| 		p     any | ||||
| 		r1    T1 | ||||
| 		r2    T2 | ||||
| 	) | ||||
| 	g := func() { | ||||
| 		defer func() { | ||||
| 			p = recover() | ||||
| 			if !valid { | ||||
| 				panic(p) | ||||
| 			} | ||||
| 		}() | ||||
| 		r1, r2 = f() | ||||
| 		f = nil | ||||
| 		valid = true | ||||
| 	} | ||||
| 	return func() (T1, T2) { | ||||
| 		once.Do(g) | ||||
| 		if !valid { | ||||
| 			panic(p) | ||||
| 		} | ||||
| 		return r1, r2 | ||||
| 	} | ||||
| } | ||||
|  | @ -1,15 +0,0 @@ | |||
| // 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 | ||||
|  | @ -1,5 +1,3 @@ | |||
| // 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
 | ||||
|  | @ -13,10 +11,10 @@ import ( | |||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/cyphar/filepath-securejoin/internal/consts" | ||||
| ) | ||||
| 
 | ||||
| const maxSymlinkLimit = 255 | ||||
| 
 | ||||
| // 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].
 | ||||
|  | @ -51,13 +49,12 @@ func hasDotDot(path string) bool { | |||
| 	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).
 | ||||
| // 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
 | ||||
|  | @ -81,7 +78,7 @@ func hasDotDot(path string) bool { | |||
| // 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
 | ||||
| func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { | ||||
| 	// 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
 | ||||
|  | @ -141,7 +138,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint: | |||
| 		// It's a symlink, so get its contents and expand it by prepending it
 | ||||
| 		// to the yet-unparsed path.
 | ||||
| 		linksWalked++ | ||||
| 		if linksWalked > consts.MaxSymlinkLimit { | ||||
| 		if linksWalked > maxSymlinkLimit { | ||||
| 			return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,15 +1,10 @@ | |||
| // 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/.
 | ||||
| // 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 pathrs | ||||
| package securejoin | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
|  | @ -20,12 +15,6 @@ import ( | |||
| 	"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 { | ||||
|  | @ -123,12 +112,12 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro | |||
| 		return nil | ||||
| 	} | ||||
| 	// Split the link target and clean up any "" parts.
 | ||||
| 	linkTargetParts := gocompat.SlicesDeleteFunc( | ||||
| 	linkTargetParts := slices_DeleteFunc( | ||||
| 		strings.Split(linkTarget, "/"), | ||||
| 		func(part string) bool { return part == "" || part == "." }) | ||||
| 
 | ||||
| 	// Copy the directory so the caller doesn't close our copy.
 | ||||
| 	dirCopy, err := fd.Dup(dir) | ||||
| 	dirCopy, err := dupFile(dir) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -170,11 +159,11 @@ 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 fd.Fd, unsafePath string) (*os.File, string, error) { | ||||
| func partialLookupInRoot(root *os.File, unsafePath string) (*os.File, string, error) { | ||||
| 	return lookupInRoot(root, unsafePath, true) | ||||
| } | ||||
| 
 | ||||
| func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) { | ||||
| func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) { | ||||
| 	handle, remainingPath, err := lookupInRoot(root, unsafePath, false) | ||||
| 	if remainingPath != "" && err == nil { | ||||
| 		// should never happen
 | ||||
|  | @ -185,7 +174,7 @@ func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) { | |||
| 	return handle, err | ||||
| } | ||||
| 
 | ||||
| func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) { | ||||
| func lookupInRoot(root *os.File, 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
 | ||||
|  | @ -193,20 +182,20 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, | |||
| 	// managed open, along with the remaining path components not opened.
 | ||||
| 
 | ||||
| 	// Try to use openat2 if possible.
 | ||||
| 	if linux.HasOpenat2() { | ||||
| 	if 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 procfs.CheckProcSelfFdPath that we are using the
 | ||||
| 	// correct root path.
 | ||||
| 	logicalRootPath, err := procfs.ProcSelfFdReadlink(root) | ||||
| 	// make sure when we do checkProcSelfFdPath that we are using the correct
 | ||||
| 	// root path.
 | ||||
| 	logicalRootPath, err := procSelfFdReadlink(root) | ||||
| 	if err != nil { | ||||
| 		return nil, "", fmt.Errorf("get real root path: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	currentDir, err := fd.Dup(root) | ||||
| 	currentDir, err := dupFile(root) | ||||
| 	if err != nil { | ||||
| 		return nil, "", fmt.Errorf("clone root fd: %w", err) | ||||
| 	} | ||||
|  | @ -271,7 +260,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, | |||
| 				return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err) | ||||
| 			} | ||||
| 			// Jump to root.
 | ||||
| 			rootClone, err := fd.Dup(root) | ||||
| 			rootClone, err := dupFile(root) | ||||
| 			if err != nil { | ||||
| 				return nil, "", fmt.Errorf("clone root fd: %w", err) | ||||
| 			} | ||||
|  | @ -282,21 +271,21 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, | |||
| 		} | ||||
| 
 | ||||
| 		// Try to open the next component.
 | ||||
| 		nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		switch err { | ||||
| 		case nil: | ||||
| 		nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		switch { | ||||
| 		case err == 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 { //nolint:exhaustive // just a glorified if statement
 | ||||
| 			switch st.Mode() & os.ModeType { | ||||
| 			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, "") | ||||
| 				linkDest, err := readlinkatFile(nextDir, "") | ||||
| 				// We don't need the handle anymore.
 | ||||
| 				_ = nextDir.Close() | ||||
| 				if err != nil { | ||||
|  | @ -304,7 +293,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, | |||
| 				} | ||||
| 
 | ||||
| 				linksWalked++ | ||||
| 				if linksWalked > consts.MaxSymlinkLimit { | ||||
| 				if linksWalked > maxSymlinkLimit { | ||||
| 					return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP} | ||||
| 				} | ||||
| 
 | ||||
|  | @ -318,7 +307,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, | |||
| 				// Absolute symlinks reset any work we've already done.
 | ||||
| 				if path.IsAbs(linkDest) { | ||||
| 					// Jump to root.
 | ||||
| 					rootClone, err := fd.Dup(root) | ||||
| 					rootClone, err := dupFile(root) | ||||
| 					if err != nil { | ||||
| 						return nil, "", fmt.Errorf("clone root fd: %w", err) | ||||
| 					} | ||||
|  | @ -346,12 +335,12 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, | |||
| 				// rename or mount on the system.
 | ||||
| 				if part == ".." { | ||||
| 					// Make sure the root hasn't moved.
 | ||||
| 					if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil { | ||||
| 					if err := 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 := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil { | ||||
| 					if err := checkProcSelfFdPath(fullPath, currentDir); err != nil { | ||||
| 						return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err) | ||||
| 					} | ||||
| 				} | ||||
|  | @ -382,7 +371,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, | |||
| 	// 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) | ||||
| 		nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		if err != nil { | ||||
| 			if !partial { | ||||
| 				_ = currentDir.Close() | ||||
|  | @ -1,15 +1,10 @@ | |||
| // 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/.
 | ||||
| // 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 pathrs | ||||
| package securejoin | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
|  | @ -19,13 +14,12 @@ import ( | |||
| 	"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") | ||||
| var ( | ||||
| 	errInvalidMode    = errors.New("invalid permission mode") | ||||
| 	errPossibleAttack = errors.New("possible attack detected") | ||||
| ) | ||||
| 
 | ||||
| // modePermExt is like os.ModePerm except that it also includes the set[ug]id
 | ||||
| // and sticky bits.
 | ||||
|  | @ -72,8 +66,6 @@ func toUnixMode(mode os.FileMode) (uint32, error) { | |||
| // 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 { | ||||
|  | @ -110,7 +102,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | |||
| 	//
 | ||||
| 	// 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 { | ||||
| 	if err := isDeadInode(currentDir); err != nil { | ||||
| 		return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -121,13 +113,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | |||
| 		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
 | ||||
| 	} else { | ||||
| 		_ = currentDir.Close() | ||||
| 		currentDir = reopenDir | ||||
| 	} | ||||
| 
 | ||||
| 	remainingParts := strings.Split(remainingPath, string(filepath.Separator)) | ||||
| 	if gocompat.SlicesContains(remainingParts, "..") { | ||||
| 	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
 | ||||
|  | @ -158,12 +150,12 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | |||
| 		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 { | ||||
| 			if deadErr := 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) | ||||
| 				err = wrapBaseError(err, deadErr) | ||||
| 			} | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | @ -171,13 +163,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | |||
| 		// 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{ | ||||
| 		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 = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 			nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
|  | @ -228,14 +220,12 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | |||
| // 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
 | ||||
| 	defer rootDir.Close() | ||||
| 
 | ||||
| 	f, err := MkdirAllHandle(rootDir, unsafePath, mode) | ||||
| 	if err != nil { | ||||
|  | @ -1,22 +1,17 @@ | |||
| // 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/.
 | ||||
| // 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 pathrs | ||||
| package securejoin | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"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
 | ||||
|  | @ -45,14 +40,12 @@ func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { | |||
| // 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].
 | ||||
| //
 | ||||
| // [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() //nolint:errcheck // close failures aren't critical here
 | ||||
| 	defer rootDir.Close() | ||||
| 	return OpenatInRoot(rootDir, unsafePath) | ||||
| } | ||||
| 
 | ||||
|  | @ -70,5 +63,41 @@ func OpenInRoot(root, unsafePath string) (*os.File, error) { | |||
| //
 | ||||
| // [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
 | ||||
| func Reopen(handle *os.File, flags int) (*os.File, error) { | ||||
| 	return procfs.ReopenFd(handle, flags) | ||||
| 	procRoot, err := getProcRoot() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// 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 := procThreadSelf(procRoot, "fd/") | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err) | ||||
| 	} | ||||
| 	defer procFdDir.Close() | ||||
| 	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 := checkSymlinkOvermount(procRoot, 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 openatFile, 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 | ||||
| } | ||||
|  | @ -0,0 +1,127 @@ | |||
| //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" | ||||
| 
 | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
| 
 | ||||
| var hasOpenat2 = sync_OnceValue(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 | ||||
| }) | ||||
| 
 | ||||
| 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} | ||||
| } | ||||
| 
 | ||||
| func lookupOpenat2(root *os.File, unsafePath string, partial bool) (*os.File, string, error) { | ||||
| 	if !partial { | ||||
| 		file, err := openat2File(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 *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) | ||||
| 	var lastError error | ||||
| 	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:], 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 := dupFile(root) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 	return rootClone, unsafePath, lastError | ||||
| } | ||||
|  | @ -0,0 +1,59 @@ | |||
| //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 | ||||
| 	} | ||||
| } | ||||
|  | @ -1,33 +0,0 @@ | |||
| ## `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 | ||||
|  | @ -1,14 +0,0 @@ | |||
| // 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 | ||||
							
								
								
									
										30
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										30
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,30 +0,0 @@ | |||
| // 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...)) | ||||
| } | ||||
|  | @ -1,30 +0,0 @@ | |||
| // 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 internal contains unexported common code for filepath-securejoin.
 | ||||
| package internal | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// ErrPossibleAttack indicates that some attack was detected.
 | ||||
| 	ErrPossibleAttack = errors.New("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 = errors.New("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") | ||||
| ) | ||||
							
								
								
									
										148
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										148
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,148 +0,0 @@ | |||
| // 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 | ||||
| } | ||||
|  | @ -1,55 +0,0 @@ | |||
| // 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} } | ||||
							
								
								
									
										78
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										78
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,78 +0,0 @@ | |||
| // 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 | ||||
| } | ||||
							
								
								
									
										54
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										54
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,54 +0,0 @@ | |||
| // 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 | ||||
| } | ||||
							
								
								
									
										62
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										62
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,62 +0,0 @@ | |||
| // 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" | ||||
| 
 | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal" | ||||
| ) | ||||
| 
 | ||||
| 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 = 32 | ||||
| 
 | ||||
| // 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 tries < scopedLookupMaxRetries { | ||||
| 		fd, err := unix.Openat2(dirFd, 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} | ||||
| 		} | ||||
| 		runtime.KeepAlive(dir) | ||||
| 		return os.NewFile(uintptr(fd), fullPath), nil | ||||
| 	} | ||||
| 	return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: internal.ErrPossibleAttack} | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										10
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,10 +0,0 @@ | |||
| ## 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. | ||||
							
								
								
									
										13
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										13
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,13 +0,0 @@ | |||
| // 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 | ||||
|  | @ -1,53 +0,0 @@ | |||
| // 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) | ||||
| } | ||||
|  | @ -1,187 +0,0 @@ | |||
| // 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 | ||||
| } | ||||
|  | @ -1,123 +0,0 @@ | |||
| // 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 | ||||
| } | ||||
							
								
								
									
										12
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										12
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,12 +0,0 @@ | |||
| // 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 | ||||
							
								
								
									
										47
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										47
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,47 +0,0 @@ | |||
| // 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 | ||||
| }) | ||||
							
								
								
									
										31
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										31
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,31 +0,0 @@ | |||
| // 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 | ||||
| }) | ||||
							
								
								
									
										544
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										544
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,544 +0,0 @@ | |||
| // 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 } | ||||
|  | @ -1,222 +0,0 @@ | |||
| // 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 | ||||
| } | ||||
|  | @ -1,101 +0,0 @@ | |||
| // 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 | ||||
| } | ||||
							
								
								
									
										157
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
								
								
									generated
								
								
									vendored
								
								
							
							
						
						
									
										157
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
								
								
									generated
								
								
									vendored
								
								
							|  | @ -1,157 +0,0 @@ | |||
| // 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) | ||||
| } | ||||
|  | @ -0,0 +1,452 @@ | |||
| //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" | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"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 hasNewMountApi = sync_OnceValue(func() bool { | ||||
| 	// 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 { | ||||
| 		return false | ||||
| 	} | ||||
| 	_ = unix.Close(fd) | ||||
| 	return true | ||||
| }) | ||||
| 
 | ||||
| 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 || hookForcePrivateProcRootOpenTreeAtRecursive(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() || 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() | ||||
| 	if err != nil || hookForcePrivateProcRootOpenTree(procRoot) { | ||||
| 		// Try to clone /proc then...
 | ||||
| 		procRoot, err = clonePrivateProcMount() | ||||
| 	} | ||||
| 	return procRoot, err | ||||
| } | ||||
| 
 | ||||
| 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 { | ||||
| 		// 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 | ||||
| } | ||||
| 
 | ||||
| var getProcRoot = sync_OnceValues(func() (*os.File, error) { | ||||
| 	return doGetProcRoot() | ||||
| }) | ||||
| 
 | ||||
| var hasProcThreadSelf = sync_OnceValue(func() bool { | ||||
| 	return unix.Access("/proc/thread-self/", unix.F_OK) == nil | ||||
| }) | ||||
| 
 | ||||
| var errUnsafeProcfs = errors.New("unsafe procfs detected") | ||||
| 
 | ||||
| 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) { | ||||
| 	// 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 !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 := fstatatFile(procRoot, threadSelf, 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/" | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// 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
 | ||||
| 		// 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.
 | ||||
| 		//
 | ||||
| 		// 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_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, nil, wrapBaseError(err, errUnsafeProcfs) | ||||
| 		} | ||||
| 	} else { | ||||
| 		handle, err = openatFile(procRoot, threadSelf+subpath, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		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, nil, wrapBaseError(err, errUnsafeProcfs) | ||||
| 		} | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| // 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.
 | ||||
| const STATX_MNT_ID_UNIQUE = 0x4000 | ||||
| 
 | ||||
| var hasStatxMountId = sync_OnceValue(func() bool { | ||||
| 	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 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID | ||||
| 	) | ||||
| 	err := unix.Statx(-int(unix.EBADF), "/", 0, int(wantStxMask), &stx) | ||||
| 	return err == nil && stx.Mask&wantStxMask != 0 | ||||
| }) | ||||
| 
 | ||||
| func getMountId(dir *os.File, path string) (uint64, error) { | ||||
| 	// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
 | ||||
| 	if !hasStatxMountId() { | ||||
| 		return 0, 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 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID | ||||
| 	) | ||||
| 
 | ||||
| 	err := unix.Statx(int(dir.Fd()), path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, int(wantStxMask), &stx) | ||||
| 	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.
 | ||||
| 		err = fmt.Errorf("%w: could not get mount id", errUnsafeProcfs) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: dir.Name() + "/" + path, Err: err} | ||||
| 	} | ||||
| 	return stx.Mnt_id, nil | ||||
| } | ||||
| 
 | ||||
| func checkSymlinkOvermount(procRoot *os.File, dir *os.File, path string) error { | ||||
| 	// Get the mntId of our procfs handle.
 | ||||
| 	expectedMountId, err := getMountId(procRoot, "") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// Get the mntId of the target magic-link.
 | ||||
| 	gotMountId, err := getMountId(dir, path) | ||||
| 	if err != nil { | ||||
| 		return 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: 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 doRawProcSelfFdReadlink(procRoot *os.File, fd int) (string, error) { | ||||
| 	fdPath := fmt.Sprintf("fd/%d", fd) | ||||
| 	procFdLink, closer, err := procThreadSelf(procRoot, fdPath) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("get safe /proc/thread-self/%s handle: %w", fdPath, err) | ||||
| 	} | ||||
| 	defer procFdLink.Close() | ||||
| 	defer closer() | ||||
| 
 | ||||
| 	// Try to detect if there is a mount on top of the magic-link. Since we use the handle directly
 | ||||
| 	// provide to the closure. If the closure uses the handle directly, 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 := checkSymlinkOvermount(procRoot, procFdLink, ""); err != nil { | ||||
| 		return "", fmt.Errorf("check safety of /proc/thread-self/fd/%d magiclink: %w", fd, err) | ||||
| 	} | ||||
| 
 | ||||
| 	// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit
 | ||||
| 	// 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty
 | ||||
| 	// relative pathnames").
 | ||||
| 	return readlinkatFile(procFdLink, "") | ||||
| } | ||||
| 
 | ||||
| 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 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 | ||||
| } | ||||
| 
 | ||||
| // 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(_ *os.File) bool { return false } | ||||
|  | @ -1,5 +1,3 @@ | |||
| // 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.
 | ||||
|  |  | |||
|  | @ -93,19 +93,9 @@ github.com/coreos/go-systemd/v22/dbus | |||
| # github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 | ||||
| ## explicit | ||||
| github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer | ||||
| # github.com/cyphar/filepath-securejoin v0.5.0 | ||||
| # github.com/cyphar/filepath-securejoin v0.4.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.2-0.20180830191138-d8f796af33cc | ||||
| ## explicit | ||||
| github.com/davecgh/go-spew/spew | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue