mirror of
https://github.com/moby/moby.git
synced 2026-01-11 10:41:43 +00:00
vendor: github.com/opencontainers/selinux v1.13.0
full diff: https://github.com/opencontainers/selinux/compare/v1.12.0...v1.13.0 Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
7
go.mod
7
go.mod
@@ -82,7 +82,7 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
github.com/opencontainers/runtime-spec v1.2.1
|
github.com/opencontainers/runtime-spec v1.2.1
|
||||||
github.com/opencontainers/selinux v1.12.0
|
github.com/opencontainers/selinux v1.13.0
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
@@ -122,6 +122,7 @@ require (
|
|||||||
cloud.google.com/go/auth v0.9.3 // indirect
|
cloud.google.com/go/auth v0.9.3 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||||
cloud.google.com/go/longrunning v0.6.1 // indirect
|
cloud.google.com/go/longrunning v0.6.1 // indirect
|
||||||
|
cyphar.com/go-pathrs v0.2.1 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 // indirect
|
||||||
@@ -155,7 +156,7 @@ require (
|
|||||||
github.com/containerd/ttrpc v1.2.7 // indirect
|
github.com/containerd/ttrpc v1.2.7 // indirect
|
||||||
github.com/containernetworking/cni v1.3.0 // indirect
|
github.com/containernetworking/cni v1.3.0 // indirect
|
||||||
github.com/containernetworking/plugins v1.7.1 // indirect
|
github.com/containernetworking/plugins v1.7.1 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
|
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 // indirect
|
||||||
@@ -207,7 +208,7 @@ require (
|
|||||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
||||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||||
github.com/spdx/tools-golang v0.5.5 // indirect
|
github.com/spdx/tools-golang v0.5.5 // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/tinylib/msgp v1.3.0 // indirect
|
github.com/tinylib/msgp v1.3.0 // indirect
|
||||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
|
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -16,6 +16,8 @@ cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTS
|
|||||||
cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0=
|
cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0=
|
||||||
code.cloudfoundry.org/clock v1.37.0 h1:7e/FmrQ8f3cJW1aR4jhKWaEimBp5Ub39dOeNXQHq8HM=
|
code.cloudfoundry.org/clock v1.37.0 h1:7e/FmrQ8f3cJW1aR4jhKWaEimBp5Ub39dOeNXQHq8HM=
|
||||||
code.cloudfoundry.org/clock v1.37.0/go.mod h1:9bvV2riUok6o34gOGGVIkX1v37wwsZbuSCBx8Y4laL0=
|
code.cloudfoundry.org/clock v1.37.0/go.mod h1:9bvV2riUok6o34gOGGVIkX1v37wwsZbuSCBx8Y4laL0=
|
||||||
|
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
|
||||||
|
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||||
@@ -166,8 +168,8 @@ github.com/cpuguy83/tar2go v0.3.1 h1:DMWlaIyoh9FBWR4hyfZSOEDA7z8rmCiGF1IJIzlTlR8
|
|||||||
github.com/cpuguy83/tar2go v0.3.1/go.mod h1:2Ys2/Hu+iPHQRa4DjIVJ7UAaKnDhAhNACeK3A0Rr5rM=
|
github.com/cpuguy83/tar2go v0.3.1/go.mod h1:2Ys2/Hu+iPHQRa4DjIVJ7UAaKnDhAhNACeK3A0Rr5rM=
|
||||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/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=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -470,8 +472,8 @@ github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU
|
|||||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 h1:2xZEHOdeQBV6PW8ZtimN863bIOl7OCW/X10K0cnxKeA=
|
github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 h1:2xZEHOdeQBV6PW8ZtimN863bIOl7OCW/X10K0cnxKeA=
|
||||||
github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2/go.mod h1:MXdPzqAA8pHC58USHqNCSjyLnRQ6D+NjbpP+02Z1U/0=
|
github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2/go.mod h1:MXdPzqAA8pHC58USHqNCSjyLnRQ6D+NjbpP+02Z1U/0=
|
||||||
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84=
|
||||||
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s=
|
||||||
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
|
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
|
||||||
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
|
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
@@ -572,8 +574,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26 h1:mWCRvpoEMVlslxEvvptKgIUb35va9yj9Oq5wGw/er5I=
|
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26 h1:mWCRvpoEMVlslxEvvptKgIUb35va9yj9Oq5wGw/er5I=
|
||||||
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26/go.mod h1:0uD3VMXkZ7Bw0ojGCwDzebBBzPBXtzEZeXai+56BLX4=
|
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26/go.mod h1:0uD3VMXkZ7Bw0ojGCwDzebBBzPBXtzEZeXai+56BLX4=
|
||||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||||
|
|||||||
43
vendor/cyphar.com/go-pathrs/.golangci.yml
generated
vendored
Normal file
43
vendor/cyphar.com/go-pathrs/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
#
|
||||||
|
# libpathrs: safe path resolution on Linux
|
||||||
|
# Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
# Copyright (C) 2019-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:
|
||||||
|
- bidichk
|
||||||
|
- cyclop
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
- goconst
|
||||||
|
- godot
|
||||||
|
- gomoddirectives
|
||||||
|
- gosec
|
||||||
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- mnd
|
||||||
|
- nilerr
|
||||||
|
- nilnil
|
||||||
|
- perfsprint
|
||||||
|
- prealloc
|
||||||
|
- reassign
|
||||||
|
- revive
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
- wastedassign
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
settings:
|
||||||
|
goimports:
|
||||||
|
local-prefixes:
|
||||||
|
- cyphar.com/go-pathrs
|
||||||
373
vendor/cyphar.com/go-pathrs/COPYING
generated
vendored
Normal file
373
vendor/cyphar.com/go-pathrs/COPYING
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
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.
|
||||||
14
vendor/cyphar.com/go-pathrs/doc.go
generated
vendored
Normal file
14
vendor/cyphar.com/go-pathrs/doc.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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 provides bindings for libpathrs, a library for safe path
|
||||||
|
// resolution on Linux.
|
||||||
|
package pathrs
|
||||||
114
vendor/cyphar.com/go-pathrs/handle_linux.go
generated
vendored
Normal file
114
vendor/cyphar.com/go-pathrs/handle_linux.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"cyphar.com/go-pathrs/internal/fdutils"
|
||||||
|
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle is a handle for a path within a given [Root]. This handle references
|
||||||
|
// an already-resolved path which can be used for only one purpose -- to
|
||||||
|
// "re-open" the handle and get an actual [os.File] which can be used for
|
||||||
|
// ordinary operations.
|
||||||
|
//
|
||||||
|
// If you wish to open a file without having an intermediate [Handle] object,
|
||||||
|
// you can try to use [Root.Open] or [Root.OpenFile].
|
||||||
|
//
|
||||||
|
// It is critical that perform all relevant operations through this [Handle]
|
||||||
|
// (rather than fetching the file descriptor yourself with [Handle.IntoRaw]),
|
||||||
|
// because the security properties of libpathrs depend on users doing all
|
||||||
|
// relevant filesystem operations through libpathrs.
|
||||||
|
//
|
||||||
|
// [os.File]: https://pkg.go.dev/os#File
|
||||||
|
type Handle struct {
|
||||||
|
inner *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFromFile creates a new [Handle] from an existing file handle. The
|
||||||
|
// handle will be copied by this method, so the original handle should still be
|
||||||
|
// freed by the caller.
|
||||||
|
//
|
||||||
|
// This is effectively the inverse operation of [Handle.IntoRaw], and is used
|
||||||
|
// for "deserialising" pathrs root handles.
|
||||||
|
func HandleFromFile(file *os.File) (*Handle, error) {
|
||||||
|
newFile, err := fdutils.DupFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("duplicate handle fd: %w", err)
|
||||||
|
}
|
||||||
|
return &Handle{inner: newFile}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open creates an "upgraded" file handle to the file referenced by the
|
||||||
|
// [Handle]. Note that the original [Handle] is not consumed by this operation,
|
||||||
|
// and can be opened multiple times.
|
||||||
|
//
|
||||||
|
// The handle returned is only usable for reading, and this is method is
|
||||||
|
// shorthand for [Handle.OpenFile] with os.O_RDONLY.
|
||||||
|
//
|
||||||
|
// TODO: Rename these to "Reopen" or something.
|
||||||
|
func (h *Handle) Open() (*os.File, error) {
|
||||||
|
return h.OpenFile(os.O_RDONLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFile creates an "upgraded" file handle to the file referenced by the
|
||||||
|
// [Handle]. Note that the original [Handle] is not consumed by this operation,
|
||||||
|
// and can be opened multiple times.
|
||||||
|
//
|
||||||
|
// The provided flags indicate which open(2) flags are used to create the new
|
||||||
|
// handle.
|
||||||
|
//
|
||||||
|
// TODO: Rename these to "Reopen" or something.
|
||||||
|
func (h *Handle) OpenFile(flags int) (*os.File, error) {
|
||||||
|
return fdutils.WithFileFd(h.inner, func(fd uintptr) (*os.File, error) {
|
||||||
|
newFd, err := libpathrs.Reopen(fd, flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return os.NewFile(newFd, h.inner.Name()), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntoFile unwraps the [Handle] into its underlying [os.File].
|
||||||
|
//
|
||||||
|
// You almost certainly want to use [Handle.OpenFile] to get a non-O_PATH
|
||||||
|
// version of this [Handle].
|
||||||
|
//
|
||||||
|
// This operation returns the internal [os.File] of the [Handle] directly, so
|
||||||
|
// calling [Handle.Close] will also close any copies of the returned [os.File].
|
||||||
|
// If you want to get an independent copy, use [Handle.Clone] followed by
|
||||||
|
// [Handle.IntoFile] on the cloned [Handle].
|
||||||
|
//
|
||||||
|
// [os.File]: https://pkg.go.dev/os#File
|
||||||
|
func (h *Handle) IntoFile() *os.File {
|
||||||
|
// TODO: Figure out if we really don't want to make a copy.
|
||||||
|
// TODO: We almost certainly want to clear r.inner here, but we can't do
|
||||||
|
// that easily atomically (we could use atomic.Value but that'll make
|
||||||
|
// things quite a bit uglier).
|
||||||
|
return h.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a copy of a [Handle], such that it has a separate lifetime to
|
||||||
|
// the original (while referring to the same underlying file).
|
||||||
|
func (h *Handle) Clone() (*Handle, error) {
|
||||||
|
return HandleFromFile(h.inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees all of the resources used by the [Handle].
|
||||||
|
func (h *Handle) Close() error {
|
||||||
|
return h.inner.Close()
|
||||||
|
}
|
||||||
75
vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go
generated
vendored
Normal file
75
vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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 fdutils contains a few helper methods when dealing with *os.File and
|
||||||
|
// file descriptors.
|
||||||
|
package fdutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DupFd makes a duplicate of the given fd.
|
||||||
|
func DupFd(fd uintptr, name string) (*os.File, error) {
|
||||||
|
newFd, err := unix.FcntlInt(fd, unix.F_DUPFD_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fcntl(F_DUPFD_CLOEXEC): %w", err)
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(newFd), name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFileFd is a more ergonomic wrapper around file.SyscallConn().Control().
|
||||||
|
func WithFileFd[T any](file *os.File, fn func(fd uintptr) (T, error)) (T, error) {
|
||||||
|
conn, err := file.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return *new(T), err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
ret T
|
||||||
|
innerErr error
|
||||||
|
)
|
||||||
|
if err := conn.Control(func(fd uintptr) {
|
||||||
|
ret, innerErr = fn(fd)
|
||||||
|
}); err != nil {
|
||||||
|
return *new(T), err
|
||||||
|
}
|
||||||
|
return ret, innerErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// DupFile makes a duplicate of the given file.
|
||||||
|
func DupFile(file *os.File) (*os.File, error) {
|
||||||
|
return WithFileFd(file, func(fd uintptr) (*os.File, error) {
|
||||||
|
return DupFd(fd, file.Name())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkFile creates a new *os.File from the provided file descriptor. However,
|
||||||
|
// unlike os.NewFile, the file's Name is based on the real path (provided by
|
||||||
|
// /proc/self/fd/$n).
|
||||||
|
func MkFile(fd uintptr) (*os.File, error) {
|
||||||
|
fdPath := fmt.Sprintf("fd/%d", fd)
|
||||||
|
fdName, err := libpathrs.ProcReadlinkat(libpathrs.ProcDefaultRootFd, libpathrs.ProcThreadSelf, fdPath)
|
||||||
|
if err != nil {
|
||||||
|
_ = unix.Close(int(fd))
|
||||||
|
return nil, fmt.Errorf("failed to fetch real name of fd %d: %w", fd, err)
|
||||||
|
}
|
||||||
|
// TODO: Maybe we should prefix this name with something to indicate to
|
||||||
|
// users that they must not use this path as a "safe" path. Something like
|
||||||
|
// "//pathrs-handle:/foo/bar"?
|
||||||
|
return os.NewFile(fd, fdName), nil
|
||||||
|
}
|
||||||
40
vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go
generated
vendored
Normal file
40
vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// TODO: Use "go:build unix" once we bump the minimum Go version 1.19.
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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 libpathrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents an underlying libpathrs error.
|
||||||
|
type Error struct {
|
||||||
|
description string
|
||||||
|
errno syscall.Errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a textual description of the error.
|
||||||
|
func (err *Error) Error() string {
|
||||||
|
return err.description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying error which was wrapped by this error (if
|
||||||
|
// applicable).
|
||||||
|
func (err *Error) Unwrap() error {
|
||||||
|
if err.errno != 0 {
|
||||||
|
return err.errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
337
vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go
generated
vendored
Normal file
337
vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go
generated
vendored
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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 libpathrs is an internal thin wrapper around the libpathrs C API.
|
||||||
|
package libpathrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// TODO: Figure out if we need to add support for linking against libpathrs
|
||||||
|
// statically even if in dynamically linked builds in order to make
|
||||||
|
// packaging a bit easier (using "-Wl,-Bstatic -lpathrs -Wl,-Bdynamic" or
|
||||||
|
// "-l:pathrs.a").
|
||||||
|
#cgo pkg-config: pathrs
|
||||||
|
#include <pathrs.h>
|
||||||
|
|
||||||
|
// This is a workaround for unsafe.Pointer() not working for non-void pointers.
|
||||||
|
char *cast_ptr(void *ptr) { return ptr; }
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func fetchError(errID C.int) error {
|
||||||
|
if errID >= C.__PATHRS_MAX_ERR_VALUE {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cErr := C.pathrs_errorinfo(errID)
|
||||||
|
defer C.pathrs_errorinfo_free(cErr)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if cErr != nil {
|
||||||
|
err = &Error{
|
||||||
|
errno: syscall.Errno(cErr.saved_errno),
|
||||||
|
description: C.GoString(cErr.description),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenRoot wraps pathrs_open_root.
|
||||||
|
func OpenRoot(path string) (uintptr, error) {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
fd := C.pathrs_open_root(cPath)
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen wraps pathrs_reopen.
|
||||||
|
func Reopen(fd uintptr, flags int) (uintptr, error) {
|
||||||
|
newFd := C.pathrs_reopen(C.int(fd), C.int(flags))
|
||||||
|
return uintptr(newFd), fetchError(newFd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootResolve wraps pathrs_inroot_resolve.
|
||||||
|
func InRootResolve(rootFd uintptr, path string) (uintptr, error) {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
fd := C.pathrs_inroot_resolve(C.int(rootFd), cPath)
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootResolveNoFollow wraps pathrs_inroot_resolve_nofollow.
|
||||||
|
func InRootResolveNoFollow(rootFd uintptr, path string) (uintptr, error) {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
fd := C.pathrs_inroot_resolve_nofollow(C.int(rootFd), cPath)
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootOpen wraps pathrs_inroot_open.
|
||||||
|
func InRootOpen(rootFd uintptr, path string, flags int) (uintptr, error) {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
fd := C.pathrs_inroot_open(C.int(rootFd), cPath, C.int(flags))
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootReadlink wraps pathrs_inroot_readlink.
|
||||||
|
func InRootReadlink(rootFd uintptr, path string) (string, error) {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
size := 128
|
||||||
|
for {
|
||||||
|
linkBuf := make([]byte, size)
|
||||||
|
n := C.pathrs_inroot_readlink(C.int(rootFd), cPath, C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
|
||||||
|
switch {
|
||||||
|
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
|
||||||
|
return "", fetchError(n)
|
||||||
|
case int(n) <= len(linkBuf):
|
||||||
|
return string(linkBuf[:int(n)]), nil
|
||||||
|
default:
|
||||||
|
// The contents were truncated. Unlike readlinkat, pathrs returns
|
||||||
|
// the size of the link when it checked. So use the returned size
|
||||||
|
// as a basis for the reallocated size (but in order to avoid a DoS
|
||||||
|
// where a magic-link is growing by a single byte each iteration,
|
||||||
|
// make sure we are a fair bit larger).
|
||||||
|
size += int(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootRmdir wraps pathrs_inroot_rmdir.
|
||||||
|
func InRootRmdir(rootFd uintptr, path string) error {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_rmdir(C.int(rootFd), cPath)
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootUnlink wraps pathrs_inroot_unlink.
|
||||||
|
func InRootUnlink(rootFd uintptr, path string) error {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_unlink(C.int(rootFd), cPath)
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootRemoveAll wraps pathrs_inroot_remove_all.
|
||||||
|
func InRootRemoveAll(rootFd uintptr, path string) error {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_remove_all(C.int(rootFd), cPath)
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootCreat wraps pathrs_inroot_creat.
|
||||||
|
func InRootCreat(rootFd uintptr, path string, flags int, mode uint32) (uintptr, error) {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
fd := C.pathrs_inroot_creat(C.int(rootFd), cPath, C.int(flags), C.uint(mode))
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootRename wraps pathrs_inroot_rename.
|
||||||
|
func InRootRename(rootFd uintptr, src, dst string, flags uint) error {
|
||||||
|
cSrc := C.CString(src)
|
||||||
|
defer C.free(unsafe.Pointer(cSrc))
|
||||||
|
|
||||||
|
cDst := C.CString(dst)
|
||||||
|
defer C.free(unsafe.Pointer(cDst))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_rename(C.int(rootFd), cSrc, cDst, C.uint(flags))
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootMkdir wraps pathrs_inroot_mkdir.
|
||||||
|
func InRootMkdir(rootFd uintptr, path string, mode uint32) error {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_mkdir(C.int(rootFd), cPath, C.uint(mode))
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootMkdirAll wraps pathrs_inroot_mkdir_all.
|
||||||
|
func InRootMkdirAll(rootFd uintptr, path string, mode uint32) (uintptr, error) {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
fd := C.pathrs_inroot_mkdir_all(C.int(rootFd), cPath, C.uint(mode))
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootMknod wraps pathrs_inroot_mknod.
|
||||||
|
func InRootMknod(rootFd uintptr, path string, mode uint32, dev uint64) error {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_mknod(C.int(rootFd), cPath, C.uint(mode), C.dev_t(dev))
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootSymlink wraps pathrs_inroot_symlink.
|
||||||
|
func InRootSymlink(rootFd uintptr, path, target string) error {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
cTarget := C.CString(target)
|
||||||
|
defer C.free(unsafe.Pointer(cTarget))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_symlink(C.int(rootFd), cPath, cTarget)
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRootHardlink wraps pathrs_inroot_hardlink.
|
||||||
|
func InRootHardlink(rootFd uintptr, path, target string) error {
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
cTarget := C.CString(target)
|
||||||
|
defer C.free(unsafe.Pointer(cTarget))
|
||||||
|
|
||||||
|
err := C.pathrs_inroot_hardlink(C.int(rootFd), cPath, cTarget)
|
||||||
|
return fetchError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcBase is pathrs_proc_base_t (uint64_t).
|
||||||
|
type ProcBase C.pathrs_proc_base_t
|
||||||
|
|
||||||
|
// FIXME: We need to open-code the constants because CGo unfortunately will
|
||||||
|
// implicitly convert any non-literal constants (i.e. those resolved using gcc)
|
||||||
|
// to signed integers. See <https://github.com/golang/go/issues/39136> for some
|
||||||
|
// more information on the underlying issue (though.
|
||||||
|
const (
|
||||||
|
// ProcRoot is PATHRS_PROC_ROOT.
|
||||||
|
ProcRoot ProcBase = 0xFFFF_FFFE_7072_6F63 // C.PATHRS_PROC_ROOT
|
||||||
|
// ProcSelf is PATHRS_PROC_SELF.
|
||||||
|
ProcSelf ProcBase = 0xFFFF_FFFE_091D_5E1F // C.PATHRS_PROC_SELF
|
||||||
|
// ProcThreadSelf is PATHRS_PROC_THREAD_SELF.
|
||||||
|
ProcThreadSelf ProcBase = 0xFFFF_FFFE_3EAD_5E1F // C.PATHRS_PROC_THREAD_SELF
|
||||||
|
|
||||||
|
// ProcBaseTypeMask is __PATHRS_PROC_TYPE_MASK.
|
||||||
|
ProcBaseTypeMask ProcBase = 0xFFFF_FFFF_0000_0000 // C.__PATHRS_PROC_TYPE_MASK
|
||||||
|
// ProcBaseTypePid is __PATHRS_PROC_TYPE_PID.
|
||||||
|
ProcBaseTypePid ProcBase = 0x8000_0000_0000_0000 // C.__PATHRS_PROC_TYPE_PID
|
||||||
|
|
||||||
|
// ProcDefaultRootFd is PATHRS_PROC_DEFAULT_ROOTFD.
|
||||||
|
ProcDefaultRootFd = -int(syscall.EBADF) // C.PATHRS_PROC_DEFAULT_ROOTFD
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertEqual[T comparable](a, b T, msg string) {
|
||||||
|
if a != b {
|
||||||
|
panic(fmt.Sprintf("%s ((%T) %#v != (%T) %#v)", msg, a, a, b, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the values above match the actual C values. Unfortunately, Go
|
||||||
|
// only allows us to forcefully cast int64 to uint64 if you use a temporary
|
||||||
|
// variable, which means we cannot do it in a const context and thus need to do
|
||||||
|
// it at runtime (even though it is a check that fundamentally could be done at
|
||||||
|
// compile-time)...
|
||||||
|
func init() {
|
||||||
|
var (
|
||||||
|
actualProcRoot int64 = C.PATHRS_PROC_ROOT
|
||||||
|
actualProcSelf int64 = C.PATHRS_PROC_SELF
|
||||||
|
actualProcThreadSelf int64 = C.PATHRS_PROC_THREAD_SELF
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEqual(ProcRoot, ProcBase(actualProcRoot), "PATHRS_PROC_ROOT")
|
||||||
|
assertEqual(ProcSelf, ProcBase(actualProcSelf), "PATHRS_PROC_SELF")
|
||||||
|
assertEqual(ProcThreadSelf, ProcBase(actualProcThreadSelf), "PATHRS_PROC_THREAD_SELF")
|
||||||
|
|
||||||
|
var (
|
||||||
|
actualProcBaseTypeMask uint64 = C.__PATHRS_PROC_TYPE_MASK
|
||||||
|
actualProcBaseTypePid uint64 = C.__PATHRS_PROC_TYPE_PID
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEqual(ProcBaseTypeMask, ProcBase(actualProcBaseTypeMask), "__PATHRS_PROC_TYPE_MASK")
|
||||||
|
assertEqual(ProcBaseTypePid, ProcBase(actualProcBaseTypePid), "__PATHRS_PROC_TYPE_PID")
|
||||||
|
|
||||||
|
assertEqual(ProcDefaultRootFd, int(C.PATHRS_PROC_DEFAULT_ROOTFD), "PATHRS_PROC_DEFAULT_ROOTFD")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcPid reimplements the PROC_PID(x) conversion.
|
||||||
|
func ProcPid(pid uint32) ProcBase { return ProcBaseTypePid | ProcBase(pid) }
|
||||||
|
|
||||||
|
// ProcOpenat wraps pathrs_proc_openat.
|
||||||
|
func ProcOpenat(procRootFd int, base ProcBase, path string, flags int) (uintptr, error) {
|
||||||
|
cBase := C.pathrs_proc_base_t(base)
|
||||||
|
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
fd := C.pathrs_proc_openat(C.int(procRootFd), cBase, cPath, C.int(flags))
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcReadlinkat wraps pathrs_proc_readlinkat.
|
||||||
|
func ProcReadlinkat(procRootFd int, base ProcBase, path string) (string, error) {
|
||||||
|
// TODO: See if we can unify this code with InRootReadlink.
|
||||||
|
|
||||||
|
cBase := C.pathrs_proc_base_t(base)
|
||||||
|
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
size := 128
|
||||||
|
for {
|
||||||
|
linkBuf := make([]byte, size)
|
||||||
|
n := C.pathrs_proc_readlinkat(
|
||||||
|
C.int(procRootFd), cBase, cPath,
|
||||||
|
C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
|
||||||
|
switch {
|
||||||
|
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
|
||||||
|
return "", fetchError(n)
|
||||||
|
case int(n) <= len(linkBuf):
|
||||||
|
return string(linkBuf[:int(n)]), nil
|
||||||
|
default:
|
||||||
|
// The contents were truncated. Unlike readlinkat, pathrs returns
|
||||||
|
// the size of the link when it checked. So use the returned size
|
||||||
|
// as a basis for the reallocated size (but in order to avoid a DoS
|
||||||
|
// where a magic-link is growing by a single byte each iteration,
|
||||||
|
// make sure we are a fair bit larger).
|
||||||
|
size += int(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcfsOpenHow is pathrs_procfs_open_how (struct).
|
||||||
|
type ProcfsOpenHow C.pathrs_procfs_open_how
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ProcfsNewUnmasked is PATHRS_PROCFS_NEW_UNMASKED.
|
||||||
|
ProcfsNewUnmasked = C.PATHRS_PROCFS_NEW_UNMASKED
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flags returns a pointer to the internal flags field to allow other packages
|
||||||
|
// to modify structure fields that are internal due to Go's visibility model.
|
||||||
|
func (how *ProcfsOpenHow) Flags() *C.uint64_t { return &how.flags }
|
||||||
|
|
||||||
|
// ProcfsOpen is pathrs_procfs_open (sizeof(*how) is passed automatically).
|
||||||
|
func ProcfsOpen(how *ProcfsOpenHow) (uintptr, error) {
|
||||||
|
fd := C.pathrs_procfs_open((*C.pathrs_procfs_open_how)(how), C.size_t(unsafe.Sizeof(*how)))
|
||||||
|
return uintptr(fd), fetchError(fd)
|
||||||
|
}
|
||||||
246
vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go
generated
vendored
Normal file
246
vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"cyphar.com/go-pathrs/internal/fdutils"
|
||||||
|
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcBase is used with [ProcReadlink] and related functions to indicate what
|
||||||
|
// /proc subpath path operations should be done relative to.
|
||||||
|
type ProcBase struct {
|
||||||
|
inner libpathrs.ProcBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ProcRoot indicates to use /proc. Note that this mode may be more
|
||||||
|
// expensive because we have to take steps to try to avoid leaking unmasked
|
||||||
|
// procfs handles, so you should use [ProcBaseSelf] if you can.
|
||||||
|
ProcRoot = ProcBase{inner: libpathrs.ProcRoot}
|
||||||
|
// ProcSelf indicates to use /proc/self. For most programs, this is the
|
||||||
|
// standard choice.
|
||||||
|
ProcSelf = ProcBase{inner: libpathrs.ProcSelf}
|
||||||
|
// ProcThreadSelf indicates to use /proc/thread-self. In multi-threaded
|
||||||
|
// 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.
|
||||||
|
ProcThreadSelf = ProcBase{inner: libpathrs.ProcThreadSelf}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcPid returns a ProcBase which indicates to use /proc/$pid for the given
|
||||||
|
// PID (or TID). Be aware that due to PID recycling, using this is generally
|
||||||
|
// not safe except in certain circumstances. Namely:
|
||||||
|
//
|
||||||
|
// - PID 1 (the init process), as that PID cannot ever get recycled.
|
||||||
|
// - Your current PID (though you should just use [ProcBaseSelf]).
|
||||||
|
// - Your current TID if you have used [runtime.LockOSThread] (though you
|
||||||
|
// should just use [ProcBaseThreadSelf]).
|
||||||
|
// - PIDs of child processes (as long as you are sure that no other part of
|
||||||
|
// your program incorrectly catches or ignores SIGCHLD, and that you do it
|
||||||
|
// *before* you call wait(2)or any equivalent method that could reap
|
||||||
|
// zombies).
|
||||||
|
func ProcPid(pid int) ProcBase {
|
||||||
|
if pid < 0 || pid >= 1<<31 {
|
||||||
|
panic("invalid ProcBasePid value") // TODO: should this be an error?
|
||||||
|
}
|
||||||
|
return ProcBase{inner: libpathrs.ProcPid(uint32(pid))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThreadCloser 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 ThreadCloser func()
|
||||||
|
|
||||||
|
// 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 *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases all internal resources for this [Handle].
|
||||||
|
//
|
||||||
|
// Note that if the handle is actually the global cached handle, this operation
|
||||||
|
// is a no-op.
|
||||||
|
func (proc *Handle) Close() error {
|
||||||
|
var err error
|
||||||
|
if proc.inner != nil {
|
||||||
|
err = proc.inner.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenOption is a configuration function passed as an argument to [Open].
|
||||||
|
type OpenOption func(*libpathrs.ProcfsOpenHow) error
|
||||||
|
|
||||||
|
// UnmaskedProcRoot can be passed to [Open] to request an unmasked procfs
|
||||||
|
// handle be created.
|
||||||
|
//
|
||||||
|
// procfs, err := procfs.OpenRoot(procfs.UnmaskedProcRoot)
|
||||||
|
func UnmaskedProcRoot(how *libpathrs.ProcfsOpenHow) error {
|
||||||
|
*how.Flags() |= libpathrs.ProcfsNewUnmasked
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open creates a new [Handle] to a safe "/proc", based on the passed
|
||||||
|
// configuration options (in the form of a series of [OpenOption]s).
|
||||||
|
func Open(opts ...OpenOption) (*Handle, error) {
|
||||||
|
var how libpathrs.ProcfsOpenHow
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&how); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fd, err := libpathrs.ProcfsOpen(&how)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var procFile *os.File
|
||||||
|
if int(fd) >= 0 {
|
||||||
|
procFile = os.NewFile(fd, "/proc")
|
||||||
|
}
|
||||||
|
// TODO: Check that fd == PATHRS_PROC_DEFAULT_ROOTFD in the <0 case?
|
||||||
|
return &Handle{inner: procFile}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Switch to something fdutils.WithFileFd-like.
|
||||||
|
func (proc *Handle) fd() int {
|
||||||
|
if proc.inner != nil {
|
||||||
|
return int(proc.inner.Fd())
|
||||||
|
}
|
||||||
|
return libpathrs.ProcDefaultRootFd
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should we expose open?
|
||||||
|
func (proc *Handle) open(base ProcBase, path string, flags int) (_ *os.File, Closer ThreadCloser, Err error) {
|
||||||
|
var closer ThreadCloser
|
||||||
|
if base == ProcThreadSelf {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
closer = runtime.UnlockOSThread
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if closer != nil && Err != nil {
|
||||||
|
closer()
|
||||||
|
Closer = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fd, err := libpathrs.ProcOpenat(proc.fd(), base.inner, path, flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
file, err := fdutils.MkFile(fd)
|
||||||
|
return file, closer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenRoot safely opens a given path from inside /proc/.
|
||||||
|
//
|
||||||
|
// This function must only be used for accessing global information from procfs
|
||||||
|
// (such as /proc/cpuinfo) or information about other processes (such as
|
||||||
|
// /proc/1). Accessing your own process information should be done using
|
||||||
|
// [Handle.OpenSelf] or [Handle.OpenThreadSelf].
|
||||||
|
func (proc *Handle) OpenRoot(path string, flags int) (*os.File, error) {
|
||||||
|
file, closer, err := proc.open(ProcRoot, path, flags)
|
||||||
|
if closer != nil {
|
||||||
|
// should not happen
|
||||||
|
panic("non-zero closer returned from procOpen(ProcRoot)")
|
||||||
|
}
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenSelf safely opens a given path from inside /proc/self/.
|
||||||
|
//
|
||||||
|
// This method is recommend for getting process information about the current
|
||||||
|
// process for almost all Go processes *except* for cases where there are
|
||||||
|
// [runtime.LockOSThread] threads that have changed some aspect of their state
|
||||||
|
// (such as through unshare(CLONE_FS) or changing namespaces).
|
||||||
|
//
|
||||||
|
// For such non-heterogeneous processes, /proc/self may reference to a task
|
||||||
|
// that has different state from the current goroutine and so it may be
|
||||||
|
// preferable to use [Handle.OpenThreadSelf]. The same is true if a user
|
||||||
|
// really wants to inspect the current OS thread's information (such as
|
||||||
|
// /proc/thread-self/stack or /proc/thread-self/status which is always uniquely
|
||||||
|
// per-thread).
|
||||||
|
//
|
||||||
|
// Unlike [Handle.OpenThreadSelf], this method does not involve locking
|
||||||
|
// the goroutine to the current OS thread and so is simpler to use and
|
||||||
|
// theoretically has slightly less overhead.
|
||||||
|
//
|
||||||
|
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
|
||||||
|
func (proc *Handle) OpenSelf(path string, flags int) (*os.File, error) {
|
||||||
|
file, closer, err := proc.open(ProcSelf, path, flags)
|
||||||
|
if closer != nil {
|
||||||
|
// should not happen
|
||||||
|
panic("non-zero closer returned from procOpen(ProcSelf)")
|
||||||
|
}
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenPid safely opens a given path from inside /proc/$pid/, where pid can be
|
||||||
|
// either a PID or TID.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to calling [Handle.OpenRoot] with the
|
||||||
|
// pid prefixed to the subpath.
|
||||||
|
//
|
||||||
|
// Be aware that due to PID recycling, using this is generally not safe except
|
||||||
|
// in certain circumstances. See the documentation of [ProcPid] for more
|
||||||
|
// details.
|
||||||
|
func (proc *Handle) OpenPid(pid int, path string, flags int) (*os.File, error) {
|
||||||
|
file, closer, err := proc.open(ProcPid(pid), path, flags)
|
||||||
|
if closer != nil {
|
||||||
|
// should not happen
|
||||||
|
panic("non-zero closer returned from procOpen(ProcPidOpen)")
|
||||||
|
}
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenThreadSelf safely opens a given path from inside /proc/thread-self/.
|
||||||
|
//
|
||||||
|
// Most Go processes have heterogeneous threads (all threads have most of the
|
||||||
|
// same kernel state such as CLONE_FS) and so [Handle.OpenSelf] is
|
||||||
|
// preferable for most users.
|
||||||
|
//
|
||||||
|
// For non-heterogeneous threads, or users that actually want thread-specific
|
||||||
|
// information (such as /proc/thread-self/stack or /proc/thread-self/status),
|
||||||
|
// this method is necessary.
|
||||||
|
//
|
||||||
|
// Because Go can change the running OS thread of your goroutine without notice
|
||||||
|
// (and then subsequently kill the old thread), this method will lock the
|
||||||
|
// current goroutine to the OS thread (with [runtime.LockOSThread]) and the
|
||||||
|
// caller is responsible for unlocking the the OS thread with the
|
||||||
|
// [ThreadCloser] callback once they are done using the returned file. This
|
||||||
|
// callback MUST be called AFTER you have finished using the returned
|
||||||
|
// [os.File]. This callback is completely separate to [os.File.Close], so it
|
||||||
|
// must be called regardless of how you close the handle.
|
||||||
|
//
|
||||||
|
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
|
||||||
|
// [os.File]: https://pkg.go.dev/os#File
|
||||||
|
// [os.File.Close]: https://pkg.go.dev/os#File.Close
|
||||||
|
func (proc *Handle) OpenThreadSelf(path string, flags int) (*os.File, ThreadCloser, error) {
|
||||||
|
return proc.open(ProcThreadSelf, path, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readlink safely reads the contents of a symlink from the given procfs base.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to doing an Open*(O_PATH|O_NOFOLLOW) of the
|
||||||
|
// path and then doing unix.Readlinkat(fd, ""), but with the benefit that
|
||||||
|
// thread locking is not necessary for [ProcThreadSelf].
|
||||||
|
func (proc *Handle) Readlink(base ProcBase, path string) (string, error) {
|
||||||
|
return libpathrs.ProcReadlinkat(proc.fd(), base.inner, path)
|
||||||
|
}
|
||||||
367
vendor/cyphar.com/go-pathrs/root_linux.go
generated
vendored
Normal file
367
vendor/cyphar.com/go-pathrs/root_linux.go
generated
vendored
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"cyphar.com/go-pathrs/internal/fdutils"
|
||||||
|
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Root is a handle to the root of a directory tree to resolve within. The only
|
||||||
|
// purpose of this "root handle" is to perform operations within the directory
|
||||||
|
// tree, or to get a [Handle] to inodes within the directory tree.
|
||||||
|
//
|
||||||
|
// At time of writing, it is considered a *VERY BAD IDEA* to open a [Root]
|
||||||
|
// inside a possibly-attacker-controlled directory tree. While we do have
|
||||||
|
// protections that should defend against it, it's far more dangerous than just
|
||||||
|
// opening a directory tree which is not inside a potentially-untrusted
|
||||||
|
// directory.
|
||||||
|
type Root struct {
|
||||||
|
inner *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenRoot creates a new [Root] handle to the directory at the given path.
|
||||||
|
func OpenRoot(path string) (*Root, error) {
|
||||||
|
fd, err := libpathrs.OpenRoot(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
file, err := fdutils.MkFile(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Root{inner: file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootFromFile creates a new [Root] handle from an [os.File] referencing a
|
||||||
|
// directory. The provided file will be duplicated, so the original file should
|
||||||
|
// still be closed by the caller.
|
||||||
|
//
|
||||||
|
// This is effectively the inverse operation of [Root.IntoFile].
|
||||||
|
//
|
||||||
|
// [os.File]: https://pkg.go.dev/os#File
|
||||||
|
func RootFromFile(file *os.File) (*Root, error) {
|
||||||
|
newFile, err := fdutils.DupFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("duplicate root fd: %w", err)
|
||||||
|
}
|
||||||
|
return &Root{inner: newFile}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve resolves the given path within the [Root]'s directory tree, and
|
||||||
|
// returns a [Handle] to the resolved path. The path must already exist,
|
||||||
|
// otherwise an error will occur.
|
||||||
|
//
|
||||||
|
// All symlinks (including trailing symlinks) are followed, but they are
|
||||||
|
// resolved within the rootfs. If you wish to open a handle to the symlink
|
||||||
|
// itself, use [ResolveNoFollow].
|
||||||
|
func (r *Root) Resolve(path string) (*Handle, error) {
|
||||||
|
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
|
||||||
|
handleFd, err := libpathrs.InRootResolve(rootFd, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handleFile, err := fdutils.MkFile(handleFd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Handle{inner: handleFile}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveNoFollow is effectively an O_NOFOLLOW version of [Resolve]. Their
|
||||||
|
// behaviour is identical, except that *trailing* symlinks will not be
|
||||||
|
// followed. If the final component is a trailing symlink, an O_PATH|O_NOFOLLOW
|
||||||
|
// handle to the symlink itself is returned.
|
||||||
|
func (r *Root) ResolveNoFollow(path string) (*Handle, error) {
|
||||||
|
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
|
||||||
|
handleFd, err := libpathrs.InRootResolveNoFollow(rootFd, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handleFile, err := fdutils.MkFile(handleFd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Handle{inner: handleFile}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open is effectively shorthand for [Resolve] followed by [Handle.Open], but
|
||||||
|
// can be slightly more efficient (it reduces CGo overhead and the number of
|
||||||
|
// syscalls used when using the openat2-based resolver) and is arguably more
|
||||||
|
// ergonomic to use.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.Open].
|
||||||
|
//
|
||||||
|
// [os.Open]: https://pkg.go.dev/os#Open
|
||||||
|
func (r *Root) Open(path string) (*os.File, error) {
|
||||||
|
return r.OpenFile(path, os.O_RDONLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFile is effectively shorthand for [Resolve] followed by
|
||||||
|
// [Handle.OpenFile], but can be slightly more efficient (it reduces CGo
|
||||||
|
// overhead and the number of syscalls used when using the openat2-based
|
||||||
|
// resolver) and is arguably more ergonomic to use.
|
||||||
|
//
|
||||||
|
// However, if flags contains os.O_NOFOLLOW and the path is a symlink, then
|
||||||
|
// OpenFile's behaviour will match that of openat2. In most cases an error will
|
||||||
|
// be returned, but if os.O_PATH is provided along with os.O_NOFOLLOW then a
|
||||||
|
// file equivalent to [ResolveNoFollow] will be returned instead.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.OpenFile], except that os.O_CREAT is
|
||||||
|
// not supported.
|
||||||
|
//
|
||||||
|
// [os.OpenFile]: https://pkg.go.dev/os#OpenFile
|
||||||
|
func (r *Root) OpenFile(path string, flags int) (*os.File, error) {
|
||||||
|
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
|
||||||
|
fd, err := libpathrs.InRootOpen(rootFd, path, flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fdutils.MkFile(fd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a file within the [Root]'s directory tree at the given path,
|
||||||
|
// and returns a handle to the file. The provided mode is used for the new file
|
||||||
|
// (the process's umask applies).
|
||||||
|
//
|
||||||
|
// Unlike [os.Create], if the file already exists an error is created rather
|
||||||
|
// than the file being opened and truncated.
|
||||||
|
//
|
||||||
|
// [os.Create]: https://pkg.go.dev/os#Create
|
||||||
|
func (r *Root) Create(path string, flags int, mode os.FileMode) (*os.File, error) {
|
||||||
|
unixMode, err := toUnixMode(mode, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
|
||||||
|
handleFd, err := libpathrs.InRootCreat(rootFd, path, flags, unixMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fdutils.MkFile(handleFd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename two paths within a [Root]'s directory tree. The flags argument is
|
||||||
|
// identical to the RENAME_* flags to the renameat2(2) system call.
|
||||||
|
func (r *Root) Rename(src, dst string, flags uint) error {
|
||||||
|
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootRename(rootFd, src, dst, flags)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDir removes the named empty directory within a [Root]'s directory
|
||||||
|
// tree.
|
||||||
|
func (r *Root) RemoveDir(path string) error {
|
||||||
|
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootRmdir(rootFd, path)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFile removes the named file within a [Root]'s directory tree.
|
||||||
|
func (r *Root) RemoveFile(path string) error {
|
||||||
|
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootUnlink(rootFd, path)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the named file or (empty) directory within a [Root]'s
|
||||||
|
// directory tree.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.Remove].
|
||||||
|
//
|
||||||
|
// [os.Remove]: https://pkg.go.dev/os#Remove
|
||||||
|
func (r *Root) Remove(path string) error {
|
||||||
|
// In order to match os.Remove's implementation we need to also do both
|
||||||
|
// syscalls unconditionally and adjust the error based on whether
|
||||||
|
// pathrs_inroot_rmdir() returned ENOTDIR.
|
||||||
|
unlinkErr := r.RemoveFile(path)
|
||||||
|
if unlinkErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rmdirErr := r.RemoveDir(path)
|
||||||
|
if rmdirErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Both failed, adjust the error in the same way that os.Remove does.
|
||||||
|
err := rmdirErr
|
||||||
|
if errors.Is(err, syscall.ENOTDIR) {
|
||||||
|
err = unlinkErr
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll recursively deletes a path and all of its children.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.RemoveAll].
|
||||||
|
//
|
||||||
|
// [os.RemoveAll]: https://pkg.go.dev/os#RemoveAll
|
||||||
|
func (r *Root) RemoveAll(path string) error {
|
||||||
|
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootRemoveAll(rootFd, path)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mkdir creates a directory within a [Root]'s directory tree. The provided
|
||||||
|
// mode is used for the new directory (the process's umask applies).
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.Mkdir].
|
||||||
|
//
|
||||||
|
// [os.Mkdir]: https://pkg.go.dev/os#Mkdir
|
||||||
|
func (r *Root) Mkdir(path string, mode os.FileMode) error {
|
||||||
|
unixMode, err := toUnixMode(mode, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootMkdir(rootFd, path, unixMode)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll creates a directory (and any parent path components if they don't
|
||||||
|
// exist) within a [Root]'s directory tree. The provided mode is used for any
|
||||||
|
// directories created by this function (the process's umask applies).
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.MkdirAll].
|
||||||
|
//
|
||||||
|
// [os.MkdirAll]: https://pkg.go.dev/os#MkdirAll
|
||||||
|
func (r *Root) MkdirAll(path string, mode os.FileMode) (*Handle, error) {
|
||||||
|
unixMode, err := toUnixMode(mode, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
|
||||||
|
handleFd, err := libpathrs.InRootMkdirAll(rootFd, path, unixMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handleFile, err := fdutils.MkFile(handleFd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Handle{inner: handleFile}, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mknod creates a new device inode of the given type within a [Root]'s
|
||||||
|
// directory tree. The provided mode is used for the new directory (the
|
||||||
|
// process's umask applies).
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [unix.Mknod].
|
||||||
|
//
|
||||||
|
// [unix.Mknod]: https://pkg.go.dev/golang.org/x/sys/unix#Mknod
|
||||||
|
func (r *Root) Mknod(path string, mode os.FileMode, dev uint64) error {
|
||||||
|
unixMode, err := toUnixMode(mode, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootMknod(rootFd, path, unixMode, dev)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symlink creates a symlink within a [Root]'s directory tree. The symlink is
|
||||||
|
// created at path and is a link to target.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.Symlink].
|
||||||
|
//
|
||||||
|
// [os.Symlink]: https://pkg.go.dev/os#Symlink
|
||||||
|
func (r *Root) Symlink(path, target string) error {
|
||||||
|
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootSymlink(rootFd, path, target)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardlink creates a hardlink within a [Root]'s directory tree. The hardlink
|
||||||
|
// is created at path and is a link to target. Both paths are within the
|
||||||
|
// [Root]'s directory tree (you cannot hardlink to a different [Root] or the
|
||||||
|
// host).
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.Link].
|
||||||
|
//
|
||||||
|
// [os.Link]: https://pkg.go.dev/os#Link
|
||||||
|
func (r *Root) Hardlink(path, target string) error {
|
||||||
|
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||||
|
err := libpathrs.InRootHardlink(rootFd, path, target)
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readlink returns the target of a symlink with a [Root]'s directory tree.
|
||||||
|
//
|
||||||
|
// This is effectively equivalent to [os.Readlink].
|
||||||
|
//
|
||||||
|
// [os.Readlink]: https://pkg.go.dev/os#Readlink
|
||||||
|
func (r *Root) Readlink(path string) (string, error) {
|
||||||
|
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (string, error) {
|
||||||
|
return libpathrs.InRootReadlink(rootFd, path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntoFile unwraps the [Root] into its underlying [os.File].
|
||||||
|
//
|
||||||
|
// It is critical that you do not operate on this file descriptor yourself,
|
||||||
|
// because the security properties of libpathrs depend on users doing all
|
||||||
|
// relevant filesystem operations through libpathrs.
|
||||||
|
//
|
||||||
|
// This operation returns the internal [os.File] of the [Root] directly, so
|
||||||
|
// calling [Root.Close] will also close any copies of the returned [os.File].
|
||||||
|
// If you want to get an independent copy, use [Root.Clone] followed by
|
||||||
|
// [Root.IntoFile] on the cloned [Root].
|
||||||
|
//
|
||||||
|
// [os.File]: https://pkg.go.dev/os#File
|
||||||
|
func (r *Root) IntoFile() *os.File {
|
||||||
|
// TODO: Figure out if we really don't want to make a copy.
|
||||||
|
// TODO: We almost certainly want to clear r.inner here, but we can't do
|
||||||
|
// that easily atomically (we could use atomic.Value but that'll make
|
||||||
|
// things quite a bit uglier).
|
||||||
|
return r.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a copy of a [Root] handle, such that it has a separate
|
||||||
|
// lifetime to the original (while referring to the same underlying directory).
|
||||||
|
func (r *Root) Clone() (*Root, error) {
|
||||||
|
return RootFromFile(r.inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees all of the resources used by the [Root] handle.
|
||||||
|
func (r *Root) Close() error {
|
||||||
|
return r.inner.Close()
|
||||||
|
}
|
||||||
56
vendor/cyphar.com/go-pathrs/utils_linux.go
generated
vendored
Normal file
56
vendor/cyphar.com/go-pathrs/utils_linux.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
/*
|
||||||
|
* libpathrs: safe path resolution on Linux
|
||||||
|
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
|
* Copyright (C) 2019-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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:cyclop // this function needs to handle a lot of cases
|
||||||
|
func toUnixMode(mode os.FileMode, needsType bool) (uint32, error) {
|
||||||
|
sysMode := uint32(mode.Perm())
|
||||||
|
switch mode & os.ModeType { //nolint:exhaustive // we only care about ModeType bits
|
||||||
|
case 0:
|
||||||
|
if needsType {
|
||||||
|
sysMode |= unix.S_IFREG
|
||||||
|
}
|
||||||
|
case os.ModeDir:
|
||||||
|
sysMode |= unix.S_IFDIR
|
||||||
|
case os.ModeSymlink:
|
||||||
|
sysMode |= unix.S_IFLNK
|
||||||
|
case os.ModeCharDevice | os.ModeDevice:
|
||||||
|
sysMode |= unix.S_IFCHR
|
||||||
|
case os.ModeDevice:
|
||||||
|
sysMode |= unix.S_IFBLK
|
||||||
|
case os.ModeNamedPipe:
|
||||||
|
sysMode |= unix.S_IFIFO
|
||||||
|
case os.ModeSocket:
|
||||||
|
sysMode |= unix.S_IFSOCK
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("invalid mode filetype %+o", mode)
|
||||||
|
}
|
||||||
|
if mode&os.ModeSetuid != 0 {
|
||||||
|
sysMode |= unix.S_ISUID
|
||||||
|
}
|
||||||
|
if mode&os.ModeSetgid != 0 {
|
||||||
|
sysMode |= unix.S_ISGID
|
||||||
|
}
|
||||||
|
if mode&os.ModeSticky != 0 {
|
||||||
|
sysMode |= unix.S_ISVTX
|
||||||
|
}
|
||||||
|
return sysMode, nil
|
||||||
|
}
|
||||||
60
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
Normal file
60
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 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"
|
||||||
|
|
||||||
|
run:
|
||||||
|
build-tags:
|
||||||
|
- libpathrs
|
||||||
|
|
||||||
|
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
|
||||||
209
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
209
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
@@ -6,6 +6,208 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased] ##
|
## [Unreleased] ##
|
||||||
|
|
||||||
|
## [0.6.0] - 2025-11-03 ##
|
||||||
|
|
||||||
|
> By the Power of Greyskull!
|
||||||
|
|
||||||
|
While quite small code-wise, this release marks a very key point in the
|
||||||
|
development of filepath-securejoin.
|
||||||
|
|
||||||
|
filepath-securejoin was originally intended (back in 2017) to simply be a
|
||||||
|
single-purpose library that would take some common code used in container
|
||||||
|
runtimes (specifically, Docker's `FollowSymlinksInScope`) and make it more
|
||||||
|
general-purpose (with the eventual goals of it ending up in the Go stdlib).
|
||||||
|
|
||||||
|
Of course, I quickly discovered that this problem was actually far more
|
||||||
|
complicated to solve when dealing with racing attackers, which lead to me
|
||||||
|
developing `openat2(2)` and [libpathrs][]. I had originally planned for
|
||||||
|
libpathrs to completely replace filepath-securejoin "once it was ready" but in
|
||||||
|
the interim we needed to fix several race attacks in runc as part of security
|
||||||
|
advisories. Obviously we couldn't require the usage of a pre-0.1 Rust library
|
||||||
|
in runc so it was necessary to port bits of libpathrs into filepath-securejoin.
|
||||||
|
(Ironically the first prototypes of libpathrs were originally written in Go and
|
||||||
|
then rewritten to Rust, so the code in filepath-securejoin is actually Go code
|
||||||
|
that was rewritten to Rust then re-rewritten to Go.)
|
||||||
|
|
||||||
|
It then became clear that pure-Go libraries will likely not be willing to
|
||||||
|
require CGo for all of their builds, so it was necessary to accept that
|
||||||
|
filepath-securejoin will need to stay. As such, in v0.5.0 we provided more
|
||||||
|
pure-Go implementations of features from libpathrs but moved them into
|
||||||
|
`pathrs-lite` subpackage to clarify what purpose these helpers serve.
|
||||||
|
|
||||||
|
This release finally closes the loop and makes it so that pathrs-lite can
|
||||||
|
transparently use libpathrs (via a `libpathrs` build-tag). This means that
|
||||||
|
upstream libraries can use the pure Go version if they prefer, but downstreams
|
||||||
|
(either downstream library users or even downstream distributions) are able to
|
||||||
|
migrate to libpathrs for all usages of pathrs-lite in an entire Go binary.
|
||||||
|
|
||||||
|
I should make it clear that I do not plan to port the rest of libpathrs to Go,
|
||||||
|
as I do not wish to maintain two copies of the same codebase. pathrs-lite
|
||||||
|
already provides the core essentials necessary to operate on paths safely for
|
||||||
|
most modern systems. Users who want additional hardening or more ergonomic APIs
|
||||||
|
are free to use [`cyphar.com/go-pathrs`][go-pathrs] (libpathrs's Go bindings).
|
||||||
|
|
||||||
|
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||||
|
[go-pathrs]: https://cyphar.com/go-pathrs
|
||||||
|
|
||||||
|
### Breaking ###
|
||||||
|
- The deprecated `MkdirAll`, `MkdirAllHandle`, `OpenInRoot`, `OpenatInRoot` and
|
||||||
|
`Reopen` wrappers have been removed. Please switch to using `pathrs-lite`
|
||||||
|
directly.
|
||||||
|
|
||||||
|
### Added ###
|
||||||
|
- `pathrs-lite` now has support for using [libpathrs][libpathrs] as a backend.
|
||||||
|
This is opt-in and can be enabled at build time with the `libpathrs` build
|
||||||
|
tag. The intention is to allow for downstream libraries and other projects to
|
||||||
|
make use of the pure-Go `github.com/cyphar/filepath-securejoin/pathrs-lite`
|
||||||
|
package and distributors can then opt-in to using `libpathrs` for the entire
|
||||||
|
binary if they wish.
|
||||||
|
|
||||||
|
## [0.5.1] - 2025-10-31 ##
|
||||||
|
|
||||||
|
> Spooky scary skeletons send shivers down your spine!
|
||||||
|
|
||||||
|
### Changed ###
|
||||||
|
- `openat2` can return `-EAGAIN` if it detects a possible attack in certain
|
||||||
|
scenarios (namely if there was a rename or mount while walking a path with a
|
||||||
|
`..` component). While this is necessary to avoid a denial-of-service in the
|
||||||
|
kernel, it does require retry loops in userspace.
|
||||||
|
|
||||||
|
In previous versions, `pathrs-lite` would retry `openat2` 32 times before
|
||||||
|
returning an error, but we've received user reports that this limit can be
|
||||||
|
hit on systems with very heavy load. In some synthetic benchmarks (testing
|
||||||
|
the worst-case of an attacker doing renames in a tight loop on every core of
|
||||||
|
a 16-core machine) we managed to get a ~3% failure rate in runc. We have
|
||||||
|
improved this situation in two ways:
|
||||||
|
|
||||||
|
* We have now increased this limit to 128, which should be good enough for
|
||||||
|
most use-cases without becoming a denial-of-service vector (the number of
|
||||||
|
syscalls called by the `O_PATH` resolver in a typical case is within the
|
||||||
|
same ballpark). The same benchmarks show a failure rate of ~0.12% which
|
||||||
|
(while not zero) is probably sufficient for most users.
|
||||||
|
|
||||||
|
* In addition, we now return a `unix.EAGAIN` error that is bubbled up and can
|
||||||
|
be detected by callers. This means that callers with stricter requirements
|
||||||
|
to avoid spurious errors can choose to do their own infinite `EAGAIN` retry
|
||||||
|
loop (though we would strongly recommend users use time-based deadlines in
|
||||||
|
such retry loops to avoid potentially unbounded denials-of-service).
|
||||||
|
|
||||||
|
## [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 ##
|
## [0.4.1] - 2025-01-28 ##
|
||||||
|
|
||||||
### Fixed ###
|
### Fixed ###
|
||||||
@@ -173,7 +375,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
safe to start migrating to as we have extensive tests ensuring they behave
|
safe to start migrating to as we have extensive tests ensuring they behave
|
||||||
correctly and are safe against various races and other attacks.
|
correctly and are safe against various races and other attacks.
|
||||||
|
|
||||||
[libpathrs]: https://github.com/openSUSE/libpathrs
|
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||||
[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html
|
[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html
|
||||||
|
|
||||||
## [0.2.5] - 2024-05-03 ##
|
## [0.2.5] - 2024-05-03 ##
|
||||||
@@ -238,7 +440,10 @@ This is our first release of `github.com/cyphar/filepath-securejoin`,
|
|||||||
containing a full implementation with a coverage of 93.5% (the only missing
|
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).
|
cases are the error cases, which are hard to mocktest at the moment).
|
||||||
|
|
||||||
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD
|
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...HEAD
|
||||||
|
[0.6.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...v0.6.0
|
||||||
|
[0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1
|
||||||
|
[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0
|
||||||
[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1
|
[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.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
|
[0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6
|
||||||
|
|||||||
447
vendor/github.com/cyphar/filepath-securejoin/COPYING.md
generated
vendored
Normal file
447
vendor/github.com/cyphar/filepath-securejoin/COPYING.md
generated
vendored
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
## 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.
|
||||||
|
```
|
||||||
373
vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
generated
vendored
Normal file
373
vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
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.
|
||||||
21
vendor/github.com/cyphar/filepath-securejoin/README.md
generated
vendored
21
vendor/github.com/cyphar/filepath-securejoin/README.md
generated
vendored
@@ -67,7 +67,8 @@ func SecureJoin(root, unsafePath string) (string, error) {
|
|||||||
[libpathrs]: https://github.com/openSUSE/libpathrs
|
[libpathrs]: https://github.com/openSUSE/libpathrs
|
||||||
[go#20126]: https://github.com/golang/go/issues/20126
|
[go#20126]: https://github.com/golang/go/issues/20126
|
||||||
|
|
||||||
### New API ###
|
### <a name="new-api" /> New API ###
|
||||||
|
[#new-api]: #new-api
|
||||||
|
|
||||||
While we recommend users switch to [libpathrs][libpathrs] as soon as it has a
|
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
|
stable release, some methods implemented by libpathrs have been ported to this
|
||||||
@@ -165,5 +166,19 @@ after `MkdirAll`).
|
|||||||
|
|
||||||
### License ###
|
### License ###
|
||||||
|
|
||||||
The license of this project is the same as Go, which is a BSD 3-clause license
|
`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`
|
||||||
available in the `LICENSE` file.
|
|
||||||
|
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
|
||||||
|
|||||||
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
@@ -1 +1 @@
|
|||||||
0.4.1
|
0.6.0
|
||||||
|
|||||||
29
vendor/github.com/cyphar/filepath-securejoin/codecov.yml
generated
vendored
Normal file
29
vendor/github.com/cyphar/filepath-securejoin/codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 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
|
||||||
34
vendor/github.com/cyphar/filepath-securejoin/doc.go
generated
vendored
34
vendor/github.com/cyphar/filepath-securejoin/doc.go
generated
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||||
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
@@ -14,14 +16,13 @@
|
|||||||
// **not** safe against race conditions where an attacker changes the
|
// **not** safe against race conditions where an attacker changes the
|
||||||
// filesystem after (or during) the [SecureJoin] operation.
|
// filesystem after (or during) the [SecureJoin] operation.
|
||||||
//
|
//
|
||||||
// The new API is made up of [OpenInRoot] and [MkdirAll] (and derived
|
// The new API is available in the [pathrs-lite] subpackage, and provide
|
||||||
// functions). These are safe against racing attackers and have several other
|
// protections against racing attackers as well as several other key
|
||||||
// protections that are not provided by the legacy API. There are many more
|
// protections against attacks often seen by container runtimes. As the name
|
||||||
// operations that most programs expect to be able to do safely, but we do not
|
// suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of
|
||||||
// provide explicit support for them because we want to encourage users to
|
// [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and
|
||||||
// switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a
|
// [procfs.Handle] -- other APIs are not planned to be ported. The long-term
|
||||||
// cross-language next-generation library that is entirely designed around
|
// goal is for users to migrate to [libpathrs] which is more fully-featured.
|
||||||
// operating on paths safely.
|
|
||||||
//
|
//
|
||||||
// securejoin has been used by several container runtimes (Docker, runc,
|
// securejoin has been used by several container runtimes (Docker, runc,
|
||||||
// Kubernetes, etc) for quite a few years as a de-facto standard for operating
|
// Kubernetes, etc) for quite a few years as a de-facto standard for operating
|
||||||
@@ -31,9 +32,16 @@
|
|||||||
// API as soon as possible (or even better, switch to libpathrs).
|
// API as soon as possible (or even better, switch to libpathrs).
|
||||||
//
|
//
|
||||||
// This project was initially intended to be included in the Go standard
|
// This project was initially intended to be included in the Go standard
|
||||||
// library, but [it was rejected](https://go.dev/issue/20126). There is now a
|
// library, but it was rejected (see https://go.dev/issue/20126). Much later,
|
||||||
// [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API
|
// [os.Root] was added to the Go stdlib that shares some of the goals of
|
||||||
// that shares some of the goals of filepath-securejoin. However, that design
|
// filepath-securejoin. However, its design is intended to work like
|
||||||
// is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the
|
// openat2(RESOLVE_BENEATH) which does not fit the usecase of container
|
||||||
// usecase of container runtimes and most system tools.
|
// 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
|
||||||
package securejoin
|
package securejoin
|
||||||
|
|||||||
32
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
generated
vendored
32
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
generated
vendored
@@ -1,32 +0,0 @@
|
|||||||
//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
124
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
generated
vendored
@@ -1,124 +0,0 @@
|
|||||||
//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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
generated
vendored
Normal file
15
vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// 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
|
||||||
23
vendor/github.com/cyphar/filepath-securejoin/join.go
generated
vendored
23
vendor/github.com/cyphar/filepath-securejoin/join.go
generated
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||||
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
|
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
@@ -11,9 +13,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
|
||||||
|
|
||||||
const maxSymlinkLimit = 255
|
"github.com/cyphar/filepath-securejoin/internal/consts"
|
||||||
|
)
|
||||||
|
|
||||||
// IsNotExist tells you if err is an error that implies that either the path
|
// 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
|
// accessed does not exist (or path components don't exist). This is
|
||||||
@@ -49,12 +51,13 @@ func hasDotDot(path string) bool {
|
|||||||
return strings.Contains("/"+path+"/", "/../")
|
return strings.Contains("/"+path+"/", "/../")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except
|
// SecureJoinVFS joins the two given path components (similar to
|
||||||
// that the returned path is guaranteed to be scoped inside the provided root
|
// [filepath.Join]) except that the returned path is guaranteed to be scoped
|
||||||
// path (when evaluated). Any symbolic links in the path are evaluated with the
|
// inside the provided root path (when evaluated). Any symbolic links in the
|
||||||
// given root treated as the root of the filesystem, similar to a chroot. The
|
// path are evaluated with the given root treated as the root of the
|
||||||
// filesystem state is evaluated through the given [VFS] interface (if nil, the
|
// filesystem, similar to a chroot. The filesystem state is evaluated through
|
||||||
// standard [os].* family of functions are used).
|
// 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
|
// 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
|
// components in the returned string are not modified (in other words are not
|
||||||
@@ -78,7 +81,7 @@ func hasDotDot(path string) bool {
|
|||||||
// fully resolved using [filepath.EvalSymlinks] or otherwise constructed to
|
// fully resolved using [filepath.EvalSymlinks] or otherwise constructed to
|
||||||
// avoid containing symlink components. Of course, the root also *must not* be
|
// avoid containing symlink components. Of course, the root also *must not* be
|
||||||
// attacker-controlled.
|
// attacker-controlled.
|
||||||
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
|
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:revive // name is part of public API
|
||||||
// The root path must not contain ".." components, otherwise when we join
|
// 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
|
// 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
|
// in other ways but users shouldn't be giving us non-lexical root paths in
|
||||||
@@ -138,7 +141,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
|
|||||||
// It's a symlink, so get its contents and expand it by prepending it
|
// It's a symlink, so get its contents and expand it by prepending it
|
||||||
// to the yet-unparsed path.
|
// to the yet-unparsed path.
|
||||||
linksWalked++
|
linksWalked++
|
||||||
if linksWalked > maxSymlinkLimit {
|
if linksWalked > consts.MaxSymlinkLimit {
|
||||||
return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP}
|
return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
103
vendor/github.com/cyphar/filepath-securejoin/open_linux.go
generated
vendored
103
vendor/github.com/cyphar/filepath-securejoin/open_linux.go
generated
vendored
@@ -1,103 +0,0 @@
|
|||||||
//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 (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
|
||||||
// using an *[os.File] handle, to ensure that the correct root directory is used.
|
|
||||||
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
|
||||||
handle, err := completeLookupInRoot(root, unsafePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err}
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenInRoot safely opens the provided unsafePath within the root.
|
|
||||||
// Effectively, OpenInRoot(root, unsafePath) is equivalent to
|
|
||||||
//
|
|
||||||
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
|
||||||
// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC)
|
|
||||||
//
|
|
||||||
// But is much safer. The above implementation is unsafe because if an attacker
|
|
||||||
// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is
|
|
||||||
// possible for the returned file to be outside of the root.
|
|
||||||
//
|
|
||||||
// Note that the returned handle is an O_PATH handle, meaning that only a very
|
|
||||||
// limited set of operations will work on the handle. This is done to avoid
|
|
||||||
// accidentally opening an untrusted file that could cause issues (such as a
|
|
||||||
// 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].
|
|
||||||
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()
|
|
||||||
return OpenatInRoot(rootDir, unsafePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd.
|
|
||||||
// Reopen(file, flags) is effectively equivalent to
|
|
||||||
//
|
|
||||||
// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
|
|
||||||
// os.OpenFile(fdPath, flags|unix.O_CLOEXEC)
|
|
||||||
//
|
|
||||||
// But with some extra hardenings to ensure that we are not tricked by a
|
|
||||||
// maliciously-configured /proc mount. While this attack scenario is not
|
|
||||||
// common, in container runtimes it is possible for higher-level runtimes to be
|
|
||||||
// tricked into configuring an unsafe /proc that can be used to attack file
|
|
||||||
// operations. See [CVE-2019-19921] for more details.
|
|
||||||
//
|
|
||||||
// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
|
|
||||||
func Reopen(handle *os.File, flags int) (*os.File, error) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
127
vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
generated
vendored
127
vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
generated
vendored
@@ -1,127 +0,0 @@
|
|||||||
//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
|
|
||||||
}
|
|
||||||
59
vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
generated
vendored
59
vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
generated
vendored
@@ -1,59 +0,0 @@
|
|||||||
//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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
Normal file
35
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
## `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.
|
||||||
|
|
||||||
|
`pathrs-lite` also provides a very easy way to switch to `libpathrs` (even for
|
||||||
|
downstreams where `pathrs-lite` is being used in a third-party package and is
|
||||||
|
not interested in using CGo). At build time, if you use the `libpathrs` build
|
||||||
|
tag then `pathrs-lite` will use `libpathrs` directly instead of the pure Go
|
||||||
|
implementation. The two backends are functionally equivalent (and we have
|
||||||
|
integration tests to verify this), so this migration should be very easy with
|
||||||
|
no user-visible impact.
|
||||||
|
|
||||||
|
[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
|
||||||
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
Normal file
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// 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].
|
||||||
|
//
|
||||||
|
// [libpathrs]: https://github.com/cyphar/libpathrs
|
||||||
|
package pathrs
|
||||||
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
Normal file
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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...))
|
||||||
|
}
|
||||||
41
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go
generated
vendored
Normal file
41
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// 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 internal contains unexported common code for filepath-securejoin.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xdevErrorish struct {
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err xdevErrorish) Error() string { return err.description }
|
||||||
|
func (err xdevErrorish) Is(target error) bool { return target == unix.EXDEV }
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPossibleAttack indicates that some attack was detected.
|
||||||
|
ErrPossibleAttack error = xdevErrorish{"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 error = xdevErrorish{"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
Normal file
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
Normal file
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// 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
Normal file
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// 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
Normal file
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// 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
Normal file
62
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a fairly arbitrary limit we have just to avoid an attacker being
|
||||||
|
// able to make us spin in an infinite retry loop -- callers can choose to
|
||||||
|
// retry on EAGAIN if they prefer.
|
||||||
|
const scopedLookupMaxRetries = 128
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
fd, err := unix.Openat2(dirFd, path, how)
|
||||||
|
if err != nil {
|
||||||
|
if scopedLookupShouldRetry(how, err) && tries < scopedLookupMaxRetries {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
Normal file
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 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
Normal file
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// 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,18 +1,19 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
//go:build linux && go1.20
|
//go:build linux && go1.20
|
||||||
|
|
||||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package securejoin
|
package gocompat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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)
|
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
||||||
// is only guaranteed to give you baseErr.
|
// 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)
|
return fmt.Errorf("%w: %w", extraErr, baseErr)
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
//go:build linux && !go1.20
|
//go:build linux && !go1.20
|
||||||
|
|
||||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package securejoin
|
package gocompat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -27,10 +29,10 @@ func (err wrappedError) Error() string {
|
|||||||
return fmt.Sprintf("%v: %v", err.isError, err.inner)
|
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)
|
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
||||||
// is only guaranteed to give you baseErr.
|
// is only guaranteed to give you baseErr.
|
||||||
func wrapBaseError(baseErr, extraErr error) error {
|
func WrapBaseError(baseErr, extraErr error) error {
|
||||||
return wrappedError{
|
return wrappedError{
|
||||||
inner: baseErr,
|
inner: baseErr,
|
||||||
isError: extraErr,
|
isError: extraErr,
|
||||||
53
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
generated
vendored
Normal file
53
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
187
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
generated
vendored
Normal file
187
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go
generated
vendored
Normal file
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// 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 gopathrs is a less complete pure Go implementation of some of the
|
||||||
|
// APIs provided by [libpathrs].
|
||||||
|
//
|
||||||
|
// [libpathrs]: https://github.com/cyphar/libpathrs
|
||||||
|
package gopathrs
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
//go:build linux
|
//go:build linux
|
||||||
|
|
||||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
// Use of this source code is governed by a BSD-style
|
// Copyright (C) 2024-2025 SUSE LLC
|
||||||
// license that can be found in the LICENSE file.
|
//
|
||||||
|
// 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
|
package gopathrs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -15,6 +20,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"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 {
|
type symlinkStackEntry struct {
|
||||||
@@ -112,12 +123,12 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Split the link target and clean up any "" parts.
|
// Split the link target and clean up any "" parts.
|
||||||
linkTargetParts := slices_DeleteFunc(
|
linkTargetParts := gocompat.SlicesDeleteFunc(
|
||||||
strings.Split(linkTarget, "/"),
|
strings.Split(linkTarget, "/"),
|
||||||
func(part string) bool { return part == "" || part == "." })
|
func(part string) bool { return part == "" || part == "." })
|
||||||
|
|
||||||
// Copy the directory so the caller doesn't close our copy.
|
// Copy the directory so the caller doesn't close our copy.
|
||||||
dirCopy, err := dupFile(dir)
|
dirCopy, err := fd.Dup(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -155,15 +166,15 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) {
|
|||||||
return tailEntry.dir, tailEntry.remainingPath, true
|
return tailEntry.dir, tailEntry.remainingPath, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// partialLookupInRoot tries to lookup as much of the request path as possible
|
// PartialLookupInRoot tries to lookup as much of the request path as possible
|
||||||
// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing
|
// 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
|
// component of the requested path, returning a file handle to the final
|
||||||
// existing component and a string containing the remaining path components.
|
// existing component and a string containing the remaining path components.
|
||||||
func partialLookupInRoot(root *os.File, unsafePath string) (*os.File, string, error) {
|
func PartialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) {
|
||||||
return lookupInRoot(root, unsafePath, true)
|
return lookupInRoot(root, unsafePath, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) {
|
||||||
handle, remainingPath, err := lookupInRoot(root, unsafePath, false)
|
handle, remainingPath, err := lookupInRoot(root, unsafePath, false)
|
||||||
if remainingPath != "" && err == nil {
|
if remainingPath != "" && err == nil {
|
||||||
// should never happen
|
// should never happen
|
||||||
@@ -174,7 +185,7 @@ func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
|||||||
return handle, err
|
return handle, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) {
|
func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) {
|
||||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||||
|
|
||||||
// This is very similar to SecureJoin, except that we operate on the
|
// This is very similar to SecureJoin, except that we operate on the
|
||||||
@@ -182,20 +193,20 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
|||||||
// managed open, along with the remaining path components not opened.
|
// managed open, along with the remaining path components not opened.
|
||||||
|
|
||||||
// Try to use openat2 if possible.
|
// Try to use openat2 if possible.
|
||||||
if hasOpenat2() {
|
if linux.HasOpenat2() {
|
||||||
return lookupOpenat2(root, unsafePath, partial)
|
return lookupOpenat2(root, unsafePath, partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the "actual" root path from /proc/self/fd. This is necessary if the
|
// 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
|
// root is some magic-link like /proc/$pid/root, in which case we want to
|
||||||
// make sure when we do checkProcSelfFdPath that we are using the correct
|
// make sure when we do procfs.CheckProcSelfFdPath that we are using the
|
||||||
// root path.
|
// correct root path.
|
||||||
logicalRootPath, err := procSelfFdReadlink(root)
|
logicalRootPath, err := procfs.ProcSelfFdReadlink(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("get real root path: %w", err)
|
return nil, "", fmt.Errorf("get real root path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDir, err := dupFile(root)
|
currentDir, err := fd.Dup(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||||
}
|
}
|
||||||
@@ -260,7 +271,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
|||||||
return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err)
|
return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err)
|
||||||
}
|
}
|
||||||
// Jump to root.
|
// Jump to root.
|
||||||
rootClone, err := dupFile(root)
|
rootClone, err := fd.Dup(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||||
}
|
}
|
||||||
@@ -271,21 +282,21 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to open the next component.
|
// Try to open the next component.
|
||||||
nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||||
switch {
|
switch err {
|
||||||
case err == nil:
|
case nil:
|
||||||
st, err := nextDir.Stat()
|
st, err := nextDir.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = nextDir.Close()
|
_ = nextDir.Close()
|
||||||
return nil, "", fmt.Errorf("stat component %q: %w", part, err)
|
return nil, "", fmt.Errorf("stat component %q: %w", part, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch st.Mode() & os.ModeType {
|
switch st.Mode() & os.ModeType { //nolint:exhaustive // just a glorified if statement
|
||||||
case os.ModeSymlink:
|
case os.ModeSymlink:
|
||||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See
|
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See
|
||||||
// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and
|
// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and
|
||||||
// fstatat() with empty relative pathnames").
|
// fstatat() with empty relative pathnames").
|
||||||
linkDest, err := readlinkatFile(nextDir, "")
|
linkDest, err := fd.Readlinkat(nextDir, "")
|
||||||
// We don't need the handle anymore.
|
// We don't need the handle anymore.
|
||||||
_ = nextDir.Close()
|
_ = nextDir.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -293,7 +304,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
linksWalked++
|
linksWalked++
|
||||||
if linksWalked > maxSymlinkLimit {
|
if linksWalked > consts.MaxSymlinkLimit {
|
||||||
return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP}
|
return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +318,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
|||||||
// Absolute symlinks reset any work we've already done.
|
// Absolute symlinks reset any work we've already done.
|
||||||
if path.IsAbs(linkDest) {
|
if path.IsAbs(linkDest) {
|
||||||
// Jump to root.
|
// Jump to root.
|
||||||
rootClone, err := dupFile(root)
|
rootClone, err := fd.Dup(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||||
}
|
}
|
||||||
@@ -335,12 +346,12 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
|||||||
// rename or mount on the system.
|
// rename or mount on the system.
|
||||||
if part == ".." {
|
if part == ".." {
|
||||||
// Make sure the root hasn't moved.
|
// Make sure the root hasn't moved.
|
||||||
if err := checkProcSelfFdPath(logicalRootPath, root); err != nil {
|
if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil {
|
||||||
return nil, "", fmt.Errorf("root path moved during lookup: %w", err)
|
return nil, "", fmt.Errorf("root path moved during lookup: %w", err)
|
||||||
}
|
}
|
||||||
// Make sure the path is what we expect.
|
// Make sure the path is what we expect.
|
||||||
fullPath := logicalRootPath + nextPath
|
fullPath := logicalRootPath + nextPath
|
||||||
if err := checkProcSelfFdPath(fullPath, currentDir); err != nil {
|
if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil {
|
||||||
return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err)
|
return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +382,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
|||||||
// context of openat2, a trailing slash and a trailing "/." are completely
|
// context of openat2, a trailing slash and a trailing "/." are completely
|
||||||
// equivalent.
|
// equivalent.
|
||||||
if strings.HasSuffix(unsafePath, "/") {
|
if strings.HasSuffix(unsafePath, "/") {
|
||||||
nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !partial {
|
if !partial {
|
||||||
_ = currentDir.Close()
|
_ = currentDir.Close()
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
//go:build linux
|
//go:build linux
|
||||||
|
|
||||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||||
// Use of this source code is governed by a BSD-style
|
// Copyright (C) 2024-2025 SUSE LLC
|
||||||
// license that can be found in the LICENSE file.
|
//
|
||||||
|
// 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
|
package gopathrs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -14,12 +19,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"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"
|
||||||
|
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// ErrInvalidMode is returned from [MkdirAll] when the requested mode is
|
||||||
errInvalidMode = errors.New("invalid permission mode")
|
// invalid.
|
||||||
errPossibleAttack = errors.New("possible attack detected")
|
var ErrInvalidMode = errors.New("invalid permission mode")
|
||||||
)
|
|
||||||
|
|
||||||
// modePermExt is like os.ModePerm except that it also includes the set[ug]id
|
// modePermExt is like os.ModePerm except that it also includes the set[ug]id
|
||||||
// and sticky bits.
|
// and sticky bits.
|
||||||
@@ -39,11 +48,11 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
|
|||||||
}
|
}
|
||||||
// We don't allow file type bits.
|
// We don't allow file type bits.
|
||||||
if mode&os.ModeType != 0 {
|
if mode&os.ModeType != 0 {
|
||||||
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode)
|
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", ErrInvalidMode, mode, mode)
|
||||||
}
|
}
|
||||||
// We don't allow other unknown modes.
|
// We don't allow other unknown modes.
|
||||||
if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 {
|
if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 {
|
||||||
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode)
|
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", ErrInvalidMode, mode, mode)
|
||||||
}
|
}
|
||||||
return sysMode, nil
|
return sysMode, nil
|
||||||
}
|
}
|
||||||
@@ -66,6 +75,8 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
|
|||||||
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
|
// 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
|
// doing [MkdirAll]. If you intend to open the directory after creating it, you
|
||||||
// should use MkdirAllHandle.
|
// 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) {
|
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) {
|
||||||
unixMode, err := toUnixMode(mode)
|
unixMode, err := toUnixMode(mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -76,11 +87,11 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
|||||||
// users it seems more prudent to return an error so users notice that
|
// users it seems more prudent to return an error so users notice that
|
||||||
// these bits will not be set.
|
// these bits will not be set.
|
||||||
if unixMode&^0o1777 != 0 {
|
if unixMode&^0o1777 != 0 {
|
||||||
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode)
|
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", ErrInvalidMode, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to open as much of the path as possible.
|
// Try to open as much of the path as possible.
|
||||||
currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath)
|
currentDir, remainingPath, err := PartialLookupInRoot(root, unsafePath)
|
||||||
defer func() {
|
defer func() {
|
||||||
if Err != nil {
|
if Err != nil {
|
||||||
_ = currentDir.Close()
|
_ = currentDir.Close()
|
||||||
@@ -102,24 +113,24 @@ 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
|
// This is mostly a quality-of-life check, because mkdir will simply fail
|
||||||
// later if the attacker deletes the tree after this check.
|
// later if the attacker deletes the tree after this check.
|
||||||
if err := isDeadInode(currentDir); err != nil {
|
if err := fd.IsDeadInode(currentDir); err != nil {
|
||||||
return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err)
|
return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-open the path to match the O_DIRECTORY reopen loop later (so that we
|
// Re-open the path to match the O_DIRECTORY reopen loop later (so that we
|
||||||
// always return a non-O_PATH handle). We also check that we actually got a
|
// always return a non-O_PATH handle). We also check that we actually got a
|
||||||
// directory.
|
// directory.
|
||||||
if reopenDir, err := Reopen(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) {
|
if reopenDir, err := procfs.ReopenFd(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) {
|
||||||
return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR)
|
return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err)
|
return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err)
|
||||||
} else {
|
} else { //nolint:revive // indent-error-flow lint doesn't make sense here
|
||||||
_ = currentDir.Close()
|
_ = currentDir.Close()
|
||||||
currentDir = reopenDir
|
currentDir = reopenDir
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingParts := strings.Split(remainingPath, string(filepath.Separator))
|
remainingParts := strings.Split(remainingPath, string(filepath.Separator))
|
||||||
if slices_Contains(remainingParts, "..") {
|
if gocompat.SlicesContains(remainingParts, "..") {
|
||||||
// The path contained ".." components after the end of the "real"
|
// The path contained ".." components after the end of the "real"
|
||||||
// components. We could try to safely resolve ".." here but that would
|
// 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
|
// add a bunch of extra logic for something that it's not clear even
|
||||||
@@ -150,12 +161,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) {
|
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}
|
err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err}
|
||||||
// Make the error a bit nicer if the directory is dead.
|
// Make the error a bit nicer if the directory is dead.
|
||||||
if deadErr := isDeadInode(currentDir); deadErr != nil {
|
if deadErr := fd.IsDeadInode(currentDir); deadErr != nil {
|
||||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
// 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
|
// multiple %w verbs for this wrapping. For now we need to use a
|
||||||
// compatibility shim for older Go versions.
|
// compatibility shim for older Go versions.
|
||||||
//err = fmt.Errorf("%w (%w)", err, deadErr)
|
// err = fmt.Errorf("%w (%w)", err, deadErr)
|
||||||
err = wrapBaseError(err, deadErr)
|
err = gocompat.WrapBaseError(err, deadErr)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -163,13 +174,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
|
// Get a handle to the next component. O_DIRECTORY means we don't need
|
||||||
// to use O_PATH.
|
// to use O_PATH.
|
||||||
var nextDir *os.File
|
var nextDir *os.File
|
||||||
if hasOpenat2() {
|
if linux.HasOpenat2() {
|
||||||
nextDir, err = openat2File(currentDir, part, &unix.OpenHow{
|
nextDir, err = openat2(currentDir, part, &unix.OpenHow{
|
||||||
Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC,
|
Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC,
|
||||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
|
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
nextDir, err = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -199,38 +210,3 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
|||||||
}
|
}
|
||||||
return currentDir, nil
|
return currentDir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirAll is a race-safe alternative to the [os.MkdirAll] function,
|
|
||||||
// where the new directory is guaranteed to be within the root directory (if an
|
|
||||||
// attacker can move directories from inside the root to outside the root, the
|
|
||||||
// created directory tree might be outside of the root but the key constraint
|
|
||||||
// is that at no point will we walk outside of the directory tree we are
|
|
||||||
// creating).
|
|
||||||
//
|
|
||||||
// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to
|
|
||||||
//
|
|
||||||
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
|
||||||
// err := os.MkdirAll(path, mode)
|
|
||||||
//
|
|
||||||
// But is much safer. The above implementation is unsafe because if an attacker
|
|
||||||
// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is
|
|
||||||
// possible for MkdirAll to resolve unsafe symlink components and create
|
|
||||||
// directories outside of the root.
|
|
||||||
//
|
|
||||||
// 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].
|
|
||||||
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()
|
|
||||||
|
|
||||||
f, err := MkdirAllHandle(rootDir, unsafePath, mode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = f.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
26
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go
generated
vendored
Normal file
26
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// 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 gopathrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
||||||
|
// using an *[os.File] handle, to ensure that the correct root directory is used.
|
||||||
|
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||||
|
handle, err := completeLookupInRoot(root, unsafePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err}
|
||||||
|
}
|
||||||
|
return handle, nil
|
||||||
|
}
|
||||||
101
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go
generated
vendored
Normal file
101
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// 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 gopathrs
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
123
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
generated
vendored
Normal file
123
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// 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
Normal file
12
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// 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
Normal file
47
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// 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
Normal file
31
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// 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
Normal file
544
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
generated
vendored
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
// 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 }
|
||||||
222
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
generated
vendored
Normal file
222
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go
generated
vendored
Normal file
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MkdirAll is a race-safe alternative to the [os.MkdirAll] function,
|
||||||
|
// where the new directory is guaranteed to be within the root directory (if an
|
||||||
|
// attacker can move directories from inside the root to outside the root, the
|
||||||
|
// created directory tree might be outside of the root but the key constraint
|
||||||
|
// is that at no point will we walk outside of the directory tree we are
|
||||||
|
// creating).
|
||||||
|
//
|
||||||
|
// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to
|
||||||
|
//
|
||||||
|
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
||||||
|
// err := os.MkdirAll(path, mode)
|
||||||
|
//
|
||||||
|
// But is much safer. The above implementation is unsafe because if an attacker
|
||||||
|
// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is
|
||||||
|
// possible for MkdirAll to resolve unsafe symlink components and create
|
||||||
|
// directories outside of the root.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
f, err := MkdirAllHandle(rootDir, unsafePath, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = f.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
52
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go
generated
vendored
Normal file
52
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//go:build libpathrs
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"cyphar.com/go-pathrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use
|
||||||
|
// in two respects:
|
||||||
|
//
|
||||||
|
// - The caller provides the root directory as an *[os.File] (preferably O_PATH)
|
||||||
|
// handle. This means that the caller can be sure which root directory is
|
||||||
|
// being used. Note that this can be emulated by using /proc/self/fd/... as
|
||||||
|
// the root path with [os.MkdirAll].
|
||||||
|
//
|
||||||
|
// - Once all of the directories have been created, an *[os.File] O_PATH handle
|
||||||
|
// to the directory at unsafePath is returned to the caller. This is done in
|
||||||
|
// an effectively-race-free way (an attacker would only be able to swap the
|
||||||
|
// final directory component), which is not possible to emulate with
|
||||||
|
// [MkdirAll].
|
||||||
|
//
|
||||||
|
// In addition, the returned handle is obtained far more efficiently than doing
|
||||||
|
// 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, error) {
|
||||||
|
rootRef, err := pathrs.RootFromFile(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rootRef.Close() //nolint:errcheck // close failures aren't critical here
|
||||||
|
|
||||||
|
handle, err := rootRef.MkdirAll(unsafePath, mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return handle.IntoFile(), nil
|
||||||
|
}
|
||||||
42
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go
generated
vendored
Normal file
42
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//go:build linux && !libpathrs
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use
|
||||||
|
// in two respects:
|
||||||
|
//
|
||||||
|
// - The caller provides the root directory as an *[os.File] (preferably O_PATH)
|
||||||
|
// handle. This means that the caller can be sure which root directory is
|
||||||
|
// being used. Note that this can be emulated by using /proc/self/fd/... as
|
||||||
|
// the root path with [os.MkdirAll].
|
||||||
|
//
|
||||||
|
// - Once all of the directories have been created, an *[os.File] O_PATH handle
|
||||||
|
// to the directory at unsafePath is returned to the caller. This is done in
|
||||||
|
// an effectively-race-free way (an attacker would only be able to swap the
|
||||||
|
// final directory component), which is not possible to emulate with
|
||||||
|
// [MkdirAll].
|
||||||
|
//
|
||||||
|
// In addition, the returned handle is obtained far more efficiently than doing
|
||||||
|
// 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, error) {
|
||||||
|
return gopathrs.MkdirAllHandle(root, unsafePath, mode)
|
||||||
|
}
|
||||||
45
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go
generated
vendored
Normal file
45
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenInRoot safely opens the provided unsafePath within the root.
|
||||||
|
// Effectively, OpenInRoot(root, unsafePath) is equivalent to
|
||||||
|
//
|
||||||
|
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
||||||
|
// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC)
|
||||||
|
//
|
||||||
|
// But is much safer. The above implementation is unsafe because if an attacker
|
||||||
|
// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is
|
||||||
|
// possible for the returned file to be outside of the root.
|
||||||
|
//
|
||||||
|
// Note that the returned handle is an O_PATH handle, meaning that only a very
|
||||||
|
// limited set of operations will work on the handle. This is done to avoid
|
||||||
|
// accidentally opening an untrusted file that could cause issues (such as a
|
||||||
|
// 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
|
||||||
|
return OpenatInRoot(rootDir, unsafePath)
|
||||||
|
}
|
||||||
57
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go
generated
vendored
Normal file
57
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//go:build libpathrs
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"cyphar.com/go-pathrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
||||||
|
// using an *[os.File] handle, to ensure that the correct root directory is used.
|
||||||
|
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||||
|
rootRef, err := pathrs.RootFromFile(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rootRef.Close() //nolint:errcheck // close failures aren't critical here
|
||||||
|
|
||||||
|
handle, err := rootRef.Resolve(unsafePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return handle.IntoFile(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd.
|
||||||
|
// Reopen(file, flags) is effectively equivalent to
|
||||||
|
//
|
||||||
|
// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
|
||||||
|
// os.OpenFile(fdPath, flags|unix.O_CLOEXEC)
|
||||||
|
//
|
||||||
|
// But with some extra hardenings to ensure that we are not tricked by a
|
||||||
|
// maliciously-configured /proc mount. While this attack scenario is not
|
||||||
|
// common, in container runtimes it is possible for higher-level runtimes to be
|
||||||
|
// tricked into configuring an unsafe /proc that can be used to attack file
|
||||||
|
// operations. See [CVE-2019-19921] for more details.
|
||||||
|
//
|
||||||
|
// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
|
||||||
|
func Reopen(file *os.File, flags int) (*os.File, error) {
|
||||||
|
handle, err := pathrs.HandleFromFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer handle.Close() //nolint:errcheck // close failures aren't critical here
|
||||||
|
|
||||||
|
return handle.OpenFile(flags)
|
||||||
|
}
|
||||||
42
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go
generated
vendored
Normal file
42
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//go:build linux && !libpathrs
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs"
|
||||||
|
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
||||||
|
// using an *[os.File] handle, to ensure that the correct root directory is used.
|
||||||
|
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||||
|
return gopathrs.OpenatInRoot(root, unsafePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd.
|
||||||
|
// Reopen(file, flags) is effectively equivalent to
|
||||||
|
//
|
||||||
|
// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
|
||||||
|
// os.OpenFile(fdPath, flags|unix.O_CLOEXEC)
|
||||||
|
//
|
||||||
|
// But with some extra hardenings to ensure that we are not tricked by a
|
||||||
|
// maliciously-configured /proc mount. While this attack scenario is not
|
||||||
|
// common, in container runtimes it is possible for higher-level runtimes to be
|
||||||
|
// tricked into configuring an unsafe /proc that can be used to attack file
|
||||||
|
// operations. See [CVE-2019-19921] for more details.
|
||||||
|
//
|
||||||
|
// [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)
|
||||||
|
}
|
||||||
161
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go
generated
vendored
Normal file
161
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//go:build libpathrs
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"cyphar.com/go-pathrs/procfs"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.ThreadCloser
|
||||||
|
|
||||||
|
// 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.Open()
|
||||||
|
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.Open(procfs.UnmaskedProcRoot)
|
||||||
|
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, unix.O_PATH|unix.O_NOFOLLOW)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, unix.O_PATH|unix.O_NOFOLLOW)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, unix.O_PATH|unix.O_NOFOLLOW)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, unix.O_PATH|unix.O_NOFOLLOW)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
proc, err := procfs.Open()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer proc.Close() //nolint:errcheck // close failures aren't critical here
|
||||||
|
|
||||||
|
fdPath := "fd/" + strconv.Itoa(int(f.Fd()))
|
||||||
|
return proc.Readlink(procfs.ProcThreadSelf, fdPath)
|
||||||
|
}
|
||||||
157
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go
generated
vendored
Normal file
157
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//go:build linux && !libpathrs
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
452
vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
generated
vendored
452
vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
generated
vendored
@@ -1,452 +0,0 @@
|
|||||||
//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 }
|
|
||||||
2
vendor/github.com/cyphar/filepath-securejoin/vfs.go
generated
vendored
2
vendor/github.com/cyphar/filepath-securejoin/vfs.go
generated
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|||||||
6
vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go
generated
vendored
6
vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go
generated
vendored
@@ -18,7 +18,7 @@ var validOptions = map[string]bool{
|
|||||||
"level": true,
|
"level": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrIncompatibleLabel = errors.New("Bad SELinux option z and Z can not be used together")
|
var ErrIncompatibleLabel = errors.New("bad SELinux option: z and Z can not be used together")
|
||||||
|
|
||||||
// InitLabels returns the process label and file labels to be used within
|
// InitLabels returns the process label and file labels to be used within
|
||||||
// the container. A list of options can be passed into this function to alter
|
// the container. A list of options can be passed into this function to alter
|
||||||
@@ -52,11 +52,11 @@ func InitLabels(options []string) (plabel string, mlabel string, retErr error) {
|
|||||||
return "", selinux.PrivContainerMountLabel(), nil
|
return "", selinux.PrivContainerMountLabel(), nil
|
||||||
}
|
}
|
||||||
if i := strings.Index(opt, ":"); i == -1 {
|
if i := strings.Index(opt, ":"); i == -1 {
|
||||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
|
return "", "", fmt.Errorf("bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
|
||||||
}
|
}
|
||||||
con := strings.SplitN(opt, ":", 2)
|
con := strings.SplitN(opt, ":", 2)
|
||||||
if !validOptions[con[0]] {
|
if !validOptions[con[0]] {
|
||||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
|
return "", "", fmt.Errorf("bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
|
||||||
}
|
}
|
||||||
if con[0] == "filetype" {
|
if con[0] == "filetype" {
|
||||||
mcon["type"] = con[1]
|
mcon["type"] = con[1]
|
||||||
|
|||||||
10
vendor/github.com/opencontainers/selinux/go-selinux/selinux.go
generated
vendored
10
vendor/github.com/opencontainers/selinux/go-selinux/selinux.go
generated
vendored
@@ -153,7 +153,7 @@ func CalculateGlbLub(sourceRange, targetRange string) (string, error) {
|
|||||||
// of the program is finished to guarantee another goroutine does not migrate to the current
|
// of the program is finished to guarantee another goroutine does not migrate to the current
|
||||||
// thread before execution is complete.
|
// thread before execution is complete.
|
||||||
func SetExecLabel(label string) error {
|
func SetExecLabel(label string) error {
|
||||||
return writeCon(attrPath("exec"), label)
|
return writeConThreadSelf("attr/exec", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTaskLabel sets the SELinux label for the current thread, or an error.
|
// SetTaskLabel sets the SELinux label for the current thread, or an error.
|
||||||
@@ -161,7 +161,7 @@ func SetExecLabel(label string) error {
|
|||||||
// be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() to guarantee
|
// be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() to guarantee
|
||||||
// the current thread does not run in a new mislabeled thread.
|
// the current thread does not run in a new mislabeled thread.
|
||||||
func SetTaskLabel(label string) error {
|
func SetTaskLabel(label string) error {
|
||||||
return writeCon(attrPath("current"), label)
|
return writeConThreadSelf("attr/current", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSocketLabel takes a process label and tells the kernel to assign the
|
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||||
@@ -170,12 +170,12 @@ func SetTaskLabel(label string) error {
|
|||||||
// the socket is created to guarantee another goroutine does not migrate
|
// the socket is created to guarantee another goroutine does not migrate
|
||||||
// to the current thread before execution is complete.
|
// to the current thread before execution is complete.
|
||||||
func SetSocketLabel(label string) error {
|
func SetSocketLabel(label string) error {
|
||||||
return writeCon(attrPath("sockcreate"), label)
|
return writeConThreadSelf("attr/sockcreate", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketLabel retrieves the current socket label setting
|
// SocketLabel retrieves the current socket label setting
|
||||||
func SocketLabel() (string, error) {
|
func SocketLabel() (string, error) {
|
||||||
return readCon(attrPath("sockcreate"))
|
return readConThreadSelf("attr/sockcreate")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerLabel retrieves the label of the client on the other side of a socket
|
// PeerLabel retrieves the label of the client on the other side of a socket
|
||||||
@@ -198,7 +198,7 @@ func SetKeyLabel(label string) error {
|
|||||||
|
|
||||||
// KeyLabel retrieves the current kernel keyring label setting
|
// KeyLabel retrieves the current kernel keyring label setting
|
||||||
func KeyLabel() (string, error) {
|
func KeyLabel() (string, error) {
|
||||||
return readCon("/proc/self/attr/keycreate")
|
return keyLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the Context as a string
|
// Get returns the Context as a string
|
||||||
|
|||||||
294
vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
generated
vendored
294
vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
generated
vendored
@@ -17,8 +17,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/opencontainers/selinux/pkg/pwalkdir"
|
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||||
|
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/opencontainers/selinux/pkg/pwalkdir"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -73,10 +76,6 @@ var (
|
|||||||
mcsList: make(map[string]bool),
|
mcsList: make(map[string]bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
// for attrPath()
|
|
||||||
attrPathOnce sync.Once
|
|
||||||
haveThreadSelf bool
|
|
||||||
|
|
||||||
// for policyRoot()
|
// for policyRoot()
|
||||||
policyRootOnce sync.Once
|
policyRootOnce sync.Once
|
||||||
policyRootVal string
|
policyRootVal string
|
||||||
@@ -256,42 +255,6 @@ func readConfig(target string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func isProcHandle(fh *os.File) error {
|
|
||||||
var buf unix.Statfs_t
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := unix.Fstatfs(int(fh.Fd()), &buf)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != unix.EINTR {
|
|
||||||
return &os.PathError{Op: "fstatfs", Path: fh.Name(), Err: err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if buf.Type != unix.PROC_SUPER_MAGIC {
|
|
||||||
return fmt.Errorf("file %q is not on procfs", fh.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readCon(fpath string) (string, error) {
|
|
||||||
if fpath == "" {
|
|
||||||
return "", ErrEmptyPath
|
|
||||||
}
|
|
||||||
|
|
||||||
in, err := os.Open(fpath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
if err := isProcHandle(in); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return readConFd(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConFd(in *os.File) (string, error) {
|
func readConFd(in *os.File) (string, error) {
|
||||||
data, err := io.ReadAll(in)
|
data, err := io.ReadAll(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -300,6 +263,177 @@ func readConFd(in *os.File) (string, error) {
|
|||||||
return string(bytes.TrimSuffix(data, []byte{0})), nil
|
return string(bytes.TrimSuffix(data, []byte{0})), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeConFd(out *os.File, val string) error {
|
||||||
|
var err error
|
||||||
|
if val != "" {
|
||||||
|
_, err = out.Write([]byte(val))
|
||||||
|
} else {
|
||||||
|
_, err = out.Write(nil)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// openProcThreadSelf is a small wrapper around [procfs.Handle.OpenThreadSelf]
|
||||||
|
// and [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The
|
||||||
|
// provided mode must be os.O_* flags to indicate what mode the returned file
|
||||||
|
// should be opened with (flags like os.O_CREAT and os.O_EXCL are not
|
||||||
|
// supported).
|
||||||
|
//
|
||||||
|
// If no error occurred, the returned handle is guaranteed to be exactly
|
||||||
|
// /proc/thread-self/<subpath> with no tricky mounts or symlinks causing you to
|
||||||
|
// operate on an unexpected path (with some caveats on pre-openat2 or
|
||||||
|
// pre-fsopen kernels).
|
||||||
|
func openProcThreadSelf(subpath string, mode int) (*os.File, procfs.ProcThreadSelfCloser, error) {
|
||||||
|
if subpath == "" {
|
||||||
|
return nil, nil, ErrEmptyPath
|
||||||
|
}
|
||||||
|
|
||||||
|
proc, err := procfs.OpenProcRoot()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer proc.Close()
|
||||||
|
|
||||||
|
handle, closer, err := proc.OpenThreadSelf(subpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("open /proc/thread-self/%s handle: %w", subpath, err)
|
||||||
|
}
|
||||||
|
defer handle.Close() // we will return a re-opened handle
|
||||||
|
|
||||||
|
file, err := pathrs.Reopen(handle, mode)
|
||||||
|
if err != nil {
|
||||||
|
closer()
|
||||||
|
return nil, nil, fmt.Errorf("reopen /proc/thread-self/%s handle (%#x): %w", subpath, mode, err)
|
||||||
|
}
|
||||||
|
return file, closer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the contents of /proc/thread-self/<fpath>.
|
||||||
|
func readConThreadSelf(fpath string) (string, error) {
|
||||||
|
in, closer, err := openProcThreadSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
return readConFd(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write <val> to /proc/thread-self/<fpath>.
|
||||||
|
func writeConThreadSelf(fpath, val string) error {
|
||||||
|
if val == "" {
|
||||||
|
if !getEnabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out, closer, err := openProcThreadSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
return writeConFd(out, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// openProcSelf is a small wrapper around [procfs.Handle.OpenSelf] and
|
||||||
|
// [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The
|
||||||
|
// provided mode must be os.O_* flags to indicate what mode the returned file
|
||||||
|
// should be opened with (flags like os.O_CREAT and os.O_EXCL are not
|
||||||
|
// supported).
|
||||||
|
//
|
||||||
|
// If no error occurred, the returned handle is guaranteed to be exactly
|
||||||
|
// /proc/self/<subpath> with no tricky mounts or symlinks causing you to
|
||||||
|
// operate on an unexpected path (with some caveats on pre-openat2 or
|
||||||
|
// pre-fsopen kernels).
|
||||||
|
func openProcSelf(subpath string, mode int) (*os.File, error) {
|
||||||
|
if subpath == "" {
|
||||||
|
return nil, ErrEmptyPath
|
||||||
|
}
|
||||||
|
|
||||||
|
proc, err := procfs.OpenProcRoot()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer proc.Close()
|
||||||
|
|
||||||
|
handle, err := proc.OpenSelf(subpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open /proc/self/%s handle: %w", subpath, err)
|
||||||
|
}
|
||||||
|
defer handle.Close() // we will return a re-opened handle
|
||||||
|
|
||||||
|
file, err := pathrs.Reopen(handle, mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reopen /proc/self/%s handle (%#x): %w", subpath, mode, err)
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the contents of /proc/self/<fpath>.
|
||||||
|
func readConSelf(fpath string) (string, error) {
|
||||||
|
in, err := openProcSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
return readConFd(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write <val> to /proc/self/<fpath>.
|
||||||
|
func writeConSelf(fpath, val string) error {
|
||||||
|
if val == "" {
|
||||||
|
if !getEnabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := openProcSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
return writeConFd(out, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// openProcPid is a small wrapper around [procfs.Handle.OpenPid] and
|
||||||
|
// [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The
|
||||||
|
// provided mode must be os.O_* flags to indicate what mode the returned file
|
||||||
|
// should be opened with (flags like os.O_CREAT and os.O_EXCL are not
|
||||||
|
// supported).
|
||||||
|
//
|
||||||
|
// If no error occurred, the returned handle is guaranteed to be exactly
|
||||||
|
// /proc/self/<subpath> with no tricky mounts or symlinks causing you to
|
||||||
|
// operate on an unexpected path (with some caveats on pre-openat2 or
|
||||||
|
// pre-fsopen kernels).
|
||||||
|
func openProcPid(pid int, subpath string, mode int) (*os.File, error) {
|
||||||
|
if subpath == "" {
|
||||||
|
return nil, ErrEmptyPath
|
||||||
|
}
|
||||||
|
|
||||||
|
proc, err := procfs.OpenProcRoot()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer proc.Close()
|
||||||
|
|
||||||
|
handle, err := proc.OpenPid(pid, subpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open /proc/%d/%s handle: %w", pid, subpath, err)
|
||||||
|
}
|
||||||
|
defer handle.Close() // we will return a re-opened handle
|
||||||
|
|
||||||
|
file, err := pathrs.Reopen(handle, mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reopen /proc/%d/%s handle (%#x): %w", pid, subpath, mode, err)
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
// classIndex returns the int index for an object class in the loaded policy,
|
// classIndex returns the int index for an object class in the loaded policy,
|
||||||
// or -1 and an error
|
// or -1 and an error
|
||||||
func classIndex(class string) (int, error) {
|
func classIndex(class string) (int, error) {
|
||||||
@@ -393,78 +527,34 @@ func lFileLabel(fpath string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setFSCreateLabel(label string) error {
|
func setFSCreateLabel(label string) error {
|
||||||
return writeCon(attrPath("fscreate"), label)
|
return writeConThreadSelf("attr/fscreate", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fsCreateLabel returns the default label the kernel which the kernel is using
|
// fsCreateLabel returns the default label the kernel which the kernel is using
|
||||||
// for file system objects created by this task. "" indicates default.
|
// for file system objects created by this task. "" indicates default.
|
||||||
func fsCreateLabel() (string, error) {
|
func fsCreateLabel() (string, error) {
|
||||||
return readCon(attrPath("fscreate"))
|
return readConThreadSelf("attr/fscreate")
|
||||||
}
|
}
|
||||||
|
|
||||||
// currentLabel returns the SELinux label of the current process thread, or an error.
|
// currentLabel returns the SELinux label of the current process thread, or an error.
|
||||||
func currentLabel() (string, error) {
|
func currentLabel() (string, error) {
|
||||||
return readCon(attrPath("current"))
|
return readConThreadSelf("attr/current")
|
||||||
}
|
}
|
||||||
|
|
||||||
// pidLabel returns the SELinux label of the given pid, or an error.
|
// pidLabel returns the SELinux label of the given pid, or an error.
|
||||||
func pidLabel(pid int) (string, error) {
|
func pidLabel(pid int) (string, error) {
|
||||||
return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
|
it, err := openProcPid(pid, "attr/current", os.O_RDONLY|unix.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
defer it.Close()
|
||||||
|
return readConFd(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecLabel returns the SELinux label that the kernel will use for any programs
|
// ExecLabel returns the SELinux label that the kernel will use for any programs
|
||||||
// that are executed by the current process thread, or an error.
|
// that are executed by the current process thread, or an error.
|
||||||
func execLabel() (string, error) {
|
func execLabel() (string, error) {
|
||||||
return readCon(attrPath("exec"))
|
return readConThreadSelf("exec")
|
||||||
}
|
|
||||||
|
|
||||||
func writeCon(fpath, val string) error {
|
|
||||||
if fpath == "" {
|
|
||||||
return ErrEmptyPath
|
|
||||||
}
|
|
||||||
if val == "" {
|
|
||||||
if !getEnabled() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := os.OpenFile(fpath, os.O_WRONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
if err := isProcHandle(out); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if val != "" {
|
|
||||||
_, err = out.Write([]byte(val))
|
|
||||||
} else {
|
|
||||||
_, err = out.Write(nil)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrPath(attr string) string {
|
|
||||||
// Linux >= 3.17 provides this
|
|
||||||
const threadSelfPrefix = "/proc/thread-self/attr"
|
|
||||||
|
|
||||||
attrPathOnce.Do(func() {
|
|
||||||
st, err := os.Stat(threadSelfPrefix)
|
|
||||||
if err == nil && st.Mode().IsDir() {
|
|
||||||
haveThreadSelf = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if haveThreadSelf {
|
|
||||||
return filepath.Join(threadSelfPrefix, attr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join("/proc/self/task", strconv.Itoa(unix.Gettid()), "attr", attr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// canonicalizeContext takes a context string and writes it to the kernel
|
// canonicalizeContext takes a context string and writes it to the kernel
|
||||||
@@ -728,19 +818,29 @@ func peerLabel(fd uintptr) (string, error) {
|
|||||||
// setKeyLabel takes a process label and tells the kernel to assign the
|
// setKeyLabel takes a process label and tells the kernel to assign the
|
||||||
// label to the next kernel keyring that gets created
|
// label to the next kernel keyring that gets created
|
||||||
func setKeyLabel(label string) error {
|
func setKeyLabel(label string) error {
|
||||||
err := writeCon("/proc/self/attr/keycreate", label)
|
// Rather than using /proc/thread-self, we want to use /proc/self to
|
||||||
|
// operate on the thread-group leader.
|
||||||
|
err := writeConSelf("attr/keycreate", label)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if label == "" && errors.Is(err, os.ErrPermission) {
|
if label == "" && errors.Is(err, os.ErrPermission) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if errors.Is(err, unix.EACCES) && unix.Getuid() != unix.Gettid() {
|
if errors.Is(err, unix.EACCES) && unix.Getpid() != unix.Gettid() {
|
||||||
return ErrNotTGLeader
|
return ErrNotTGLeader
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyLabel retrieves the current kernel keyring label setting for this
|
||||||
|
// thread-group.
|
||||||
|
func keyLabel() (string, error) {
|
||||||
|
// Rather than using /proc/thread-self, we want to use /proc/self to
|
||||||
|
// operate on the thread-group leader.
|
||||||
|
return readConSelf("attr/keycreate")
|
||||||
|
}
|
||||||
|
|
||||||
// get returns the Context as a string
|
// get returns the Context as a string
|
||||||
func (c Context) get() string {
|
func (c Context) get() string {
|
||||||
if l := c["level"]; l != "" {
|
if l := c["level"]; l != "" {
|
||||||
|
|||||||
12
vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
generated
vendored
12
vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
generated
vendored
@@ -3,15 +3,11 @@
|
|||||||
|
|
||||||
package selinux
|
package selinux
|
||||||
|
|
||||||
func attrPath(string) string {
|
func readConThreadSelf(string) (string, error) {
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func readCon(string) (string, error) {
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCon(string, string) error {
|
func writeConThreadSelf(string, string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +77,10 @@ func setKeyLabel(string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func keyLabel() (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c Context) get() string {
|
func (c Context) get() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
22
vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
22
vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
@@ -390,7 +390,8 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface
|
|||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
}
|
}
|
||||||
return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
failMessage := fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, failMessage, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||||
@@ -403,7 +404,8 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in
|
|||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
}
|
}
|
||||||
return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
failMessage := fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, failMessage, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less asserts that the first element is less than the second
|
// Less asserts that the first element is less than the second
|
||||||
@@ -415,7 +417,8 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{})
|
|||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
}
|
}
|
||||||
return compareTwoValues(t, e1, e2, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
failMessage := fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareLess}, failMessage, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LessOrEqual asserts that the first element is less than or equal to the second
|
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||||
@@ -428,7 +431,8 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
|
|||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
}
|
}
|
||||||
return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
failMessage := fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, failMessage, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Positive asserts that the specified element is positive
|
// Positive asserts that the specified element is positive
|
||||||
@@ -440,7 +444,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
|||||||
h.Helper()
|
h.Helper()
|
||||||
}
|
}
|
||||||
zero := reflect.Zero(reflect.TypeOf(e))
|
zero := reflect.Zero(reflect.TypeOf(e))
|
||||||
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, "\"%v\" is not positive", msgAndArgs...)
|
failMessage := fmt.Sprintf("\"%v\" is not positive", e)
|
||||||
|
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Negative asserts that the specified element is negative
|
// Negative asserts that the specified element is negative
|
||||||
@@ -452,7 +457,8 @@ func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
|||||||
h.Helper()
|
h.Helper()
|
||||||
}
|
}
|
||||||
zero := reflect.Zero(reflect.TypeOf(e))
|
zero := reflect.Zero(reflect.TypeOf(e))
|
||||||
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, "\"%v\" is not negative", msgAndArgs...)
|
failMessage := fmt.Sprintf("\"%v\" is not negative", e)
|
||||||
|
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
|
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
|
||||||
@@ -468,11 +474,11 @@ func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedCompare
|
|||||||
|
|
||||||
compareResult, isComparable := compare(e1, e2, e1Kind)
|
compareResult, isComparable := compare(e1, e2, e1Kind)
|
||||||
if !isComparable {
|
if !isComparable {
|
||||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
return Fail(t, fmt.Sprintf(`Can not compare type "%T"`, e1), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !containsValue(allowedComparesResults, compareResult) {
|
if !containsValue(allowedComparesResults, compareResult) {
|
||||||
return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
|
return Fail(t, failMessage, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
51
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
51
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
@@ -50,10 +50,19 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
|
|||||||
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// Emptyf asserts that the given value is "empty".
|
||||||
// a slice or a channel with len == 0.
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -117,10 +126,8 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri
|
|||||||
|
|
||||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
// assert.Errorf(t, err, "error message %s", "formatted")
|
||||||
// assert.Equal(t, expectedErrorf, err)
|
|
||||||
// }
|
|
||||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -438,7 +445,19 @@ func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interf
|
|||||||
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNotTypef asserts that the specified objects are not of the same type.
|
||||||
|
//
|
||||||
|
// assert.IsNotTypef(t, &NotMyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
|
func IsNotTypef(t TestingT, theType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsNotType(t, theType, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// IsTypef asserts that the specified objects are of the same type.
|
// IsTypef asserts that the specified objects are of the same type.
|
||||||
|
//
|
||||||
|
// assert.IsTypef(t, &MyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -585,8 +604,7 @@ func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg str
|
|||||||
return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmptyf asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||||
// assert.Equal(t, "two", obj[1])
|
// assert.Equal(t, "two", obj[1])
|
||||||
@@ -693,12 +711,15 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string,
|
|||||||
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
|
// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
|
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
|
||||||
// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
||||||
|
// assert.NotSubsetf(t, [1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// assert.NotSubsetf(t, {"x": 1, "y": 2}, ["z"], "error message %s", "formatted")
|
||||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -782,11 +803,15 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg
|
|||||||
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsetf asserts that the specified list(array, slice...) or map contains all
|
// Subsetf asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
|
// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
|
||||||
// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
||||||
|
// assert.Subsetf(t, [1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// assert.Subsetf(t, {"x": 1, "y": 2}, ["x"], "error message %s", "formatted")
|
||||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
|
|||||||
102
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
102
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
@@ -92,10 +92,19 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st
|
|||||||
return ElementsMatchf(a.t, listA, listB, msg, args...)
|
return ElementsMatchf(a.t, listA, listB, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// Empty asserts that the given value is "empty".
|
||||||
// a slice or a channel with len == 0.
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// a.Empty(obj)
|
// a.Empty(obj)
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
|
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -103,10 +112,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
|
|||||||
return Empty(a.t, object, msgAndArgs...)
|
return Empty(a.t, object, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// Emptyf asserts that the given value is "empty".
|
||||||
// a slice or a channel with len == 0.
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// a.Emptyf(obj, "error message %s", "formatted")
|
// a.Emptyf(obj, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
|
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -224,10 +242,8 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string
|
|||||||
|
|
||||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if a.Error(err) {
|
// a.Error(err)
|
||||||
// assert.Equal(t, expectedError, err)
|
|
||||||
// }
|
|
||||||
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
|
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -297,10 +313,8 @@ func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...inter
|
|||||||
|
|
||||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if a.Errorf(err, "error message %s", "formatted") {
|
// a.Errorf(err, "error message %s", "formatted")
|
||||||
// assert.Equal(t, expectedErrorf, err)
|
|
||||||
// }
|
|
||||||
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
|
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -868,7 +882,29 @@ func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...in
|
|||||||
return IsNonIncreasingf(a.t, object, msg, args...)
|
return IsNonIncreasingf(a.t, object, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNotType asserts that the specified objects are not of the same type.
|
||||||
|
//
|
||||||
|
// a.IsNotType(&NotMyStruct{}, &MyStruct{})
|
||||||
|
func (a *Assertions) IsNotType(theType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsNotType(a.t, theType, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotTypef asserts that the specified objects are not of the same type.
|
||||||
|
//
|
||||||
|
// a.IsNotTypef(&NotMyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
|
func (a *Assertions) IsNotTypef(theType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsNotTypef(a.t, theType, object, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// IsType asserts that the specified objects are of the same type.
|
// IsType asserts that the specified objects are of the same type.
|
||||||
|
//
|
||||||
|
// a.IsType(&MyStruct{}, &MyStruct{})
|
||||||
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -877,6 +913,8 @@ func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAnd
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsTypef asserts that the specified objects are of the same type.
|
// IsTypef asserts that the specified objects are of the same type.
|
||||||
|
//
|
||||||
|
// a.IsTypef(&MyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1162,8 +1200,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg
|
|||||||
return NotElementsMatchf(a.t, listA, listB, msg, args...)
|
return NotElementsMatchf(a.t, listA, listB, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmpty asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if a.NotEmpty(obj) {
|
// if a.NotEmpty(obj) {
|
||||||
// assert.Equal(t, "two", obj[1])
|
// assert.Equal(t, "two", obj[1])
|
||||||
@@ -1175,8 +1212,7 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) boo
|
|||||||
return NotEmpty(a.t, object, msgAndArgs...)
|
return NotEmpty(a.t, object, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmptyf asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if a.NotEmptyf(obj, "error message %s", "formatted") {
|
// if a.NotEmptyf(obj, "error message %s", "formatted") {
|
||||||
// assert.Equal(t, "two", obj[1])
|
// assert.Equal(t, "two", obj[1])
|
||||||
@@ -1378,12 +1414,15 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri
|
|||||||
return NotSamef(a.t, expected, actual, msg, args...)
|
return NotSamef(a.t, expected, actual, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubset asserts that the specified list(array, slice...) or map does NOT
|
// NotSubset asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.NotSubset([1, 3, 4], [1, 2])
|
// a.NotSubset([1, 3, 4], [1, 2])
|
||||||
// a.NotSubset({"x": 1, "y": 2}, {"z": 3})
|
// a.NotSubset({"x": 1, "y": 2}, {"z": 3})
|
||||||
|
// a.NotSubset([1, 3, 4], {1: "one", 2: "two"})
|
||||||
|
// a.NotSubset({"x": 1, "y": 2}, ["z"])
|
||||||
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1391,12 +1430,15 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs
|
|||||||
return NotSubset(a.t, list, subset, msgAndArgs...)
|
return NotSubset(a.t, list, subset, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
|
// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted")
|
// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted")
|
||||||
// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
||||||
|
// a.NotSubsetf([1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// a.NotSubsetf({"x": 1, "y": 2}, ["z"], "error message %s", "formatted")
|
||||||
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1556,11 +1598,15 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string,
|
|||||||
return Samef(a.t, expected, actual, msg, args...)
|
return Samef(a.t, expected, actual, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subset asserts that the specified list(array, slice...) or map contains all
|
// Subset asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.Subset([1, 2, 3], [1, 2])
|
// a.Subset([1, 2, 3], [1, 2])
|
||||||
// a.Subset({"x": 1, "y": 2}, {"x": 1})
|
// a.Subset({"x": 1, "y": 2}, {"x": 1})
|
||||||
|
// a.Subset([1, 2, 3], {1: "one", 2: "two"})
|
||||||
|
// a.Subset({"x": 1, "y": 2}, ["x"])
|
||||||
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1568,11 +1614,15 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...
|
|||||||
return Subset(a.t, list, subset, msgAndArgs...)
|
return Subset(a.t, list, subset, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsetf asserts that the specified list(array, slice...) or map contains all
|
// Subsetf asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted")
|
// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted")
|
||||||
// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
||||||
|
// a.Subsetf([1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// a.Subsetf({"x": 1, "y": 2}, ["x"], "error message %s", "formatted")
|
||||||
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
|
|||||||
2
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
2
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
@@ -33,7 +33,7 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareR
|
|||||||
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
|
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
|
||||||
|
|
||||||
if !isComparable {
|
if !isComparable {
|
||||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...)
|
return Fail(t, fmt.Sprintf(`Can not compare type "%T" and "%T"`, value, prevValue), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !containsValue(allowedComparesResults, compareResult) {
|
if !containsValue(allowedComparesResults, compareResult) {
|
||||||
|
|||||||
379
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
379
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
@@ -210,59 +210,77 @@ the problem actually occurred in calling code.*/
|
|||||||
// of each stack frame leading from the current test to the assert call that
|
// of each stack frame leading from the current test to the assert call that
|
||||||
// failed.
|
// failed.
|
||||||
func CallerInfo() []string {
|
func CallerInfo() []string {
|
||||||
|
|
||||||
var pc uintptr
|
var pc uintptr
|
||||||
var ok bool
|
|
||||||
var file string
|
var file string
|
||||||
var line int
|
var line int
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
|
const stackFrameBufferSize = 10
|
||||||
|
pcs := make([]uintptr, stackFrameBufferSize)
|
||||||
|
|
||||||
callers := []string{}
|
callers := []string{}
|
||||||
for i := 0; ; i++ {
|
offset := 1
|
||||||
pc, file, line, ok = runtime.Caller(i)
|
|
||||||
if !ok {
|
for {
|
||||||
// The breaks below failed to terminate the loop, and we ran off the
|
n := runtime.Callers(offset, pcs)
|
||||||
// end of the call stack.
|
|
||||||
|
if n == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a huge edge case, but it will panic if this is the case, see #180
|
frames := runtime.CallersFrames(pcs[:n])
|
||||||
if file == "<autogenerated>" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
f := runtime.FuncForPC(pc)
|
for {
|
||||||
if f == nil {
|
frame, more := frames.Next()
|
||||||
break
|
pc = frame.PC
|
||||||
}
|
file = frame.File
|
||||||
name = f.Name()
|
line = frame.Line
|
||||||
|
|
||||||
// testing.tRunner is the standard library function that calls
|
// This is a huge edge case, but it will panic if this is the case, see #180
|
||||||
// tests. Subtests are called directly by tRunner, without going through
|
if file == "<autogenerated>" {
|
||||||
// the Test/Benchmark/Example function that contains the t.Run calls, so
|
break
|
||||||
// with subtests we should break when we hit tRunner, without adding it
|
}
|
||||||
// to the list of callers.
|
|
||||||
if name == "testing.tRunner" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(file, "/")
|
f := runtime.FuncForPC(pc)
|
||||||
if len(parts) > 1 {
|
if f == nil {
|
||||||
filename := parts[len(parts)-1]
|
break
|
||||||
dir := parts[len(parts)-2]
|
}
|
||||||
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
|
name = f.Name()
|
||||||
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
|
|
||||||
|
// testing.tRunner is the standard library function that calls
|
||||||
|
// tests. Subtests are called directly by tRunner, without going through
|
||||||
|
// the Test/Benchmark/Example function that contains the t.Run calls, so
|
||||||
|
// with subtests we should break when we hit tRunner, without adding it
|
||||||
|
// to the list of callers.
|
||||||
|
if name == "testing.tRunner" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(file, "/")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
filename := parts[len(parts)-1]
|
||||||
|
dir := parts[len(parts)-2]
|
||||||
|
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
|
||||||
|
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the package
|
||||||
|
dotPos := strings.LastIndexByte(name, '.')
|
||||||
|
name = name[dotPos+1:]
|
||||||
|
if isTest(name, "Test") ||
|
||||||
|
isTest(name, "Benchmark") ||
|
||||||
|
isTest(name, "Example") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop the package
|
// Next batch
|
||||||
segments := strings.Split(name, ".")
|
offset += cap(pcs)
|
||||||
name = segments[len(segments)-1]
|
|
||||||
if isTest(name, "Test") ||
|
|
||||||
isTest(name, "Benchmark") ||
|
|
||||||
isTest(name, "Example") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return callers
|
return callers
|
||||||
@@ -437,17 +455,34 @@ func NotImplements(t TestingT, interfaceObject interface{}, object interface{},
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isType(expectedType, object interface{}) bool {
|
||||||
|
return ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType))
|
||||||
|
}
|
||||||
|
|
||||||
// IsType asserts that the specified objects are of the same type.
|
// IsType asserts that the specified objects are of the same type.
|
||||||
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
//
|
||||||
|
// assert.IsType(t, &MyStruct{}, &MyStruct{})
|
||||||
|
func IsType(t TestingT, expectedType, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if isType(expectedType, object) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
}
|
}
|
||||||
|
return Fail(t, fmt.Sprintf("Object expected to be of type %T, but was %T", expectedType, object), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) {
|
// IsNotType asserts that the specified objects are not of the same type.
|
||||||
return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...)
|
//
|
||||||
|
// assert.IsNotType(t, &NotMyStruct{}, &MyStruct{})
|
||||||
|
func IsNotType(t TestingT, theType, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if !isType(theType, object) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
return true
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Fail(t, fmt.Sprintf("Object type expected to be different than %T", theType), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal asserts that two objects are equal.
|
// Equal asserts that two objects are equal.
|
||||||
@@ -475,7 +510,6 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateEqualArgs checks whether provided arguments can be safely used in the
|
// validateEqualArgs checks whether provided arguments can be safely used in the
|
||||||
@@ -510,8 +544,9 @@ func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) b
|
|||||||
if !same {
|
if !same {
|
||||||
// both are pointers but not the same type & pointing to the same address
|
// both are pointers but not the same type & pointing to the same address
|
||||||
return Fail(t, fmt.Sprintf("Not same: \n"+
|
return Fail(t, fmt.Sprintf("Not same: \n"+
|
||||||
"expected: %p %#v\n"+
|
"expected: %p %#[1]v\n"+
|
||||||
"actual : %p %#v", expected, expected, actual, actual), msgAndArgs...)
|
"actual : %p %#[2]v",
|
||||||
|
expected, actual), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -530,14 +565,14 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
|
|||||||
|
|
||||||
same, ok := samePointers(expected, actual)
|
same, ok := samePointers(expected, actual)
|
||||||
if !ok {
|
if !ok {
|
||||||
//fails when the arguments are not pointers
|
// fails when the arguments are not pointers
|
||||||
return !(Fail(t, "Both arguments must be pointers", msgAndArgs...))
|
return !(Fail(t, "Both arguments must be pointers", msgAndArgs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
if same {
|
if same {
|
||||||
return Fail(t, fmt.Sprintf(
|
return Fail(t, fmt.Sprintf(
|
||||||
"Expected and actual point to the same object: %p %#v",
|
"Expected and actual point to the same object: %p %#[1]v",
|
||||||
expected, expected), msgAndArgs...)
|
expected), msgAndArgs...)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -549,7 +584,7 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
|
|||||||
func samePointers(first, second interface{}) (same bool, ok bool) {
|
func samePointers(first, second interface{}) (same bool, ok bool) {
|
||||||
firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second)
|
firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second)
|
||||||
if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr {
|
if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr {
|
||||||
return false, false //not both are pointers
|
return false, false // not both are pointers
|
||||||
}
|
}
|
||||||
|
|
||||||
firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second)
|
firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second)
|
||||||
@@ -610,7 +645,6 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EqualExportedValues asserts that the types of two objects are equal and their public
|
// EqualExportedValues asserts that the types of two objects are equal and their public
|
||||||
@@ -665,7 +699,6 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Equal(t, expected, actual, msgAndArgs...)
|
return Equal(t, expected, actual, msgAndArgs...)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotNil asserts that the specified object is not nil.
|
// NotNil asserts that the specified object is not nil.
|
||||||
@@ -715,37 +748,45 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|||||||
|
|
||||||
// isEmpty gets whether the specified object is considered empty or not.
|
// isEmpty gets whether the specified object is considered empty or not.
|
||||||
func isEmpty(object interface{}) bool {
|
func isEmpty(object interface{}) bool {
|
||||||
|
|
||||||
// get nil case out of the way
|
// get nil case out of the way
|
||||||
if object == nil {
|
if object == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
objValue := reflect.ValueOf(object)
|
return isEmptyValue(reflect.ValueOf(object))
|
||||||
|
|
||||||
switch objValue.Kind() {
|
|
||||||
// collection types are empty when they have no element
|
|
||||||
case reflect.Chan, reflect.Map, reflect.Slice:
|
|
||||||
return objValue.Len() == 0
|
|
||||||
// pointers are empty if nil or if the value they point to is empty
|
|
||||||
case reflect.Ptr:
|
|
||||||
if objValue.IsNil() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
deref := objValue.Elem().Interface()
|
|
||||||
return isEmpty(deref)
|
|
||||||
// for all other types, compare against the zero value
|
|
||||||
// array types are empty when they match their zero-initialized state
|
|
||||||
default:
|
|
||||||
zero := reflect.Zero(objValue.Type())
|
|
||||||
return reflect.DeepEqual(object, zero.Interface())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// isEmptyValue gets whether the specified reflect.Value is considered empty or not.
|
||||||
// a slice or a channel with len == 0.
|
func isEmptyValue(objValue reflect.Value) bool {
|
||||||
|
if objValue.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Special cases of non-zero values that we consider empty
|
||||||
|
switch objValue.Kind() {
|
||||||
|
// collection types are empty when they have no element
|
||||||
|
// Note: array types are empty when they match their zero-initialized state.
|
||||||
|
case reflect.Chan, reflect.Map, reflect.Slice:
|
||||||
|
return objValue.Len() == 0
|
||||||
|
// non-nil pointers are empty if the value they point to is empty
|
||||||
|
case reflect.Ptr:
|
||||||
|
return isEmptyValue(objValue.Elem())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty asserts that the given value is "empty".
|
||||||
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// assert.Empty(t, obj)
|
// assert.Empty(t, obj)
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
pass := isEmpty(object)
|
pass := isEmpty(object)
|
||||||
if !pass {
|
if !pass {
|
||||||
@@ -756,11 +797,9 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pass
|
return pass
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmpty asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if assert.NotEmpty(t, obj) {
|
// if assert.NotEmpty(t, obj) {
|
||||||
// assert.Equal(t, "two", obj[1])
|
// assert.Equal(t, "two", obj[1])
|
||||||
@@ -775,7 +814,6 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pass
|
return pass
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLen tries to get the length of an object.
|
// getLen tries to get the length of an object.
|
||||||
@@ -819,7 +857,6 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// False asserts that the specified value is false.
|
// False asserts that the specified value is false.
|
||||||
@@ -834,7 +871,6 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEqual asserts that the specified values are NOT equal.
|
// NotEqual asserts that the specified values are NOT equal.
|
||||||
@@ -857,7 +893,6 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
||||||
@@ -880,7 +915,6 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte
|
|||||||
// return (true, false) if element was not found.
|
// return (true, false) if element was not found.
|
||||||
// return (true, true) if element was found.
|
// return (true, true) if element was found.
|
||||||
func containsElement(list interface{}, element interface{}) (ok, found bool) {
|
func containsElement(list interface{}, element interface{}) (ok, found bool) {
|
||||||
|
|
||||||
listValue := reflect.ValueOf(list)
|
listValue := reflect.ValueOf(list)
|
||||||
listType := reflect.TypeOf(list)
|
listType := reflect.TypeOf(list)
|
||||||
if listType == nil {
|
if listType == nil {
|
||||||
@@ -915,7 +949,6 @@ func containsElement(list interface{}, element interface{}) (ok, found bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, false
|
return true, false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||||
@@ -938,7 +971,6 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||||
@@ -961,14 +993,17 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subset asserts that the specified list(array, slice...) or map contains all
|
// Subset asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// assert.Subset(t, [1, 2, 3], [1, 2])
|
// assert.Subset(t, [1, 2, 3], [1, 2])
|
||||||
// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1})
|
// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1})
|
||||||
|
// assert.Subset(t, [1, 2, 3], {1: "one", 2: "two"})
|
||||||
|
// assert.Subset(t, {"x": 1, "y": 2}, ["x"])
|
||||||
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -983,7 +1018,7 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
|
|||||||
}
|
}
|
||||||
|
|
||||||
subsetKind := reflect.TypeOf(subset).Kind()
|
subsetKind := reflect.TypeOf(subset).Kind()
|
||||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
|
if subsetKind != reflect.Array && subsetKind != reflect.Slice && subsetKind != reflect.Map {
|
||||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,6 +1042,13 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
|
|||||||
}
|
}
|
||||||
|
|
||||||
subsetList := reflect.ValueOf(subset)
|
subsetList := reflect.ValueOf(subset)
|
||||||
|
if subsetKind == reflect.Map {
|
||||||
|
keys := make([]interface{}, subsetList.Len())
|
||||||
|
for idx, key := range subsetList.MapKeys() {
|
||||||
|
keys[idx] = key.Interface()
|
||||||
|
}
|
||||||
|
subsetList = reflect.ValueOf(keys)
|
||||||
|
}
|
||||||
for i := 0; i < subsetList.Len(); i++ {
|
for i := 0; i < subsetList.Len(); i++ {
|
||||||
element := subsetList.Index(i).Interface()
|
element := subsetList.Index(i).Interface()
|
||||||
ok, found := containsElement(list, element)
|
ok, found := containsElement(list, element)
|
||||||
@@ -1021,12 +1063,15 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubset asserts that the specified list(array, slice...) or map does NOT
|
// NotSubset asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// assert.NotSubset(t, [1, 3, 4], [1, 2])
|
// assert.NotSubset(t, [1, 3, 4], [1, 2])
|
||||||
// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3})
|
// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3})
|
||||||
|
// assert.NotSubset(t, [1, 3, 4], {1: "one", 2: "two"})
|
||||||
|
// assert.NotSubset(t, {"x": 1, "y": 2}, ["z"])
|
||||||
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1041,7 +1086,7 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
subsetKind := reflect.TypeOf(subset).Kind()
|
subsetKind := reflect.TypeOf(subset).Kind()
|
||||||
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
|
if subsetKind != reflect.Array && subsetKind != reflect.Slice && subsetKind != reflect.Map {
|
||||||
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1065,11 +1110,18 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
subsetList := reflect.ValueOf(subset)
|
subsetList := reflect.ValueOf(subset)
|
||||||
|
if subsetKind == reflect.Map {
|
||||||
|
keys := make([]interface{}, subsetList.Len())
|
||||||
|
for idx, key := range subsetList.MapKeys() {
|
||||||
|
keys[idx] = key.Interface()
|
||||||
|
}
|
||||||
|
subsetList = reflect.ValueOf(keys)
|
||||||
|
}
|
||||||
for i := 0; i < subsetList.Len(); i++ {
|
for i := 0; i < subsetList.Len(); i++ {
|
||||||
element := subsetList.Index(i).Interface()
|
element := subsetList.Index(i).Interface()
|
||||||
ok, found := containsElement(list, element)
|
ok, found := containsElement(list, element)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
|
return Fail(t, fmt.Sprintf("%q could not be applied builtin len()", list), msgAndArgs...)
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
return true
|
return true
|
||||||
@@ -1591,10 +1643,8 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
|||||||
|
|
||||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if assert.Error(t, err) {
|
// assert.Error(t, err)
|
||||||
// assert.Equal(t, expectedError, err)
|
|
||||||
// }
|
|
||||||
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
@@ -1667,7 +1717,6 @@ func matchRegexp(rx interface{}, str interface{}) bool {
|
|||||||
default:
|
default:
|
||||||
return r.MatchString(fmt.Sprint(v))
|
return r.MatchString(fmt.Sprint(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regexp asserts that a specified regexp matches a string.
|
// Regexp asserts that a specified regexp matches a string.
|
||||||
@@ -1703,7 +1752,6 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf
|
|||||||
}
|
}
|
||||||
|
|
||||||
return !match
|
return !match
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zero asserts that i is the zero value for its type.
|
// Zero asserts that i is the zero value for its type.
|
||||||
@@ -1814,6 +1862,11 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{
|
|||||||
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...)
|
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shortcut if same bytes
|
||||||
|
if actual == expected {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil {
|
if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil {
|
||||||
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...)
|
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...)
|
||||||
}
|
}
|
||||||
@@ -1832,6 +1885,11 @@ func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{
|
|||||||
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid yaml.\nYAML parsing error: '%s'", expected, err.Error()), msgAndArgs...)
|
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid yaml.\nYAML parsing error: '%s'", expected, err.Error()), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shortcut if same bytes
|
||||||
|
if actual == expected {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal([]byte(actual), &actualYAMLAsInterface); err != nil {
|
if err := yaml.Unmarshal([]byte(actual), &actualYAMLAsInterface); err != nil {
|
||||||
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid yaml.\nYAML error: '%s'", actual, err.Error()), msgAndArgs...)
|
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid yaml.\nYAML error: '%s'", actual, err.Error()), msgAndArgs...)
|
||||||
}
|
}
|
||||||
@@ -1933,6 +1991,7 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
|
|||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan bool, 1)
|
ch := make(chan bool, 1)
|
||||||
|
checkCond := func() { ch <- condition() }
|
||||||
|
|
||||||
timer := time.NewTimer(waitFor)
|
timer := time.NewTimer(waitFor)
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
@@ -1940,18 +1999,23 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
|
|||||||
ticker := time.NewTicker(tick)
|
ticker := time.NewTicker(tick)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for tick := ticker.C; ; {
|
var tickC <-chan time.Time
|
||||||
|
|
||||||
|
// Check the condition once first on the initial call.
|
||||||
|
go checkCond()
|
||||||
|
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
||||||
case <-tick:
|
case <-tickC:
|
||||||
tick = nil
|
tickC = nil
|
||||||
go func() { ch <- condition() }()
|
go checkCond()
|
||||||
case v := <-ch:
|
case v := <-ch:
|
||||||
if v {
|
if v {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
tick = ticker.C
|
tickC = ticker.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1964,6 +2028,9 @@ type CollectT struct {
|
|||||||
errors []error
|
errors []error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper is like [testing.T.Helper] but does nothing.
|
||||||
|
func (CollectT) Helper() {}
|
||||||
|
|
||||||
// Errorf collects the error.
|
// Errorf collects the error.
|
||||||
func (c *CollectT) Errorf(format string, args ...interface{}) {
|
func (c *CollectT) Errorf(format string, args ...interface{}) {
|
||||||
c.errors = append(c.errors, fmt.Errorf(format, args...))
|
c.errors = append(c.errors, fmt.Errorf(format, args...))
|
||||||
@@ -2021,35 +2088,42 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time
|
|||||||
var lastFinishedTickErrs []error
|
var lastFinishedTickErrs []error
|
||||||
ch := make(chan *CollectT, 1)
|
ch := make(chan *CollectT, 1)
|
||||||
|
|
||||||
|
checkCond := func() {
|
||||||
|
collect := new(CollectT)
|
||||||
|
defer func() {
|
||||||
|
ch <- collect
|
||||||
|
}()
|
||||||
|
condition(collect)
|
||||||
|
}
|
||||||
|
|
||||||
timer := time.NewTimer(waitFor)
|
timer := time.NewTimer(waitFor)
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
|
|
||||||
ticker := time.NewTicker(tick)
|
ticker := time.NewTicker(tick)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for tick := ticker.C; ; {
|
var tickC <-chan time.Time
|
||||||
|
|
||||||
|
// Check the condition once first on the initial call.
|
||||||
|
go checkCond()
|
||||||
|
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
for _, err := range lastFinishedTickErrs {
|
for _, err := range lastFinishedTickErrs {
|
||||||
t.Errorf("%v", err)
|
t.Errorf("%v", err)
|
||||||
}
|
}
|
||||||
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
||||||
case <-tick:
|
case <-tickC:
|
||||||
tick = nil
|
tickC = nil
|
||||||
go func() {
|
go checkCond()
|
||||||
collect := new(CollectT)
|
|
||||||
defer func() {
|
|
||||||
ch <- collect
|
|
||||||
}()
|
|
||||||
condition(collect)
|
|
||||||
}()
|
|
||||||
case collect := <-ch:
|
case collect := <-ch:
|
||||||
if !collect.failed() {
|
if !collect.failed() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached.
|
// Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached.
|
||||||
lastFinishedTickErrs = collect.errors
|
lastFinishedTickErrs = collect.errors
|
||||||
tick = ticker.C
|
tickC = ticker.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2064,6 +2138,7 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D
|
|||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan bool, 1)
|
ch := make(chan bool, 1)
|
||||||
|
checkCond := func() { ch <- condition() }
|
||||||
|
|
||||||
timer := time.NewTimer(waitFor)
|
timer := time.NewTimer(waitFor)
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
@@ -2071,18 +2146,23 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D
|
|||||||
ticker := time.NewTicker(tick)
|
ticker := time.NewTicker(tick)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for tick := ticker.C; ; {
|
var tickC <-chan time.Time
|
||||||
|
|
||||||
|
// Check the condition once first on the initial call.
|
||||||
|
go checkCond()
|
||||||
|
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return true
|
return true
|
||||||
case <-tick:
|
case <-tickC:
|
||||||
tick = nil
|
tickC = nil
|
||||||
go func() { ch <- condition() }()
|
go checkCond()
|
||||||
case v := <-ch:
|
case v := <-ch:
|
||||||
if v {
|
if v {
|
||||||
return Fail(t, "Condition satisfied", msgAndArgs...)
|
return Fail(t, "Condition satisfied", msgAndArgs...)
|
||||||
}
|
}
|
||||||
tick = ticker.C
|
tickC = ticker.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2100,9 +2180,12 @@ func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
|
|||||||
var expectedText string
|
var expectedText string
|
||||||
if target != nil {
|
if target != nil {
|
||||||
expectedText = target.Error()
|
expectedText = target.Error()
|
||||||
|
if err == nil {
|
||||||
|
return Fail(t, fmt.Sprintf("Expected error with %q in chain but got nil.", expectedText), msgAndArgs...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chain := buildErrorChainString(err)
|
chain := buildErrorChainString(err, false)
|
||||||
|
|
||||||
return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+
|
return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+
|
||||||
"expected: %q\n"+
|
"expected: %q\n"+
|
||||||
@@ -2125,7 +2208,7 @@ func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
|
|||||||
expectedText = target.Error()
|
expectedText = target.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
chain := buildErrorChainString(err)
|
chain := buildErrorChainString(err, false)
|
||||||
|
|
||||||
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
|
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
|
||||||
"found: %q\n"+
|
"found: %q\n"+
|
||||||
@@ -2143,11 +2226,17 @@ func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
chain := buildErrorChainString(err)
|
expectedType := reflect.TypeOf(target).Elem().String()
|
||||||
|
if err == nil {
|
||||||
|
return Fail(t, fmt.Sprintf("An error is expected but got nil.\n"+
|
||||||
|
"expected: %s", expectedType), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain := buildErrorChainString(err, true)
|
||||||
|
|
||||||
return Fail(t, fmt.Sprintf("Should be in error chain:\n"+
|
return Fail(t, fmt.Sprintf("Should be in error chain:\n"+
|
||||||
"expected: %q\n"+
|
"expected: %s\n"+
|
||||||
"in chain: %s", target, chain,
|
"in chain: %s", expectedType, chain,
|
||||||
), msgAndArgs...)
|
), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2161,24 +2250,46 @@ func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interfa
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
chain := buildErrorChainString(err)
|
chain := buildErrorChainString(err, true)
|
||||||
|
|
||||||
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
|
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
|
||||||
"found: %q\n"+
|
"found: %s\n"+
|
||||||
"in chain: %s", target, chain,
|
"in chain: %s", reflect.TypeOf(target).Elem().String(), chain,
|
||||||
), msgAndArgs...)
|
), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildErrorChainString(err error) string {
|
func unwrapAll(err error) (errs []error) {
|
||||||
|
errs = append(errs, err)
|
||||||
|
switch x := err.(type) {
|
||||||
|
case interface{ Unwrap() error }:
|
||||||
|
err = x.Unwrap()
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errs = append(errs, unwrapAll(err)...)
|
||||||
|
case interface{ Unwrap() []error }:
|
||||||
|
for _, err := range x.Unwrap() {
|
||||||
|
errs = append(errs, unwrapAll(err)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildErrorChainString(err error, withType bool) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
e := errors.Unwrap(err)
|
var chain string
|
||||||
chain := fmt.Sprintf("%q", err.Error())
|
errs := unwrapAll(err)
|
||||||
for e != nil {
|
for i := range errs {
|
||||||
chain += fmt.Sprintf("\n\t%q", e.Error())
|
if i != 0 {
|
||||||
e = errors.Unwrap(e)
|
chain += "\n\t"
|
||||||
|
}
|
||||||
|
chain += fmt.Sprintf("%q", errs[i].Error())
|
||||||
|
if withType {
|
||||||
|
chain += fmt.Sprintf(" (%T)", errs[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return chain
|
return chain
|
||||||
}
|
}
|
||||||
|
|||||||
4
vendor/github.com/stretchr/testify/assert/doc.go
generated
vendored
4
vendor/github.com/stretchr/testify/assert/doc.go
generated
vendored
@@ -1,5 +1,9 @@
|
|||||||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||||
//
|
//
|
||||||
|
// # Note
|
||||||
|
//
|
||||||
|
// All functions in this package return a bool value indicating whether the assertion has passed.
|
||||||
|
//
|
||||||
// # Example Usage
|
// # Example Usage
|
||||||
//
|
//
|
||||||
// The following is a complete example using assert in a standard test function:
|
// The following is a complete example using assert in a standard test function:
|
||||||
|
|||||||
4
vendor/github.com/stretchr/testify/assert/http_assertions.go
generated
vendored
4
vendor/github.com/stretchr/testify/assert/http_assertions.go
generated
vendored
@@ -138,7 +138,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
|
|||||||
|
|
||||||
contains := strings.Contains(body, fmt.Sprint(str))
|
contains := strings.Contains(body, fmt.Sprint(str))
|
||||||
if !contains {
|
if !contains {
|
||||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...)
|
Fail(t, fmt.Sprintf("Expected response body for %q to contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return contains
|
return contains
|
||||||
@@ -158,7 +158,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url strin
|
|||||||
|
|
||||||
contains := strings.Contains(body, fmt.Sprint(str))
|
contains := strings.Contains(body, fmt.Sprint(str))
|
||||||
if contains {
|
if contains {
|
||||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...)
|
Fail(t, fmt.Sprintf("Expected response body for %q to NOT contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return !contains
|
return !contains
|
||||||
|
|||||||
1
vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go
generated
vendored
1
vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go
generated
vendored
@@ -1,5 +1,4 @@
|
|||||||
//go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default
|
//go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default
|
||||||
// +build testify_yaml_custom,!testify_yaml_fail,!testify_yaml_default
|
|
||||||
|
|
||||||
// Package yaml is an implementation of YAML functions that calls a pluggable implementation.
|
// Package yaml is an implementation of YAML functions that calls a pluggable implementation.
|
||||||
//
|
//
|
||||||
|
|||||||
1
vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go
generated
vendored
1
vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go
generated
vendored
@@ -1,5 +1,4 @@
|
|||||||
//go:build !testify_yaml_fail && !testify_yaml_custom
|
//go:build !testify_yaml_fail && !testify_yaml_custom
|
||||||
// +build !testify_yaml_fail,!testify_yaml_custom
|
|
||||||
|
|
||||||
// Package yaml is just an indirection to handle YAML deserialization.
|
// Package yaml is just an indirection to handle YAML deserialization.
|
||||||
//
|
//
|
||||||
|
|||||||
1
vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go
generated
vendored
1
vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go
generated
vendored
@@ -1,5 +1,4 @@
|
|||||||
//go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default
|
//go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default
|
||||||
// +build testify_yaml_fail,!testify_yaml_custom,!testify_yaml_default
|
|
||||||
|
|
||||||
// Package yaml is an implementation of YAML functions that always fail.
|
// Package yaml is an implementation of YAML functions that always fail.
|
||||||
//
|
//
|
||||||
|
|||||||
2
vendor/github.com/stretchr/testify/require/doc.go
generated
vendored
2
vendor/github.com/stretchr/testify/require/doc.go
generated
vendored
@@ -23,6 +23,8 @@
|
|||||||
//
|
//
|
||||||
// The `require` package have same global functions as in the `assert` package,
|
// The `require` package have same global functions as in the `assert` package,
|
||||||
// but instead of returning a boolean result they call `t.FailNow()`.
|
// but instead of returning a boolean result they call `t.FailNow()`.
|
||||||
|
// A consequence of this is that it must be called from the goroutine running
|
||||||
|
// the test function, not from other goroutines created during the test.
|
||||||
//
|
//
|
||||||
// Every assertion function also takes an optional string message as the final argument,
|
// Every assertion function also takes an optional string message as the final argument,
|
||||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||||
|
|||||||
108
vendor/github.com/stretchr/testify/require/require.go
generated
vendored
108
vendor/github.com/stretchr/testify/require/require.go
generated
vendored
@@ -117,10 +117,19 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// Empty asserts that the given value is "empty".
|
||||||
// a slice or a channel with len == 0.
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// require.Empty(t, obj)
|
// require.Empty(t, obj)
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -131,10 +140,19 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// Emptyf asserts that the given value is "empty".
|
||||||
// a slice or a channel with len == 0.
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// require.Emptyf(t, obj, "error message %s", "formatted")
|
// require.Emptyf(t, obj, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) {
|
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -279,10 +297,8 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar
|
|||||||
|
|
||||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if require.Error(t, err) {
|
// require.Error(t, err)
|
||||||
// require.Equal(t, expectedError, err)
|
|
||||||
// }
|
|
||||||
func Error(t TestingT, err error, msgAndArgs ...interface{}) {
|
func Error(t TestingT, err error, msgAndArgs ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -373,10 +389,8 @@ func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface
|
|||||||
|
|
||||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if require.Errorf(t, err, "error message %s", "formatted") {
|
// require.Errorf(t, err, "error message %s", "formatted")
|
||||||
// require.Equal(t, expectedErrorf, err)
|
|
||||||
// }
|
|
||||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) {
|
func Errorf(t TestingT, err error, msg string, args ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1097,7 +1111,35 @@ func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interf
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNotType asserts that the specified objects are not of the same type.
|
||||||
|
//
|
||||||
|
// require.IsNotType(t, &NotMyStruct{}, &MyStruct{})
|
||||||
|
func IsNotType(t TestingT, theType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
if assert.IsNotType(t, theType, object, msgAndArgs...) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotTypef asserts that the specified objects are not of the same type.
|
||||||
|
//
|
||||||
|
// require.IsNotTypef(t, &NotMyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
|
func IsNotTypef(t TestingT, theType interface{}, object interface{}, msg string, args ...interface{}) {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
if assert.IsNotTypef(t, theType, object, msg, args...) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
// IsType asserts that the specified objects are of the same type.
|
// IsType asserts that the specified objects are of the same type.
|
||||||
|
//
|
||||||
|
// require.IsType(t, &MyStruct{}, &MyStruct{})
|
||||||
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1109,6 +1151,8 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsTypef asserts that the specified objects are of the same type.
|
// IsTypef asserts that the specified objects are of the same type.
|
||||||
|
//
|
||||||
|
// require.IsTypef(t, &MyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) {
|
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1469,8 +1513,7 @@ func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg str
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmpty asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if require.NotEmpty(t, obj) {
|
// if require.NotEmpty(t, obj) {
|
||||||
// require.Equal(t, "two", obj[1])
|
// require.Equal(t, "two", obj[1])
|
||||||
@@ -1485,8 +1528,7 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmptyf asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if require.NotEmptyf(t, obj, "error message %s", "formatted") {
|
// if require.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||||
// require.Equal(t, "two", obj[1])
|
// require.Equal(t, "two", obj[1])
|
||||||
@@ -1745,12 +1787,15 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string,
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubset asserts that the specified list(array, slice...) or map does NOT
|
// NotSubset asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// require.NotSubset(t, [1, 3, 4], [1, 2])
|
// require.NotSubset(t, [1, 3, 4], [1, 2])
|
||||||
// require.NotSubset(t, {"x": 1, "y": 2}, {"z": 3})
|
// require.NotSubset(t, {"x": 1, "y": 2}, {"z": 3})
|
||||||
|
// require.NotSubset(t, [1, 3, 4], {1: "one", 2: "two"})
|
||||||
|
// require.NotSubset(t, {"x": 1, "y": 2}, ["z"])
|
||||||
func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1761,12 +1806,15 @@ func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...i
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
|
// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// require.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
|
// require.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
|
||||||
// require.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
// require.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
||||||
|
// require.NotSubsetf(t, [1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// require.NotSubsetf(t, {"x": 1, "y": 2}, ["z"], "error message %s", "formatted")
|
||||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
|
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1971,11 +2019,15 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subset asserts that the specified list(array, slice...) or map contains all
|
// Subset asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// require.Subset(t, [1, 2, 3], [1, 2])
|
// require.Subset(t, [1, 2, 3], [1, 2])
|
||||||
// require.Subset(t, {"x": 1, "y": 2}, {"x": 1})
|
// require.Subset(t, {"x": 1, "y": 2}, {"x": 1})
|
||||||
|
// require.Subset(t, [1, 2, 3], {1: "one", 2: "two"})
|
||||||
|
// require.Subset(t, {"x": 1, "y": 2}, ["x"])
|
||||||
func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1986,11 +2038,15 @@ func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...inte
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsetf asserts that the specified list(array, slice...) or map contains all
|
// Subsetf asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// require.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
|
// require.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
|
||||||
// require.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
// require.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
||||||
|
// require.Subsetf(t, [1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// require.Subsetf(t, {"x": 1, "y": 2}, ["x"], "error message %s", "formatted")
|
||||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
|
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := t.(tHelper); ok {
|
if h, ok := t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
|
|||||||
102
vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
102
vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
@@ -93,10 +93,19 @@ func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg st
|
|||||||
ElementsMatchf(a.t, listA, listB, msg, args...)
|
ElementsMatchf(a.t, listA, listB, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// Empty asserts that the given value is "empty".
|
||||||
// a slice or a channel with len == 0.
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// a.Empty(obj)
|
// a.Empty(obj)
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
|
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -104,10 +113,19 @@ func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
|
|||||||
Empty(a.t, object, msgAndArgs...)
|
Empty(a.t, object, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
// Emptyf asserts that the given value is "empty".
|
||||||
// a slice or a channel with len == 0.
|
//
|
||||||
|
// [Zero values] are "empty".
|
||||||
|
//
|
||||||
|
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
|
||||||
|
//
|
||||||
|
// Slices, maps and channels with zero length are "empty".
|
||||||
|
//
|
||||||
|
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
|
||||||
//
|
//
|
||||||
// a.Emptyf(obj, "error message %s", "formatted")
|
// a.Emptyf(obj, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// [Zero values]: https://go.dev/ref/spec#The_zero_value
|
||||||
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) {
|
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -225,10 +243,8 @@ func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string
|
|||||||
|
|
||||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if a.Error(err) {
|
// a.Error(err)
|
||||||
// assert.Equal(t, expectedError, err)
|
|
||||||
// }
|
|
||||||
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) {
|
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -298,10 +314,8 @@ func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...inter
|
|||||||
|
|
||||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// if a.Errorf(err, "error message %s", "formatted") {
|
// a.Errorf(err, "error message %s", "formatted")
|
||||||
// assert.Equal(t, expectedErrorf, err)
|
|
||||||
// }
|
|
||||||
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) {
|
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -869,7 +883,29 @@ func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...in
|
|||||||
IsNonIncreasingf(a.t, object, msg, args...)
|
IsNonIncreasingf(a.t, object, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNotType asserts that the specified objects are not of the same type.
|
||||||
|
//
|
||||||
|
// a.IsNotType(&NotMyStruct{}, &MyStruct{})
|
||||||
|
func (a *Assertions) IsNotType(theType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
IsNotType(a.t, theType, object, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotTypef asserts that the specified objects are not of the same type.
|
||||||
|
//
|
||||||
|
// a.IsNotTypef(&NotMyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
|
func (a *Assertions) IsNotTypef(theType interface{}, object interface{}, msg string, args ...interface{}) {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
IsNotTypef(a.t, theType, object, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// IsType asserts that the specified objects are of the same type.
|
// IsType asserts that the specified objects are of the same type.
|
||||||
|
//
|
||||||
|
// a.IsType(&MyStruct{}, &MyStruct{})
|
||||||
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -878,6 +914,8 @@ func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAnd
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsTypef asserts that the specified objects are of the same type.
|
// IsTypef asserts that the specified objects are of the same type.
|
||||||
|
//
|
||||||
|
// a.IsTypef(&MyStruct{}, &MyStruct{}, "error message %s", "formatted")
|
||||||
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) {
|
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1163,8 +1201,7 @@ func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg
|
|||||||
NotElementsMatchf(a.t, listA, listB, msg, args...)
|
NotElementsMatchf(a.t, listA, listB, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmpty asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if a.NotEmpty(obj) {
|
// if a.NotEmpty(obj) {
|
||||||
// assert.Equal(t, "two", obj[1])
|
// assert.Equal(t, "two", obj[1])
|
||||||
@@ -1176,8 +1213,7 @@ func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) {
|
|||||||
NotEmpty(a.t, object, msgAndArgs...)
|
NotEmpty(a.t, object, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmptyf asserts that the specified object is NOT [Empty].
|
||||||
// a slice or a channel with len == 0.
|
|
||||||
//
|
//
|
||||||
// if a.NotEmptyf(obj, "error message %s", "formatted") {
|
// if a.NotEmptyf(obj, "error message %s", "formatted") {
|
||||||
// assert.Equal(t, "two", obj[1])
|
// assert.Equal(t, "two", obj[1])
|
||||||
@@ -1379,12 +1415,15 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri
|
|||||||
NotSamef(a.t, expected, actual, msg, args...)
|
NotSamef(a.t, expected, actual, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubset asserts that the specified list(array, slice...) or map does NOT
|
// NotSubset asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.NotSubset([1, 3, 4], [1, 2])
|
// a.NotSubset([1, 3, 4], [1, 2])
|
||||||
// a.NotSubset({"x": 1, "y": 2}, {"z": 3})
|
// a.NotSubset({"x": 1, "y": 2}, {"z": 3})
|
||||||
|
// a.NotSubset([1, 3, 4], {1: "one", 2: "two"})
|
||||||
|
// a.NotSubset({"x": 1, "y": 2}, ["z"])
|
||||||
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1392,12 +1431,15 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs
|
|||||||
NotSubset(a.t, list, subset, msgAndArgs...)
|
NotSubset(a.t, list, subset, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
|
// NotSubsetf asserts that the list (array, slice, or map) does NOT contain all
|
||||||
// contain all elements given in the specified subset list(array, slice...) or
|
// elements given in the subset (array, slice, or map).
|
||||||
// map.
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted")
|
// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted")
|
||||||
// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
||||||
|
// a.NotSubsetf([1, 3, 4], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// a.NotSubsetf({"x": 1, "y": 2}, ["z"], "error message %s", "formatted")
|
||||||
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1557,11 +1599,15 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string,
|
|||||||
Samef(a.t, expected, actual, msg, args...)
|
Samef(a.t, expected, actual, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subset asserts that the specified list(array, slice...) or map contains all
|
// Subset asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.Subset([1, 2, 3], [1, 2])
|
// a.Subset([1, 2, 3], [1, 2])
|
||||||
// a.Subset({"x": 1, "y": 2}, {"x": 1})
|
// a.Subset({"x": 1, "y": 2}, {"x": 1})
|
||||||
|
// a.Subset([1, 2, 3], {1: "one", 2: "two"})
|
||||||
|
// a.Subset({"x": 1, "y": 2}, ["x"])
|
||||||
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
@@ -1569,11 +1615,15 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...
|
|||||||
Subset(a.t, list, subset, msgAndArgs...)
|
Subset(a.t, list, subset, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsetf asserts that the specified list(array, slice...) or map contains all
|
// Subsetf asserts that the list (array, slice, or map) contains all elements
|
||||||
// elements given in the specified subset list(array, slice...) or map.
|
// given in the subset (array, slice, or map).
|
||||||
|
// Map elements are key-value pairs unless compared with an array or slice where
|
||||||
|
// only the map key is evaluated.
|
||||||
//
|
//
|
||||||
// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted")
|
// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted")
|
||||||
// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
||||||
|
// a.Subsetf([1, 2, 3], {1: "one", 2: "two"}, "error message %s", "formatted")
|
||||||
|
// a.Subsetf({"x": 1, "y": 2}, ["x"], "error message %s", "formatted")
|
||||||
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||||
if h, ok := a.t.(tHelper); ok {
|
if h, ok := a.t.(tHelper); ok {
|
||||||
h.Helper()
|
h.Helper()
|
||||||
|
|||||||
16
vendor/github.com/stretchr/testify/suite/stats.go
generated
vendored
16
vendor/github.com/stretchr/testify/suite/stats.go
generated
vendored
@@ -16,26 +16,30 @@ type TestInformation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newSuiteInformation() *SuiteInformation {
|
func newSuiteInformation() *SuiteInformation {
|
||||||
testStats := make(map[string]*TestInformation)
|
|
||||||
|
|
||||||
return &SuiteInformation{
|
return &SuiteInformation{
|
||||||
TestStats: testStats,
|
TestStats: make(map[string]*TestInformation),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SuiteInformation) start(testName string) {
|
func (s *SuiteInformation) start(testName string) {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
s.TestStats[testName] = &TestInformation{
|
s.TestStats[testName] = &TestInformation{
|
||||||
TestName: testName,
|
TestName: testName,
|
||||||
Start: time.Now(),
|
Start: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SuiteInformation) end(testName string, passed bool) {
|
func (s *SuiteInformation) end(testName string, passed bool) {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
s.TestStats[testName].End = time.Now()
|
s.TestStats[testName].End = time.Now()
|
||||||
s.TestStats[testName].Passed = passed
|
s.TestStats[testName].Passed = passed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SuiteInformation) Passed() bool {
|
func (s *SuiteInformation) Passed() bool {
|
||||||
for _, stats := range s.TestStats {
|
for _, stats := range s.TestStats {
|
||||||
if !stats.Passed {
|
if !stats.Passed {
|
||||||
return false
|
return false
|
||||||
|
|||||||
112
vendor/github.com/stretchr/testify/suite/suite.go
generated
vendored
112
vendor/github.com/stretchr/testify/suite/suite.go
generated
vendored
@@ -7,6 +7,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,7 +16,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allTestsFilter = func(_, _ string) (bool, error) { return true, nil }
|
|
||||||
var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
|
var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
|
||||||
|
|
||||||
// Suite is a basic testing suite with methods for storing and
|
// Suite is a basic testing suite with methods for storing and
|
||||||
@@ -116,6 +116,11 @@ func (suite *Suite) Run(name string, subtest func()) bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type test = struct {
|
||||||
|
name string
|
||||||
|
run func(t *testing.T)
|
||||||
|
}
|
||||||
|
|
||||||
// Run takes a testing suite and runs all of the tests attached
|
// Run takes a testing suite and runs all of the tests attached
|
||||||
// to it.
|
// to it.
|
||||||
func Run(t *testing.T, suite TestingSuite) {
|
func Run(t *testing.T, suite TestingSuite) {
|
||||||
@@ -124,45 +129,39 @@ func Run(t *testing.T, suite TestingSuite) {
|
|||||||
suite.SetT(t)
|
suite.SetT(t)
|
||||||
suite.SetS(suite)
|
suite.SetS(suite)
|
||||||
|
|
||||||
var suiteSetupDone bool
|
|
||||||
|
|
||||||
var stats *SuiteInformation
|
var stats *SuiteInformation
|
||||||
if _, ok := suite.(WithStats); ok {
|
if _, ok := suite.(WithStats); ok {
|
||||||
stats = newSuiteInformation()
|
stats = newSuiteInformation()
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []testing.InternalTest{}
|
var tests []test
|
||||||
methodFinder := reflect.TypeOf(suite)
|
methodFinder := reflect.TypeOf(suite)
|
||||||
suiteName := methodFinder.Elem().Name()
|
suiteName := methodFinder.Elem().Name()
|
||||||
|
|
||||||
for i := 0; i < methodFinder.NumMethod(); i++ {
|
var matchMethodRE *regexp.Regexp
|
||||||
method := methodFinder.Method(i)
|
if *matchMethod != "" {
|
||||||
|
var err error
|
||||||
ok, err := methodFilter(method.Name)
|
matchMethodRE, err = regexp.Compile(*matchMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
|
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !ok {
|
for i := 0; i < methodFinder.NumMethod(); i++ {
|
||||||
|
method := methodFinder.Method(i)
|
||||||
|
|
||||||
|
if !strings.HasPrefix(method.Name, "Test") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Apply -testify.m filter
|
||||||
|
if matchMethodRE != nil && !matchMethodRE.MatchString(method.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !suiteSetupDone {
|
test := test{
|
||||||
if stats != nil {
|
name: method.Name,
|
||||||
stats.Start = time.Now()
|
run: func(t *testing.T) {
|
||||||
}
|
|
||||||
|
|
||||||
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
|
|
||||||
setupAllSuite.SetupSuite()
|
|
||||||
}
|
|
||||||
|
|
||||||
suiteSetupDone = true
|
|
||||||
}
|
|
||||||
|
|
||||||
test := testing.InternalTest{
|
|
||||||
Name: method.Name,
|
|
||||||
F: func(t *testing.T) {
|
|
||||||
parentT := suite.T()
|
parentT := suite.T()
|
||||||
suite.SetT(t)
|
suite.SetT(t)
|
||||||
defer recoverAndFailOnPanic(t)
|
defer recoverAndFailOnPanic(t)
|
||||||
@@ -171,10 +170,7 @@ func Run(t *testing.T, suite TestingSuite) {
|
|||||||
|
|
||||||
r := recover()
|
r := recover()
|
||||||
|
|
||||||
if stats != nil {
|
stats.end(method.Name, !t.Failed() && r == nil)
|
||||||
passed := !t.Failed() && r == nil
|
|
||||||
stats.end(method.Name, passed)
|
|
||||||
}
|
|
||||||
|
|
||||||
if afterTestSuite, ok := suite.(AfterTest); ok {
|
if afterTestSuite, ok := suite.(AfterTest); ok {
|
||||||
afterTestSuite.AfterTest(suiteName, method.Name)
|
afterTestSuite.AfterTest(suiteName, method.Name)
|
||||||
@@ -195,59 +191,47 @@ func Run(t *testing.T, suite TestingSuite) {
|
|||||||
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
|
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if stats != nil {
|
stats.start(method.Name)
|
||||||
stats.start(method.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
|
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tests = append(tests, test)
|
tests = append(tests, test)
|
||||||
}
|
}
|
||||||
if suiteSetupDone {
|
|
||||||
defer func() {
|
|
||||||
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
|
||||||
tearDownAllSuite.TearDownSuite()
|
|
||||||
}
|
|
||||||
|
|
||||||
if suiteWithStats, measureStats := suite.(WithStats); measureStats {
|
if len(tests) == 0 {
|
||||||
stats.End = time.Now()
|
return
|
||||||
suiteWithStats.HandleStats(suiteName, stats)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if stats != nil {
|
||||||
|
stats.Start = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
|
||||||
|
setupAllSuite.SetupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
||||||
|
tearDownAllSuite.TearDownSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
if suiteWithStats, measureStats := suite.(WithStats); measureStats {
|
||||||
|
stats.End = time.Now()
|
||||||
|
suiteWithStats.HandleStats(suiteName, stats)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
runTests(t, tests)
|
runTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtering method according to set regular expression
|
func runTests(t *testing.T, tests []test) {
|
||||||
// specified command-line argument -m
|
|
||||||
func methodFilter(name string) (bool, error) {
|
|
||||||
if ok, _ := regexp.MatchString("^Test", name); !ok {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return regexp.MatchString(*matchMethod, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTests(t testing.TB, tests []testing.InternalTest) {
|
|
||||||
if len(tests) == 0 {
|
if len(tests) == 0 {
|
||||||
t.Log("warning: no tests to run")
|
t.Log("warning: no tests to run")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := t.(runner)
|
|
||||||
if !ok { // backwards compatibility with Go 1.6 and below
|
|
||||||
if !testing.RunTests(allTestsFilter, tests) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
r.Run(test.Name, test.F)
|
t.Run(test.name, test.run)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type runner interface {
|
|
||||||
Run(name string, f func(t *testing.T)) bool
|
|
||||||
}
|
|
||||||
|
|||||||
23
vendor/modules.txt
vendored
23
vendor/modules.txt
vendored
@@ -38,6 +38,12 @@ cloud.google.com/go/longrunning/autogen/longrunningpb
|
|||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
code.cloudfoundry.org/clock
|
code.cloudfoundry.org/clock
|
||||||
code.cloudfoundry.org/clock/fakeclock
|
code.cloudfoundry.org/clock/fakeclock
|
||||||
|
# cyphar.com/go-pathrs v0.2.1
|
||||||
|
## explicit; go 1.18
|
||||||
|
cyphar.com/go-pathrs
|
||||||
|
cyphar.com/go-pathrs/internal/fdutils
|
||||||
|
cyphar.com/go-pathrs/internal/libpathrs
|
||||||
|
cyphar.com/go-pathrs/procfs
|
||||||
# dario.cat/mergo v1.0.2
|
# dario.cat/mergo v1.0.2
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
dario.cat/mergo
|
dario.cat/mergo
|
||||||
@@ -493,9 +499,20 @@ github.com/cpuguy83/tar2go
|
|||||||
# github.com/creack/pty v1.1.24
|
# github.com/creack/pty v1.1.24
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/creack/pty
|
github.com/creack/pty
|
||||||
# github.com/cyphar/filepath-securejoin v0.4.1
|
# github.com/cyphar/filepath-securejoin v0.6.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/cyphar/filepath-securejoin
|
github.com/cyphar/filepath-securejoin
|
||||||
|
github.com/cyphar/filepath-securejoin/internal/consts
|
||||||
|
github.com/cyphar/filepath-securejoin/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/gopathrs
|
||||||
|
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
|
# github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||||
## explicit
|
## explicit
|
||||||
github.com/davecgh/go-spew/spew
|
github.com/davecgh/go-spew/spew
|
||||||
@@ -1134,7 +1151,7 @@ github.com/opencontainers/runtime-spec/specs-go/features
|
|||||||
github.com/opencontainers/runtime-tools/generate
|
github.com/opencontainers/runtime-tools/generate
|
||||||
github.com/opencontainers/runtime-tools/generate/seccomp
|
github.com/opencontainers/runtime-tools/generate/seccomp
|
||||||
github.com/opencontainers/runtime-tools/validate/capabilities
|
github.com/opencontainers/runtime-tools/validate/capabilities
|
||||||
# github.com/opencontainers/selinux v1.12.0
|
# github.com/opencontainers/selinux v1.13.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/opencontainers/selinux/go-selinux
|
github.com/opencontainers/selinux/go-selinux
|
||||||
github.com/opencontainers/selinux/go-selinux/label
|
github.com/opencontainers/selinux/go-selinux/label
|
||||||
@@ -1229,7 +1246,7 @@ github.com/spf13/cobra
|
|||||||
# github.com/spf13/pflag v1.0.10
|
# github.com/spf13/pflag v1.0.10
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/spf13/pflag
|
github.com/spf13/pflag
|
||||||
# github.com/stretchr/testify v1.10.0
|
# github.com/stretchr/testify v1.11.1
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/stretchr/testify/assert
|
github.com/stretchr/testify/assert
|
||||||
github.com/stretchr/testify/assert/yaml
|
github.com/stretchr/testify/assert/yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user