mirror of
https://github.com/opencontainers/runc.git
synced 2025-12-24 11:50:58 +08:00
selinux: use safe procfs API for labels
Due to the sensitive nature of these fixes, it was not possible to submit these upstream and vendor the upstream library. Instead, this patch uses a fork of github.com/opencontainers/selinux, branched at commit opencontainers/selinux@879a755db5. In order to permit downstreams to build with this patched version, a snapshot of the forked version has been included in internal/third_party/selinux. Note that since we use "go mod vendor", the patched code is usable even without being "go get"-able. Once the embargo for this issue is lifted we can submit the patches upstream and switch back to a proper upstream go.mod entry. Also, this requires us to temporarily disable the CI job we have that disallows "replace" directives. Fixes: GHSA-cgrx-mc8f-2prm CVE-2025-52881 Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
9
.github/workflows/validate.yml
vendored
9
.github/workflows/validate.yml
vendored
@@ -152,9 +152,12 @@ 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
|
||||
- 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
|
||||
# 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
|
||||
|
||||
|
||||
commit:
|
||||
|
||||
5
go.mod
5
go.mod
@@ -32,3 +32,8 @@ require (
|
||||
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
|
||||
|
||||
2
go.sum
2
go.sum
@@ -48,8 +48,6 @@ 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.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
||||
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
||||
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=
|
||||
|
||||
2
internal/third_party/selinux/.codespellrc
vendored
Normal file
2
internal/third_party/selinux/.codespellrc
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[codespell]
|
||||
skip = ./.git,./go.sum,./go-selinux/testdata
|
||||
10
internal/third_party/selinux/.github/dependabot.yml
vendored
Normal file
10
internal/third_party/selinux/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# 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"
|
||||
163
internal/third_party/selinux/.github/workflows/validate.yml
vendored
Normal file
163
internal/third_party/selinux/.github/workflows/validate.yml
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
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"
|
||||
1
internal/third_party/selinux/.gitignore
vendored
Normal file
1
internal/third_party/selinux/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
44
internal/third_party/selinux/.golangci.yml
vendored
Normal file
44
internal/third_party/selinux/.golangci.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
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
|
||||
1
internal/third_party/selinux/CODEOWNERS
vendored
Normal file
1
internal/third_party/selinux/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @kolyshkin @mrunalp @rhatdan @runcom @thajeztah
|
||||
119
internal/third_party/selinux/CONTRIBUTING.md
vendored
Normal file
119
internal/third_party/selinux/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
## 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 <joe@gmail.com>
|
||||
|
||||
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`.
|
||||
201
internal/third_party/selinux/LICENSE
vendored
Normal file
201
internal/third_party/selinux/LICENSE
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
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.
|
||||
5
internal/third_party/selinux/MAINTAINERS
vendored
Normal file
5
internal/third_party/selinux/MAINTAINERS
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Antonio Murdaca <runcom@redhat.com> (@runcom)
|
||||
Daniel J Walsh <dwalsh@redhat.com> (@rhatdan)
|
||||
Mrunal Patel <mpatel@redhat.com> (@mrunalp)
|
||||
Sebastiaan van Stijn <github@gone.nl> (@thaJeztah)
|
||||
Kirill Kolyshikin <kolyshkin@gmail.com> (@kolyshkin)
|
||||
37
internal/third_party/selinux/Makefile
vendored
Normal file
37
internal/third_party/selinux/Makefile
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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
|
||||
23
internal/third_party/selinux/README.md
vendored
Normal file
23
internal/third_party/selinux/README.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# selinux
|
||||
|
||||
[](https://godoc.org/github.com/opencontainers/selinux) [](https://goreportcard.com/report/github.com/opencontainers/selinux) [](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
|
||||
13
internal/third_party/selinux/go-selinux/doc.go
vendored
Normal file
13
internal/third_party/selinux/go-selinux/doc.go
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
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
|
||||
48
internal/third_party/selinux/go-selinux/label/label.go
vendored
Normal file
48
internal/third_party/selinux/go-selinux/label/label.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
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
|
||||
}
|
||||
136
internal/third_party/selinux/go-selinux/label/label_linux.go
vendored
Normal file
136
internal/third_party/selinux/go-selinux/label/label_linux.go
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
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")
|
||||
}
|
||||
130
internal/third_party/selinux/go-selinux/label/label_linux_test.go
vendored
Normal file
130
internal/third_party/selinux/go-selinux/label/label_linux_test.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
44
internal/third_party/selinux/go-selinux/label/label_stub.go
vendored
Normal file
44
internal/third_party/selinux/go-selinux/label/label_stub.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
//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
|
||||
}
|
||||
76
internal/third_party/selinux/go-selinux/label/label_stub_test.go
vendored
Normal file
76
internal/third_party/selinux/go-selinux/label/label_stub_test.go
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
//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")
|
||||
}
|
||||
}
|
||||
35
internal/third_party/selinux/go-selinux/label/label_test.go
vendored
Normal file
35
internal/third_party/selinux/go-selinux/label/label_test.go
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
322
internal/third_party/selinux/go-selinux/selinux.go
vendored
Normal file
322
internal/third_party/selinux/go-selinux/selinux.go
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
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/<username> 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
|
||||
}
|
||||
1405
internal/third_party/selinux/go-selinux/selinux_linux.go
vendored
Normal file
1405
internal/third_party/selinux/go-selinux/selinux_linux.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
711
internal/third_party/selinux/go-selinux/selinux_linux_test.go
vendored
Normal file
711
internal/third_party/selinux/go-selinux/selinux_linux_test.go
vendored
Normal file
@@ -0,0 +1,711 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
159
internal/third_party/selinux/go-selinux/selinux_stub.go
vendored
Normal file
159
internal/third_party/selinux/go-selinux/selinux_stub.go
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
//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 ""
|
||||
}
|
||||
127
internal/third_party/selinux/go-selinux/selinux_stub_test.go
vendored
Normal file
127
internal/third_party/selinux/go-selinux/selinux_stub_test.go
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
//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)
|
||||
}
|
||||
}
|
||||
71
internal/third_party/selinux/go-selinux/xattrs_linux.go
vendored
Normal file
71
internal/third_party/selinux/go-selinux/xattrs_linux.go
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
8
internal/third_party/selinux/go.mod
vendored
Normal file
8
internal/third_party/selinux/go.mod
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
module github.com/opencontainers/selinux
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/cyphar/filepath-securejoin v0.5.0
|
||||
golang.org/x/sys v0.18.0
|
||||
)
|
||||
8
internal/third_party/selinux/go.sum
vendored
Normal file
8
internal/third_party/selinux/go.sum
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
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=
|
||||
52
internal/third_party/selinux/pkg/pwalk/README.md
vendored
Normal file
52
internal/third_party/selinux/pkg/pwalk/README.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
## 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.
|
||||
131
internal/third_party/selinux/pkg/pwalk/pwalk.go
vendored
Normal file
131
internal/third_party/selinux/pkg/pwalk/pwalk.go
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
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
|
||||
}
|
||||
236
internal/third_party/selinux/pkg/pwalk/pwalk_test.go
vendored
Normal file
236
internal/third_party/selinux/pkg/pwalk/pwalk_test.go
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
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
|
||||
}
|
||||
56
internal/third_party/selinux/pkg/pwalkdir/README.md
vendored
Normal file
56
internal/third_party/selinux/pkg/pwalkdir/README.md
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
## 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.
|
||||
123
internal/third_party/selinux/pkg/pwalkdir/pwalkdir.go
vendored
Normal file
123
internal/third_party/selinux/pkg/pwalkdir/pwalkdir.go
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
//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
|
||||
}
|
||||
239
internal/third_party/selinux/pkg/pwalkdir/pwalkdir_test.go
vendored
Normal file
239
internal/third_party/selinux/pkg/pwalkdir/pwalkdir_test.go
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
//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
|
||||
}
|
||||
6
vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go
generated
vendored
6
vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go
generated
vendored
@@ -18,7 +18,7 @@ var validOptions = map[string]bool{
|
||||
"level": true,
|
||||
}
|
||||
|
||||
var ErrIncompatibleLabel = errors.New("Bad SELinux option z and Z can not be used together")
|
||||
var ErrIncompatibleLabel = errors.New("bad SELinux option: z and Z can not be used together")
|
||||
|
||||
// InitLabels returns the process label and file labels to be used within
|
||||
// the container. A list of options can be passed into this function to alter
|
||||
@@ -52,11 +52,11 @@ func InitLabels(options []string) (plabel string, mlabel string, retErr error) {
|
||||
return "", selinux.PrivContainerMountLabel(), nil
|
||||
}
|
||||
if i := strings.Index(opt, ":"); i == -1 {
|
||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
|
||||
return "", "", fmt.Errorf("bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
|
||||
}
|
||||
con := strings.SplitN(opt, ":", 2)
|
||||
if !validOptions[con[0]] {
|
||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
|
||||
return "", "", fmt.Errorf("bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
|
||||
}
|
||||
if con[0] == "filetype" {
|
||||
mcon["type"] = con[1]
|
||||
|
||||
10
vendor/github.com/opencontainers/selinux/go-selinux/selinux.go
generated
vendored
10
vendor/github.com/opencontainers/selinux/go-selinux/selinux.go
generated
vendored
@@ -153,7 +153,7 @@ func CalculateGlbLub(sourceRange, targetRange string) (string, error) {
|
||||
// of the program is finished to guarantee another goroutine does not migrate to the current
|
||||
// thread before execution is complete.
|
||||
func SetExecLabel(label string) error {
|
||||
return writeCon(attrPath("exec"), label)
|
||||
return writeConThreadSelf("attr/exec", label)
|
||||
}
|
||||
|
||||
// SetTaskLabel sets the SELinux label for the current thread, or an error.
|
||||
@@ -161,7 +161,7 @@ func SetExecLabel(label string) error {
|
||||
// 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 writeCon(attrPath("current"), label)
|
||||
return writeConThreadSelf("attr/current", label)
|
||||
}
|
||||
|
||||
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||
@@ -170,12 +170,12 @@ func SetTaskLabel(label string) error {
|
||||
// the socket is created to guarantee another goroutine does not migrate
|
||||
// to the current thread before execution is complete.
|
||||
func SetSocketLabel(label string) error {
|
||||
return writeCon(attrPath("sockcreate"), label)
|
||||
return writeConThreadSelf("attr/sockcreate", label)
|
||||
}
|
||||
|
||||
// SocketLabel retrieves the current socket label setting
|
||||
func SocketLabel() (string, error) {
|
||||
return readCon(attrPath("sockcreate"))
|
||||
return readConThreadSelf("attr/sockcreate")
|
||||
}
|
||||
|
||||
// PeerLabel retrieves the label of the client on the other side of a socket
|
||||
@@ -198,7 +198,7 @@ func SetKeyLabel(label string) error {
|
||||
|
||||
// KeyLabel retrieves the current kernel keyring label setting
|
||||
func KeyLabel() (string, error) {
|
||||
return readCon("/proc/self/attr/keycreate")
|
||||
return keyLabel()
|
||||
}
|
||||
|
||||
// Get returns the Context as a string
|
||||
|
||||
298
vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
generated
vendored
298
vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
generated
vendored
@@ -17,8 +17,11 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/opencontainers/selinux/pkg/pwalkdir"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/opencontainers/selinux/pkg/pwalkdir"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,10 +76,6 @@ var (
|
||||
mcsList: make(map[string]bool),
|
||||
}
|
||||
|
||||
// for attrPath()
|
||||
attrPathOnce sync.Once
|
||||
haveThreadSelf bool
|
||||
|
||||
// for policyRoot()
|
||||
policyRootOnce sync.Once
|
||||
policyRootVal string
|
||||
@@ -256,42 +255,6 @@ func readConfig(target string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func isProcHandle(fh *os.File) error {
|
||||
var buf unix.Statfs_t
|
||||
|
||||
for {
|
||||
err := unix.Fstatfs(int(fh.Fd()), &buf)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err != unix.EINTR {
|
||||
return &os.PathError{Op: "fstatfs", Path: fh.Name(), Err: err}
|
||||
}
|
||||
}
|
||||
if buf.Type != unix.PROC_SUPER_MAGIC {
|
||||
return fmt.Errorf("file %q is not on procfs", fh.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readCon(fpath string) (string, error) {
|
||||
if fpath == "" {
|
||||
return "", ErrEmptyPath
|
||||
}
|
||||
|
||||
in, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
if err := isProcHandle(in); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return readConFd(in)
|
||||
}
|
||||
|
||||
func readConFd(in *os.File) (string, error) {
|
||||
data, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
@@ -300,6 +263,181 @@ func readConFd(in *os.File) (string, error) {
|
||||
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/<subpath> with no tricky mounts or symlinks causing you to
|
||||
// operate on an unexpected path (with some caveats on pre-openat2 or
|
||||
// pre-fsopen kernels).
|
||||
//
|
||||
// [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/<fpath>.
|
||||
func readConThreadSelf(fpath string) (string, error) {
|
||||
in, closer, err := openProcThreadSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer closer()
|
||||
defer in.Close()
|
||||
|
||||
return readConFd(in)
|
||||
}
|
||||
|
||||
// Write <val> to /proc/thread-self/<fpath>.
|
||||
func writeConThreadSelf(fpath, val string) error {
|
||||
if val == "" {
|
||||
if !getEnabled() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
out, closer, err := openProcThreadSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
defer out.Close()
|
||||
|
||||
return writeConFd(out, val)
|
||||
}
|
||||
|
||||
// openProcSelf is a small wrapper around [OpenSelf] and [pathrs.Reopen] to
|
||||
// make "one-shot opens" slightly more ergonomic. The provided mode must be
|
||||
// os.O_* flags to indicate what mode the returned file should be opened with
|
||||
// (flags like os.O_CREAT and os.O_EXCL are not supported).
|
||||
//
|
||||
// If no error occurred, the returned handle is guaranteed to be exactly
|
||||
// /proc/self/<subpath> with no tricky mounts or symlinks causing you to
|
||||
// operate on an unexpected path (with some caveats on pre-openat2 or
|
||||
// pre-fsopen kernels).
|
||||
//
|
||||
// [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/<fpath>.
|
||||
func readConSelf(fpath string) (string, error) {
|
||||
in, err := openProcSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
return readConFd(in)
|
||||
}
|
||||
|
||||
// Write <val> to /proc/self/<fpath>.
|
||||
func writeConSelf(fpath, val string) error {
|
||||
if val == "" {
|
||||
if !getEnabled() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
out, err := openProcSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
return writeConFd(out, val)
|
||||
}
|
||||
|
||||
// openProcPid is a small wrapper around [OpenPid] and [pathrs.Reopen] to make
|
||||
// "one-shot opens" slightly more ergonomic. The provided mode must be os.O_*
|
||||
// flags to indicate what mode the returned file should be opened with (flags
|
||||
// like os.O_CREAT and os.O_EXCL are not supported).
|
||||
//
|
||||
// If no error occurred, the returned handle is guaranteed to be exactly
|
||||
// /proc/self/<subpath> with no tricky mounts or symlinks causing you to
|
||||
// operate on an unexpected path (with some caveats on pre-openat2 or
|
||||
// pre-fsopen kernels).
|
||||
//
|
||||
// [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) {
|
||||
@@ -393,78 +531,34 @@ func lFileLabel(fpath string) (string, error) {
|
||||
}
|
||||
|
||||
func setFSCreateLabel(label string) error {
|
||||
return writeCon(attrPath("fscreate"), label)
|
||||
return writeConThreadSelf("attr/fscreate", label)
|
||||
}
|
||||
|
||||
// fsCreateLabel returns the default label the kernel which the kernel is using
|
||||
// for file system objects created by this task. "" indicates default.
|
||||
func fsCreateLabel() (string, error) {
|
||||
return readCon(attrPath("fscreate"))
|
||||
return readConThreadSelf("attr/fscreate")
|
||||
}
|
||||
|
||||
// currentLabel returns the SELinux label of the current process thread, or an error.
|
||||
func currentLabel() (string, error) {
|
||||
return readCon(attrPath("current"))
|
||||
return readConThreadSelf("attr/current")
|
||||
}
|
||||
|
||||
// pidLabel returns the SELinux label of the given pid, or an error.
|
||||
func pidLabel(pid int) (string, error) {
|
||||
return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
|
||||
it, err := openProcPid(pid, "attr/current", os.O_RDONLY|unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
defer it.Close()
|
||||
return readConFd(it)
|
||||
}
|
||||
|
||||
// ExecLabel returns the SELinux label that the kernel will use for any programs
|
||||
// that are executed by the current process thread, or an error.
|
||||
func execLabel() (string, error) {
|
||||
return readCon(attrPath("exec"))
|
||||
}
|
||||
|
||||
func writeCon(fpath, val string) error {
|
||||
if fpath == "" {
|
||||
return ErrEmptyPath
|
||||
}
|
||||
if val == "" {
|
||||
if !getEnabled() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
out, err := os.OpenFile(fpath, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
if err := isProcHandle(out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if val != "" {
|
||||
_, err = out.Write([]byte(val))
|
||||
} else {
|
||||
_, err = out.Write(nil)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func attrPath(attr string) string {
|
||||
// Linux >= 3.17 provides this
|
||||
const threadSelfPrefix = "/proc/thread-self/attr"
|
||||
|
||||
attrPathOnce.Do(func() {
|
||||
st, err := os.Stat(threadSelfPrefix)
|
||||
if err == nil && st.Mode().IsDir() {
|
||||
haveThreadSelf = true
|
||||
}
|
||||
})
|
||||
|
||||
if haveThreadSelf {
|
||||
return filepath.Join(threadSelfPrefix, attr)
|
||||
}
|
||||
|
||||
return filepath.Join("/proc/self/task", strconv.Itoa(unix.Gettid()), "attr", attr)
|
||||
return readConThreadSelf("exec")
|
||||
}
|
||||
|
||||
// canonicalizeContext takes a context string and writes it to the kernel
|
||||
@@ -728,19 +822,29 @@ func peerLabel(fd uintptr) (string, error) {
|
||||
// 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 {
|
||||
err := writeCon("/proc/self/attr/keycreate", label)
|
||||
// Rather than using /proc/thread-self, we want to use /proc/self to
|
||||
// operate on the thread-group leader.
|
||||
err := writeConSelf("attr/keycreate", label)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
if label == "" && errors.Is(err, os.ErrPermission) {
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, unix.EACCES) && unix.Getuid() != unix.Gettid() {
|
||||
if errors.Is(err, unix.EACCES) && unix.Getpid() != unix.Gettid() {
|
||||
return ErrNotTGLeader
|
||||
}
|
||||
return 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 != "" {
|
||||
|
||||
8
vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
generated
vendored
8
vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
generated
vendored
@@ -7,11 +7,11 @@ func attrPath(string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func readCon(string) (string, error) {
|
||||
func readConThreadSelf(string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func writeCon(string, string) error {
|
||||
func writeConThreadSelf(string, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@ func setKeyLabel(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (c Context) get() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -76,7 +76,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
|
||||
# github.com/opencontainers/selinux v1.12.0 => ./internal/third_party/selinux
|
||||
## explicit; go 1.19
|
||||
github.com/opencontainers/selinux/go-selinux
|
||||
github.com/opencontainers/selinux/go-selinux/label
|
||||
@@ -137,3 +137,4 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user