From 96f1962f9164b476d787663a3617d792a99cf158 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Sat, 8 Nov 2025 02:10:48 +1100 Subject: [PATCH] deps: update to github.com/opencontainers/selinux@v0.13.0 This new version includes the fixes for CVE-2025-52881, so we can remove the internal/third_party copy of the library we added in commit ed6b1693b8b3 ("selinux: use safe procfs API for labels") as well as the "replace" directive in go.mod (which is problematic for "go get" installs). Fixes: ed6b1693b8b3 ("selinux: use safe procfs API for labels") Signed-off-by: Aleksa Sarai --- .github/workflows/validate.yml | 9 +- go.mod | 10 +- go.sum | 11 +- internal/third_party/selinux/.codespellrc | 2 - .../selinux/.github/dependabot.yml | 10 - .../selinux/.github/workflows/validate.yml | 163 -- internal/third_party/selinux/.gitignore | 1 - internal/third_party/selinux/.golangci.yml | 44 - internal/third_party/selinux/CODEOWNERS | 1 - internal/third_party/selinux/CONTRIBUTING.md | 119 -- internal/third_party/selinux/LICENSE | 201 --- internal/third_party/selinux/MAINTAINERS | 5 - internal/third_party/selinux/Makefile | 37 - internal/third_party/selinux/README.md | 23 - .../third_party/selinux/go-selinux/doc.go | 13 - .../selinux/go-selinux/label/label.go | 48 - .../selinux/go-selinux/label/label_linux.go | 136 -- .../go-selinux/label/label_linux_test.go | 130 -- .../selinux/go-selinux/label/label_stub.go | 44 - .../go-selinux/label/label_stub_test.go | 76 - .../selinux/go-selinux/label/label_test.go | 35 - .../third_party/selinux/go-selinux/selinux.go | 322 ---- .../selinux/go-selinux/selinux_linux.go | 1405 ----------------- .../selinux/go-selinux/selinux_linux_test.go | 711 --------- .../selinux/go-selinux/selinux_stub.go | 159 -- .../selinux/go-selinux/selinux_stub_test.go | 127 -- .../selinux/go-selinux/xattrs_linux.go | 71 - internal/third_party/selinux/go.mod | 8 - internal/third_party/selinux/go.sum | 8 - .../third_party/selinux/pkg/pwalk/README.md | 52 - .../third_party/selinux/pkg/pwalk/pwalk.go | 131 -- .../selinux/pkg/pwalk/pwalk_test.go | 236 --- .../selinux/pkg/pwalkdir/README.md | 56 - .../selinux/pkg/pwalkdir/pwalkdir.go | 123 -- .../selinux/pkg/pwalkdir/pwalkdir_test.go | 239 --- vendor/cyphar.com/go-pathrs/.golangci.yml | 43 + vendor/cyphar.com/go-pathrs/COPYING | 373 +++++ vendor/cyphar.com/go-pathrs/doc.go | 14 + vendor/cyphar.com/go-pathrs/handle_linux.go | 114 ++ .../go-pathrs/internal/fdutils/fd_linux.go | 75 + .../internal/libpathrs/error_unix.go | 40 + .../internal/libpathrs/libpathrs_linux.go | 337 ++++ .../go-pathrs/procfs/procfs_linux.go | 246 +++ vendor/cyphar.com/go-pathrs/root_linux.go | 367 +++++ vendor/cyphar.com/go-pathrs/utils_linux.go | 56 + .../cyphar/filepath-securejoin/.golangci.yml | 4 + .../cyphar/filepath-securejoin/CHANGELOG.md | 62 +- .../cyphar/filepath-securejoin/VERSION | 2 +- .../filepath-securejoin/deprecated_linux.go | 48 - .../filepath-securejoin/pathrs-lite/README.md | 12 +- .../filepath-securejoin/pathrs-lite/doc.go | 2 + .../pathrs-lite/internal/gopathrs/doc.go | 16 + .../{ => internal/gopathrs}/lookup_linux.go | 6 +- .../{ => internal/gopathrs}/mkdir_linux.go | 54 +- .../internal/gopathrs/open_linux.go | 26 + .../{ => internal/gopathrs}/openat2_linux.go | 2 +- .../filepath-securejoin/pathrs-lite/mkdir.go | 55 + .../pathrs-lite/mkdir_libpathrs.go | 52 + .../pathrs-lite/mkdir_purego.go | 42 + .../pathrs-lite/{open_linux.go => open.go} | 29 - .../pathrs-lite/open_libpathrs.go | 57 + .../pathrs-lite/open_purego.go | 42 + .../pathrs-lite/procfs/procfs_libpathrs.go | 161 ++ .../{procfs_linux.go => procfs_purego.go} | 2 +- .../selinux/go-selinux/selinux_linux.go | 28 +- .../selinux/go-selinux/selinux_stub.go | 4 - vendor/modules.txt | 12 +- 67 files changed, 2240 insertions(+), 4909 deletions(-) delete mode 100644 internal/third_party/selinux/.codespellrc delete mode 100644 internal/third_party/selinux/.github/dependabot.yml delete mode 100644 internal/third_party/selinux/.github/workflows/validate.yml delete mode 100644 internal/third_party/selinux/.gitignore delete mode 100644 internal/third_party/selinux/.golangci.yml delete mode 100644 internal/third_party/selinux/CODEOWNERS delete mode 100644 internal/third_party/selinux/CONTRIBUTING.md delete mode 100644 internal/third_party/selinux/LICENSE delete mode 100644 internal/third_party/selinux/MAINTAINERS delete mode 100644 internal/third_party/selinux/Makefile delete mode 100644 internal/third_party/selinux/README.md delete mode 100644 internal/third_party/selinux/go-selinux/doc.go delete mode 100644 internal/third_party/selinux/go-selinux/label/label.go delete mode 100644 internal/third_party/selinux/go-selinux/label/label_linux.go delete mode 100644 internal/third_party/selinux/go-selinux/label/label_linux_test.go delete mode 100644 internal/third_party/selinux/go-selinux/label/label_stub.go delete mode 100644 internal/third_party/selinux/go-selinux/label/label_stub_test.go delete mode 100644 internal/third_party/selinux/go-selinux/label/label_test.go delete mode 100644 internal/third_party/selinux/go-selinux/selinux.go delete mode 100644 internal/third_party/selinux/go-selinux/selinux_linux.go delete mode 100644 internal/third_party/selinux/go-selinux/selinux_linux_test.go delete mode 100644 internal/third_party/selinux/go-selinux/selinux_stub.go delete mode 100644 internal/third_party/selinux/go-selinux/selinux_stub_test.go delete mode 100644 internal/third_party/selinux/go-selinux/xattrs_linux.go delete mode 100644 internal/third_party/selinux/go.mod delete mode 100644 internal/third_party/selinux/go.sum delete mode 100644 internal/third_party/selinux/pkg/pwalk/README.md delete mode 100644 internal/third_party/selinux/pkg/pwalk/pwalk.go delete mode 100644 internal/third_party/selinux/pkg/pwalk/pwalk_test.go delete mode 100644 internal/third_party/selinux/pkg/pwalkdir/README.md delete mode 100644 internal/third_party/selinux/pkg/pwalkdir/pwalkdir.go delete mode 100644 internal/third_party/selinux/pkg/pwalkdir/pwalkdir_test.go create mode 100644 vendor/cyphar.com/go-pathrs/.golangci.yml create mode 100644 vendor/cyphar.com/go-pathrs/COPYING create mode 100644 vendor/cyphar.com/go-pathrs/doc.go create mode 100644 vendor/cyphar.com/go-pathrs/handle_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go create mode 100644 vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/root_linux.go create mode 100644 vendor/cyphar.com/go-pathrs/utils_linux.go delete mode 100644 vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go rename vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/{ => internal/gopathrs}/lookup_linux.go (98%) rename vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/{ => internal/gopathrs}/mkdir_linux.go (81%) create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go rename vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/{ => internal/gopathrs}/openat2_linux.go (99%) create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go rename vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/{open_linux.go => open.go} (57%) create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go rename vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/{procfs_linux.go => procfs_purego.go} (99%) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 08cc7cd78..d61fbdf27 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -153,12 +153,9 @@ jobs: - name: no toolchain in go.mod # See https://github.com/opencontainers/runc/pull/4717, https://github.com/dependabot/dependabot-core/issues/11933. run: | if grep -q '^toolchain ' go.mod; then echo "Error: go.mod must not have toolchain directive, please fix"; exit 1; fi - # FIXME: This check needed to be disabled for the go-selinux patch addded - # when patching CVE-2025-52881. This needs to be removed as soon as - # the embargo is lifted, along with the replace directive in go.mod. - #- name: no exclude nor replace in go.mod - # run: | - # if grep -Eq '^\s*(exclude|replace) ' go.mod; then echo "Error: go.mod must not have exclude/replace directive, it breaks go install. Please fix"; exit 1; fi + - name: no exclude nor replace in go.mod + run: | + if grep -Eq '^\s*(exclude|replace) ' go.mod; then echo "Error: go.mod must not have exclude/replace directive, it breaks go install. Please fix"; exit 1; fi commit: diff --git a/go.mod b/go.mod index dab09f009..678085648 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/checkpoint-restore/go-criu/v7 v7.2.0 github.com/containerd/console v1.0.5 github.com/coreos/go-systemd/v22 v22.6.0 - github.com/cyphar/filepath-securejoin v0.5.1 + github.com/cyphar/filepath-securejoin v0.6.0 github.com/docker/go-units v0.5.0 github.com/godbus/dbus/v5 v5.1.0 github.com/moby/sys/capability v0.4.0 @@ -16,7 +16,7 @@ require ( github.com/mrunalp/fileutils v0.5.1 github.com/opencontainers/cgroups v0.0.5 github.com/opencontainers/runtime-spec v1.2.2-0.20250818071321-383cadbf08c0 - github.com/opencontainers/selinux v1.12.0 + github.com/opencontainers/selinux v1.13.0 github.com/seccomp/libseccomp-golang v0.11.1 github.com/sirupsen/logrus v1.9.3 github.com/urfave/cli v1.22.17 @@ -28,12 +28,8 @@ require ( ) require ( + cyphar.com/go-pathrs v0.2.1 // indirect github.com/cilium/ebpf v0.17.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect ) - -// FIXME: This is only intended as a short-term solution to include a patch for -// CVE-2025-52881 in go-selinux without pushing the patches upstream. This -// should be removed as soon as possible after the embargo is lifted. -replace github.com/opencontainers/selinux => ./internal/third_party/selinux diff --git a/go.sum b/go.sum index 28fe8a2ca..1503aa3ed 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +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= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo= github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo= @@ -9,8 +11,8 @@ github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5z github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48= -github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -48,6 +50,8 @@ github.com/opencontainers/cgroups v0.0.5 h1:DRITAqcOnY0uSBzIpt1RYWLjh5DPDiqUs4fY github.com/opencontainers/cgroups v0.0.5/go.mod h1:oWVzJsKK0gG9SCRBfTpnn16WcGEqDI8PAcpMGbqWxcs= github.com/opencontainers/runtime-spec v1.2.2-0.20250818071321-383cadbf08c0 h1:RLn0YfUWkiqPGtgUANvJrcjIkCHGRl3jcz/c557M28M= github.com/opencontainers/runtime-spec v1.2.2-0.20250818071321-383cadbf08c0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= +github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -66,8 +70,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= diff --git a/internal/third_party/selinux/.codespellrc b/internal/third_party/selinux/.codespellrc deleted file mode 100644 index 8f0866a3e..000000000 --- a/internal/third_party/selinux/.codespellrc +++ /dev/null @@ -1,2 +0,0 @@ -[codespell] -skip = ./.git,./go.sum,./go-selinux/testdata diff --git a/internal/third_party/selinux/.github/dependabot.yml b/internal/third_party/selinux/.github/dependabot.yml deleted file mode 100644 index b534a2b9f..000000000 --- a/internal/third_party/selinux/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Please see the documentation for all configuration options: -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - # Dependencies listed in .github/workflows/*.yml - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" diff --git a/internal/third_party/selinux/.github/workflows/validate.yml b/internal/third_party/selinux/.github/workflows/validate.yml deleted file mode 100644 index fab1cb494..000000000 --- a/internal/third_party/selinux/.github/workflows/validate.yml +++ /dev/null @@ -1,163 +0,0 @@ -name: validate -on: - push: - tags: - - v* - branches: - - master - pull_request: - -jobs: - - commit: - runs-on: ubuntu-24.04 - # Only check commits on pull requests. - if: github.event_name == 'pull_request' - steps: - - name: get pr commits - id: 'get-pr-commits' - uses: tim-actions/get-pr-commits@v1.3.1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: check subject line length - uses: tim-actions/commit-message-checker-with-regex@v0.3.2 - with: - commits: ${{ steps.get-pr-commits.outputs.commits }} - pattern: '^.{0,72}(\n.*)*$' - error: 'Subject too long (max 72)' - - lint: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-go@v6 - with: - go-version: 1.24.x - - uses: golangci/golangci-lint-action@v7 - with: - version: v2.0 - - codespell: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v5 - - name: install deps - # Version of codespell bundled with Ubuntu is way old, so use pip. - run: pip install codespell - - name: run codespell - run: codespell - - cross: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v5 - - name: cross - run: make build-cross - - test-stubs: - runs-on: macos-latest - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-go@v6 - with: - go-version: 1.24.x - - uses: golangci/golangci-lint-action@v7 - with: - version: v2.0 - - name: test-stubs - run: make test - - test: - strategy: - fail-fast: false - matrix: - go-version: [1.19.x, 1.23.x, 1.24.x] - race: ["-race", ""] - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v5 - - - name: install go ${{ matrix.go-version }} - uses: actions/setup-go@v6 - with: - go-version: ${{ matrix.go-version }} - - - name: build - run: make BUILDFLAGS="${{ matrix.race }}" build - - - name: test - run: make TESTFLAGS="${{ matrix.race }}" test - - vm: - name: "VM" - strategy: - fail-fast: false - matrix: - template: - - template://almalinux-8 - - template://centos-stream-9 - - template://fedora - - template://experimental/opensuse-tumbleweed - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v5 - - - name: "Install Lima" - uses: lima-vm/lima-actions/setup@v1 - id: lima-actions-setup - - - name: "Cache ~/.cache/lima" - uses: actions/cache@v4 - with: - path: ~/.cache/lima - key: lima-${{ steps.lima-actions-setup.outputs.version }}-${{ matrix.template }} - - - name: "Start VM" - # --plain is set to disable file sharing, port forwarding, built-in containerd, etc. for faster start up - run: limactl start --plain --name=default ${{ matrix.template }} - - - name: "Initialize VM" - run: | - set -eux -o pipefail - # Sync the current directory to /tmp/selinux in the guest - limactl cp -r . default:/tmp/selinux - # Install packages - if lima command -v dnf >/dev/null; then - lima sudo dnf install --setopt=install_weak_deps=false --setopt=tsflags=nodocs -y git-core make golang - elif lima command -v zypper >/dev/null; then - lima sudo zypper install -y git make go - else - echo >&2 "Unsupported distribution" - exit 1 - fi - - - name: "make test" - continue-on-error: true - run: lima make -C /tmp/selinux test - - - name: "32-bit test" - continue-on-error: true - run: lima make -C /tmp/selinux GOARCH=386 test - - # https://github.com/opencontainers/selinux/issues/222 - # https://github.com/opencontainers/selinux/issues/225 - - name: "racy test" - continue-on-error: true - run: lima bash -c 'cd /tmp/selinux && go test -timeout 10m -count 100000 ./go-selinux' - - - name: "Show AVC denials" - run: lima sudo ausearch -m AVC,USER_AVC || true - - all-done: - needs: - - commit - - lint - - codespell - - cross - - test-stubs - - test - - vm - runs-on: ubuntu-24.04 - steps: - - run: echo "All jobs completed" diff --git a/internal/third_party/selinux/.gitignore b/internal/third_party/selinux/.gitignore deleted file mode 100644 index 378eac25d..000000000 --- a/internal/third_party/selinux/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/internal/third_party/selinux/.golangci.yml b/internal/third_party/selinux/.golangci.yml deleted file mode 100644 index b1b989251..000000000 --- a/internal/third_party/selinux/.golangci.yml +++ /dev/null @@ -1,44 +0,0 @@ -version: "2" - -formatters: - enable: - - gofumpt - -linters: - enable: - # - copyloopvar # Detects places where loop variables are copied. TODO enable for Go 1.22+ - - dupword # Detects duplicate words. - - errorlint # Detects code that may cause problems with Go 1.13 error wrapping. - - gocritic # Metalinter; detects bugs, performance, and styling issues. - - gosec # Detects security problems. - - misspell # Detects commonly misspelled English words in comments. - - nilerr # Detects code that returns nil even if it checks that the error is not nil. - - nolintlint # Detects ill-formed or insufficient nolint directives. - - prealloc # Detects slice declarations that could potentially be pre-allocated. - - predeclared # Detects code that shadows one of Go's predeclared identifiers - - revive # Metalinter; drop-in replacement for golint. - - thelper # Detects test helpers without t.Helper(). - - tparallel # Detects inappropriate usage of t.Parallel(). - - unconvert # Detects unnecessary type conversions. - - usetesting # Reports uses of functions with replacement inside the testing package. - settings: - govet: - enable-all: true - settings: - shadow: - strict: true - exclusions: - generated: strict - presets: - - comments - - common-false-positives - - legacy - - std-error-handling - rules: - - linters: - - govet - text: '^shadow: declaration of "err" shadows declaration' - -issues: - max-issues-per-linter: 0 - max-same-issues: 0 diff --git a/internal/third_party/selinux/CODEOWNERS b/internal/third_party/selinux/CODEOWNERS deleted file mode 100644 index 14392178a..000000000 --- a/internal/third_party/selinux/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @kolyshkin @mrunalp @rhatdan @runcom @thajeztah diff --git a/internal/third_party/selinux/CONTRIBUTING.md b/internal/third_party/selinux/CONTRIBUTING.md deleted file mode 100644 index dc3ff6a51..000000000 --- a/internal/third_party/selinux/CONTRIBUTING.md +++ /dev/null @@ -1,119 +0,0 @@ -## Contribution Guidelines - -### Security issues - -If you are reporting a security issue, do not create an issue or file a pull -request on GitHub. Instead, disclose the issue responsibly by sending an email -to security@opencontainers.org (which is inhabited only by the maintainers of -the various OCI projects). - -### Pull requests are always welcome - -We are always thrilled to receive pull requests, and do our best to -process them as fast as possible. Not sure if that typo is worth a pull -request? Do it! We will appreciate it. - -If your pull request is not accepted on the first try, don't be -discouraged! If there's a problem with the implementation, hopefully you -received feedback on what to improve. - -We're trying very hard to keep the project lean and focused. We don't want it -to do everything for everybody. This means that we might decide against -incorporating a new feature. - - -### Conventions - -Fork the repo and make changes on your fork in a feature branch. -For larger bugs and enhancements, consider filing a leader issue or mailing-list thread for discussion that is independent of the implementation. -Small changes or changes that have been discussed on the project mailing list may be submitted without a leader issue. - -If the project has a test suite, submit unit tests for your changes. Take a -look at existing tests for inspiration. Run the full test suite on your branch -before submitting a pull request. - -Update the documentation when creating or modifying features. Test -your documentation changes for clarity, concision, and correctness, as -well as a clean documentation build. See ``docs/README.md`` for more -information on building the docs and how docs get released. - -Write clean code. Universally formatted code promotes ease of writing, reading, -and maintenance. Always run `gofmt -s -w file.go` on each changed file before -committing your changes. Most editors have plugins that do this automatically. - -Pull requests descriptions should be as clear as possible and include a -reference to all the issues that they address. - -Commit messages must start with a capitalized and short summary -written in the imperative, followed by an optional, more detailed -explanatory text which is separated from the summary by an empty line. - -Code review comments may be added to your pull request. Discuss, then make the -suggested modifications and push additional commits to your feature branch. Be -sure to post a comment after pushing. The new commits will show up in the pull -request automatically, but the reviewers will not be notified unless you -comment. - -Before the pull request is merged, make sure that you squash your commits into -logical units of work using `git rebase -i` and `git push -f`. After every -commit the test suite (if any) should be passing. Include documentation changes -in the same commit so that a revert would remove all traces of the feature or -fix. - -Commits that fix or close an issue should include a reference like `Closes #XXX` -or `Fixes #XXX`, which will automatically close the issue when merged. - -### Sign your work - -The sign-off is a simple line at the end of the explanation for the -patch, which certifies that you wrote it or otherwise have the right to -pass it on as an open-source patch. The rules are pretty simple: if you -can certify the below (from -[developercertificate.org](http://developercertificate.org/)): - -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -660 York Street, Suite 102, -San Francisco, CA 94110 USA - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -using your real name (sorry, no pseudonyms or anonymous contributions.) - -You can add the sign off when creating the git commit via `git commit -s`. diff --git a/internal/third_party/selinux/LICENSE b/internal/third_party/selinux/LICENSE deleted file mode 100644 index 8dada3eda..000000000 --- a/internal/third_party/selinux/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/internal/third_party/selinux/MAINTAINERS b/internal/third_party/selinux/MAINTAINERS deleted file mode 100644 index 748c18b4c..000000000 --- a/internal/third_party/selinux/MAINTAINERS +++ /dev/null @@ -1,5 +0,0 @@ -Antonio Murdaca (@runcom) -Daniel J Walsh (@rhatdan) -Mrunal Patel (@mrunalp) -Sebastiaan van Stijn (@thaJeztah) -Kirill Kolyshikin (@kolyshkin) diff --git a/internal/third_party/selinux/Makefile b/internal/third_party/selinux/Makefile deleted file mode 100644 index f7b9c3dac..000000000 --- a/internal/third_party/selinux/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -GO ?= go - -all: build build-cross - -define go-build - GOOS=$(1) GOARCH=$(2) $(GO) build ${BUILDFLAGS} ./... -endef - -.PHONY: build -build: - $(call go-build,linux,amd64) - -.PHONY: build-cross -build-cross: - $(call go-build,linux,386) - $(call go-build,linux,arm) - $(call go-build,linux,arm64) - $(call go-build,linux,ppc64le) - $(call go-build,linux,s390x) - $(call go-build,linux,mips64le) - $(call go-build,linux,riscv64) - $(call go-build,windows,amd64) - $(call go-build,windows,386) - - -.PHONY: test -test: - $(GO) test -timeout 3m ${TESTFLAGS} -v ./... - -.PHONY: lint -lint: - golangci-lint run - -.PHONY: vendor -vendor: - $(GO) mod tidy - $(GO) mod verify diff --git a/internal/third_party/selinux/README.md b/internal/third_party/selinux/README.md deleted file mode 100644 index cd6a60f80..000000000 --- a/internal/third_party/selinux/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# selinux - -[![GoDoc](https://godoc.org/github.com/opencontainers/selinux?status.svg)](https://godoc.org/github.com/opencontainers/selinux) [![Go Report Card](https://goreportcard.com/badge/github.com/opencontainers/selinux)](https://goreportcard.com/report/github.com/opencontainers/selinux) [![Build Status](https://travis-ci.org/opencontainers/selinux.svg?branch=master)](https://travis-ci.org/opencontainers/selinux) - -Common SELinux package used across the container ecosystem. - -## Usage - -Prior to v1.8.0, the `selinux` build tag had to be used to enable selinux functionality for compiling consumers of this project. -Starting with v1.8.0, the `selinux` build tag is no longer needed. - -For complete documentation, see [godoc](https://godoc.org/github.com/opencontainers/selinux). - -## Code of Conduct - -Participation in the OpenContainers community is governed by [OpenContainer's Code of Conduct][code-of-conduct]. - -## Security - -If you find an issue, please follow the [security][security] protocol to report it. - -[security]: https://github.com/opencontainers/org/blob/master/SECURITY.md -[code-of-conduct]: https://github.com/opencontainers/org/blob/master/CODE_OF_CONDUCT.md diff --git a/internal/third_party/selinux/go-selinux/doc.go b/internal/third_party/selinux/go-selinux/doc.go deleted file mode 100644 index 57a15c9a1..000000000 --- a/internal/third_party/selinux/go-selinux/doc.go +++ /dev/null @@ -1,13 +0,0 @@ -/* -Package selinux provides a high-level interface for interacting with selinux. - -Usage: - - import "github.com/opencontainers/selinux/go-selinux" - - // Ensure that selinux is enforcing mode. - if selinux.EnforceMode() != selinux.Enforcing { - selinux.SetEnforceMode(selinux.Enforcing) - } -*/ -package selinux diff --git a/internal/third_party/selinux/go-selinux/label/label.go b/internal/third_party/selinux/go-selinux/label/label.go deleted file mode 100644 index 884a8b805..000000000 --- a/internal/third_party/selinux/go-selinux/label/label.go +++ /dev/null @@ -1,48 +0,0 @@ -package label - -import ( - "fmt" - - "github.com/opencontainers/selinux/go-selinux" -) - -// Init initialises the labeling system -func Init() { - _ = selinux.GetEnabled() -} - -// FormatMountLabel returns a string to be used by the mount command. Using -// the SELinux `context` mount option. Changing labels of files on mount -// points with this option can never be changed. -// FormatMountLabel returns a string to be used by the mount command. -// The format of this string will be used to alter the labeling of the mountpoint. -// The string returned is suitable to be used as the options field of the mount command. -// If you need to have additional mount point options, you can pass them in as -// the first parameter. Second parameter is the label that you wish to apply -// to all content in the mount point. -func FormatMountLabel(src, mountLabel string) string { - return FormatMountLabelByType(src, mountLabel, "context") -} - -// FormatMountLabelByType returns a string to be used by the mount command. -// Allow caller to specify the mount options. For example using the SELinux -// `fscontext` mount option would allow certain container processes to change -// labels of files created on the mount points, where as `context` option does -// not. -// FormatMountLabelByType returns a string to be used by the mount command. -// The format of this string will be used to alter the labeling of the mountpoint. -// The string returned is suitable to be used as the options field of the mount command. -// If you need to have additional mount point options, you can pass them in as -// the first parameter. Second parameter is the label that you wish to apply -// to all content in the mount point. -func FormatMountLabelByType(src, mountLabel, contextType string) string { - if mountLabel != "" { - switch src { - case "": - src = fmt.Sprintf("%s=%q", contextType, mountLabel) - default: - src = fmt.Sprintf("%s,%s=%q", src, contextType, mountLabel) - } - } - return src -} diff --git a/internal/third_party/selinux/go-selinux/label/label_linux.go b/internal/third_party/selinux/go-selinux/label/label_linux.go deleted file mode 100644 index 95f29e21f..000000000 --- a/internal/third_party/selinux/go-selinux/label/label_linux.go +++ /dev/null @@ -1,136 +0,0 @@ -package label - -import ( - "errors" - "fmt" - "strings" - - "github.com/opencontainers/selinux/go-selinux" -) - -// Valid Label Options -var validOptions = map[string]bool{ - "disable": true, - "type": true, - "filetype": true, - "user": true, - "role": true, - "level": true, -} - -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 -// the container. A list of options can be passed into this function to alter -// the labels. The labels returned will include a random MCS String, that is -// guaranteed to be unique. -// If the disabled flag is passed in, the process label will not be set, but the mount label will be set -// to the container_file label with the maximum category. This label is not usable by any confined label. -func InitLabels(options []string) (plabel string, mlabel string, retErr error) { - if !selinux.GetEnabled() { - return "", "", nil - } - processLabel, mountLabel := selinux.ContainerLabels() - if processLabel != "" { - defer func() { - if retErr != nil { - selinux.ReleaseLabel(mountLabel) - } - }() - pcon, err := selinux.NewContext(processLabel) - if err != nil { - return "", "", err - } - mcsLevel := pcon["level"] - mcon, err := selinux.NewContext(mountLabel) - if err != nil { - return "", "", err - } - for _, opt := range options { - if opt == "disable" { - selinux.ReleaseLabel(mountLabel) - return "", selinux.PrivContainerMountLabel(), nil - } - 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) - } - con := strings.SplitN(opt, ":", 2) - if !validOptions[con[0]] { - return "", "", fmt.Errorf("bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0]) - } - if con[0] == "filetype" { - mcon["type"] = con[1] - continue - } - pcon[con[0]] = con[1] - if con[0] == "level" || con[0] == "user" { - mcon[con[0]] = con[1] - } - } - if pcon.Get() != processLabel { - if pcon["level"] != mcsLevel { - selinux.ReleaseLabel(processLabel) - } - processLabel = pcon.Get() - selinux.ReserveLabel(processLabel) - } - mountLabel = mcon.Get() - } - return processLabel, mountLabel, nil -} - -// SetFileLabel modifies the "path" label to the specified file label -func SetFileLabel(path string, fileLabel string) error { - if !selinux.GetEnabled() || fileLabel == "" { - return nil - } - return selinux.SetFileLabel(path, fileLabel) -} - -// SetFileCreateLabel tells the kernel the label for all files to be created -func SetFileCreateLabel(fileLabel string) error { - if !selinux.GetEnabled() { - return nil - } - return selinux.SetFSCreateLabel(fileLabel) -} - -// Relabel changes the label of path and all the entries beneath the path. -// It changes the MCS label to s0 if shared is true. -// This will allow all containers to share the content. -// -// The path itself is guaranteed to be relabeled last. -func Relabel(path string, fileLabel string, shared bool) error { - if !selinux.GetEnabled() || fileLabel == "" { - return nil - } - - if shared { - c, err := selinux.NewContext(fileLabel) - if err != nil { - return err - } - - c["level"] = "s0" - fileLabel = c.Get() - } - return selinux.Chcon(path, fileLabel, true) -} - -// Validate checks that the label does not include unexpected options -func Validate(label string) error { - if strings.Contains(label, "z") && strings.Contains(label, "Z") { - return ErrIncompatibleLabel - } - return nil -} - -// RelabelNeeded checks whether the user requested a relabel -func RelabelNeeded(label string) bool { - return strings.Contains(label, "z") || strings.Contains(label, "Z") -} - -// IsShared checks that the label includes a "shared" mark -func IsShared(label string) bool { - return strings.Contains(label, "z") -} diff --git a/internal/third_party/selinux/go-selinux/label/label_linux_test.go b/internal/third_party/selinux/go-selinux/label/label_linux_test.go deleted file mode 100644 index e25ead795..000000000 --- a/internal/third_party/selinux/go-selinux/label/label_linux_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package label - -import ( - "errors" - "os" - "testing" - - "github.com/opencontainers/selinux/go-selinux" -) - -func needSELinux(t *testing.T) { - t.Helper() - if !selinux.GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } -} - -func TestInit(t *testing.T) { - needSELinux(t) - - var testNull []string - _, _, err := InitLabels(testNull) - if err != nil { - t.Fatalf("InitLabels failed: %v:", err) - } - testDisabled := []string{"disable"} - if selinux.ROFileLabel() == "" { - t.Fatal("selinux.ROFileLabel: empty") - } - plabel, mlabel, err := InitLabels(testDisabled) - if err != nil { - t.Fatalf("InitLabels(disabled) failed: %v", err) - } - if plabel != "" { - t.Fatalf("InitLabels(disabled): %q not empty", plabel) - } - if mlabel != "system_u:object_r:container_file_t:s0:c1022,c1023" { - t.Fatalf("InitLabels Disabled mlabel Failed, %s", mlabel) - } - - testUser := []string{"user:user_u", "role:user_r", "type:user_t", "level:s0:c1,c15"} - plabel, mlabel, err = InitLabels(testUser) - if err != nil { - t.Fatalf("InitLabels(user) failed: %v", err) - } - if plabel != "user_u:user_r:user_t:s0:c1,c15" || (mlabel != "user_u:object_r:container_file_t:s0:c1,c15" && mlabel != "user_u:object_r:svirt_sandbox_file_t:s0:c1,c15") { - t.Fatalf("InitLabels(user) failed (plabel=%q, mlabel=%q)", plabel, mlabel) - } - - testBadData := []string{"user", "role:user_r", "type:user_t", "level:s0:c1,c15"} - if _, _, err = InitLabels(testBadData); err == nil { - t.Fatal("InitLabels(bad): expected error, got nil") - } -} - -func TestRelabel(t *testing.T) { - needSELinux(t) - - testdir := t.TempDir() - label := "system_u:object_r:container_file_t:s0:c1,c2" - if err := Relabel(testdir, "", true); err != nil { - t.Fatalf("Relabel with no label failed: %v", err) - } - if err := Relabel(testdir, label, true); err != nil { - t.Fatalf("Relabel shared failed: %v", err) - } - if err := Relabel(testdir, label, false); err != nil { - t.Fatalf("Relabel unshared failed: %v", err) - } - if err := Relabel("/etc", label, false); err == nil { - t.Fatalf("Relabel /etc succeeded") - } - if err := Relabel("/", label, false); err == nil { - t.Fatalf("Relabel / succeeded") - } - if err := Relabel("/usr", label, false); err == nil { - t.Fatalf("Relabel /usr succeeded") - } - if err := Relabel("/usr/", label, false); err == nil { - t.Fatalf("Relabel /usr/ succeeded") - } - if err := Relabel("/etc/passwd", label, false); err == nil { - t.Fatalf("Relabel /etc/passwd succeeded") - } - if home := os.Getenv("HOME"); home != "" { - if err := Relabel(home, label, false); err == nil { - t.Fatalf("Relabel %s succeeded", home) - } - } -} - -func TestValidate(t *testing.T) { - if err := Validate("zZ"); !errors.Is(err, ErrIncompatibleLabel) { - t.Fatalf("Expected incompatible error, got %v", err) - } - if err := Validate("Z"); err != nil { - t.Fatal(err) - } - if err := Validate("z"); err != nil { - t.Fatal(err) - } - if err := Validate(""); err != nil { - t.Fatal(err) - } -} - -func TestIsShared(t *testing.T) { - if shared := IsShared("Z"); shared { - t.Fatalf("Expected label `Z` to not be shared, got %v", shared) - } - if shared := IsShared("z"); !shared { - t.Fatalf("Expected label `z` to be shared, got %v", shared) - } - if shared := IsShared("Zz"); !shared { - t.Fatalf("Expected label `Zz` to be shared, got %v", shared) - } -} - -func TestFileLabel(t *testing.T) { - needSELinux(t) - - testUser := []string{"filetype:test_file_t", "level:s0:c1,c15"} - _, mlabel, err := InitLabels(testUser) - if err != nil { - t.Fatalf("InitLabels(user) failed: %v", err) - } - if mlabel != "system_u:object_r:test_file_t:s0:c1,c15" { - t.Fatalf("InitLabels(filetype) failed: %v", err) - } -} diff --git a/internal/third_party/selinux/go-selinux/label/label_stub.go b/internal/third_party/selinux/go-selinux/label/label_stub.go deleted file mode 100644 index 7a54afc5e..000000000 --- a/internal/third_party/selinux/go-selinux/label/label_stub.go +++ /dev/null @@ -1,44 +0,0 @@ -//go:build !linux -// +build !linux - -package label - -// 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 labels. -func InitLabels([]string) (string, string, error) { - return "", "", nil -} - -func SetFileLabel(string, string) error { - return nil -} - -func SetFileCreateLabel(string) error { - return nil -} - -func Relabel(string, string, bool) error { - return nil -} - -// DisableSecOpt returns a security opt that can disable labeling -// support for future container processes -func DisableSecOpt() []string { - return nil -} - -// Validate checks that the label does not include unexpected options -func Validate(string) error { - return nil -} - -// RelabelNeeded checks whether the user requested a relabel -func RelabelNeeded(string) bool { - return false -} - -// IsShared checks that the label includes a "shared" mark -func IsShared(string) bool { - return false -} diff --git a/internal/third_party/selinux/go-selinux/label/label_stub_test.go b/internal/third_party/selinux/go-selinux/label/label_stub_test.go deleted file mode 100644 index e92cc8b94..000000000 --- a/internal/third_party/selinux/go-selinux/label/label_stub_test.go +++ /dev/null @@ -1,76 +0,0 @@ -//go:build !linux -// +build !linux - -package label - -import ( - "testing" - - "github.com/opencontainers/selinux/go-selinux" -) - -const testLabel = "system_u:object_r:container_file_t:s0:c1,c2" - -func TestInit(t *testing.T) { - var testNull []string - _, _, err := InitLabels(testNull) - if err != nil { - t.Log("InitLabels Failed") - t.Fatal(err) - } - testDisabled := []string{"disable"} - if selinux.ROFileLabel() != "" { - t.Error("selinux.ROFileLabel Failed") - } - plabel, mlabel, err := InitLabels(testDisabled) - if err != nil { - t.Log("InitLabels Disabled Failed") - t.Fatal(err) - } - if plabel != "" { - t.Fatal("InitLabels Disabled Failed") - } - if mlabel != "" { - t.Fatal("InitLabels Disabled mlabel Failed") - } - testUser := []string{"user:user_u", "role:user_r", "type:user_t", "level:s0:c1,c15"} - _, _, err = InitLabels(testUser) - if err != nil { - t.Log("InitLabels User Failed") - t.Fatal(err) - } -} - -func TestRelabel(t *testing.T) { - if err := Relabel("/etc", testLabel, false); err != nil { - t.Fatalf("Relabel /etc succeeded") - } -} - -func TestCheckLabelCompile(t *testing.T) { - if _, _, err := InitLabels(nil); err != nil { - t.Fatal(err) - } - - tmpDir := t.TempDir() - - if err := SetFileLabel(tmpDir, "foobar"); err != nil { - t.Fatal(err) - } - - if err := SetFileCreateLabel("foobar"); err != nil { - t.Fatal(err) - } - - DisableSecOpt() - - if err := Validate("foobar"); err != nil { - t.Fatal(err) - } - if relabel := RelabelNeeded("foobar"); relabel { - t.Fatal("Relabel failed") - } - if shared := IsShared("foobar"); shared { - t.Fatal("isshared failed") - } -} diff --git a/internal/third_party/selinux/go-selinux/label/label_test.go b/internal/third_party/selinux/go-selinux/label/label_test.go deleted file mode 100644 index fb172f3ff..000000000 --- a/internal/third_party/selinux/go-selinux/label/label_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package label - -import "testing" - -func TestFormatMountLabel(t *testing.T) { - expected := `context="foobar"` - if test := FormatMountLabel("", "foobar"); test != expected { - t.Fatalf("Format failed. Expected %s, got %s", expected, test) - } - - expected = `src,context="foobar"` - if test := FormatMountLabel("src", "foobar"); test != expected { - t.Fatalf("Format failed. Expected %s, got %s", expected, test) - } - - expected = `src` - if test := FormatMountLabel("src", ""); test != expected { - t.Fatalf("Format failed. Expected %s, got %s", expected, test) - } - - expected = `fscontext="foobar"` - if test := FormatMountLabelByType("", "foobar", "fscontext"); test != expected { - t.Fatalf("Format failed. Expected %s, got %s", expected, test) - } - - expected = `src,fscontext="foobar"` - if test := FormatMountLabelByType("src", "foobar", "fscontext"); test != expected { - t.Fatalf("Format failed. Expected %s, got %s", expected, test) - } - - expected = `src` - if test := FormatMountLabelByType("src", "", "rootcontext"); test != expected { - t.Fatalf("Format failed. Expected %s, got %s", expected, test) - } -} diff --git a/internal/third_party/selinux/go-selinux/selinux.go b/internal/third_party/selinux/go-selinux/selinux.go deleted file mode 100644 index 15150d475..000000000 --- a/internal/third_party/selinux/go-selinux/selinux.go +++ /dev/null @@ -1,322 +0,0 @@ -package selinux - -import ( - "errors" -) - -const ( - // Enforcing constant indicate SELinux is in enforcing mode - Enforcing = 1 - // Permissive constant to indicate SELinux is in permissive mode - Permissive = 0 - // Disabled constant to indicate SELinux is disabled - Disabled = -1 - // maxCategory is the maximum number of categories used within containers - maxCategory = 1024 - // DefaultCategoryRange is the upper bound on the category range - DefaultCategoryRange = uint32(maxCategory) -) - -var ( - // ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS. - ErrMCSAlreadyExists = errors.New("MCS label already exists") - // ErrEmptyPath is returned when an empty path has been specified. - ErrEmptyPath = errors.New("empty path") - - // ErrInvalidLabel is returned when an invalid label is specified. - ErrInvalidLabel = errors.New("invalid Label") - - // InvalidLabel is returned when an invalid label is specified. - // - // Deprecated: use [ErrInvalidLabel]. - InvalidLabel = ErrInvalidLabel - - // ErrIncomparable is returned two levels are not comparable - ErrIncomparable = errors.New("incomparable levels") - // ErrLevelSyntax is returned when a sensitivity or category do not have correct syntax in a level - ErrLevelSyntax = errors.New("invalid level syntax") - - // ErrContextMissing is returned if a requested context is not found in a file. - ErrContextMissing = errors.New("context does not have a match") - // ErrVerifierNil is returned when a context verifier function is nil. - ErrVerifierNil = errors.New("verifier function is nil") - - // ErrNotTGLeader is returned by [SetKeyLabel] if the calling thread - // is not the thread group leader. - ErrNotTGLeader = errors.New("calling thread is not the thread group leader") - - // CategoryRange allows the upper bound on the category range to be adjusted - CategoryRange = DefaultCategoryRange - - privContainerMountLabel string -) - -// Context is a representation of the SELinux label broken into 4 parts -type Context map[string]string - -// SetDisabled disables SELinux support for the package -func SetDisabled() { - setDisabled() -} - -// GetEnabled returns whether SELinux is currently enabled. -func GetEnabled() bool { - return getEnabled() -} - -// ClassIndex returns the int index for an object class in the loaded policy, -// or -1 and an error -func ClassIndex(class string) (int, error) { - return classIndex(class) -} - -// SetFileLabel sets the SELinux label for this path, following symlinks, -// or returns an error. -func SetFileLabel(fpath string, label string) error { - return setFileLabel(fpath, label) -} - -// LsetFileLabel sets the SELinux label for this path, not following symlinks, -// or returns an error. -func LsetFileLabel(fpath string, label string) error { - return lSetFileLabel(fpath, label) -} - -// FileLabel returns the SELinux label for this path, following symlinks, -// or returns an error. -func FileLabel(fpath string) (string, error) { - return fileLabel(fpath) -} - -// LfileLabel returns the SELinux label for this path, not following symlinks, -// or returns an error. -func LfileLabel(fpath string) (string, error) { - return lFileLabel(fpath) -} - -// SetFSCreateLabel tells the kernel what label to use for all file system objects -// created by this task. -// Set the label to an empty string to return to the default label. Calls to SetFSCreateLabel -// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until file system -// objects created by this task are finished to guarantee another goroutine does not migrate -// to the current thread before execution is complete. -func SetFSCreateLabel(label string) error { - return setFSCreateLabel(label) -} - -// FSCreateLabel returns the default label the kernel which the kernel is using -// for file system objects created by this task. "" indicates default. -func FSCreateLabel() (string, error) { - return fsCreateLabel() -} - -// CurrentLabel returns the SELinux label of the current process thread, or an error. -func CurrentLabel() (string, error) { - return currentLabel() -} - -// PidLabel returns the SELinux label of the given pid, or an error. -func PidLabel(pid int) (string, error) { - return pidLabel(pid) -} - -// ExecLabel returns the SELinux label that the kernel will use for any programs -// that are executed by the current process thread, or an error. -func ExecLabel() (string, error) { - return execLabel() -} - -// CanonicalizeContext takes a context string and writes it to the kernel -// the function then returns the context that the kernel will use. Use this -// function to check if two contexts are equivalent -func CanonicalizeContext(val string) (string, error) { - return canonicalizeContext(val) -} - -// ComputeCreateContext requests the type transition from source to target for -// class from the kernel. -func ComputeCreateContext(source string, target string, class string) (string, error) { - return computeCreateContext(source, target, class) -} - -// CalculateGlbLub computes the glb (greatest lower bound) and lub (least upper bound) -// of a source and target range. -// The glblub is calculated as the greater of the low sensitivities and -// the lower of the high sensitivities and the and of each category bitset. -func CalculateGlbLub(sourceRange, targetRange string) (string, error) { - return calculateGlbLub(sourceRange, targetRange) -} - -// SetExecLabel sets the SELinux label that the kernel will use for any programs -// that are executed by the current process thread, or an error. Calls to SetExecLabel -// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until execution -// of the program is finished to guarantee another goroutine does not migrate to the current -// thread before execution is complete. -func SetExecLabel(label string) error { - return writeConThreadSelf("attr/exec", label) -} - -// SetTaskLabel sets the SELinux label for the current thread, or an error. -// This requires the dyntransition permission. Calls to SetTaskLabel should -// be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() to guarantee -// the current thread does not run in a new mislabeled thread. -func SetTaskLabel(label string) error { - return writeConThreadSelf("attr/current", label) -} - -// SetSocketLabel takes a process label and tells the kernel to assign the -// label to the next socket that gets created. Calls to SetSocketLabel -// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until -// the socket is created to guarantee another goroutine does not migrate -// to the current thread before execution is complete. -func SetSocketLabel(label string) error { - return writeConThreadSelf("attr/sockcreate", label) -} - -// SocketLabel retrieves the current socket label setting -func SocketLabel() (string, error) { - return readConThreadSelf("attr/sockcreate") -} - -// PeerLabel retrieves the label of the client on the other side of a socket -func PeerLabel(fd uintptr) (string, error) { - return peerLabel(fd) -} - -// SetKeyLabel takes a process label and tells the kernel to assign the -// label to the next kernel keyring that gets created. -// -// Calls to SetKeyLabel should be wrapped in -// runtime.LockOSThread()/runtime.UnlockOSThread() until the kernel keyring is -// created to guarantee another goroutine does not migrate to the current -// thread before execution is complete. -// -// Only the thread group leader can set key label. -func SetKeyLabel(label string) error { - return setKeyLabel(label) -} - -// KeyLabel retrieves the current kernel keyring label setting -func KeyLabel() (string, error) { - return keyLabel() -} - -// Get returns the Context as a string -func (c Context) Get() string { - return c.get() -} - -// NewContext creates a new Context struct from the specified label -func NewContext(label string) (Context, error) { - return newContext(label) -} - -// ClearLabels clears all reserved labels -func ClearLabels() { - clearLabels() -} - -// ReserveLabel reserves the MLS/MCS level component of the specified label -func ReserveLabel(label string) { - reserveLabel(label) -} - -// MLSEnabled checks if MLS is enabled. -func MLSEnabled() bool { - return isMLSEnabled() -} - -// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled -func EnforceMode() int { - return enforceMode() -} - -// SetEnforceMode sets the current SELinux mode Enforcing, Permissive. -// Disabled is not valid, since this needs to be set at boot time. -func SetEnforceMode(mode int) error { - return setEnforceMode(mode) -} - -// DefaultEnforceMode returns the systems default SELinux mode Enforcing, -// Permissive or Disabled. Note this is just the default at boot time. -// EnforceMode tells you the systems current mode. -func DefaultEnforceMode() int { - return defaultEnforceMode() -} - -// ReleaseLabel un-reserves the MLS/MCS Level field of the specified label, -// allowing it to be used by another process. -func ReleaseLabel(label string) { - releaseLabel(label) -} - -// ROFileLabel returns the specified SELinux readonly file label -func ROFileLabel() string { - return roFileLabel() -} - -// KVMContainerLabels returns the default processLabel and mountLabel to be used -// for kvm containers by the calling process. -func KVMContainerLabels() (string, string) { - return kvmContainerLabels() -} - -// InitContainerLabels returns the default processLabel and file labels to be -// used for containers running an init system like systemd by the calling process. -func InitContainerLabels() (string, string) { - return initContainerLabels() -} - -// ContainerLabels returns an allocated processLabel and fileLabel to be used for -// container labeling by the calling process. -func ContainerLabels() (processLabel string, fileLabel string) { - return containerLabels() -} - -// SecurityCheckContext validates that the SELinux label is understood by the kernel -func SecurityCheckContext(val string) error { - return securityCheckContext(val) -} - -// CopyLevel returns a label with the MLS/MCS level from src label replaced on -// the dest label. -func CopyLevel(src, dest string) (string, error) { - return copyLevel(src, dest) -} - -// Chcon changes the fpath file object to the SELinux label. -// If fpath is a directory and recurse is true, then Chcon walks the -// directory tree setting the label. -// -// The fpath itself is guaranteed to be relabeled last. -func Chcon(fpath string, label string, recurse bool) error { - return chcon(fpath, label, recurse) -} - -// DupSecOpt takes an SELinux process label and returns security options that -// can be used to set the SELinux Type and Level for future container processes. -func DupSecOpt(src string) ([]string, error) { - return dupSecOpt(src) -} - -// DisableSecOpt returns a security opt that can be used to disable SELinux -// labeling support for future container processes. -func DisableSecOpt() []string { - return []string{"disable"} -} - -// GetDefaultContextWithLevel gets a single context for the specified SELinux user -// identity that is reachable from the specified scon context. The context is based -// on the per-user /etc/selinux/{SELINUXTYPE}/contexts/users/ if it exists, -// and falls back to the global /etc/selinux/{SELINUXTYPE}/contexts/default_contexts -// file. -func GetDefaultContextWithLevel(user, level, scon string) (string, error) { - return getDefaultContextWithLevel(user, level, scon) -} - -// PrivContainerMountLabel returns mount label for privileged containers -func PrivContainerMountLabel() string { - // Make sure label is initialized. - _ = label("") - return privContainerMountLabel -} diff --git a/internal/third_party/selinux/go-selinux/selinux_linux.go b/internal/third_party/selinux/go-selinux/selinux_linux.go deleted file mode 100644 index 70392d989..000000000 --- a/internal/third_party/selinux/go-selinux/selinux_linux.go +++ /dev/null @@ -1,1405 +0,0 @@ -package selinux - -import ( - "bufio" - "bytes" - "crypto/rand" - "encoding/binary" - "errors" - "fmt" - "io" - "io/fs" - "math/big" - "os" - "os/user" - "path/filepath" - "strconv" - "strings" - "sync" - - "github.com/cyphar/filepath-securejoin/pathrs-lite" - "github.com/cyphar/filepath-securejoin/pathrs-lite/procfs" - "golang.org/x/sys/unix" - - "github.com/opencontainers/selinux/pkg/pwalkdir" -) - -const ( - minSensLen = 2 - contextFile = "/usr/share/containers/selinux/contexts" - selinuxDir = "/etc/selinux/" - selinuxUsersDir = "contexts/users" - defaultContexts = "contexts/default_contexts" - selinuxConfig = selinuxDir + "config" - selinuxfsMount = "/sys/fs/selinux" - selinuxTypeTag = "SELINUXTYPE" - selinuxTag = "SELINUX" - xattrNameSelinux = "security.selinux" -) - -type selinuxState struct { - mcsList map[string]bool - selinuxfs string - selinuxfsOnce sync.Once - enabledSet bool - enabled bool - sync.Mutex -} - -type level struct { - cats *big.Int - sens int -} - -type mlsRange struct { - low *level - high *level -} - -type defaultSECtx struct { - userRdr io.Reader - verifier func(string) error - defaultRdr io.Reader - user, level, scon string -} - -type levelItem byte - -const ( - sensitivity levelItem = 's' - category levelItem = 'c' -) - -var ( - readOnlyFileLabel string - state = selinuxState{ - mcsList: make(map[string]bool), - } - - // for policyRoot() - policyRootOnce sync.Once - policyRootVal string - - // for label() - loadLabelsOnce sync.Once - labels map[string]string -) - -func policyRoot() string { - policyRootOnce.Do(func() { - policyRootVal = filepath.Join(selinuxDir, readConfig(selinuxTypeTag)) - }) - - return policyRootVal -} - -func (s *selinuxState) setEnable(enabled bool) bool { - s.Lock() - defer s.Unlock() - s.enabledSet = true - s.enabled = enabled - return s.enabled -} - -func (s *selinuxState) getEnabled() bool { - s.Lock() - enabled := s.enabled - enabledSet := s.enabledSet - s.Unlock() - if enabledSet { - return enabled - } - - enabled = false - if fs := getSelinuxMountPoint(); fs != "" { - if con, _ := CurrentLabel(); con != "kernel" { - enabled = true - } - } - return s.setEnable(enabled) -} - -// setDisabled disables SELinux support for the package -func setDisabled() { - state.setEnable(false) -} - -func verifySELinuxfsMount(mnt string) bool { - var buf unix.Statfs_t - for { - err := unix.Statfs(mnt, &buf) - if err == nil { - break - } - if err == unix.EAGAIN || err == unix.EINTR { - continue - } - return false - } - - //#nosec G115 -- there is no overflow here. - if uint32(buf.Type) != uint32(unix.SELINUX_MAGIC) { - return false - } - if (buf.Flags & unix.ST_RDONLY) != 0 { - return false - } - - return true -} - -func findSELinuxfs() string { - // fast path: check the default mount first - if verifySELinuxfsMount(selinuxfsMount) { - return selinuxfsMount - } - - // check if selinuxfs is available before going the slow path - fs, err := os.ReadFile("/proc/filesystems") - if err != nil { - return "" - } - if !bytes.Contains(fs, []byte("\tselinuxfs\n")) { - return "" - } - - // slow path: try to find among the mounts - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return "" - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for { - mnt := findSELinuxfsMount(scanner) - if mnt == "" { // error or not found - return "" - } - if verifySELinuxfsMount(mnt) { - return mnt - } - } -} - -// findSELinuxfsMount returns a next selinuxfs mount point found, -// if there is one, or an empty string in case of EOF or error. -func findSELinuxfsMount(s *bufio.Scanner) string { - for s.Scan() { - txt := s.Bytes() - // The first field after - is fs type. - // Safe as spaces in mountpoints are encoded as \040 - if !bytes.Contains(txt, []byte(" - selinuxfs ")) { - continue - } - const mPos = 5 // mount point is 5th field - fields := bytes.SplitN(txt, []byte(" "), mPos+1) - if len(fields) < mPos+1 { - continue - } - return string(fields[mPos-1]) - } - - return "" -} - -func (s *selinuxState) getSELinuxfs() string { - s.selinuxfsOnce.Do(func() { - s.selinuxfs = findSELinuxfs() - }) - - return s.selinuxfs -} - -// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs -// filesystem or an empty string if no mountpoint is found. Selinuxfs is -// a proc-like pseudo-filesystem that exposes the SELinux policy API to -// processes. The existence of an selinuxfs mount is used to determine -// whether SELinux is currently enabled or not. -func getSelinuxMountPoint() string { - return state.getSELinuxfs() -} - -// getEnabled returns whether SELinux is currently enabled. -func getEnabled() bool { - return state.getEnabled() -} - -func readConfig(target string) string { - in, err := os.Open(selinuxConfig) - if err != nil { - return "" - } - defer in.Close() - - scanner := bufio.NewScanner(in) - - for scanner.Scan() { - line := bytes.TrimSpace(scanner.Bytes()) - if len(line) == 0 { - // Skip blank lines - continue - } - if line[0] == ';' || line[0] == '#' { - // Skip comments - continue - } - fields := bytes.SplitN(line, []byte{'='}, 2) - if len(fields) != 2 { - continue - } - if bytes.Equal(fields[0], []byte(target)) { - return string(bytes.Trim(fields[1], `"`)) - } - } - return "" -} - -func readConFd(in *os.File) (string, error) { - data, err := io.ReadAll(in) - if err != nil { - return "", err - } - 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 [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/ with no tricky mounts or symlinks causing you to -// operate on an unexpected path (with some caveats on pre-openat2 or -// pre-fsopen kernels). -// -// [OpenThreadSelf]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle.OpenThreadSelf -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/. -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 to /proc/thread-self/. -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 [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/ with no tricky mounts or symlinks causing you to -// operate on an unexpected path (with some caveats on pre-openat2 or -// pre-fsopen kernels). -// -// [OpenSelf]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle.OpenSelf -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/. -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 to /proc/self/. -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 [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/ with no tricky mounts or symlinks causing you to -// operate on an unexpected path (with some caveats on pre-openat2 or -// pre-fsopen kernels). -// -// [OpenPid]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle.OpenPid -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, -// or -1 and an error -func classIndex(class string) (int, error) { - permpath := fmt.Sprintf("class/%s/index", class) - indexpath := filepath.Join(getSelinuxMountPoint(), permpath) - - indexB, err := os.ReadFile(indexpath) - if err != nil { - return -1, err - } - index, err := strconv.Atoi(string(indexB)) - if err != nil { - return -1, err - } - - return index, nil -} - -// lSetFileLabel sets the SELinux label for this path, not following symlinks, -// or returns an error. -func lSetFileLabel(fpath string, label string) error { - if fpath == "" { - return ErrEmptyPath - } - for { - err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0) - if err == nil { - break - } - if err != unix.EINTR { - return &os.PathError{Op: fmt.Sprintf("lsetxattr(label=%s)", label), Path: fpath, Err: err} - } - } - - return nil -} - -// setFileLabel sets the SELinux label for this path, following symlinks, -// or returns an error. -func setFileLabel(fpath string, label string) error { - if fpath == "" { - return ErrEmptyPath - } - for { - err := unix.Setxattr(fpath, xattrNameSelinux, []byte(label), 0) - if err == nil { - break - } - if err != unix.EINTR { - return &os.PathError{Op: fmt.Sprintf("setxattr(label=%s)", label), Path: fpath, Err: err} - } - } - - return nil -} - -// fileLabel returns the SELinux label for this path, following symlinks, -// or returns an error. -func fileLabel(fpath string) (string, error) { - if fpath == "" { - return "", ErrEmptyPath - } - - label, err := getxattr(fpath, xattrNameSelinux) - if err != nil { - return "", &os.PathError{Op: "getxattr", Path: fpath, Err: err} - } - // Trim the NUL byte at the end of the byte buffer, if present. - if len(label) > 0 && label[len(label)-1] == '\x00' { - label = label[:len(label)-1] - } - return string(label), nil -} - -// lFileLabel returns the SELinux label for this path, not following symlinks, -// or returns an error. -func lFileLabel(fpath string) (string, error) { - if fpath == "" { - return "", ErrEmptyPath - } - - label, err := lgetxattr(fpath, xattrNameSelinux) - if err != nil { - return "", &os.PathError{Op: "lgetxattr", Path: fpath, Err: err} - } - // Trim the NUL byte at the end of the byte buffer, if present. - if len(label) > 0 && label[len(label)-1] == '\x00' { - label = label[:len(label)-1] - } - return string(label), nil -} - -func setFSCreateLabel(label string) error { - return writeConThreadSelf("attr/fscreate", label) -} - -// fsCreateLabel returns the default label the kernel which the kernel is using -// for file system objects created by this task. "" indicates default. -func fsCreateLabel() (string, error) { - return readConThreadSelf("attr/fscreate") -} - -// currentLabel returns the SELinux label of the current process thread, or an error. -func currentLabel() (string, error) { - return readConThreadSelf("attr/current") -} - -// pidLabel returns the SELinux label of the given pid, or an error. -func pidLabel(pid int) (string, error) { - 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 -// that are executed by the current process thread, or an error. -func execLabel() (string, error) { - return readConThreadSelf("exec") -} - -// canonicalizeContext takes a context string and writes it to the kernel -// the function then returns the context that the kernel will use. Use this -// function to check if two contexts are equivalent -func canonicalizeContext(val string) (string, error) { - return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val) -} - -// computeCreateContext requests the type transition from source to target for -// class from the kernel. -func computeCreateContext(source string, target string, class string) (string, error) { - classidx, err := classIndex(class) - if err != nil { - return "", err - } - - return readWriteCon(filepath.Join(getSelinuxMountPoint(), "create"), fmt.Sprintf("%s %s %d", source, target, classidx)) -} - -// catsToBitset stores categories in a bitset. -func catsToBitset(cats string) (*big.Int, error) { - bitset := new(big.Int) - - catlist := strings.Split(cats, ",") - for _, r := range catlist { - ranges := strings.SplitN(r, ".", 2) - if len(ranges) > 1 { - catstart, err := parseLevelItem(ranges[0], category) - if err != nil { - return nil, err - } - catend, err := parseLevelItem(ranges[1], category) - if err != nil { - return nil, err - } - for i := catstart; i <= catend; i++ { - bitset.SetBit(bitset, i, 1) - } - } else { - cat, err := parseLevelItem(ranges[0], category) - if err != nil { - return nil, err - } - bitset.SetBit(bitset, cat, 1) - } - } - - return bitset, nil -} - -// parseLevelItem parses and verifies that a sensitivity or category are valid -func parseLevelItem(s string, sep levelItem) (int, error) { - if len(s) < minSensLen || levelItem(s[0]) != sep { - return 0, ErrLevelSyntax - } - const bitSize = 31 // Make sure the result fits into signed int32. - val, err := strconv.ParseUint(s[1:], 10, bitSize) - if err != nil { - return 0, err - } - - return int(val), nil -} - -// parseLevel fills a level from a string that contains -// a sensitivity and categories -func (l *level) parseLevel(levelStr string) error { - lvl := strings.SplitN(levelStr, ":", 2) - sens, err := parseLevelItem(lvl[0], sensitivity) - if err != nil { - return fmt.Errorf("failed to parse sensitivity: %w", err) - } - l.sens = sens - if len(lvl) > 1 { - cats, err := catsToBitset(lvl[1]) - if err != nil { - return fmt.Errorf("failed to parse categories: %w", err) - } - l.cats = cats - } - - return nil -} - -// rangeStrToMLSRange marshals a string representation of a range. -func rangeStrToMLSRange(rangeStr string) (*mlsRange, error) { - r := &mlsRange{} - l := strings.SplitN(rangeStr, "-", 2) - - switch len(l) { - // rangeStr that has a low and a high level, e.g. s4:c0.c1023-s6:c0.c1023 - case 2: - r.high = &level{} - if err := r.high.parseLevel(l[1]); err != nil { - return nil, fmt.Errorf("failed to parse high level %q: %w", l[1], err) - } - fallthrough - // rangeStr that is single level, e.g. s6:c0,c3,c5,c30.c1023 - case 1: - r.low = &level{} - if err := r.low.parseLevel(l[0]); err != nil { - return nil, fmt.Errorf("failed to parse low level %q: %w", l[0], err) - } - } - - if r.high == nil { - r.high = r.low - } - - return r, nil -} - -// bitsetToStr takes a category bitset and returns it in the -// canonical selinux syntax -func bitsetToStr(c *big.Int) string { - var str string - - length := 0 - i0 := int(c.TrailingZeroBits()) //#nosec G115 -- don't expect TralingZeroBits to return values with highest bit set. - for i := i0; i < c.BitLen(); i++ { - if c.Bit(i) == 0 { - continue - } - if length == 0 { - if str != "" { - str += "," - } - str += "c" + strconv.Itoa(i) - } - if c.Bit(i+1) == 1 { - length++ - continue - } - if length == 1 { - str += ",c" + strconv.Itoa(i) - } else if length > 1 { - str += ".c" + strconv.Itoa(i) - } - length = 0 - } - - return str -} - -func (l *level) equal(l2 *level) bool { - if l2 == nil || l == nil { - return l == l2 - } - if l2.sens != l.sens { - return false - } - if l2.cats == nil || l.cats == nil { - return l2.cats == l.cats - } - return l.cats.Cmp(l2.cats) == 0 -} - -// String returns an mlsRange as a string. -func (m mlsRange) String() string { - low := "s" + strconv.Itoa(m.low.sens) - if m.low.cats != nil && m.low.cats.BitLen() > 0 { - low += ":" + bitsetToStr(m.low.cats) - } - - if m.low.equal(m.high) { - return low - } - - high := "s" + strconv.Itoa(m.high.sens) - if m.high.cats != nil && m.high.cats.BitLen() > 0 { - high += ":" + bitsetToStr(m.high.cats) - } - - return low + "-" + high -} - -// TODO: remove these in favor of built-in min/max -// once we stop supporting Go < 1.21. -func maxInt(a, b int) int { - if a > b { - return a - } - return b -} - -func minInt(a, b int) int { - if a < b { - return a - } - return b -} - -// calculateGlbLub computes the glb (greatest lower bound) and lub (least upper bound) -// of a source and target range. -// The glblub is calculated as the greater of the low sensitivities and -// the lower of the high sensitivities and the and of each category bitset. -func calculateGlbLub(sourceRange, targetRange string) (string, error) { - s, err := rangeStrToMLSRange(sourceRange) - if err != nil { - return "", err - } - t, err := rangeStrToMLSRange(targetRange) - if err != nil { - return "", err - } - - if s.high.sens < t.low.sens || t.high.sens < s.low.sens { - /* these ranges have no common sensitivities */ - return "", ErrIncomparable - } - - outrange := &mlsRange{low: &level{}, high: &level{}} - - /* take the greatest of the low */ - outrange.low.sens = maxInt(s.low.sens, t.low.sens) - - /* take the least of the high */ - outrange.high.sens = minInt(s.high.sens, t.high.sens) - - /* find the intersecting categories */ - if s.low.cats != nil && t.low.cats != nil { - outrange.low.cats = new(big.Int) - outrange.low.cats.And(s.low.cats, t.low.cats) - } - if s.high.cats != nil && t.high.cats != nil { - outrange.high.cats = new(big.Int) - outrange.high.cats.And(s.high.cats, t.high.cats) - } - - return outrange.String(), nil -} - -func readWriteCon(fpath string, val string) (string, error) { - if fpath == "" { - return "", ErrEmptyPath - } - f, err := os.OpenFile(fpath, os.O_RDWR, 0) - if err != nil { - return "", err - } - defer f.Close() - - _, err = f.Write([]byte(val)) - if err != nil { - return "", err - } - - return readConFd(f) -} - -// peerLabel retrieves the label of the client on the other side of a socket -func peerLabel(fd uintptr) (string, error) { - l, err := unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC) - if err != nil { - return "", &os.PathError{Op: "getsockopt", Path: "fd " + strconv.Itoa(int(fd)), Err: err} - } - return l, nil -} - -// setKeyLabel takes a process label and tells the kernel to assign the -// label to the next kernel keyring that gets created -func setKeyLabel(label string) error { - // 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) { - return nil - } - if label == "" && errors.Is(err, os.ErrPermission) { - return nil - } - if errors.Is(err, unix.EACCES) && unix.Getpid() != unix.Gettid() { - return ErrNotTGLeader - } - 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 -func (c Context) get() string { - if l := c["level"]; l != "" { - return c["user"] + ":" + c["role"] + ":" + c["type"] + ":" + l - } - return c["user"] + ":" + c["role"] + ":" + c["type"] -} - -// newContext creates a new Context struct from the specified label -func newContext(label string) (Context, error) { - c := make(Context) - - if len(label) != 0 { - con := strings.SplitN(label, ":", 4) - if len(con) < 3 { - return c, ErrInvalidLabel - } - c["user"] = con[0] - c["role"] = con[1] - c["type"] = con[2] - if len(con) > 3 { - c["level"] = con[3] - } - } - return c, nil -} - -// clearLabels clears all reserved labels -func clearLabels() { - state.Lock() - state.mcsList = make(map[string]bool) - state.Unlock() -} - -// reserveLabel reserves the MLS/MCS level component of the specified label -func reserveLabel(label string) { - if len(label) != 0 { - con := strings.SplitN(label, ":", 4) - if len(con) > 3 { - _ = mcsAdd(con[3]) - } - } -} - -func selinuxEnforcePath() string { - return filepath.Join(getSelinuxMountPoint(), "enforce") -} - -// isMLSEnabled checks if MLS is enabled. -func isMLSEnabled() bool { - enabledB, err := os.ReadFile(filepath.Join(getSelinuxMountPoint(), "mls")) - if err != nil { - return false - } - return bytes.Equal(enabledB, []byte{'1'}) -} - -// enforceMode returns the current SELinux mode Enforcing, Permissive, Disabled -func enforceMode() int { - var enforce int - - enforceB, err := os.ReadFile(selinuxEnforcePath()) - if err != nil { - return -1 - } - enforce, err = strconv.Atoi(string(enforceB)) - if err != nil { - return -1 - } - return enforce -} - -// setEnforceMode sets the current SELinux mode Enforcing, Permissive. -// Disabled is not valid, since this needs to be set at boot time. -func setEnforceMode(mode int) error { - return os.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0) -} - -// defaultEnforceMode returns the systems default SELinux mode Enforcing, -// Permissive or Disabled. Note this is just the default at boot time. -// EnforceMode tells you the systems current mode. -func defaultEnforceMode() int { - switch readConfig(selinuxTag) { - case "enforcing": - return Enforcing - case "permissive": - return Permissive - } - return Disabled -} - -func mcsAdd(mcs string) error { - if mcs == "" { - return nil - } - state.Lock() - defer state.Unlock() - if state.mcsList[mcs] { - return ErrMCSAlreadyExists - } - state.mcsList[mcs] = true - return nil -} - -func mcsDelete(mcs string) { - if mcs == "" { - return - } - state.Lock() - defer state.Unlock() - state.mcsList[mcs] = false -} - -func intToMcs(id int, catRange uint32) string { - var ( - SETSIZE = int(catRange) - TIER = SETSIZE - ORD = id - ) - - if id < 1 || id > 523776 { - return "" - } - - for ORD > TIER { - ORD -= TIER - TIER-- - } - TIER = SETSIZE - TIER - ORD += TIER - return fmt.Sprintf("s0:c%d,c%d", TIER, ORD) -} - -func uniqMcs(catRange uint32) string { - var ( - n uint32 - c1, c2 uint32 - mcs string - ) - - for { - _ = binary.Read(rand.Reader, binary.LittleEndian, &n) - c1 = n % catRange - _ = binary.Read(rand.Reader, binary.LittleEndian, &n) - c2 = n % catRange - if c1 == c2 { - continue - } else if c1 > c2 { - c1, c2 = c2, c1 - } - mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2) - if err := mcsAdd(mcs); err != nil { - continue - } - break - } - return mcs -} - -// releaseLabel un-reserves the MLS/MCS Level field of the specified label, -// allowing it to be used by another process. -func releaseLabel(label string) { - if len(label) != 0 { - con := strings.SplitN(label, ":", 4) - if len(con) > 3 { - mcsDelete(con[3]) - } - } -} - -// roFileLabel returns the specified SELinux readonly file label -func roFileLabel() string { - return readOnlyFileLabel -} - -func openContextFile() (*os.File, error) { - if f, err := os.Open(contextFile); err == nil { - return f, nil - } - return os.Open(filepath.Join(policyRoot(), "contexts", "lxc_contexts")) -} - -func loadLabels() { - labels = make(map[string]string) - in, err := openContextFile() - if err != nil { - return - } - defer in.Close() - - scanner := bufio.NewScanner(in) - - for scanner.Scan() { - line := bytes.TrimSpace(scanner.Bytes()) - if len(line) == 0 { - // Skip blank lines - continue - } - if line[0] == ';' || line[0] == '#' { - // Skip comments - continue - } - fields := bytes.SplitN(line, []byte{'='}, 2) - if len(fields) != 2 { - continue - } - key, val := bytes.TrimSpace(fields[0]), bytes.TrimSpace(fields[1]) - labels[string(key)] = string(bytes.Trim(val, `"`)) - } - - con, _ := NewContext(labels["file"]) - con["level"] = fmt.Sprintf("s0:c%d,c%d", maxCategory-2, maxCategory-1) - privContainerMountLabel = con.get() - reserveLabel(privContainerMountLabel) -} - -func label(key string) string { - loadLabelsOnce.Do(func() { - loadLabels() - }) - return labels[key] -} - -// kvmContainerLabels returns the default processLabel and mountLabel to be used -// for kvm containers by the calling process. -func kvmContainerLabels() (string, string) { - processLabel := label("kvm_process") - if processLabel == "" { - processLabel = label("process") - } - - return addMcs(processLabel, label("file")) -} - -// initContainerLabels returns the default processLabel and file labels to be -// used for containers running an init system like systemd by the calling process. -func initContainerLabels() (string, string) { - processLabel := label("init_process") - if processLabel == "" { - processLabel = label("process") - } - - return addMcs(processLabel, label("file")) -} - -// containerLabels returns an allocated processLabel and fileLabel to be used for -// container labeling by the calling process. -func containerLabels() (processLabel string, fileLabel string) { - if !getEnabled() { - return "", "" - } - - processLabel = label("process") - fileLabel = label("file") - readOnlyFileLabel = label("ro_file") - - if processLabel == "" || fileLabel == "" { - return "", fileLabel - } - - if readOnlyFileLabel == "" { - readOnlyFileLabel = fileLabel - } - - return addMcs(processLabel, fileLabel) -} - -func addMcs(processLabel, fileLabel string) (string, string) { - scon, _ := NewContext(processLabel) - if scon["level"] != "" { - mcs := uniqMcs(CategoryRange) - scon["level"] = mcs - processLabel = scon.Get() - scon, _ = NewContext(fileLabel) - scon["level"] = mcs - fileLabel = scon.Get() - } - return processLabel, fileLabel -} - -// securityCheckContext validates that the SELinux label is understood by the kernel -func securityCheckContext(val string) error { - return os.WriteFile(filepath.Join(getSelinuxMountPoint(), "context"), []byte(val), 0) -} - -// copyLevel returns a label with the MLS/MCS level from src label replaced on -// the dest label. -func copyLevel(src, dest string) (string, error) { - if src == "" { - return "", nil - } - if err := SecurityCheckContext(src); err != nil { - return "", err - } - if err := SecurityCheckContext(dest); err != nil { - return "", err - } - scon, err := NewContext(src) - if err != nil { - return "", err - } - tcon, err := NewContext(dest) - if err != nil { - return "", err - } - mcsDelete(tcon["level"]) - _ = mcsAdd(scon["level"]) - tcon["level"] = scon["level"] - return tcon.Get(), nil -} - -// chcon changes the fpath file object to the SELinux label. -// If fpath is a directory and recurse is true, then chcon walks the -// directory tree setting the label. -func chcon(fpath string, label string, recurse bool) error { - if fpath == "" { - return ErrEmptyPath - } - if label == "" { - return nil - } - - excludePaths := map[string]bool{ - "/": true, - "/bin": true, - "/boot": true, - "/dev": true, - "/etc": true, - "/etc/passwd": true, - "/etc/pki": true, - "/etc/shadow": true, - "/home": true, - "/lib": true, - "/lib64": true, - "/media": true, - "/opt": true, - "/proc": true, - "/root": true, - "/run": true, - "/sbin": true, - "/srv": true, - "/sys": true, - "/tmp": true, - "/usr": true, - "/var": true, - "/var/lib": true, - "/var/log": true, - } - - if home := os.Getenv("HOME"); home != "" { - excludePaths[home] = true - } - - if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { - if usr, err := user.Lookup(sudoUser); err == nil { - excludePaths[usr.HomeDir] = true - } - } - - if fpath != "/" { - fpath = strings.TrimSuffix(fpath, "/") - } - if excludePaths[fpath] { - return fmt.Errorf("SELinux relabeling of %s is not allowed", fpath) - } - - if !recurse { - err := lSetFileLabel(fpath, label) - if err != nil { - // Check if file doesn't exist, must have been removed - if errors.Is(err, os.ErrNotExist) { - return nil - } - // Check if current label is correct on disk - flabel, nerr := lFileLabel(fpath) - if nerr == nil && flabel == label { - return nil - } - // Check if file doesn't exist, must have been removed - if errors.Is(nerr, os.ErrNotExist) { - return nil - } - return err - } - return nil - } - - return rchcon(fpath, label) -} - -func rchcon(fpath, label string) error { //revive:disable:cognitive-complexity - fastMode := false - // If the current label matches the new label, assume - // other labels are correct. - if cLabel, err := lFileLabel(fpath); err == nil && cLabel == label { - fastMode = true - } - return pwalkdir.Walk(fpath, func(p string, _ fs.DirEntry, _ error) error { - if fastMode { - if cLabel, err := lFileLabel(p); err == nil && cLabel == label { - return nil - } - } - err := lSetFileLabel(p, label) - // Walk a file tree can race with removal, so ignore ENOENT. - if errors.Is(err, os.ErrNotExist) { - return nil - } - return err - }) -} - -// dupSecOpt takes an SELinux process label and returns security options that -// can be used to set the SELinux Type and Level for future container processes. -func dupSecOpt(src string) ([]string, error) { - if src == "" { - return nil, nil - } - con, err := NewContext(src) - if err != nil { - return nil, err - } - if con["user"] == "" || - con["role"] == "" || - con["type"] == "" { - return nil, nil - } - dup := []string{ - "user:" + con["user"], - "role:" + con["role"], - "type:" + con["type"], - } - - if con["level"] != "" { - dup = append(dup, "level:"+con["level"]) - } - - return dup, nil -} - -// findUserInContext scans the reader for a valid SELinux context -// match that is verified with the verifier. Invalid contexts are -// skipped. It returns a matched context or an empty string if no -// match is found. If a scanner error occurs, it is returned. -func findUserInContext(context Context, r io.Reader, verifier func(string) error) (string, error) { - fromRole := context["role"] - fromType := context["type"] - scanner := bufio.NewScanner(r) - - for scanner.Scan() { - fromConns := strings.Fields(scanner.Text()) - if len(fromConns) == 0 { - // Skip blank lines - continue - } - - line := fromConns[0] - - if line[0] == ';' || line[0] == '#' { - // Skip comments - continue - } - - // user context files contexts are formatted as - // role_r:type_t:s0 where the user is missing. - lineArr := strings.SplitN(line, ":", 4) - // skip context with typo, or role and type do not match - if len(lineArr) != 3 || - lineArr[0] != fromRole || - lineArr[1] != fromType { - continue - } - - for _, cc := range fromConns[1:] { - toConns := strings.SplitN(cc, ":", 4) - if len(toConns) != 3 { - continue - } - - context["role"] = toConns[0] - context["type"] = toConns[1] - - outConn := context.get() - if err := verifier(outConn); err != nil { - continue - } - - return outConn, nil - } - } - if err := scanner.Err(); err != nil { - return "", fmt.Errorf("failed to scan for context: %w", err) - } - - return "", nil -} - -func getDefaultContextFromReaders(c *defaultSECtx) (string, error) { - if c.verifier == nil { - return "", ErrVerifierNil - } - - context, err := newContext(c.scon) - if err != nil { - return "", fmt.Errorf("failed to create label for %s: %w", c.scon, err) - } - - // set so the verifier validates the matched context with the provided user and level. - context["user"] = c.user - context["level"] = c.level - - conn, err := findUserInContext(context, c.userRdr, c.verifier) - if err != nil { - return "", err - } - - if conn != "" { - return conn, nil - } - - conn, err = findUserInContext(context, c.defaultRdr, c.verifier) - if err != nil { - return "", err - } - - if conn != "" { - return conn, nil - } - - return "", fmt.Errorf("context %q not found: %w", c.scon, ErrContextMissing) -} - -func getDefaultContextWithLevel(user, level, scon string) (string, error) { - userPath := filepath.Join(policyRoot(), selinuxUsersDir, user) - fu, err := os.Open(userPath) - if err != nil { - return "", err - } - defer fu.Close() - - defaultPath := filepath.Join(policyRoot(), defaultContexts) - fd, err := os.Open(defaultPath) - if err != nil { - return "", err - } - defer fd.Close() - - c := defaultSECtx{ - user: user, - level: level, - scon: scon, - userRdr: fu, - defaultRdr: fd, - verifier: securityCheckContext, - } - - return getDefaultContextFromReaders(&c) -} diff --git a/internal/third_party/selinux/go-selinux/selinux_linux_test.go b/internal/third_party/selinux/go-selinux/selinux_linux_test.go deleted file mode 100644 index 71aa0b82c..000000000 --- a/internal/third_party/selinux/go-selinux/selinux_linux_test.go +++ /dev/null @@ -1,711 +0,0 @@ -package selinux - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "testing" - - "golang.org/x/sys/unix" -) - -func TestSetFileLabel(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - const ( - tmpFile = "selinux_test" - tmpLink = "selinux_test_link" - con = "system_u:object_r:bin_t:s0:c1,c2" - con2 = "system_u:object_r:bin_t:s0:c3,c4" - ) - - _ = os.Remove(tmpFile) - out, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0) - if err != nil { - t.Fatal(err) - } - out.Close() - defer os.Remove(tmpFile) - - _ = os.Remove(tmpLink) - if err := os.Symlink(tmpFile, tmpLink); err != nil { - t.Fatal(err) - } - defer os.Remove(tmpLink) - - if err := SetFileLabel(tmpLink, con); err != nil { - t.Fatalf("SetFileLabel failed: %s", err) - } - filelabel, err := FileLabel(tmpLink) - if err != nil { - t.Fatalf("FileLabel failed: %s", err) - } - if filelabel != con { - t.Fatalf("FileLabel failed, returned %s expected %s", filelabel, con) - } - - // Using LfileLabel to verify that the symlink itself is not labeled. - linkLabel, err := LfileLabel(tmpLink) - if err != nil { - t.Fatalf("LfileLabel failed: %s", err) - } - if linkLabel == con { - t.Fatalf("Label on symlink should not be set, got: %q", linkLabel) - } - - // Use LsetFileLabel to set a label on the symlink itself. - if err := LsetFileLabel(tmpLink, con2); err != nil { - t.Fatalf("LsetFileLabel failed: %s", err) - } - filelabel, err = FileLabel(tmpFile) - if err != nil { - t.Fatalf("FileLabel failed: %s", err) - } - if filelabel != con { - t.Fatalf("FileLabel was updated, returned %s expected %s", filelabel, con) - } - - linkLabel, err = LfileLabel(tmpLink) - if err != nil { - t.Fatalf("LfileLabel failed: %s", err) - } - if linkLabel != con2 { - t.Fatalf("LfileLabel failed: returned %s expected %s", linkLabel, con2) - } -} - -func TestKVMLabels(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - plabel, flabel := KVMContainerLabels() - if plabel == "" { - t.Log("Failed to read kvm label") - } - t.Log(plabel) - t.Log(flabel) - if _, err := CanonicalizeContext(plabel); err != nil { - t.Fatal(err) - } - if _, err := CanonicalizeContext(flabel); err != nil { - t.Fatal(err) - } - - ReleaseLabel(plabel) -} - -func TestInitLabels(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - plabel, flabel := InitContainerLabels() - if plabel == "" { - t.Log("Failed to read init label") - } - t.Log(plabel) - t.Log(flabel) - if _, err := CanonicalizeContext(plabel); err != nil { - t.Fatal(err) - } - if _, err := CanonicalizeContext(flabel); err != nil { - t.Fatal(err) - } - ReleaseLabel(plabel) -} - -func TestDuplicateLabel(t *testing.T) { - secopt, err := DupSecOpt("system_u:system_r:container_t:s0:c1,c2") - if err != nil { - t.Fatalf("DupSecOpt: %v", err) - } - for _, opt := range secopt { - con := strings.SplitN(opt, ":", 2) - if con[0] == "user" { - if con[1] != "system_u" { - t.Errorf("DupSecOpt Failed user incorrect") - } - continue - } - if con[0] == "role" { - if con[1] != "system_r" { - t.Errorf("DupSecOpt Failed role incorrect") - } - continue - } - if con[0] == "type" { - if con[1] != "container_t" { - t.Errorf("DupSecOpt Failed type incorrect") - } - continue - } - if con[0] == "level" { - if con[1] != "s0:c1,c2" { - t.Errorf("DupSecOpt Failed level incorrect") - } - continue - } - t.Errorf("DupSecOpt failed: invalid field %q", con[0]) - } - secopt = DisableSecOpt() - if secopt[0] != "disable" { - t.Errorf(`DisableSecOpt failed: want "disable", got %q`, secopt[0]) - } -} - -func TestSELinuxNoLevel(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - tlabel := "system_u:system_r:container_t" - dup, err := DupSecOpt(tlabel) - if err != nil { - t.Fatal(err) - } - - if len(dup) != 3 { - t.Errorf("DupSecOpt failed on non mls label: want 3, got %d", len(dup)) - } - con, err := NewContext(tlabel) - if err != nil { - t.Fatal(err) - } - if con.Get() != tlabel { - t.Errorf("NewContext and con.Get() failed on non mls label: want %q, got %q", tlabel, con.Get()) - } -} - -func TestSocketLabel(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - // Ensure the thread stays the same for duration of the test. - // Otherwise Go runtime can switch this to a different thread, - // which results in EACCES in call to SetSocketLabel. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - label := "system_u:object_r:container_t:s0:c1,c2" - if err := SetSocketLabel(label); err != nil { - t.Fatal(err) - } - nlabel, err := SocketLabel() - if err != nil { - t.Fatal(err) - } - if label != nlabel { - t.Errorf("SocketLabel %s != %s", nlabel, label) - } -} - -func TestKeyLabel(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - // Ensure the thread stays the same for duration of the test. - // Otherwise Go runtime can switch this to a different thread, - // which results in EACCES in call to SetKeyLabel. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - if unix.Getpid() != unix.Gettid() { - t.Skip(ErrNotTGLeader) - } - - label := "system_u:object_r:container_t:s0:c1,c2" - if err := SetKeyLabel(label); err != nil { - t.Fatal(err) - } - nlabel, err := KeyLabel() - if err != nil { - t.Fatal(err) - } - if label != nlabel { - t.Errorf("KeyLabel: want %q, got %q", label, nlabel) - } -} - -func BenchmarkContextGet(b *testing.B) { - ctx, err := NewContext("system_u:object_r:container_file_t:s0:c1022,c1023") - if err != nil { - b.Fatal(err) - } - str := "" - for i := 0; i < b.N; i++ { - str = ctx.get() - } - b.Log(str) -} - -func TestSELinux(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - // Ensure the thread stays the same for duration of the test. - // Otherwise Go runtime can switch this to a different thread, - // which results in EACCES in call to SetFSCreateLabel. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - var ( - err error - plabel, flabel string - ) - - plabel, flabel = ContainerLabels() - t.Log(plabel) - t.Log(flabel) - plabel, flabel = ContainerLabels() - t.Log(plabel) - t.Log(flabel) - ReleaseLabel(plabel) - - plabel, flabel = ContainerLabels() - t.Log(plabel) - t.Log(flabel) - ClearLabels() - t.Log("ClearLabels") - plabel, flabel = ContainerLabels() - t.Log(plabel) - t.Log(flabel) - ReleaseLabel(plabel) - - pid := os.Getpid() - t.Logf("PID:%d MCS:%s", pid, intToMcs(pid, 1023)) - err = SetFSCreateLabel("unconfined_u:unconfined_r:unconfined_t:s0") - if err != nil { - t.Fatal("SetFSCreateLabel failed:", err) - } - t.Log(FSCreateLabel()) - err = SetFSCreateLabel("") - if err != nil { - t.Fatal("SetFSCreateLabel failed:", err) - } - t.Log(FSCreateLabel()) - t.Log(PidLabel(1)) -} - -func TestSetEnforceMode(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - if os.Geteuid() != 0 { - t.Skip("root required, skipping") - } - - t.Log("Enforcing Mode:", EnforceMode()) - mode := DefaultEnforceMode() - t.Log("Default Enforce Mode:", mode) - defer func() { - _ = SetEnforceMode(mode) - }() - - if err := SetEnforceMode(Enforcing); err != nil { - t.Fatalf("setting selinux mode to enforcing failed: %v", err) - } - if err := SetEnforceMode(Permissive); err != nil { - t.Fatalf("setting selinux mode to permissive failed: %v", err) - } -} - -func TestCanonicalizeContext(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - con := "system_u:object_r:bin_t:s0:c1,c2,c3" - checkcon := "system_u:object_r:bin_t:s0:c1.c3" - newcon, err := CanonicalizeContext(con) - if err != nil { - t.Fatal(err) - } - if newcon != checkcon { - t.Fatalf("CanonicalizeContext(%s) returned %s expected %s", con, newcon, checkcon) - } - con = "system_u:object_r:bin_t:s0:c5,c2" - checkcon = "system_u:object_r:bin_t:s0:c2,c5" - newcon, err = CanonicalizeContext(con) - if err != nil { - t.Fatal(err) - } - if newcon != checkcon { - t.Fatalf("CanonicalizeContext(%s) returned %s expected %s", con, newcon, checkcon) - } -} - -func TestFindSELinuxfsInMountinfo(t *testing.T) { - //nolint:dupword // ignore duplicate words (sysfs sysfs) - const mountinfo = `18 62 0:17 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sysfs rw,seclabel -19 62 0:3 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw -20 62 0:5 / /dev rw,nosuid shared:2 - devtmpfs devtmpfs rw,seclabel,size=3995472k,nr_inodes=998868,mode=755 -21 18 0:16 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw -22 20 0:18 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw,seclabel -23 20 0:11 / /dev/pts rw,nosuid,noexec,relatime shared:4 - devpts devpts rw,seclabel,gid=5,mode=620,ptmxmode=000 -24 62 0:19 / /run rw,nosuid,nodev shared:23 - tmpfs tmpfs rw,seclabel,mode=755 -25 18 0:20 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:8 - tmpfs tmpfs ro,seclabel,mode=755 -26 25 0:21 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:9 - cgroup cgroup rw,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd -27 18 0:22 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:20 - pstore pstore rw -28 25 0:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,perf_event -29 25 0:24 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,devices -30 25 0:25 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:12 - cgroup cgroup rw,cpuacct,cpu -31 25 0:26 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,freezer -32 25 0:27 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,net_prio,net_cls -33 25 0:28 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset -34 25 0:29 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,memory -35 25 0:30 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,pids -36 25 0:31 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,hugetlb -37 25 0:32 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,blkio -59 18 0:33 / /sys/kernel/config rw,relatime shared:21 - configfs configfs rw -62 1 253:1 / / rw,relatime shared:1 - ext4 /dev/vda1 rw,seclabel,data=ordered -38 18 0:15 / /sys/fs/selinux rw,relatime shared:22 - selinuxfs selinuxfs rw -39 19 0:35 / /proc/sys/fs/binfmt_misc rw,relatime shared:24 - autofs systemd-1 rw,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=11601 -40 20 0:36 / /dev/hugepages rw,relatime shared:25 - hugetlbfs hugetlbfs rw,seclabel -41 20 0:14 / /dev/mqueue rw,relatime shared:26 - mqueue mqueue rw,seclabel -42 18 0:6 / /sys/kernel/debug rw,relatime shared:27 - debugfs debugfs rw -112 62 253:1 /var/lib/docker/plugins /var/lib/docker/plugins rw,relatime - ext4 /dev/vda1 rw,seclabel,data=ordered -115 62 253:1 /var/lib/docker/overlay2 /var/lib/docker/overlay2 rw,relatime - ext4 /dev/vda1 rw,seclabel,data=ordered -118 62 7:0 / /root/mnt rw,relatime shared:66 - ext4 /dev/loop0 rw,seclabel,data=ordered -121 115 0:38 / /var/lib/docker/overlay2/8cdbabf81bc89b14ea54eaf418c1922068f06917fff57e184aa26541ff291073/merged rw,relatime - overlay overlay rw,seclabel,lowerdir=/var/lib/docker/overlay2/l/CPD4XI7UD4GGTGSJVPQSHWZKTK:/var/lib/docker/overlay2/l/NQKORR3IS7KNQDER35AZECLH4Z,upperdir=/var/lib/docker/overlay2/8cdbabf81bc89b14ea54eaf418c1922068f06917fff57e184aa26541ff291073/diff,workdir=/var/lib/docker/overlay2/8cdbabf81bc89b14ea54eaf418c1922068f06917fff57e184aa26541ff291073/work -125 62 0:39 / /var/lib/docker/containers/5e3fce422957c291a5b502c2cf33d512fc1fcac424e4113136c808360e5b7215/shm rw,nosuid,nodev,noexec,relatime shared:68 - tmpfs shm rw,seclabel,size=65536k -186 24 0:3 / /run/docker/netns/0a08e7496c6d rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw -130 62 0:15 / /root/chroot/selinux rw,relatime shared:22 - selinuxfs selinuxfs rw -109 24 0:37 / /run/user/0 rw,nosuid,nodev,relatime shared:62 - tmpfs tmpfs rw,seclabel,size=801032k,mode=700 -` - s := bufio.NewScanner(bytes.NewBuffer([]byte(mountinfo))) - for _, expected := range []string{"/sys/fs/selinux", "/root/chroot/selinux", ""} { - mnt := findSELinuxfsMount(s) - t.Logf("found %q", mnt) - if mnt != expected { - t.Fatalf("expected %q, got %q", expected, mnt) - } - } -} - -func TestSecurityCheckContext(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - // check with valid context - context, err := CurrentLabel() - if err != nil { - t.Fatalf("CurrentLabel() error: %v", err) - } - if context != "" { - t.Logf("SecurityCheckContext(%q)", context) - err = SecurityCheckContext(context) - if err != nil { - t.Errorf("SecurityCheckContext(%q) error: %v", context, err) - } - } - - context = "not-syntactically-valid" - err = SecurityCheckContext(context) - if err == nil { - t.Errorf("SecurityCheckContext(%q) succeeded, expected to fail", context) - } -} - -func TestClassIndex(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - idx, err := ClassIndex("process") - if err != nil { - t.Errorf("Classindex error: %v", err) - } - // Every known policy has process as index 2, but it isn't guaranteed - if idx != 2 { - t.Errorf("ClassIndex unexpected answer %d, possibly not reference policy", idx) - } - - _, err = ClassIndex("foobar") - if err == nil { - t.Errorf("ClassIndex(\"foobar\") succeeded, expected to fail:") - } -} - -func TestComputeCreateContext(t *testing.T) { - if !GetEnabled() { - t.Skip("SELinux not enabled, skipping.") - } - - // This may or may not be in the loaded policy but any refpolicy based policy should have it - init := "system_u:system_r:init_t:s0" - tmp := "system_u:object_r:tmp_t:s0" - file := "file" - t.Logf("ComputeCreateContext(%s, %s, %s)", init, tmp, file) - context, err := ComputeCreateContext(init, tmp, file) - if err != nil { - t.Errorf("ComputeCreateContext error: %v", err) - } - if context != "system_u:object_r:init_tmp_t:s0" { - t.Errorf("ComputeCreateContext unexpected answer %s, possibly not reference policy", context) - } - - badcon := "badcon" - process := "process" - // Test to ensure that a bad context returns an error - t.Logf("ComputeCreateContext(%s, %s, %s)", badcon, tmp, process) - _, err = ComputeCreateContext(badcon, tmp, process) - if err == nil { - t.Errorf("ComputeCreateContext(%s, %s, %s) succeeded, expected failure", badcon, tmp, process) - } -} - -func TestGlbLub(t *testing.T) { - tests := []struct { - expectedErr error - sourceRange string - targetRange string - expectedRange string - }{ - { - sourceRange: "s0:c0.c100-s10:c0.c150", - targetRange: "s5:c50.c100-s15:c0.c149", - expectedRange: "s5:c50.c100-s10:c0.c149", - }, - { - sourceRange: "s5:c50.c100-s15:c0.c149", - targetRange: "s0:c0.c100-s10:c0.c150", - expectedRange: "s5:c50.c100-s10:c0.c149", - }, - { - sourceRange: "s0:c0.c100-s10:c0.c150", - targetRange: "s0", - expectedRange: "s0", - }, - { - sourceRange: "s6:c0.c1023", - targetRange: "s6:c0,c2,c11,c201.c429,c431.c511", - expectedRange: "s6:c0,c2,c11,c201.c429,c431.c511", - }, - { - sourceRange: "s0-s15:c0.c1023", - targetRange: "s6:c0,c2,c11,c201.c429,c431.c511", - expectedRange: "s6-s6:c0,c2,c11,c201.c429,c431.c511", - }, - { - sourceRange: "s0:c0.c100,c125,c140,c150-s10", - targetRange: "s4:c0.c50,c140", - expectedRange: "s4:c0.c50,c140-s4", - }, - { - sourceRange: "s5:c512.c550,c552.c1023-s5:c0.c550,c552.c1023", - targetRange: "s5:c512.c550,c553.c1023-s5:c0,c1,c4,c5,c6,c512.c550,c553.c1023", - expectedRange: "s5:c512.c550,c553.c1023-s5:c0,c1,c4.c6,c512.c550,c553.c1023", - }, - { - sourceRange: "s5:c512.c540,c542,c543,c552.c1023-s5:c0.c550,c552.c1023", - targetRange: "s5:c512.c550,c553.c1023-s5:c0,c1,c4,c5,c6,c512.c550,c553.c1023", - expectedRange: "s5:c512.c540,c542,c543,c553.c1023-s5:c0,c1,c4.c6,c512.c550,c553.c1023", - }, - { - sourceRange: "s5:c50.c100-s15:c0.c149", - targetRange: "s5:c512.c550,c552.c1023-s5:c0.c550,c552.c1023", - expectedRange: "s5-s5:c0.c149", - }, - { - sourceRange: "s5-s15", - targetRange: "s6-s7", - expectedRange: "s6-s7", - }, - { - sourceRange: "s5:c50.c100-s15:c0.c149", - targetRange: "s4-s4:c0.c1023", - expectedErr: ErrIncomparable, - }, - { - sourceRange: "s4-s4:c0.c1023", - targetRange: "s5:c50.c100-s15:c0.c149", - expectedErr: ErrIncomparable, - }, - { - sourceRange: "s4-s4:c0.c1023.c10000", - targetRange: "s5:c50.c100-s15:c0.c149", - expectedErr: strconv.ErrSyntax, - }, - { - sourceRange: "s4-s4:c0.c1023.c10000-s4", - targetRange: "s5:c50.c100-s15:c0.c149-s5", - expectedErr: strconv.ErrSyntax, - }, - { - sourceRange: "4-4", - targetRange: "s5:c50.c100-s15:c0.c149", - expectedErr: ErrLevelSyntax, - }, - { - sourceRange: "t4-t4", - targetRange: "s5:c50.c100-s15:c0.c149", - expectedErr: ErrLevelSyntax, - }, - { - sourceRange: "s5:x50.x100-s15:c0.c149", - targetRange: "s5:c50.c100-s15:c0.c149", - expectedErr: ErrLevelSyntax, - }, - } - - for _, tt := range tests { - got, err := CalculateGlbLub(tt.sourceRange, tt.targetRange) - if !errors.Is(err, tt.expectedErr) { - // Go 1.13 strconv errors are not unwrappable, - // so do that manually. - // TODO remove this once we stop supporting Go 1.13. - var numErr *strconv.NumError - if errors.As(err, &numErr) && numErr.Err == tt.expectedErr { //nolint:errorlint // see above - continue - } - t.Fatalf("want %q got %q: src: %q tgt: %q", tt.expectedErr, err, tt.sourceRange, tt.targetRange) - } - - if got != tt.expectedRange { - t.Errorf("want %q got %q", tt.expectedRange, got) - } - } -} - -func TestContextWithLevel(t *testing.T) { - want := "bob:sysadm_r:sysadm_t:SystemLow-SystemHigh" - - goodDefaultBuff := ` -foo_r:foo_t:s0 sysadm_r:sysadm_t:s0 -staff_r:staff_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0 -` - - verifier := func(con string) error { - if con != want { - return fmt.Errorf("invalid context %s", con) - } - - return nil - } - - tests := []struct { - name, userBuff, defaultBuff string - }{ - { - name: "match exists in user context file", - userBuff: `# COMMENT -foo_r:foo_t:s0 sysadm_r:sysadm_t:s0 - -staff_r:staff_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0 -`, - defaultBuff: goodDefaultBuff, - }, - { - name: "match exists in default context file, but not in user file", - userBuff: `# COMMENT -foo_r:foo_t:s0 sysadm_r:sysadm_t:s0 -fake_r:fake_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0 -`, - defaultBuff: goodDefaultBuff, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := defaultSECtx{ - user: "bob", - level: "SystemLow-SystemHigh", - scon: "system_u:staff_r:staff_t:s0", - userRdr: bytes.NewBufferString(tt.userBuff), - defaultRdr: bytes.NewBufferString(tt.defaultBuff), - verifier: verifier, - } - - got, err := getDefaultContextFromReaders(&c) - if err != nil { - t.Fatalf("err should not exist but is: %v", err) - } - - if got != want { - t.Fatalf("got context: %q but expected %q", got, want) - } - }) - } - - t.Run("no match in user or default context files", func(t *testing.T) { - badUserBuff := "" - - badDefaultBuff := ` - foo_r:foo_t:s0 sysadm_r:sysadm_t:s0 - dne_r:dne_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0 - ` - c := defaultSECtx{ - user: "bob", - level: "SystemLow-SystemHigh", - scon: "system_u:staff_r:staff_t:s0", - userRdr: bytes.NewBufferString(badUserBuff), - defaultRdr: bytes.NewBufferString(badDefaultBuff), - verifier: verifier, - } - - _, err := getDefaultContextFromReaders(&c) - if err == nil { - t.Fatalf("err was expected") - } - }) -} - -func BenchmarkChcon(b *testing.B) { - file, err := filepath.Abs(os.Args[0]) - if err != nil { - b.Fatalf("filepath.Abs: %v", err) - } - dir := filepath.Dir(file) - con, err := FileLabel(file) - if err != nil { - b.Fatalf("FileLabel(%q): %v", file, err) - } - b.Logf("Chcon(%q, %q)", dir, con) - b.ResetTimer() - for n := 0; n < b.N; n++ { - if err := Chcon(dir, con, true); err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkCurrentLabel(b *testing.B) { - var ( - l string - err error - ) - for n := 0; n < b.N; n++ { - l, err = CurrentLabel() - if err != nil { - b.Fatal(err) - } - } - b.Log(l) -} - -func BenchmarkReadConfig(b *testing.B) { - str := "" - for n := 0; n < b.N; n++ { - str = readConfig(selinuxTypeTag) - } - b.Log(str) -} - -func BenchmarkLoadLabels(b *testing.B) { - for n := 0; n < b.N; n++ { - loadLabels() - } -} diff --git a/internal/third_party/selinux/go-selinux/selinux_stub.go b/internal/third_party/selinux/go-selinux/selinux_stub.go deleted file mode 100644 index 267921239..000000000 --- a/internal/third_party/selinux/go-selinux/selinux_stub.go +++ /dev/null @@ -1,159 +0,0 @@ -//go:build !linux -// +build !linux - -package selinux - -func attrPath(string) string { - return "" -} - -func readConThreadSelf(string) (string, error) { - return "", nil -} - -func writeConThreadSelf(string, string) error { - return nil -} - -func setDisabled() {} - -func getEnabled() bool { - return false -} - -func classIndex(string) (int, error) { - return -1, nil -} - -func setFileLabel(string, string) error { - return nil -} - -func lSetFileLabel(string, string) error { - return nil -} - -func fileLabel(string) (string, error) { - return "", nil -} - -func lFileLabel(string) (string, error) { - return "", nil -} - -func setFSCreateLabel(string) error { - return nil -} - -func fsCreateLabel() (string, error) { - return "", nil -} - -func currentLabel() (string, error) { - return "", nil -} - -func pidLabel(int) (string, error) { - return "", nil -} - -func execLabel() (string, error) { - return "", nil -} - -func canonicalizeContext(string) (string, error) { - return "", nil -} - -func computeCreateContext(string, string, string) (string, error) { - return "", nil -} - -func calculateGlbLub(string, string) (string, error) { - return "", nil -} - -func peerLabel(uintptr) (string, error) { - return "", nil -} - -func setKeyLabel(string) error { - return nil -} - -func keyLabel() (string, error) { - return "", nil -} - -func (c Context) get() string { - return "" -} - -func newContext(string) (Context, error) { - return Context{}, nil -} - -func clearLabels() { -} - -func reserveLabel(string) { -} - -func isMLSEnabled() bool { - return false -} - -func enforceMode() int { - return Disabled -} - -func setEnforceMode(int) error { - return nil -} - -func defaultEnforceMode() int { - return Disabled -} - -func releaseLabel(string) { -} - -func roFileLabel() string { - return "" -} - -func kvmContainerLabels() (string, string) { - return "", "" -} - -func initContainerLabels() (string, string) { - return "", "" -} - -func containerLabels() (string, string) { - return "", "" -} - -func securityCheckContext(string) error { - return nil -} - -func copyLevel(string, string) (string, error) { - return "", nil -} - -func chcon(string, string, bool) error { - return nil -} - -func dupSecOpt(string) ([]string, error) { - return nil, nil -} - -func getDefaultContextWithLevel(string, string, string) (string, error) { - return "", nil -} - -func label(_ string) string { - return "" -} diff --git a/internal/third_party/selinux/go-selinux/selinux_stub_test.go b/internal/third_party/selinux/go-selinux/selinux_stub_test.go deleted file mode 100644 index 19ea636a4..000000000 --- a/internal/third_party/selinux/go-selinux/selinux_stub_test.go +++ /dev/null @@ -1,127 +0,0 @@ -//go:build !linux -// +build !linux - -package selinux - -import ( - "testing" -) - -const testLabel = "foobar" - -func TestSELinuxStubs(t *testing.T) { - if GetEnabled() { - t.Error("SELinux enabled on non-linux.") - } - - tmpDir := t.TempDir() - if _, err := FileLabel(tmpDir); err != nil { - t.Error(err) - } - - if err := SetFileLabel(tmpDir, testLabel); err != nil { - t.Error(err) - } - - if _, err := LfileLabel(tmpDir); err != nil { - t.Error(err) - } - if err := LsetFileLabel(tmpDir, testLabel); err != nil { - t.Error(err) - } - - if err := SetFSCreateLabel(testLabel); err != nil { - t.Error(err) - } - - if _, err := FSCreateLabel(); err != nil { - t.Error(err) - } - if _, err := CurrentLabel(); err != nil { - t.Error(err) - } - - if _, err := PidLabel(0); err != nil { - t.Error(err) - } - - ClearLabels() - - ReserveLabel(testLabel) - ReleaseLabel(testLabel) - if _, err := DupSecOpt(testLabel); err != nil { - t.Error(err) - } - if v := DisableSecOpt(); len(v) != 1 || v[0] != "disable" { - t.Errorf(`expected "disabled", got %v`, v) - } - SetDisabled() - if enabled := GetEnabled(); enabled { - t.Error("Should not be enabled") - } - if err := SetExecLabel(testLabel); err != nil { - t.Error(err) - } - if err := SetTaskLabel(testLabel); err != nil { - t.Error(err) - } - if _, err := ExecLabel(); err != nil { - t.Error(err) - } - if _, err := CanonicalizeContext(testLabel); err != nil { - t.Error(err) - } - if _, err := ComputeCreateContext("foo", "bar", testLabel); err != nil { - t.Error(err) - } - if err := SetSocketLabel(testLabel); err != nil { - t.Error(err) - } - if _, err := ClassIndex(testLabel); err != nil { - t.Error(err) - } - if _, err := SocketLabel(); err != nil { - t.Error(err) - } - if _, err := PeerLabel(0); err != nil { - t.Error(err) - } - if err := SetKeyLabel(testLabel); err != nil { - t.Error(err) - } - if _, err := KeyLabel(); err != nil { - t.Error(err) - } - if err := SetExecLabel(testLabel); err != nil { - t.Error(err) - } - if _, err := ExecLabel(); err != nil { - t.Error(err) - } - con, err := NewContext(testLabel) - if err != nil { - t.Error(err) - } - con.Get() - if err = SetEnforceMode(1); err != nil { - t.Error(err) - } - if v := DefaultEnforceMode(); v != Disabled { - t.Errorf("expected %d, got %d", Disabled, v) - } - if v := EnforceMode(); v != Disabled { - t.Errorf("expected %d, got %d", Disabled, v) - } - if v := ROFileLabel(); v != "" { - t.Errorf(`expected "", got %q`, v) - } - if processLbl, fileLbl := ContainerLabels(); processLbl != "" || fileLbl != "" { - t.Errorf(`expected fileLbl="", fileLbl="" got processLbl=%q, fileLbl=%q`, processLbl, fileLbl) - } - if err = SecurityCheckContext(testLabel); err != nil { - t.Error(err) - } - if _, err = CopyLevel("foo", "bar"); err != nil { - t.Error(err) - } -} diff --git a/internal/third_party/selinux/go-selinux/xattrs_linux.go b/internal/third_party/selinux/go-selinux/xattrs_linux.go deleted file mode 100644 index 559c85107..000000000 --- a/internal/third_party/selinux/go-selinux/xattrs_linux.go +++ /dev/null @@ -1,71 +0,0 @@ -package selinux - -import ( - "golang.org/x/sys/unix" -) - -// lgetxattr returns a []byte slice containing the value of -// an extended attribute attr set for path. -func lgetxattr(path, attr string) ([]byte, error) { - // Start with a 128 length byte array - dest := make([]byte, 128) - sz, errno := doLgetxattr(path, attr, dest) - for errno == unix.ERANGE { //nolint:errorlint // unix errors are bare - // Buffer too small, use zero-sized buffer to get the actual size - sz, errno = doLgetxattr(path, attr, []byte{}) - if errno != nil { - return nil, errno - } - - dest = make([]byte, sz) - sz, errno = doLgetxattr(path, attr, dest) - } - if errno != nil { - return nil, errno - } - - return dest[:sz], nil -} - -// doLgetxattr is a wrapper that retries on EINTR -func doLgetxattr(path, attr string, dest []byte) (int, error) { - for { - sz, err := unix.Lgetxattr(path, attr, dest) - if err != unix.EINTR { - return sz, err - } - } -} - -// getxattr returns a []byte slice containing the value of -// an extended attribute attr set for path. -func getxattr(path, attr string) ([]byte, error) { - // Start with a 128 length byte array - dest := make([]byte, 128) - sz, errno := dogetxattr(path, attr, dest) - for errno == unix.ERANGE { //nolint:errorlint // unix errors are bare - // Buffer too small, use zero-sized buffer to get the actual size - sz, errno = dogetxattr(path, attr, []byte{}) - if errno != nil { - return nil, errno - } - - dest = make([]byte, sz) - sz, errno = dogetxattr(path, attr, dest) - } - if errno != nil { - return nil, errno - } - - return dest[:sz], nil -} - -// dogetxattr is a wrapper that retries on EINTR -func dogetxattr(path, attr string, dest []byte) (int, error) { - for { - sz, err := unix.Getxattr(path, attr, dest) - if err != unix.EINTR { - return sz, err - } - } -} diff --git a/internal/third_party/selinux/go.mod b/internal/third_party/selinux/go.mod deleted file mode 100644 index 24d3261a9..000000000 --- a/internal/third_party/selinux/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module github.com/opencontainers/selinux - -go 1.19 - -require ( - github.com/cyphar/filepath-securejoin v0.5.0 - golang.org/x/sys v0.18.0 -) diff --git a/internal/third_party/selinux/go.sum b/internal/third_party/selinux/go.sum deleted file mode 100644 index b9ae09877..000000000 --- a/internal/third_party/selinux/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= -github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/third_party/selinux/pkg/pwalk/README.md b/internal/third_party/selinux/pkg/pwalk/README.md deleted file mode 100644 index a060ad364..000000000 --- a/internal/third_party/selinux/pkg/pwalk/README.md +++ /dev/null @@ -1,52 +0,0 @@ -## pwalk: parallel implementation of filepath.Walk - -This is a wrapper for [filepath.Walk](https://pkg.go.dev/path/filepath?tab=doc#Walk) -which may speed it up by calling multiple callback functions (WalkFunc) in parallel, -utilizing goroutines. - -By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks. -This can be changed by using WalkN function which has the additional -parameter, specifying the number of goroutines (concurrency). - -### pwalk vs pwalkdir - -This package is deprecated in favor of -[pwalkdir](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir), -which is faster, but requires at least Go 1.16. - -### Caveats - -Please note the following limitations of this code: - -* Unlike filepath.Walk, the order of calls is non-deterministic; - -* Only primitive error handling is supported: - - * filepath.SkipDir is not supported; - - * ErrNotExist errors from filepath.Walk are silently ignored for any path - except the top directory (Walk argument); any other error is returned to - the caller of Walk; - - * no errors are ever passed to WalkFunc; - - * once any error is returned from any WalkFunc instance, no more new calls - to WalkFunc are made, and the error is returned to the caller of Walk; - - * if more than one walkFunc instance will return an error, only one - of such errors will be propagated and returned by Walk, others - will be silently discarded. - -### Documentation - -For the official documentation, see -https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalk?tab=doc - -### Benchmarks - -For a WalkFunc that consists solely of the return statement, this -implementation is about 10% slower than the standard library's -filepath.Walk. - -Otherwise (if a WalkFunc is doing something) this is usually faster, -except when the WalkN(..., 1) is used. diff --git a/internal/third_party/selinux/pkg/pwalk/pwalk.go b/internal/third_party/selinux/pkg/pwalk/pwalk.go deleted file mode 100644 index 686c8bac3..000000000 --- a/internal/third_party/selinux/pkg/pwalk/pwalk.go +++ /dev/null @@ -1,131 +0,0 @@ -package pwalk - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "sync" -) - -// WalkFunc is the type of the function called by Walk to visit each -// file or directory. It is an alias for [filepath.WalkFunc]. -// -// Deprecated: use [github.com/opencontainers/selinux/pkg/pwalkdir] and [fs.WalkDirFunc]. -type WalkFunc = filepath.WalkFunc - -// Walk is a wrapper for filepath.Walk which can call multiple walkFn -// in parallel, allowing to handle each item concurrently. A maximum of -// twice the runtime.NumCPU() walkFn will be called at any one time. -// If you want to change the maximum, use WalkN instead. -// -// The order of calls is non-deterministic. -// -// Note that this implementation only supports primitive error handling: -// -// - no errors are ever passed to walkFn; -// -// - once a walkFn returns any error, all further processing stops -// and the error is returned to the caller of Walk; -// -// - filepath.SkipDir is not supported; -// -// - if more than one walkFn instance will return an error, only one -// of such errors will be propagated and returned by Walk, others -// will be silently discarded. -// -// Deprecated: use [github.com/opencontainers/selinux/pkg/pwalkdir.Walk] -func Walk(root string, walkFn WalkFunc) error { - return WalkN(root, walkFn, runtime.NumCPU()*2) -} - -// WalkN is a wrapper for filepath.Walk which can call multiple walkFn -// in parallel, allowing to handle each item concurrently. A maximum of -// num walkFn will be called at any one time. -// -// Please see Walk documentation for caveats of using this function. -// -// Deprecated: use [github.com/opencontainers/selinux/pkg/pwalkdir.WalkN] -func WalkN(root string, walkFn WalkFunc, num int) error { - // make sure limit is sensible - if num < 1 { - return fmt.Errorf("walk(%q): num must be > 0", root) - } - - files := make(chan *walkArgs, 2*num) - errCh := make(chan error, 1) // get the first error, ignore others - - // Start walking a tree asap - var ( - err error - wg sync.WaitGroup - - rootLen = len(root) - rootEntry *walkArgs - ) - wg.Add(1) - go func() { - err = filepath.Walk(root, func(p string, info os.FileInfo, err error) error { - if err != nil { - // Walking a file tree can race with removal, - // so ignore ENOENT, except for root. - // https://github.com/opencontainers/selinux/issues/199. - if errors.Is(err, os.ErrNotExist) && len(p) != rootLen { - return nil - } - - close(files) - return err - } - if len(p) == rootLen { - // Root entry is processed separately below. - rootEntry = &walkArgs{path: p, info: &info} - return nil - } - // add a file to the queue unless a callback sent an error - select { - case e := <-errCh: - close(files) - return e - default: - files <- &walkArgs{path: p, info: &info} - return nil - } - }) - if err == nil { - close(files) - } - wg.Done() - }() - - wg.Add(num) - for i := 0; i < num; i++ { - go func() { - for file := range files { - if e := walkFn(file.path, *file.info, nil); e != nil { - select { - case errCh <- e: // sent ok - default: // buffer full - } - } - } - wg.Done() - }() - } - - wg.Wait() - - if err == nil { - err = walkFn(rootEntry.path, *rootEntry.info, nil) - } - - return err -} - -// walkArgs holds the arguments that were passed to the Walk or WalkN -// functions. -type walkArgs struct { - info *os.FileInfo - path string -} diff --git a/internal/third_party/selinux/pkg/pwalk/pwalk_test.go b/internal/third_party/selinux/pkg/pwalk/pwalk_test.go deleted file mode 100644 index 9cca3b6b1..000000000 --- a/internal/third_party/selinux/pkg/pwalk/pwalk_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package pwalk - -import ( - "errors" - "math/rand" - "os" - "path/filepath" - "runtime" - "sync/atomic" - "testing" - "time" -) - -func TestWalk(t *testing.T) { - var ac atomic.Uint32 - concurrency := runtime.NumCPU() * 2 - - dir, total := prepareTestSet(t, 3, 2, 1) - - err := WalkN(dir, - func(_ string, _ os.FileInfo, _ error) error { - ac.Add(1) - return nil - }, - concurrency) - if err != nil { - t.Errorf("Walk failed: %v", err) - } - count := ac.Load() - if count != total { - t.Errorf("File count mismatch: found %d, expected %d", count, total) - } - - t.Logf("concurrency: %d, files found: %d", concurrency, count) -} - -func TestWalkTopLevelErrNotExistNotIgnored(t *testing.T) { - if WalkN("non-existent-directory", cbEmpty, 8) == nil { - t.Fatal("expected ErrNotExist, got nil") - } -} - -// https://github.com/opencontainers/selinux/issues/199 -func TestWalkRaceWithRemoval(t *testing.T) { - var ac atomic.Uint32 - concurrency := runtime.NumCPU() * 2 - // This test is still on a best-effort basis, meaning it can still pass - // when there is a bug in the code, but the larger the test set is, the - // higher the probability that this test fails (without a fix). - // - // With this set (4, 5, 6), and the fix commented out, it fails - // 100 out of 100 runs on my machine. - dir, total := prepareTestSet(t, 4, 5, 6) - - // Race walk with removal. - go os.RemoveAll(dir) - err := WalkN(dir, - func(_ string, _ os.FileInfo, _ error) error { - ac.Add(1) - return nil - }, - concurrency) - count := int(ac.Load()) - t.Logf("found %d of %d files", count, total) - if err != nil { - t.Fatalf("expected nil, got %v", err) - } -} - -func TestWalkDirManyErrors(t *testing.T) { - var ac atomic.Uint32 - - dir, total := prepareTestSet(t, 3, 3, 2) - - maxFiles := total / 2 - e42 := errors.New("42") - err := Walk(dir, - func(_ string, _ os.FileInfo, _ error) error { - if ac.Add(1) > maxFiles { - return e42 - } - return nil - }) - count := ac.Load() - t.Logf("found %d of %d files", count, total) - - if err == nil { - t.Errorf("Walk succeeded, but error is expected") - if count != total { - t.Errorf("File count mismatch: found %d, expected %d", count, total) - } - } -} - -func makeManyDirs(prefix string, levels, dirs, files int) (count uint32, err error) { - for d := 0; d < dirs; d++ { - var dir string - dir, err = os.MkdirTemp(prefix, "d-") - if err != nil { - return count, err - } - count++ - for f := 0; f < files; f++ { - var fi *os.File - fi, err = os.CreateTemp(dir, "f-") - if err != nil { - return count, err - } - _ = fi.Close() - count++ - } - if levels == 0 { - continue - } - var c uint32 - if c, err = makeManyDirs(dir, levels-1, dirs, files); err != nil { - return count, err - } - count += c - } - - return count, err -} - -// prepareTestSet() creates a directory tree of shallow files, -// to be used for testing or benchmarking. -// -// Total dirs: dirs^levels + dirs^(levels-1) + ... + dirs^1 -// Total files: total_dirs * files -func prepareTestSet(tb testing.TB, levels, dirs, files int) (dir string, total uint32) { - tb.Helper() - var err error - - dir = tb.TempDir() - total, err = makeManyDirs(dir, levels, dirs, files) - if err != nil { - tb.Fatal(err) - } - total++ // this dir - - return dir, total -} - -type walkerFunc func(root string, walkFn WalkFunc) error - -func genWalkN(n int) walkerFunc { - return func(root string, walkFn WalkFunc) error { - return WalkN(root, walkFn, n) - } -} - -func BenchmarkWalk(b *testing.B) { - const ( - levels = 5 // how deep - dirs = 3 // dirs on each levels - files = 8 // files on each levels - ) - - benchmarks := []struct { - walk filepath.WalkFunc - name string - }{ - {name: "Empty", walk: cbEmpty}, - {name: "ReadFile", walk: cbReadFile}, - {name: "ChownChmod", walk: cbChownChmod}, - {name: "RandomSleep", walk: cbRandomSleep}, - } - - walkers := []struct { - walker walkerFunc - name string - }{ - {name: "filepath.Walk", walker: filepath.Walk}, - {name: "pwalk.Walk", walker: Walk}, - // test WalkN with various values of N - {name: "pwalk.Walk1", walker: genWalkN(1)}, - {name: "pwalk.Walk2", walker: genWalkN(2)}, - {name: "pwalk.Walk4", walker: genWalkN(4)}, - {name: "pwalk.Walk8", walker: genWalkN(8)}, - {name: "pwalk.Walk16", walker: genWalkN(16)}, - {name: "pwalk.Walk32", walker: genWalkN(32)}, - {name: "pwalk.Walk64", walker: genWalkN(64)}, - {name: "pwalk.Walk128", walker: genWalkN(128)}, - {name: "pwalk.Walk256", walker: genWalkN(256)}, - } - - dir, total := prepareTestSet(b, levels, dirs, files) - b.Logf("dataset: %d levels x %d dirs x %d files, total entries: %d", levels, dirs, files, total) - - for _, bm := range benchmarks { - for _, w := range walkers { - walker := w.walker - walkFn := bm.walk - // preheat - if err := w.walker(dir, bm.walk); err != nil { - b.Errorf("walk failed: %v", err) - } - // benchmark - b.Run(bm.name+"/"+w.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - if err := walker(dir, walkFn); err != nil { - b.Errorf("walk failed: %v", err) - } - } - }) - } - } -} - -func cbEmpty(_ string, _ os.FileInfo, _ error) error { - return nil -} - -func cbChownChmod(path string, info os.FileInfo, _ error) error { - _ = os.Chown(path, 0, 0) - mode := os.FileMode(0o644) - if info.Mode().IsDir() { - mode = os.FileMode(0o755) - } - _ = os.Chmod(path, mode) - - return nil -} - -func cbReadFile(path string, info os.FileInfo, _ error) error { - var err error - if info.Mode().IsRegular() { - _, err = os.ReadFile(path) - } - return err -} - -func cbRandomSleep(_ string, _ os.FileInfo, _ error) error { - time.Sleep(time.Duration(rand.Intn(500)) * time.Microsecond) //nolint:gosec // ignore G404: Use of weak random number generator - return nil -} diff --git a/internal/third_party/selinux/pkg/pwalkdir/README.md b/internal/third_party/selinux/pkg/pwalkdir/README.md deleted file mode 100644 index b827e7dd7..000000000 --- a/internal/third_party/selinux/pkg/pwalkdir/README.md +++ /dev/null @@ -1,56 +0,0 @@ -## pwalkdir: parallel implementation of filepath.WalkDir - -This is a wrapper for [filepath.WalkDir](https://pkg.go.dev/path/filepath#WalkDir) -which may speed it up by calling multiple callback functions (WalkDirFunc) -in parallel, utilizing goroutines. - -By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks. -This can be changed by using WalkN function which has the additional -parameter, specifying the number of goroutines (concurrency). - -### pwalk vs pwalkdir - -This package is very similar to -[pwalk](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir), -but utilizes `filepath.WalkDir` (added to Go 1.16), which does not call stat(2) -on every entry and is therefore faster (up to 3x, depending on usage scenario). - -Users who are OK with requiring Go 1.16+ should switch to this -implementation. - -### Caveats - -Please note the following limitations of this code: - -* Unlike filepath.WalkDir, the order of calls is non-deterministic; - -* Only primitive error handling is supported: - - * fs.SkipDir is not supported; - - * ErrNotExist errors from filepath.WalkDir are silently ignored for any path - except the top directory (WalkDir argument); any other error is returned to - the caller of WalkDir; - - * once any error is returned from any walkDirFunc instance, no more calls - to WalkDirFunc are made, and the error is returned to the caller of WalkDir; - - * if more than one WalkDirFunc instance will return an error, only one - of such errors will be propagated to and returned by WalkDir, others - will be silently discarded. - -### Documentation - -For the official documentation, see -https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir - -### Benchmarks - -For a WalkDirFunc that consists solely of the return statement, this -implementation is about 15% slower than the standard library's -filepath.WalkDir. - -Otherwise (if a WalkDirFunc is actually doing something) this is usually -faster, except when the WalkDirN(..., 1) is used. Run `go test -bench .` -to see how different operations can benefit from it, as well as how the -level of parallelism affects the speed. diff --git a/internal/third_party/selinux/pkg/pwalkdir/pwalkdir.go b/internal/third_party/selinux/pkg/pwalkdir/pwalkdir.go deleted file mode 100644 index 5d2d09a29..000000000 --- a/internal/third_party/selinux/pkg/pwalkdir/pwalkdir.go +++ /dev/null @@ -1,123 +0,0 @@ -//go:build go1.16 -// +build go1.16 - -package pwalkdir - -import ( - "errors" - "fmt" - "io/fs" - "path/filepath" - "runtime" - "sync" -) - -// Walk is a wrapper for filepath.WalkDir which can call multiple walkFn -// in parallel, allowing to handle each item concurrently. A maximum of -// twice the runtime.NumCPU() walkFn will be called at any one time. -// If you want to change the maximum, use WalkN instead. -// -// The order of calls is non-deterministic. -// -// Note that this implementation only supports primitive error handling: -// -// - no errors are ever passed to walkFn; -// -// - once a walkFn returns any error, all further processing stops -// and the error is returned to the caller of Walk; -// -// - filepath.SkipDir is not supported; -// -// - if more than one walkFn instance will return an error, only one -// of such errors will be propagated and returned by Walk, others -// will be silently discarded. -func Walk(root string, walkFn fs.WalkDirFunc) error { - return WalkN(root, walkFn, runtime.NumCPU()*2) -} - -// WalkN is a wrapper for filepath.WalkDir which can call multiple walkFn -// in parallel, allowing to handle each item concurrently. A maximum of -// num walkFn will be called at any one time. -// -// Please see Walk documentation for caveats of using this function. -func WalkN(root string, walkFn fs.WalkDirFunc, num int) error { - // make sure limit is sensible - if num < 1 { - return fmt.Errorf("walk(%q): num must be > 0", root) - } - - files := make(chan *walkArgs, 2*num) - errCh := make(chan error, 1) // Get the first error, ignore others. - - // Start walking a tree asap. - var ( - err error - wg sync.WaitGroup - - rootLen = len(root) - rootEntry *walkArgs - ) - wg.Add(1) - go func() { - err = filepath.WalkDir(root, func(p string, entry fs.DirEntry, err error) error { - if err != nil { - // Walking a file tree can race with removal, - // so ignore ENOENT, except for root. - // https://github.com/opencontainers/selinux/issues/199. - if errors.Is(err, fs.ErrNotExist) && len(p) != rootLen { - return nil - } - close(files) - return err - } - if len(p) == rootLen { - // Root entry is processed separately below. - rootEntry = &walkArgs{path: p, entry: entry} - return nil - } - // Add a file to the queue unless a callback sent an error. - select { - case e := <-errCh: - close(files) - return e - default: - files <- &walkArgs{path: p, entry: entry} - return nil - } - }) - if err == nil { - close(files) - } - wg.Done() - }() - - wg.Add(num) - for i := 0; i < num; i++ { - go func() { - for file := range files { - if e := walkFn(file.path, file.entry, nil); e != nil { - select { - case errCh <- e: // sent ok - default: // buffer full - } - } - } - wg.Done() - }() - } - - wg.Wait() - - if err == nil { - err = walkFn(rootEntry.path, rootEntry.entry, nil) - } - - return err -} - -// walkArgs holds the arguments that were passed to the Walk or WalkN -// functions. -type walkArgs struct { - entry fs.DirEntry - path string -} diff --git a/internal/third_party/selinux/pkg/pwalkdir/pwalkdir_test.go b/internal/third_party/selinux/pkg/pwalkdir/pwalkdir_test.go deleted file mode 100644 index e66a80d1a..000000000 --- a/internal/third_party/selinux/pkg/pwalkdir/pwalkdir_test.go +++ /dev/null @@ -1,239 +0,0 @@ -//go:build go1.16 -// +build go1.16 - -package pwalkdir - -import ( - "errors" - "io/fs" - "math/rand" - "os" - "path/filepath" - "runtime" - "sync/atomic" - "testing" - "time" -) - -func TestWalkDir(t *testing.T) { - var ac atomic.Uint32 - concurrency := runtime.NumCPU() * 2 - dir, total := prepareTestSet(t, 3, 2, 1) - - err := WalkN(dir, - func(_ string, _ fs.DirEntry, _ error) error { - ac.Add(1) - return nil - }, - concurrency) - if err != nil { - t.Errorf("Walk failed: %v", err) - } - count := ac.Load() - if count != total { - t.Errorf("File count mismatch: found %d, expected %d", count, total) - } - - t.Logf("concurrency: %d, files found: %d", concurrency, count) -} - -func TestWalkDirTopLevelErrNotExistNotIgnored(t *testing.T) { - err := WalkN("non-existent-directory", cbEmpty, 8) - if err == nil { - t.Fatal("expected ErrNotExist, got nil") - } -} - -// https://github.com/opencontainers/selinux/issues/199 -func TestWalkDirRaceWithRemoval(t *testing.T) { - var ac atomic.Uint32 - concurrency := runtime.NumCPU() * 2 - // This test is still on a best-effort basis, meaning it can still pass - // when there is a bug in the code, but the larger the test set is, the - // higher the probability that this test fails (without a fix). - // - // With this set (4, 5, 6), and the fix commented out, it fails - // about 90 out of 100 runs on my machine. - dir, total := prepareTestSet(t, 4, 5, 6) - - // Make walk race with removal. - go os.RemoveAll(dir) - err := WalkN(dir, - func(_ string, _ fs.DirEntry, _ error) error { - ac.Add(1) - return nil - }, - concurrency) - count := ac.Load() - t.Logf("found %d of %d files", count, total) - if err != nil { - t.Fatalf("expected nil, got %v", err) - } -} - -func TestWalkDirManyErrors(t *testing.T) { - var ac atomic.Uint32 - dir, total := prepareTestSet(t, 3, 3, 2) - - maxFiles := total / 2 - e42 := errors.New("42") - err := Walk(dir, - func(_ string, _ fs.DirEntry, _ error) error { - if ac.Add(1) > maxFiles { - return e42 - } - return nil - }) - count := ac.Load() - t.Logf("found %d of %d files", count, total) - - if err == nil { - t.Error("Walk succeeded, but error is expected") - if count != total { - t.Errorf("File count mismatch: found %d, expected %d", count, total) - } - } -} - -func makeManyDirs(prefix string, levels, dirs, files int) (count uint32, err error) { - for d := 0; d < dirs; d++ { - var dir string - dir, err = os.MkdirTemp(prefix, "d-") - if err != nil { - return count, err - } - count++ - for f := 0; f < files; f++ { - var fi *os.File - fi, err = os.CreateTemp(dir, "f-") - if err != nil { - return count, err - } - fi.Close() - count++ - } - if levels == 0 { - continue - } - var c uint32 - if c, err = makeManyDirs(dir, levels-1, dirs, files); err != nil { - return count, err - } - count += c - } - - return count, err -} - -// prepareTestSet() creates a directory tree of shallow files, -// to be used for testing or benchmarking. -// -// Total dirs: dirs^levels + dirs^(levels-1) + ... + dirs^1 -// Total files: total_dirs * files -func prepareTestSet(tb testing.TB, levels, dirs, files int) (dir string, total uint32) { - tb.Helper() - var err error - - dir = tb.TempDir() - total, err = makeManyDirs(dir, levels, dirs, files) - if err != nil { - tb.Fatal(err) - } - total++ // this dir - - return dir, total -} - -type walkerFunc func(root string, walkFn fs.WalkDirFunc) error - -func genWalkN(n int) walkerFunc { - return func(root string, walkFn fs.WalkDirFunc) error { - return WalkN(root, walkFn, n) - } -} - -func BenchmarkWalk(b *testing.B) { - const ( - levels = 5 // how deep - dirs = 3 // dirs on each levels - files = 8 // files on each levels - ) - - benchmarks := []struct { - walk fs.WalkDirFunc - name string - }{ - {name: "Empty", walk: cbEmpty}, - {name: "ReadFile", walk: cbReadFile}, - {name: "ChownChmod", walk: cbChownChmod}, - {name: "RandomSleep", walk: cbRandomSleep}, - } - - walkers := []struct { - walker walkerFunc - name string - }{ - {name: "filepath.WalkDir", walker: filepath.WalkDir}, - {name: "pwalkdir.Walk", walker: Walk}, - // test WalkN with various values of N - {name: "pwalkdir.Walk1", walker: genWalkN(1)}, - {name: "pwalkdir.Walk2", walker: genWalkN(2)}, - {name: "pwalkdir.Walk4", walker: genWalkN(4)}, - {name: "pwalkdir.Walk8", walker: genWalkN(8)}, - {name: "pwalkdir.Walk16", walker: genWalkN(16)}, - {name: "pwalkdir.Walk32", walker: genWalkN(32)}, - {name: "pwalkdir.Walk64", walker: genWalkN(64)}, - {name: "pwalkdir.Walk128", walker: genWalkN(128)}, - {name: "pwalkdir.Walk256", walker: genWalkN(256)}, - } - - dir, total := prepareTestSet(b, levels, dirs, files) - b.Logf("dataset: %d levels x %d dirs x %d files, total entries: %d", levels, dirs, files, total) - - for _, bm := range benchmarks { - for _, w := range walkers { - walker := w.walker - walkFn := bm.walk - // preheat - if err := w.walker(dir, bm.walk); err != nil { - b.Errorf("walk failed: %v", err) - } - // benchmark - b.Run(bm.name+"/"+w.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - if err := walker(dir, walkFn); err != nil { - b.Errorf("walk failed: %v", err) - } - } - }) - } - } -} - -func cbEmpty(_ string, _ fs.DirEntry, _ error) error { - return nil -} - -func cbChownChmod(path string, e fs.DirEntry, _ error) error { - _ = os.Chown(path, 0, 0) - mode := os.FileMode(0o644) - if e.IsDir() { - mode = os.FileMode(0o755) - } - _ = os.Chmod(path, mode) - - return nil -} - -func cbReadFile(path string, e fs.DirEntry, _ error) error { - var err error - if e.Type().IsRegular() { - _, err = os.ReadFile(path) - } - return err -} - -func cbRandomSleep(_ string, _ fs.DirEntry, _ error) error { - time.Sleep(time.Duration(rand.Intn(500)) * time.Microsecond) //nolint:gosec // ignore G404: Use of weak random number generator - return nil -} diff --git a/vendor/cyphar.com/go-pathrs/.golangci.yml b/vendor/cyphar.com/go-pathrs/.golangci.yml new file mode 100644 index 000000000..2778a3268 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/.golangci.yml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# libpathrs: safe path resolution on Linux +# Copyright (C) 2019-2025 Aleksa Sarai +# 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 diff --git a/vendor/cyphar.com/go-pathrs/COPYING b/vendor/cyphar.com/go-pathrs/COPYING new file mode 100644 index 000000000..d0a1fa148 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/COPYING @@ -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. diff --git a/vendor/cyphar.com/go-pathrs/doc.go b/vendor/cyphar.com/go-pathrs/doc.go new file mode 100644 index 000000000..a7ee4bc48 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/doc.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 Aleksa Sarai + * 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 diff --git a/vendor/cyphar.com/go-pathrs/handle_linux.go b/vendor/cyphar.com/go-pathrs/handle_linux.go new file mode 100644 index 000000000..3221ef673 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/handle_linux.go @@ -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 + * 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() +} diff --git a/vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go b/vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go new file mode 100644 index 000000000..41aea3e4b --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go @@ -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 + * 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 +} diff --git a/vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go b/vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go new file mode 100644 index 000000000..c9f416de0 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go @@ -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 + * 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 +} diff --git a/vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go b/vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go new file mode 100644 index 000000000..c07b80e30 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go @@ -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 + * 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 + +// 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 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) +} diff --git a/vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go b/vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go new file mode 100644 index 000000000..5533c427c --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go @@ -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 + * 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) +} diff --git a/vendor/cyphar.com/go-pathrs/root_linux.go b/vendor/cyphar.com/go-pathrs/root_linux.go new file mode 100644 index 000000000..edc9e4c87 --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/root_linux.go @@ -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 + * 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() +} diff --git a/vendor/cyphar.com/go-pathrs/utils_linux.go b/vendor/cyphar.com/go-pathrs/utils_linux.go new file mode 100644 index 000000000..2208d608f --- /dev/null +++ b/vendor/cyphar.com/go-pathrs/utils_linux.go @@ -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 + * 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 +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml index e965034ed..3e8dd99bd 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml +++ b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml @@ -9,6 +9,10 @@ version: "2" +run: + build-tags: + - libpathrs + linters: enable: - asasalint diff --git a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md index 3faee0bc5..734cf61e3 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md +++ b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md @@ -4,7 +4,64 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased 0.5.z] ## +## [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 ## @@ -383,7 +440,8 @@ This is our first release of `github.com/cyphar/filepath-securejoin`, containing a full implementation with a coverage of 93.5% (the only missing cases are the error cases, which are hard to mocktest at the moment). -[Unreleased 0.5.z]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...release-0.5 +[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 diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION index 4b9fcbec1..a918a2aa1 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/VERSION +++ b/vendor/github.com/cyphar/filepath-securejoin/VERSION @@ -1 +1 @@ -0.5.1 +0.6.0 diff --git a/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go b/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go deleted file mode 100644 index 3e427b164..000000000 --- a/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//go:build linux - -// Copyright (C) 2024-2025 Aleksa Sarai -// Copyright (C) 2024-2025 SUSE LLC -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -package securejoin - -import ( - "github.com/cyphar/filepath-securejoin/pathrs-lite" -) - -var ( - // MkdirAll is a wrapper around [pathrs.MkdirAll]. - // - // Deprecated: You should use [pathrs.MkdirAll] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - MkdirAll = pathrs.MkdirAll - - // MkdirAllHandle is a wrapper around [pathrs.MkdirAllHandle]. - // - // Deprecated: You should use [pathrs.MkdirAllHandle] directly instead. - // This wrapper will be removed in filepath-securejoin v0.6. - MkdirAllHandle = pathrs.MkdirAllHandle - - // OpenInRoot is a wrapper around [pathrs.OpenInRoot]. - // - // Deprecated: You should use [pathrs.OpenInRoot] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - OpenInRoot = pathrs.OpenInRoot - - // OpenatInRoot is a wrapper around [pathrs.OpenatInRoot]. - // - // Deprecated: You should use [pathrs.OpenatInRoot] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - OpenatInRoot = pathrs.OpenatInRoot - - // Reopen is a wrapper around [pathrs.Reopen]. - // - // Deprecated: You should use [pathrs.Reopen] directly instead. This - // wrapper will be removed in filepath-securejoin v0.6. - Reopen = pathrs.Reopen -) diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md index 1be727e75..bb95b028c 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md @@ -5,11 +5,13 @@ Go** implementation of the core bits of [libpathrs][]. This is not intended to be a complete replacement for libpathrs, instead it is mainly intended to be useful as a transition tool for existing Go projects. -The long-term plan for `pathrs-lite` is to provide a build tag that will cause -all `pathrs-lite` operations to call into libpathrs directly, thus removing -code duplication for projects that wish to make use of libpathrs (and providing -the ability for software packagers to opt-in to libpathrs support without -needing to patch upstream). +`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 diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go index d3d745175..61411da37 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go @@ -11,4 +11,6 @@ // 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 diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go new file mode 100644 index 000000000..2ddb71e84 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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 diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go similarity index 98% rename from vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go index f47504e66..56480f0ce 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/lookup_linux.go @@ -9,7 +9,7 @@ // 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 +package gopathrs import ( "errors" @@ -166,11 +166,11 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) { 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 // component of the requested path, returning a file handle to the final // existing component and a string containing the remaining path components. -func partialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) { +func PartialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) { return lookupInRoot(root, unsafePath, true) } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/mkdir_linux.go similarity index 81% rename from vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/mkdir_linux.go index f3c62b0da..21a5593f4 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/mkdir_linux.go @@ -9,7 +9,7 @@ // 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 +package gopathrs import ( "errors" @@ -23,9 +23,12 @@ import ( "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 = errors.New("invalid permission mode") +// ErrInvalidMode is returned from [MkdirAll] when the requested mode is +// invalid. +var ErrInvalidMode = errors.New("invalid permission mode") // modePermExt is like os.ModePerm except that it also includes the set[ug]id // and sticky bits. @@ -45,11 +48,11 @@ func toUnixMode(mode os.FileMode) (uint32, error) { } // We don't allow file type bits. 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. 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 } @@ -84,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 // these bits will not be set. 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. - currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath) + currentDir, remainingPath, err := PartialLookupInRoot(root, unsafePath) defer func() { if Err != nil { _ = currentDir.Close() @@ -117,7 +120,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F // 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 // 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) } else if err != nil { return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err) @@ -207,40 +210,3 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F } 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]. -// -// [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 -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go new file mode 100644 index 000000000..cd9632a95 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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 +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go similarity index 99% rename from vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go index 937bc435f..b80ecd089 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go @@ -9,7 +9,7 @@ // 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 +package gopathrs import ( "errors" diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go new file mode 100644 index 000000000..b43169564 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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 +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go new file mode 100644 index 000000000..f864dbc8f --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_libpathrs.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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 +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go new file mode 100644 index 000000000..0369dfe7e --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_purego.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux && !libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go similarity index 57% rename from vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go index 7492d8cfa..41b628907 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open.go @@ -15,20 +15,8 @@ import ( "os" "golang.org/x/sys/unix" - - "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" ) -// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided -// 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 // @@ -55,20 +43,3 @@ func OpenInRoot(root, unsafePath string) (*os.File, error) { defer rootDir.Close() //nolint:errcheck // close failures aren't critical here 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) { - return procfs.ReopenFd(handle, flags) -} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go new file mode 100644 index 000000000..53352000e --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_libpathrs.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go new file mode 100644 index 000000000..6d1be12ce --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_purego.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux && !libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go new file mode 100644 index 000000000..6c4df3763 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_libpathrs.go @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build libpathrs + +// Copyright (C) 2024-2025 Aleksa Sarai +// 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/" (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/. +// +// 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/. +// +// 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/ (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/) 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/ 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) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go similarity index 99% rename from vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go rename to vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go index ec187a414..9383002f9 100644 --- a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_purego.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -//go:build linux +//go:build linux && !libpathrs // Copyright (C) 2024-2025 Aleksa Sarai // Copyright (C) 2024-2025 SUSE LLC diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index 70392d989..6d7f8e270 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -273,8 +273,8 @@ func writeConFd(out *os.File, val string) error { return err } -// openProcThreadSelf is a small wrapper around [OpenThreadSelf] and -// [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The +// 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). @@ -283,8 +283,6 @@ func writeConFd(out *os.File, val string) error { // /proc/thread-self/ with no tricky mounts or symlinks causing you to // operate on an unexpected path (with some caveats on pre-openat2 or // pre-fsopen kernels). -// -// [OpenThreadSelf]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle.OpenThreadSelf func openProcThreadSelf(subpath string, mode int) (*os.File, procfs.ProcThreadSelfCloser, error) { if subpath == "" { return nil, nil, ErrEmptyPath @@ -340,17 +338,16 @@ func writeConThreadSelf(fpath, val string) error { return writeConFd(out, val) } -// openProcSelf is a small wrapper around [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). +// 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/ with no tricky mounts or symlinks causing you to // operate on an unexpected path (with some caveats on pre-openat2 or // pre-fsopen kernels). -// -// [OpenSelf]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle.OpenSelf func openProcSelf(subpath string, mode int) (*os.File, error) { if subpath == "" { return nil, ErrEmptyPath @@ -403,17 +400,16 @@ func writeConSelf(fpath, val string) error { return writeConFd(out, val) } -// openProcPid is a small wrapper around [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). +// 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/ with no tricky mounts or symlinks causing you to // operate on an unexpected path (with some caveats on pre-openat2 or // pre-fsopen kernels). -// -// [OpenPid]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle.OpenPid func openProcPid(pid int, subpath string, mode int) (*os.File, error) { if subpath == "" { return nil, ErrEmptyPath diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go index 267921239..382244e50 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go @@ -3,10 +3,6 @@ package selinux -func attrPath(string) string { - return "" -} - func readConThreadSelf(string) (string, error) { return "", nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 74500d1ed..93f5fcf46 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,9 @@ +# 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 # github.com/checkpoint-restore/go-criu/v7 v7.2.0 ## explicit; go 1.20 github.com/checkpoint-restore/go-criu/v7 @@ -27,7 +33,7 @@ github.com/coreos/go-systemd/v22/dbus # github.com/cpuguy83/go-md2man/v2 v2.0.7 ## explicit; go 1.12 github.com/cpuguy83/go-md2man/v2/md2man -# github.com/cyphar/filepath-securejoin v0.5.1 +# github.com/cyphar/filepath-securejoin v0.6.0 ## explicit; go 1.18 github.com/cyphar/filepath-securejoin github.com/cyphar/filepath-securejoin/internal/consts @@ -36,6 +42,7 @@ 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 @@ -76,7 +83,7 @@ github.com/opencontainers/cgroups/systemd ## explicit github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/runtime-spec/specs-go/features -# github.com/opencontainers/selinux v1.12.0 => ./internal/third_party/selinux +# github.com/opencontainers/selinux v1.13.0 ## explicit; go 1.19 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label @@ -137,4 +144,3 @@ google.golang.org/protobuf/reflect/protoreflect google.golang.org/protobuf/reflect/protoregistry google.golang.org/protobuf/runtime/protoiface google.golang.org/protobuf/runtime/protoimpl -# github.com/opencontainers/selinux => ./internal/third_party/selinux