mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
deps: update to github.com/opencontainers/selinux@v0.13.0
This new version includes the fixes for CVE-2025-52881, so we can remove the internal/third_party copy of the library we added in commited6b1693b8("selinux: use safe procfs API for labels") as well as the "replace" directive in go.mod (which is problematic for "go get" installs). Fixes:ed6b1693b8("selinux: use safe procfs API for labels") Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> (cherry picked from commit96f1962f91) Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
-2
@@ -1,2 +0,0 @@
|
||||
[codespell]
|
||||
skip = ./.git,./go.sum,./go-selinux/testdata
|
||||
@@ -1,10 +0,0 @@
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Dependencies listed in .github/workflows/*.yml
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -1,163 +0,0 @@
|
||||
name: validate
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
commit:
|
||||
runs-on: ubuntu-24.04
|
||||
# Only check commits on pull requests.
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: get pr commits
|
||||
id: 'get-pr-commits'
|
||||
uses: tim-actions/get-pr-commits@v1.3.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: check subject line length
|
||||
uses: tim-actions/commit-message-checker-with-regex@v0.3.2
|
||||
with:
|
||||
commits: ${{ steps.get-pr-commits.outputs.commits }}
|
||||
pattern: '^.{0,72}(\n.*)*$'
|
||||
error: 'Subject too long (max 72)'
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
- uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
version: v2.0
|
||||
|
||||
codespell:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: install deps
|
||||
# Version of codespell bundled with Ubuntu is way old, so use pip.
|
||||
run: pip install codespell
|
||||
- name: run codespell
|
||||
run: codespell
|
||||
|
||||
cross:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: cross
|
||||
run: make build-cross
|
||||
|
||||
test-stubs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
- uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
version: v2.0
|
||||
- name: test-stubs
|
||||
run: make test
|
||||
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [1.19.x, 1.23.x, 1.24.x]
|
||||
race: ["-race", ""]
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: install go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: build
|
||||
run: make BUILDFLAGS="${{ matrix.race }}" build
|
||||
|
||||
- name: test
|
||||
run: make TESTFLAGS="${{ matrix.race }}" test
|
||||
|
||||
vm:
|
||||
name: "VM"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
template:
|
||||
- template://almalinux-8
|
||||
- template://centos-stream-9
|
||||
- template://fedora
|
||||
- template://experimental/opensuse-tumbleweed
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: "Install Lima"
|
||||
uses: lima-vm/lima-actions/setup@v1
|
||||
id: lima-actions-setup
|
||||
|
||||
- name: "Cache ~/.cache/lima"
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/lima
|
||||
key: lima-${{ steps.lima-actions-setup.outputs.version }}-${{ matrix.template }}
|
||||
|
||||
- name: "Start VM"
|
||||
# --plain is set to disable file sharing, port forwarding, built-in containerd, etc. for faster start up
|
||||
run: limactl start --plain --name=default ${{ matrix.template }}
|
||||
|
||||
- name: "Initialize VM"
|
||||
run: |
|
||||
set -eux -o pipefail
|
||||
# Sync the current directory to /tmp/selinux in the guest
|
||||
limactl cp -r . default:/tmp/selinux
|
||||
# Install packages
|
||||
if lima command -v dnf >/dev/null; then
|
||||
lima sudo dnf install --setopt=install_weak_deps=false --setopt=tsflags=nodocs -y git-core make golang
|
||||
elif lima command -v zypper >/dev/null; then
|
||||
lima sudo zypper install -y git make go
|
||||
else
|
||||
echo >&2 "Unsupported distribution"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: "make test"
|
||||
continue-on-error: true
|
||||
run: lima make -C /tmp/selinux test
|
||||
|
||||
- name: "32-bit test"
|
||||
continue-on-error: true
|
||||
run: lima make -C /tmp/selinux GOARCH=386 test
|
||||
|
||||
# https://github.com/opencontainers/selinux/issues/222
|
||||
# https://github.com/opencontainers/selinux/issues/225
|
||||
- name: "racy test"
|
||||
continue-on-error: true
|
||||
run: lima bash -c 'cd /tmp/selinux && go test -timeout 10m -count 100000 ./go-selinux'
|
||||
|
||||
- name: "Show AVC denials"
|
||||
run: lima sudo ausearch -m AVC,USER_AVC || true
|
||||
|
||||
all-done:
|
||||
needs:
|
||||
- commit
|
||||
- lint
|
||||
- codespell
|
||||
- cross
|
||||
- test-stubs
|
||||
- test
|
||||
- vm
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- run: echo "All jobs completed"
|
||||
@@ -1 +0,0 @@
|
||||
build
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
|
||||
linters:
|
||||
enable:
|
||||
# - copyloopvar # Detects places where loop variables are copied. TODO enable for Go 1.22+
|
||||
- dupword # Detects duplicate words.
|
||||
- errorlint # Detects code that may cause problems with Go 1.13 error wrapping.
|
||||
- gocritic # Metalinter; detects bugs, performance, and styling issues.
|
||||
- gosec # Detects security problems.
|
||||
- misspell # Detects commonly misspelled English words in comments.
|
||||
- nilerr # Detects code that returns nil even if it checks that the error is not nil.
|
||||
- nolintlint # Detects ill-formed or insufficient nolint directives.
|
||||
- prealloc # Detects slice declarations that could potentially be pre-allocated.
|
||||
- predeclared # Detects code that shadows one of Go's predeclared identifiers
|
||||
- revive # Metalinter; drop-in replacement for golint.
|
||||
- thelper # Detects test helpers without t.Helper().
|
||||
- tparallel # Detects inappropriate usage of t.Parallel().
|
||||
- unconvert # Detects unnecessary type conversions.
|
||||
- usetesting # Reports uses of functions with replacement inside the testing package.
|
||||
settings:
|
||||
govet:
|
||||
enable-all: true
|
||||
settings:
|
||||
shadow:
|
||||
strict: true
|
||||
exclusions:
|
||||
generated: strict
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- govet
|
||||
text: '^shadow: declaration of "err" shadows declaration'
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
-1
@@ -1 +0,0 @@
|
||||
* @kolyshkin @mrunalp @rhatdan @runcom @thajeztah
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
## Contribution Guidelines
|
||||
|
||||
### Security issues
|
||||
|
||||
If you are reporting a security issue, do not create an issue or file a pull
|
||||
request on GitHub. Instead, disclose the issue responsibly by sending an email
|
||||
to security@opencontainers.org (which is inhabited only by the maintainers of
|
||||
the various OCI projects).
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to
|
||||
process them as fast as possible. Not sure if that typo is worth a pull
|
||||
request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be
|
||||
discouraged! If there's a problem with the implementation, hopefully you
|
||||
received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep the project lean and focused. We don't want it
|
||||
to do everything for everybody. This means that we might decide against
|
||||
incorporating a new feature.
|
||||
|
||||
|
||||
### Conventions
|
||||
|
||||
Fork the repo and make changes on your fork in a feature branch.
|
||||
For larger bugs and enhancements, consider filing a leader issue or mailing-list thread for discussion that is independent of the implementation.
|
||||
Small changes or changes that have been discussed on the project mailing list may be submitted without a leader issue.
|
||||
|
||||
If the project has a test suite, submit unit tests for your changes. Take a
|
||||
look at existing tests for inspiration. Run the full test suite on your branch
|
||||
before submitting a pull request.
|
||||
|
||||
Update the documentation when creating or modifying features. Test
|
||||
your documentation changes for clarity, concision, and correctness, as
|
||||
well as a clean documentation build. See ``docs/README.md`` for more
|
||||
information on building the docs and how docs get released.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||
committing your changes. Most editors have plugins that do this automatically.
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Commit messages must start with a capitalized and short summary
|
||||
written in the imperative, followed by an optional, more detailed
|
||||
explanatory text which is separated from the summary by an empty line.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
request automatically, but the reviewers will not be notified unless you
|
||||
comment.
|
||||
|
||||
Before the pull request is merged, make sure that you squash your commits into
|
||||
logical units of work using `git rebase -i` and `git push -f`. After every
|
||||
commit the test suite (if any) should be passing. Include documentation changes
|
||||
in the same commit so that a revert would remove all traces of the feature or
|
||||
fix.
|
||||
|
||||
Commits that fix or close an issue should include a reference like `Closes #XXX`
|
||||
or `Fixes #XXX`, which will automatically close the issue when merged.
|
||||
|
||||
### Sign your work
|
||||
|
||||
The sign-off is a simple line at the end of the explanation for the
|
||||
patch, which certifies that you wrote it or otherwise have the right to
|
||||
pass it on as an open-source patch. The rules are pretty simple: if you
|
||||
can certify the below (from
|
||||
[developercertificate.org](http://developercertificate.org/)):
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
then you just add a line to every git commit message:
|
||||
|
||||
Signed-off-by: Joe Smith <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`.
|
||||
Vendored
-201
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
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
@@ -1,37 +0,0 @@
|
||||
GO ?= go
|
||||
|
||||
all: build build-cross
|
||||
|
||||
define go-build
|
||||
GOOS=$(1) GOARCH=$(2) $(GO) build ${BUILDFLAGS} ./...
|
||||
endef
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
$(call go-build,linux,amd64)
|
||||
|
||||
.PHONY: build-cross
|
||||
build-cross:
|
||||
$(call go-build,linux,386)
|
||||
$(call go-build,linux,arm)
|
||||
$(call go-build,linux,arm64)
|
||||
$(call go-build,linux,ppc64le)
|
||||
$(call go-build,linux,s390x)
|
||||
$(call go-build,linux,mips64le)
|
||||
$(call go-build,linux,riscv64)
|
||||
$(call go-build,windows,amd64)
|
||||
$(call go-build,windows,386)
|
||||
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
$(GO) test -timeout 3m ${TESTFLAGS} -v ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
$(GO) mod tidy
|
||||
$(GO) mod verify
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
# 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
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
Package selinux provides a high-level interface for interacting with selinux.
|
||||
|
||||
Usage:
|
||||
|
||||
import "github.com/opencontainers/selinux/go-selinux"
|
||||
|
||||
// Ensure that selinux is enforcing mode.
|
||||
if selinux.EnforceMode() != selinux.Enforcing {
|
||||
selinux.SetEnforceMode(selinux.Enforcing)
|
||||
}
|
||||
*/
|
||||
package selinux
|
||||
@@ -1,48 +0,0 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
)
|
||||
|
||||
// Init initialises the labeling system
|
||||
func Init() {
|
||||
_ = selinux.GetEnabled()
|
||||
}
|
||||
|
||||
// FormatMountLabel returns a string to be used by the mount command. Using
|
||||
// the SELinux `context` mount option. Changing labels of files on mount
|
||||
// points with this option can never be changed.
|
||||
// FormatMountLabel returns a string to be used by the mount command.
|
||||
// The format of this string will be used to alter the labeling of the mountpoint.
|
||||
// The string returned is suitable to be used as the options field of the mount command.
|
||||
// If you need to have additional mount point options, you can pass them in as
|
||||
// the first parameter. Second parameter is the label that you wish to apply
|
||||
// to all content in the mount point.
|
||||
func FormatMountLabel(src, mountLabel string) string {
|
||||
return FormatMountLabelByType(src, mountLabel, "context")
|
||||
}
|
||||
|
||||
// FormatMountLabelByType returns a string to be used by the mount command.
|
||||
// Allow caller to specify the mount options. For example using the SELinux
|
||||
// `fscontext` mount option would allow certain container processes to change
|
||||
// labels of files created on the mount points, where as `context` option does
|
||||
// not.
|
||||
// FormatMountLabelByType returns a string to be used by the mount command.
|
||||
// The format of this string will be used to alter the labeling of the mountpoint.
|
||||
// The string returned is suitable to be used as the options field of the mount command.
|
||||
// If you need to have additional mount point options, you can pass them in as
|
||||
// the first parameter. Second parameter is the label that you wish to apply
|
||||
// to all content in the mount point.
|
||||
func FormatMountLabelByType(src, mountLabel, contextType string) string {
|
||||
if mountLabel != "" {
|
||||
switch src {
|
||||
case "":
|
||||
src = fmt.Sprintf("%s=%q", contextType, mountLabel)
|
||||
default:
|
||||
src = fmt.Sprintf("%s,%s=%q", src, contextType, mountLabel)
|
||||
}
|
||||
}
|
||||
return src
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
)
|
||||
|
||||
// Valid Label Options
|
||||
var validOptions = map[string]bool{
|
||||
"disable": true,
|
||||
"type": true,
|
||||
"filetype": true,
|
||||
"user": true,
|
||||
"role": true,
|
||||
"level": true,
|
||||
}
|
||||
|
||||
var ErrIncompatibleLabel = errors.New("bad SELinux option: z and Z can not be used together")
|
||||
|
||||
// InitLabels returns the process label and file labels to be used within
|
||||
// the container. A list of options can be passed into this function to alter
|
||||
// the labels. The labels returned will include a random MCS String, that is
|
||||
// guaranteed to be unique.
|
||||
// If the disabled flag is passed in, the process label will not be set, but the mount label will be set
|
||||
// to the container_file label with the maximum category. This label is not usable by any confined label.
|
||||
func InitLabels(options []string) (plabel string, mlabel string, retErr error) {
|
||||
if !selinux.GetEnabled() {
|
||||
return "", "", nil
|
||||
}
|
||||
processLabel, mountLabel := selinux.ContainerLabels()
|
||||
if processLabel != "" {
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
selinux.ReleaseLabel(mountLabel)
|
||||
}
|
||||
}()
|
||||
pcon, err := selinux.NewContext(processLabel)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
mcsLevel := pcon["level"]
|
||||
mcon, err := selinux.NewContext(mountLabel)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, opt := range options {
|
||||
if opt == "disable" {
|
||||
selinux.ReleaseLabel(mountLabel)
|
||||
return "", selinux.PrivContainerMountLabel(), nil
|
||||
}
|
||||
if i := strings.Index(opt, ":"); i == -1 {
|
||||
return "", "", fmt.Errorf("bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
|
||||
}
|
||||
con := strings.SplitN(opt, ":", 2)
|
||||
if !validOptions[con[0]] {
|
||||
return "", "", fmt.Errorf("bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
|
||||
}
|
||||
if con[0] == "filetype" {
|
||||
mcon["type"] = con[1]
|
||||
continue
|
||||
}
|
||||
pcon[con[0]] = con[1]
|
||||
if con[0] == "level" || con[0] == "user" {
|
||||
mcon[con[0]] = con[1]
|
||||
}
|
||||
}
|
||||
if pcon.Get() != processLabel {
|
||||
if pcon["level"] != mcsLevel {
|
||||
selinux.ReleaseLabel(processLabel)
|
||||
}
|
||||
processLabel = pcon.Get()
|
||||
selinux.ReserveLabel(processLabel)
|
||||
}
|
||||
mountLabel = mcon.Get()
|
||||
}
|
||||
return processLabel, mountLabel, nil
|
||||
}
|
||||
|
||||
// SetFileLabel modifies the "path" label to the specified file label
|
||||
func SetFileLabel(path string, fileLabel string) error {
|
||||
if !selinux.GetEnabled() || fileLabel == "" {
|
||||
return nil
|
||||
}
|
||||
return selinux.SetFileLabel(path, fileLabel)
|
||||
}
|
||||
|
||||
// SetFileCreateLabel tells the kernel the label for all files to be created
|
||||
func SetFileCreateLabel(fileLabel string) error {
|
||||
if !selinux.GetEnabled() {
|
||||
return nil
|
||||
}
|
||||
return selinux.SetFSCreateLabel(fileLabel)
|
||||
}
|
||||
|
||||
// Relabel changes the label of path and all the entries beneath the path.
|
||||
// It changes the MCS label to s0 if shared is true.
|
||||
// This will allow all containers to share the content.
|
||||
//
|
||||
// The path itself is guaranteed to be relabeled last.
|
||||
func Relabel(path string, fileLabel string, shared bool) error {
|
||||
if !selinux.GetEnabled() || fileLabel == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if shared {
|
||||
c, err := selinux.NewContext(fileLabel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c["level"] = "s0"
|
||||
fileLabel = c.Get()
|
||||
}
|
||||
return selinux.Chcon(path, fileLabel, true)
|
||||
}
|
||||
|
||||
// Validate checks that the label does not include unexpected options
|
||||
func Validate(label string) error {
|
||||
if strings.Contains(label, "z") && strings.Contains(label, "Z") {
|
||||
return ErrIncompatibleLabel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelabelNeeded checks whether the user requested a relabel
|
||||
func RelabelNeeded(label string) bool {
|
||||
return strings.Contains(label, "z") || strings.Contains(label, "Z")
|
||||
}
|
||||
|
||||
// IsShared checks that the label includes a "shared" mark
|
||||
func IsShared(label string) bool {
|
||||
return strings.Contains(label, "z")
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
)
|
||||
|
||||
func needSELinux(t *testing.T) {
|
||||
t.Helper()
|
||||
if !selinux.GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
needSELinux(t)
|
||||
|
||||
var testNull []string
|
||||
_, _, err := InitLabels(testNull)
|
||||
if err != nil {
|
||||
t.Fatalf("InitLabels failed: %v:", err)
|
||||
}
|
||||
testDisabled := []string{"disable"}
|
||||
if selinux.ROFileLabel() == "" {
|
||||
t.Fatal("selinux.ROFileLabel: empty")
|
||||
}
|
||||
plabel, mlabel, err := InitLabels(testDisabled)
|
||||
if err != nil {
|
||||
t.Fatalf("InitLabels(disabled) failed: %v", err)
|
||||
}
|
||||
if plabel != "" {
|
||||
t.Fatalf("InitLabels(disabled): %q not empty", plabel)
|
||||
}
|
||||
if mlabel != "system_u:object_r:container_file_t:s0:c1022,c1023" {
|
||||
t.Fatalf("InitLabels Disabled mlabel Failed, %s", mlabel)
|
||||
}
|
||||
|
||||
testUser := []string{"user:user_u", "role:user_r", "type:user_t", "level:s0:c1,c15"}
|
||||
plabel, mlabel, err = InitLabels(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("InitLabels(user) failed: %v", err)
|
||||
}
|
||||
if plabel != "user_u:user_r:user_t:s0:c1,c15" || (mlabel != "user_u:object_r:container_file_t:s0:c1,c15" && mlabel != "user_u:object_r:svirt_sandbox_file_t:s0:c1,c15") {
|
||||
t.Fatalf("InitLabels(user) failed (plabel=%q, mlabel=%q)", plabel, mlabel)
|
||||
}
|
||||
|
||||
testBadData := []string{"user", "role:user_r", "type:user_t", "level:s0:c1,c15"}
|
||||
if _, _, err = InitLabels(testBadData); err == nil {
|
||||
t.Fatal("InitLabels(bad): expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelabel(t *testing.T) {
|
||||
needSELinux(t)
|
||||
|
||||
testdir := t.TempDir()
|
||||
label := "system_u:object_r:container_file_t:s0:c1,c2"
|
||||
if err := Relabel(testdir, "", true); err != nil {
|
||||
t.Fatalf("Relabel with no label failed: %v", err)
|
||||
}
|
||||
if err := Relabel(testdir, label, true); err != nil {
|
||||
t.Fatalf("Relabel shared failed: %v", err)
|
||||
}
|
||||
if err := Relabel(testdir, label, false); err != nil {
|
||||
t.Fatalf("Relabel unshared failed: %v", err)
|
||||
}
|
||||
if err := Relabel("/etc", label, false); err == nil {
|
||||
t.Fatalf("Relabel /etc succeeded")
|
||||
}
|
||||
if err := Relabel("/", label, false); err == nil {
|
||||
t.Fatalf("Relabel / succeeded")
|
||||
}
|
||||
if err := Relabel("/usr", label, false); err == nil {
|
||||
t.Fatalf("Relabel /usr succeeded")
|
||||
}
|
||||
if err := Relabel("/usr/", label, false); err == nil {
|
||||
t.Fatalf("Relabel /usr/ succeeded")
|
||||
}
|
||||
if err := Relabel("/etc/passwd", label, false); err == nil {
|
||||
t.Fatalf("Relabel /etc/passwd succeeded")
|
||||
}
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
if err := Relabel(home, label, false); err == nil {
|
||||
t.Fatalf("Relabel %s succeeded", home)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
if err := Validate("zZ"); !errors.Is(err, ErrIncompatibleLabel) {
|
||||
t.Fatalf("Expected incompatible error, got %v", err)
|
||||
}
|
||||
if err := Validate("Z"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Validate("z"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Validate(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsShared(t *testing.T) {
|
||||
if shared := IsShared("Z"); shared {
|
||||
t.Fatalf("Expected label `Z` to not be shared, got %v", shared)
|
||||
}
|
||||
if shared := IsShared("z"); !shared {
|
||||
t.Fatalf("Expected label `z` to be shared, got %v", shared)
|
||||
}
|
||||
if shared := IsShared("Zz"); !shared {
|
||||
t.Fatalf("Expected label `Zz` to be shared, got %v", shared)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileLabel(t *testing.T) {
|
||||
needSELinux(t)
|
||||
|
||||
testUser := []string{"filetype:test_file_t", "level:s0:c1,c15"}
|
||||
_, mlabel, err := InitLabels(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("InitLabels(user) failed: %v", err)
|
||||
}
|
||||
if mlabel != "system_u:object_r:test_file_t:s0:c1,c15" {
|
||||
t.Fatalf("InitLabels(filetype) failed: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package label
|
||||
|
||||
// InitLabels returns the process label and file labels to be used within
|
||||
// the container. A list of options can be passed into this function to alter
|
||||
// the labels.
|
||||
func InitLabels([]string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func SetFileLabel(string, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetFileCreateLabel(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Relabel(string, string, bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can disable labeling
|
||||
// support for future container processes
|
||||
func DisableSecOpt() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks that the label does not include unexpected options
|
||||
func Validate(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelabelNeeded checks whether the user requested a relabel
|
||||
func RelabelNeeded(string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsShared checks that the label includes a "shared" mark
|
||||
func IsShared(string) bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package label
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
)
|
||||
|
||||
const testLabel = "system_u:object_r:container_file_t:s0:c1,c2"
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
var testNull []string
|
||||
_, _, err := InitLabels(testNull)
|
||||
if err != nil {
|
||||
t.Log("InitLabels Failed")
|
||||
t.Fatal(err)
|
||||
}
|
||||
testDisabled := []string{"disable"}
|
||||
if selinux.ROFileLabel() != "" {
|
||||
t.Error("selinux.ROFileLabel Failed")
|
||||
}
|
||||
plabel, mlabel, err := InitLabels(testDisabled)
|
||||
if err != nil {
|
||||
t.Log("InitLabels Disabled Failed")
|
||||
t.Fatal(err)
|
||||
}
|
||||
if plabel != "" {
|
||||
t.Fatal("InitLabels Disabled Failed")
|
||||
}
|
||||
if mlabel != "" {
|
||||
t.Fatal("InitLabels Disabled mlabel Failed")
|
||||
}
|
||||
testUser := []string{"user:user_u", "role:user_r", "type:user_t", "level:s0:c1,c15"}
|
||||
_, _, err = InitLabels(testUser)
|
||||
if err != nil {
|
||||
t.Log("InitLabels User Failed")
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelabel(t *testing.T) {
|
||||
if err := Relabel("/etc", testLabel, false); err != nil {
|
||||
t.Fatalf("Relabel /etc succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLabelCompile(t *testing.T) {
|
||||
if _, _, err := InitLabels(nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
if err := SetFileLabel(tmpDir, "foobar"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := SetFileCreateLabel("foobar"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
DisableSecOpt()
|
||||
|
||||
if err := Validate("foobar"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if relabel := RelabelNeeded("foobar"); relabel {
|
||||
t.Fatal("Relabel failed")
|
||||
}
|
||||
if shared := IsShared("foobar"); shared {
|
||||
t.Fatal("isshared failed")
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package label
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFormatMountLabel(t *testing.T) {
|
||||
expected := `context="foobar"`
|
||||
if test := FormatMountLabel("", "foobar"); test != expected {
|
||||
t.Fatalf("Format failed. Expected %s, got %s", expected, test)
|
||||
}
|
||||
|
||||
expected = `src,context="foobar"`
|
||||
if test := FormatMountLabel("src", "foobar"); test != expected {
|
||||
t.Fatalf("Format failed. Expected %s, got %s", expected, test)
|
||||
}
|
||||
|
||||
expected = `src`
|
||||
if test := FormatMountLabel("src", ""); test != expected {
|
||||
t.Fatalf("Format failed. Expected %s, got %s", expected, test)
|
||||
}
|
||||
|
||||
expected = `fscontext="foobar"`
|
||||
if test := FormatMountLabelByType("", "foobar", "fscontext"); test != expected {
|
||||
t.Fatalf("Format failed. Expected %s, got %s", expected, test)
|
||||
}
|
||||
|
||||
expected = `src,fscontext="foobar"`
|
||||
if test := FormatMountLabelByType("src", "foobar", "fscontext"); test != expected {
|
||||
t.Fatalf("Format failed. Expected %s, got %s", expected, test)
|
||||
}
|
||||
|
||||
expected = `src`
|
||||
if test := FormatMountLabelByType("src", "", "rootcontext"); test != expected {
|
||||
t.Fatalf("Format failed. Expected %s, got %s", expected, test)
|
||||
}
|
||||
}
|
||||
-322
@@ -1,322 +0,0 @@
|
||||
package selinux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// Enforcing constant indicate SELinux is in enforcing mode
|
||||
Enforcing = 1
|
||||
// Permissive constant to indicate SELinux is in permissive mode
|
||||
Permissive = 0
|
||||
// Disabled constant to indicate SELinux is disabled
|
||||
Disabled = -1
|
||||
// maxCategory is the maximum number of categories used within containers
|
||||
maxCategory = 1024
|
||||
// DefaultCategoryRange is the upper bound on the category range
|
||||
DefaultCategoryRange = uint32(maxCategory)
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS.
|
||||
ErrMCSAlreadyExists = errors.New("MCS label already exists")
|
||||
// ErrEmptyPath is returned when an empty path has been specified.
|
||||
ErrEmptyPath = errors.New("empty path")
|
||||
|
||||
// ErrInvalidLabel is returned when an invalid label is specified.
|
||||
ErrInvalidLabel = errors.New("invalid Label")
|
||||
|
||||
// InvalidLabel is returned when an invalid label is specified.
|
||||
//
|
||||
// Deprecated: use [ErrInvalidLabel].
|
||||
InvalidLabel = ErrInvalidLabel
|
||||
|
||||
// ErrIncomparable is returned two levels are not comparable
|
||||
ErrIncomparable = errors.New("incomparable levels")
|
||||
// ErrLevelSyntax is returned when a sensitivity or category do not have correct syntax in a level
|
||||
ErrLevelSyntax = errors.New("invalid level syntax")
|
||||
|
||||
// ErrContextMissing is returned if a requested context is not found in a file.
|
||||
ErrContextMissing = errors.New("context does not have a match")
|
||||
// ErrVerifierNil is returned when a context verifier function is nil.
|
||||
ErrVerifierNil = errors.New("verifier function is nil")
|
||||
|
||||
// ErrNotTGLeader is returned by [SetKeyLabel] if the calling thread
|
||||
// is not the thread group leader.
|
||||
ErrNotTGLeader = errors.New("calling thread is not the thread group leader")
|
||||
|
||||
// CategoryRange allows the upper bound on the category range to be adjusted
|
||||
CategoryRange = DefaultCategoryRange
|
||||
|
||||
privContainerMountLabel string
|
||||
)
|
||||
|
||||
// Context is a representation of the SELinux label broken into 4 parts
|
||||
type Context map[string]string
|
||||
|
||||
// SetDisabled disables SELinux support for the package
|
||||
func SetDisabled() {
|
||||
setDisabled()
|
||||
}
|
||||
|
||||
// GetEnabled returns whether SELinux is currently enabled.
|
||||
func GetEnabled() bool {
|
||||
return getEnabled()
|
||||
}
|
||||
|
||||
// ClassIndex returns the int index for an object class in the loaded policy,
|
||||
// or -1 and an error
|
||||
func ClassIndex(class string) (int, error) {
|
||||
return classIndex(class)
|
||||
}
|
||||
|
||||
// SetFileLabel sets the SELinux label for this path, following symlinks,
|
||||
// or returns an error.
|
||||
func SetFileLabel(fpath string, label string) error {
|
||||
return setFileLabel(fpath, label)
|
||||
}
|
||||
|
||||
// LsetFileLabel sets the SELinux label for this path, not following symlinks,
|
||||
// or returns an error.
|
||||
func LsetFileLabel(fpath string, label string) error {
|
||||
return lSetFileLabel(fpath, label)
|
||||
}
|
||||
|
||||
// FileLabel returns the SELinux label for this path, following symlinks,
|
||||
// or returns an error.
|
||||
func FileLabel(fpath string) (string, error) {
|
||||
return fileLabel(fpath)
|
||||
}
|
||||
|
||||
// LfileLabel returns the SELinux label for this path, not following symlinks,
|
||||
// or returns an error.
|
||||
func LfileLabel(fpath string) (string, error) {
|
||||
return lFileLabel(fpath)
|
||||
}
|
||||
|
||||
// SetFSCreateLabel tells the kernel what label to use for all file system objects
|
||||
// created by this task.
|
||||
// Set the label to an empty string to return to the default label. Calls to SetFSCreateLabel
|
||||
// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until file system
|
||||
// objects created by this task are finished to guarantee another goroutine does not migrate
|
||||
// to the current thread before execution is complete.
|
||||
func SetFSCreateLabel(label string) error {
|
||||
return setFSCreateLabel(label)
|
||||
}
|
||||
|
||||
// FSCreateLabel returns the default label the kernel which the kernel is using
|
||||
// for file system objects created by this task. "" indicates default.
|
||||
func FSCreateLabel() (string, error) {
|
||||
return fsCreateLabel()
|
||||
}
|
||||
|
||||
// CurrentLabel returns the SELinux label of the current process thread, or an error.
|
||||
func CurrentLabel() (string, error) {
|
||||
return currentLabel()
|
||||
}
|
||||
|
||||
// PidLabel returns the SELinux label of the given pid, or an error.
|
||||
func PidLabel(pid int) (string, error) {
|
||||
return pidLabel(pid)
|
||||
}
|
||||
|
||||
// ExecLabel returns the SELinux label that the kernel will use for any programs
|
||||
// that are executed by the current process thread, or an error.
|
||||
func ExecLabel() (string, error) {
|
||||
return execLabel()
|
||||
}
|
||||
|
||||
// CanonicalizeContext takes a context string and writes it to the kernel
|
||||
// the function then returns the context that the kernel will use. Use this
|
||||
// function to check if two contexts are equivalent
|
||||
func CanonicalizeContext(val string) (string, error) {
|
||||
return canonicalizeContext(val)
|
||||
}
|
||||
|
||||
// ComputeCreateContext requests the type transition from source to target for
|
||||
// class from the kernel.
|
||||
func ComputeCreateContext(source string, target string, class string) (string, error) {
|
||||
return computeCreateContext(source, target, class)
|
||||
}
|
||||
|
||||
// CalculateGlbLub computes the glb (greatest lower bound) and lub (least upper bound)
|
||||
// of a source and target range.
|
||||
// The glblub is calculated as the greater of the low sensitivities and
|
||||
// the lower of the high sensitivities and the and of each category bitset.
|
||||
func CalculateGlbLub(sourceRange, targetRange string) (string, error) {
|
||||
return calculateGlbLub(sourceRange, targetRange)
|
||||
}
|
||||
|
||||
// SetExecLabel sets the SELinux label that the kernel will use for any programs
|
||||
// that are executed by the current process thread, or an error. Calls to SetExecLabel
|
||||
// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until execution
|
||||
// of the program is finished to guarantee another goroutine does not migrate to the current
|
||||
// thread before execution is complete.
|
||||
func SetExecLabel(label string) error {
|
||||
return writeConThreadSelf("attr/exec", label)
|
||||
}
|
||||
|
||||
// SetTaskLabel sets the SELinux label for the current thread, or an error.
|
||||
// This requires the dyntransition permission. Calls to SetTaskLabel should
|
||||
// be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() to guarantee
|
||||
// the current thread does not run in a new mislabeled thread.
|
||||
func SetTaskLabel(label string) error {
|
||||
return writeConThreadSelf("attr/current", label)
|
||||
}
|
||||
|
||||
// SetSocketLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next socket that gets created. Calls to SetSocketLabel
|
||||
// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until
|
||||
// the socket is created to guarantee another goroutine does not migrate
|
||||
// to the current thread before execution is complete.
|
||||
func SetSocketLabel(label string) error {
|
||||
return writeConThreadSelf("attr/sockcreate", label)
|
||||
}
|
||||
|
||||
// SocketLabel retrieves the current socket label setting
|
||||
func SocketLabel() (string, error) {
|
||||
return readConThreadSelf("attr/sockcreate")
|
||||
}
|
||||
|
||||
// PeerLabel retrieves the label of the client on the other side of a socket
|
||||
func PeerLabel(fd uintptr) (string, error) {
|
||||
return peerLabel(fd)
|
||||
}
|
||||
|
||||
// SetKeyLabel takes a process label and tells the kernel to assign the
|
||||
// label to the next kernel keyring that gets created.
|
||||
//
|
||||
// Calls to SetKeyLabel should be wrapped in
|
||||
// runtime.LockOSThread()/runtime.UnlockOSThread() until the kernel keyring is
|
||||
// created to guarantee another goroutine does not migrate to the current
|
||||
// thread before execution is complete.
|
||||
//
|
||||
// Only the thread group leader can set key label.
|
||||
func SetKeyLabel(label string) error {
|
||||
return setKeyLabel(label)
|
||||
}
|
||||
|
||||
// KeyLabel retrieves the current kernel keyring label setting
|
||||
func KeyLabel() (string, error) {
|
||||
return keyLabel()
|
||||
}
|
||||
|
||||
// Get returns the Context as a string
|
||||
func (c Context) Get() string {
|
||||
return c.get()
|
||||
}
|
||||
|
||||
// NewContext creates a new Context struct from the specified label
|
||||
func NewContext(label string) (Context, error) {
|
||||
return newContext(label)
|
||||
}
|
||||
|
||||
// ClearLabels clears all reserved labels
|
||||
func ClearLabels() {
|
||||
clearLabels()
|
||||
}
|
||||
|
||||
// ReserveLabel reserves the MLS/MCS level component of the specified label
|
||||
func ReserveLabel(label string) {
|
||||
reserveLabel(label)
|
||||
}
|
||||
|
||||
// MLSEnabled checks if MLS is enabled.
|
||||
func MLSEnabled() bool {
|
||||
return isMLSEnabled()
|
||||
}
|
||||
|
||||
// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled
|
||||
func EnforceMode() int {
|
||||
return enforceMode()
|
||||
}
|
||||
|
||||
// SetEnforceMode sets the current SELinux mode Enforcing, Permissive.
|
||||
// Disabled is not valid, since this needs to be set at boot time.
|
||||
func SetEnforceMode(mode int) error {
|
||||
return setEnforceMode(mode)
|
||||
}
|
||||
|
||||
// DefaultEnforceMode returns the systems default SELinux mode Enforcing,
|
||||
// Permissive or Disabled. Note this is just the default at boot time.
|
||||
// EnforceMode tells you the systems current mode.
|
||||
func DefaultEnforceMode() int {
|
||||
return defaultEnforceMode()
|
||||
}
|
||||
|
||||
// ReleaseLabel un-reserves the MLS/MCS Level field of the specified label,
|
||||
// allowing it to be used by another process.
|
||||
func ReleaseLabel(label string) {
|
||||
releaseLabel(label)
|
||||
}
|
||||
|
||||
// ROFileLabel returns the specified SELinux readonly file label
|
||||
func ROFileLabel() string {
|
||||
return roFileLabel()
|
||||
}
|
||||
|
||||
// KVMContainerLabels returns the default processLabel and mountLabel to be used
|
||||
// for kvm containers by the calling process.
|
||||
func KVMContainerLabels() (string, string) {
|
||||
return kvmContainerLabels()
|
||||
}
|
||||
|
||||
// InitContainerLabels returns the default processLabel and file labels to be
|
||||
// used for containers running an init system like systemd by the calling process.
|
||||
func InitContainerLabels() (string, string) {
|
||||
return initContainerLabels()
|
||||
}
|
||||
|
||||
// ContainerLabels returns an allocated processLabel and fileLabel to be used for
|
||||
// container labeling by the calling process.
|
||||
func ContainerLabels() (processLabel string, fileLabel string) {
|
||||
return containerLabels()
|
||||
}
|
||||
|
||||
// SecurityCheckContext validates that the SELinux label is understood by the kernel
|
||||
func SecurityCheckContext(val string) error {
|
||||
return securityCheckContext(val)
|
||||
}
|
||||
|
||||
// CopyLevel returns a label with the MLS/MCS level from src label replaced on
|
||||
// the dest label.
|
||||
func CopyLevel(src, dest string) (string, error) {
|
||||
return copyLevel(src, dest)
|
||||
}
|
||||
|
||||
// Chcon changes the fpath file object to the SELinux label.
|
||||
// If fpath is a directory and recurse is true, then Chcon walks the
|
||||
// directory tree setting the label.
|
||||
//
|
||||
// The fpath itself is guaranteed to be relabeled last.
|
||||
func Chcon(fpath string, label string, recurse bool) error {
|
||||
return chcon(fpath, label, recurse)
|
||||
}
|
||||
|
||||
// DupSecOpt takes an SELinux process label and returns security options that
|
||||
// can be used to set the SELinux Type and Level for future container processes.
|
||||
func DupSecOpt(src string) ([]string, error) {
|
||||
return dupSecOpt(src)
|
||||
}
|
||||
|
||||
// DisableSecOpt returns a security opt that can be used to disable SELinux
|
||||
// labeling support for future container processes.
|
||||
func DisableSecOpt() []string {
|
||||
return []string{"disable"}
|
||||
}
|
||||
|
||||
// GetDefaultContextWithLevel gets a single context for the specified SELinux user
|
||||
// identity that is reachable from the specified scon context. The context is based
|
||||
// on the per-user /etc/selinux/{SELINUXTYPE}/contexts/users/<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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,711 +0,0 @@
|
||||
package selinux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestSetFileLabel(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
const (
|
||||
tmpFile = "selinux_test"
|
||||
tmpLink = "selinux_test_link"
|
||||
con = "system_u:object_r:bin_t:s0:c1,c2"
|
||||
con2 = "system_u:object_r:bin_t:s0:c3,c4"
|
||||
)
|
||||
|
||||
_ = os.Remove(tmpFile)
|
||||
out, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out.Close()
|
||||
defer os.Remove(tmpFile)
|
||||
|
||||
_ = os.Remove(tmpLink)
|
||||
if err := os.Symlink(tmpFile, tmpLink); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpLink)
|
||||
|
||||
if err := SetFileLabel(tmpLink, con); err != nil {
|
||||
t.Fatalf("SetFileLabel failed: %s", err)
|
||||
}
|
||||
filelabel, err := FileLabel(tmpLink)
|
||||
if err != nil {
|
||||
t.Fatalf("FileLabel failed: %s", err)
|
||||
}
|
||||
if filelabel != con {
|
||||
t.Fatalf("FileLabel failed, returned %s expected %s", filelabel, con)
|
||||
}
|
||||
|
||||
// Using LfileLabel to verify that the symlink itself is not labeled.
|
||||
linkLabel, err := LfileLabel(tmpLink)
|
||||
if err != nil {
|
||||
t.Fatalf("LfileLabel failed: %s", err)
|
||||
}
|
||||
if linkLabel == con {
|
||||
t.Fatalf("Label on symlink should not be set, got: %q", linkLabel)
|
||||
}
|
||||
|
||||
// Use LsetFileLabel to set a label on the symlink itself.
|
||||
if err := LsetFileLabel(tmpLink, con2); err != nil {
|
||||
t.Fatalf("LsetFileLabel failed: %s", err)
|
||||
}
|
||||
filelabel, err = FileLabel(tmpFile)
|
||||
if err != nil {
|
||||
t.Fatalf("FileLabel failed: %s", err)
|
||||
}
|
||||
if filelabel != con {
|
||||
t.Fatalf("FileLabel was updated, returned %s expected %s", filelabel, con)
|
||||
}
|
||||
|
||||
linkLabel, err = LfileLabel(tmpLink)
|
||||
if err != nil {
|
||||
t.Fatalf("LfileLabel failed: %s", err)
|
||||
}
|
||||
if linkLabel != con2 {
|
||||
t.Fatalf("LfileLabel failed: returned %s expected %s", linkLabel, con2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVMLabels(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
plabel, flabel := KVMContainerLabels()
|
||||
if plabel == "" {
|
||||
t.Log("Failed to read kvm label")
|
||||
}
|
||||
t.Log(plabel)
|
||||
t.Log(flabel)
|
||||
if _, err := CanonicalizeContext(plabel); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := CanonicalizeContext(flabel); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ReleaseLabel(plabel)
|
||||
}
|
||||
|
||||
func TestInitLabels(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
plabel, flabel := InitContainerLabels()
|
||||
if plabel == "" {
|
||||
t.Log("Failed to read init label")
|
||||
}
|
||||
t.Log(plabel)
|
||||
t.Log(flabel)
|
||||
if _, err := CanonicalizeContext(plabel); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := CanonicalizeContext(flabel); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ReleaseLabel(plabel)
|
||||
}
|
||||
|
||||
func TestDuplicateLabel(t *testing.T) {
|
||||
secopt, err := DupSecOpt("system_u:system_r:container_t:s0:c1,c2")
|
||||
if err != nil {
|
||||
t.Fatalf("DupSecOpt: %v", err)
|
||||
}
|
||||
for _, opt := range secopt {
|
||||
con := strings.SplitN(opt, ":", 2)
|
||||
if con[0] == "user" {
|
||||
if con[1] != "system_u" {
|
||||
t.Errorf("DupSecOpt Failed user incorrect")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if con[0] == "role" {
|
||||
if con[1] != "system_r" {
|
||||
t.Errorf("DupSecOpt Failed role incorrect")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if con[0] == "type" {
|
||||
if con[1] != "container_t" {
|
||||
t.Errorf("DupSecOpt Failed type incorrect")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if con[0] == "level" {
|
||||
if con[1] != "s0:c1,c2" {
|
||||
t.Errorf("DupSecOpt Failed level incorrect")
|
||||
}
|
||||
continue
|
||||
}
|
||||
t.Errorf("DupSecOpt failed: invalid field %q", con[0])
|
||||
}
|
||||
secopt = DisableSecOpt()
|
||||
if secopt[0] != "disable" {
|
||||
t.Errorf(`DisableSecOpt failed: want "disable", got %q`, secopt[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSELinuxNoLevel(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
tlabel := "system_u:system_r:container_t"
|
||||
dup, err := DupSecOpt(tlabel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(dup) != 3 {
|
||||
t.Errorf("DupSecOpt failed on non mls label: want 3, got %d", len(dup))
|
||||
}
|
||||
con, err := NewContext(tlabel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if con.Get() != tlabel {
|
||||
t.Errorf("NewContext and con.Get() failed on non mls label: want %q, got %q", tlabel, con.Get())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSocketLabel(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
// Ensure the thread stays the same for duration of the test.
|
||||
// Otherwise Go runtime can switch this to a different thread,
|
||||
// which results in EACCES in call to SetSocketLabel.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
label := "system_u:object_r:container_t:s0:c1,c2"
|
||||
if err := SetSocketLabel(label); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nlabel, err := SocketLabel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if label != nlabel {
|
||||
t.Errorf("SocketLabel %s != %s", nlabel, label)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyLabel(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
// Ensure the thread stays the same for duration of the test.
|
||||
// Otherwise Go runtime can switch this to a different thread,
|
||||
// which results in EACCES in call to SetKeyLabel.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if unix.Getpid() != unix.Gettid() {
|
||||
t.Skip(ErrNotTGLeader)
|
||||
}
|
||||
|
||||
label := "system_u:object_r:container_t:s0:c1,c2"
|
||||
if err := SetKeyLabel(label); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nlabel, err := KeyLabel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if label != nlabel {
|
||||
t.Errorf("KeyLabel: want %q, got %q", label, nlabel)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContextGet(b *testing.B) {
|
||||
ctx, err := NewContext("system_u:object_r:container_file_t:s0:c1022,c1023")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
str := ""
|
||||
for i := 0; i < b.N; i++ {
|
||||
str = ctx.get()
|
||||
}
|
||||
b.Log(str)
|
||||
}
|
||||
|
||||
func TestSELinux(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
// Ensure the thread stays the same for duration of the test.
|
||||
// Otherwise Go runtime can switch this to a different thread,
|
||||
// which results in EACCES in call to SetFSCreateLabel.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
var (
|
||||
err error
|
||||
plabel, flabel string
|
||||
)
|
||||
|
||||
plabel, flabel = ContainerLabels()
|
||||
t.Log(plabel)
|
||||
t.Log(flabel)
|
||||
plabel, flabel = ContainerLabels()
|
||||
t.Log(plabel)
|
||||
t.Log(flabel)
|
||||
ReleaseLabel(plabel)
|
||||
|
||||
plabel, flabel = ContainerLabels()
|
||||
t.Log(plabel)
|
||||
t.Log(flabel)
|
||||
ClearLabels()
|
||||
t.Log("ClearLabels")
|
||||
plabel, flabel = ContainerLabels()
|
||||
t.Log(plabel)
|
||||
t.Log(flabel)
|
||||
ReleaseLabel(plabel)
|
||||
|
||||
pid := os.Getpid()
|
||||
t.Logf("PID:%d MCS:%s", pid, intToMcs(pid, 1023))
|
||||
err = SetFSCreateLabel("unconfined_u:unconfined_r:unconfined_t:s0")
|
||||
if err != nil {
|
||||
t.Fatal("SetFSCreateLabel failed:", err)
|
||||
}
|
||||
t.Log(FSCreateLabel())
|
||||
err = SetFSCreateLabel("")
|
||||
if err != nil {
|
||||
t.Fatal("SetFSCreateLabel failed:", err)
|
||||
}
|
||||
t.Log(FSCreateLabel())
|
||||
t.Log(PidLabel(1))
|
||||
}
|
||||
|
||||
func TestSetEnforceMode(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("root required, skipping")
|
||||
}
|
||||
|
||||
t.Log("Enforcing Mode:", EnforceMode())
|
||||
mode := DefaultEnforceMode()
|
||||
t.Log("Default Enforce Mode:", mode)
|
||||
defer func() {
|
||||
_ = SetEnforceMode(mode)
|
||||
}()
|
||||
|
||||
if err := SetEnforceMode(Enforcing); err != nil {
|
||||
t.Fatalf("setting selinux mode to enforcing failed: %v", err)
|
||||
}
|
||||
if err := SetEnforceMode(Permissive); err != nil {
|
||||
t.Fatalf("setting selinux mode to permissive failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalizeContext(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
con := "system_u:object_r:bin_t:s0:c1,c2,c3"
|
||||
checkcon := "system_u:object_r:bin_t:s0:c1.c3"
|
||||
newcon, err := CanonicalizeContext(con)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if newcon != checkcon {
|
||||
t.Fatalf("CanonicalizeContext(%s) returned %s expected %s", con, newcon, checkcon)
|
||||
}
|
||||
con = "system_u:object_r:bin_t:s0:c5,c2"
|
||||
checkcon = "system_u:object_r:bin_t:s0:c2,c5"
|
||||
newcon, err = CanonicalizeContext(con)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if newcon != checkcon {
|
||||
t.Fatalf("CanonicalizeContext(%s) returned %s expected %s", con, newcon, checkcon)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSELinuxfsInMountinfo(t *testing.T) {
|
||||
//nolint:dupword // ignore duplicate words (sysfs sysfs)
|
||||
const mountinfo = `18 62 0:17 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sysfs rw,seclabel
|
||||
19 62 0:3 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
|
||||
20 62 0:5 / /dev rw,nosuid shared:2 - devtmpfs devtmpfs rw,seclabel,size=3995472k,nr_inodes=998868,mode=755
|
||||
21 18 0:16 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw
|
||||
22 20 0:18 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw,seclabel
|
||||
23 20 0:11 / /dev/pts rw,nosuid,noexec,relatime shared:4 - devpts devpts rw,seclabel,gid=5,mode=620,ptmxmode=000
|
||||
24 62 0:19 / /run rw,nosuid,nodev shared:23 - tmpfs tmpfs rw,seclabel,mode=755
|
||||
25 18 0:20 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:8 - tmpfs tmpfs ro,seclabel,mode=755
|
||||
26 25 0:21 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:9 - cgroup cgroup rw,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd
|
||||
27 18 0:22 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:20 - pstore pstore rw
|
||||
28 25 0:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,perf_event
|
||||
29 25 0:24 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,devices
|
||||
30 25 0:25 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:12 - cgroup cgroup rw,cpuacct,cpu
|
||||
31 25 0:26 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,freezer
|
||||
32 25 0:27 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,net_prio,net_cls
|
||||
33 25 0:28 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset
|
||||
34 25 0:29 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,memory
|
||||
35 25 0:30 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,pids
|
||||
36 25 0:31 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,hugetlb
|
||||
37 25 0:32 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,blkio
|
||||
59 18 0:33 / /sys/kernel/config rw,relatime shared:21 - configfs configfs rw
|
||||
62 1 253:1 / / rw,relatime shared:1 - ext4 /dev/vda1 rw,seclabel,data=ordered
|
||||
38 18 0:15 / /sys/fs/selinux rw,relatime shared:22 - selinuxfs selinuxfs rw
|
||||
39 19 0:35 / /proc/sys/fs/binfmt_misc rw,relatime shared:24 - autofs systemd-1 rw,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=11601
|
||||
40 20 0:36 / /dev/hugepages rw,relatime shared:25 - hugetlbfs hugetlbfs rw,seclabel
|
||||
41 20 0:14 / /dev/mqueue rw,relatime shared:26 - mqueue mqueue rw,seclabel
|
||||
42 18 0:6 / /sys/kernel/debug rw,relatime shared:27 - debugfs debugfs rw
|
||||
112 62 253:1 /var/lib/docker/plugins /var/lib/docker/plugins rw,relatime - ext4 /dev/vda1 rw,seclabel,data=ordered
|
||||
115 62 253:1 /var/lib/docker/overlay2 /var/lib/docker/overlay2 rw,relatime - ext4 /dev/vda1 rw,seclabel,data=ordered
|
||||
118 62 7:0 / /root/mnt rw,relatime shared:66 - ext4 /dev/loop0 rw,seclabel,data=ordered
|
||||
121 115 0:38 / /var/lib/docker/overlay2/8cdbabf81bc89b14ea54eaf418c1922068f06917fff57e184aa26541ff291073/merged rw,relatime - overlay overlay rw,seclabel,lowerdir=/var/lib/docker/overlay2/l/CPD4XI7UD4GGTGSJVPQSHWZKTK:/var/lib/docker/overlay2/l/NQKORR3IS7KNQDER35AZECLH4Z,upperdir=/var/lib/docker/overlay2/8cdbabf81bc89b14ea54eaf418c1922068f06917fff57e184aa26541ff291073/diff,workdir=/var/lib/docker/overlay2/8cdbabf81bc89b14ea54eaf418c1922068f06917fff57e184aa26541ff291073/work
|
||||
125 62 0:39 / /var/lib/docker/containers/5e3fce422957c291a5b502c2cf33d512fc1fcac424e4113136c808360e5b7215/shm rw,nosuid,nodev,noexec,relatime shared:68 - tmpfs shm rw,seclabel,size=65536k
|
||||
186 24 0:3 / /run/docker/netns/0a08e7496c6d rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
|
||||
130 62 0:15 / /root/chroot/selinux rw,relatime shared:22 - selinuxfs selinuxfs rw
|
||||
109 24 0:37 / /run/user/0 rw,nosuid,nodev,relatime shared:62 - tmpfs tmpfs rw,seclabel,size=801032k,mode=700
|
||||
`
|
||||
s := bufio.NewScanner(bytes.NewBuffer([]byte(mountinfo)))
|
||||
for _, expected := range []string{"/sys/fs/selinux", "/root/chroot/selinux", ""} {
|
||||
mnt := findSELinuxfsMount(s)
|
||||
t.Logf("found %q", mnt)
|
||||
if mnt != expected {
|
||||
t.Fatalf("expected %q, got %q", expected, mnt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityCheckContext(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
// check with valid context
|
||||
context, err := CurrentLabel()
|
||||
if err != nil {
|
||||
t.Fatalf("CurrentLabel() error: %v", err)
|
||||
}
|
||||
if context != "" {
|
||||
t.Logf("SecurityCheckContext(%q)", context)
|
||||
err = SecurityCheckContext(context)
|
||||
if err != nil {
|
||||
t.Errorf("SecurityCheckContext(%q) error: %v", context, err)
|
||||
}
|
||||
}
|
||||
|
||||
context = "not-syntactically-valid"
|
||||
err = SecurityCheckContext(context)
|
||||
if err == nil {
|
||||
t.Errorf("SecurityCheckContext(%q) succeeded, expected to fail", context)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassIndex(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
idx, err := ClassIndex("process")
|
||||
if err != nil {
|
||||
t.Errorf("Classindex error: %v", err)
|
||||
}
|
||||
// Every known policy has process as index 2, but it isn't guaranteed
|
||||
if idx != 2 {
|
||||
t.Errorf("ClassIndex unexpected answer %d, possibly not reference policy", idx)
|
||||
}
|
||||
|
||||
_, err = ClassIndex("foobar")
|
||||
if err == nil {
|
||||
t.Errorf("ClassIndex(\"foobar\") succeeded, expected to fail:")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeCreateContext(t *testing.T) {
|
||||
if !GetEnabled() {
|
||||
t.Skip("SELinux not enabled, skipping.")
|
||||
}
|
||||
|
||||
// This may or may not be in the loaded policy but any refpolicy based policy should have it
|
||||
init := "system_u:system_r:init_t:s0"
|
||||
tmp := "system_u:object_r:tmp_t:s0"
|
||||
file := "file"
|
||||
t.Logf("ComputeCreateContext(%s, %s, %s)", init, tmp, file)
|
||||
context, err := ComputeCreateContext(init, tmp, file)
|
||||
if err != nil {
|
||||
t.Errorf("ComputeCreateContext error: %v", err)
|
||||
}
|
||||
if context != "system_u:object_r:init_tmp_t:s0" {
|
||||
t.Errorf("ComputeCreateContext unexpected answer %s, possibly not reference policy", context)
|
||||
}
|
||||
|
||||
badcon := "badcon"
|
||||
process := "process"
|
||||
// Test to ensure that a bad context returns an error
|
||||
t.Logf("ComputeCreateContext(%s, %s, %s)", badcon, tmp, process)
|
||||
_, err = ComputeCreateContext(badcon, tmp, process)
|
||||
if err == nil {
|
||||
t.Errorf("ComputeCreateContext(%s, %s, %s) succeeded, expected failure", badcon, tmp, process)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlbLub(t *testing.T) {
|
||||
tests := []struct {
|
||||
expectedErr error
|
||||
sourceRange string
|
||||
targetRange string
|
||||
expectedRange string
|
||||
}{
|
||||
{
|
||||
sourceRange: "s0:c0.c100-s10:c0.c150",
|
||||
targetRange: "s5:c50.c100-s15:c0.c149",
|
||||
expectedRange: "s5:c50.c100-s10:c0.c149",
|
||||
},
|
||||
{
|
||||
sourceRange: "s5:c50.c100-s15:c0.c149",
|
||||
targetRange: "s0:c0.c100-s10:c0.c150",
|
||||
expectedRange: "s5:c50.c100-s10:c0.c149",
|
||||
},
|
||||
{
|
||||
sourceRange: "s0:c0.c100-s10:c0.c150",
|
||||
targetRange: "s0",
|
||||
expectedRange: "s0",
|
||||
},
|
||||
{
|
||||
sourceRange: "s6:c0.c1023",
|
||||
targetRange: "s6:c0,c2,c11,c201.c429,c431.c511",
|
||||
expectedRange: "s6:c0,c2,c11,c201.c429,c431.c511",
|
||||
},
|
||||
{
|
||||
sourceRange: "s0-s15:c0.c1023",
|
||||
targetRange: "s6:c0,c2,c11,c201.c429,c431.c511",
|
||||
expectedRange: "s6-s6:c0,c2,c11,c201.c429,c431.c511",
|
||||
},
|
||||
{
|
||||
sourceRange: "s0:c0.c100,c125,c140,c150-s10",
|
||||
targetRange: "s4:c0.c50,c140",
|
||||
expectedRange: "s4:c0.c50,c140-s4",
|
||||
},
|
||||
{
|
||||
sourceRange: "s5:c512.c550,c552.c1023-s5:c0.c550,c552.c1023",
|
||||
targetRange: "s5:c512.c550,c553.c1023-s5:c0,c1,c4,c5,c6,c512.c550,c553.c1023",
|
||||
expectedRange: "s5:c512.c550,c553.c1023-s5:c0,c1,c4.c6,c512.c550,c553.c1023",
|
||||
},
|
||||
{
|
||||
sourceRange: "s5:c512.c540,c542,c543,c552.c1023-s5:c0.c550,c552.c1023",
|
||||
targetRange: "s5:c512.c550,c553.c1023-s5:c0,c1,c4,c5,c6,c512.c550,c553.c1023",
|
||||
expectedRange: "s5:c512.c540,c542,c543,c553.c1023-s5:c0,c1,c4.c6,c512.c550,c553.c1023",
|
||||
},
|
||||
{
|
||||
sourceRange: "s5:c50.c100-s15:c0.c149",
|
||||
targetRange: "s5:c512.c550,c552.c1023-s5:c0.c550,c552.c1023",
|
||||
expectedRange: "s5-s5:c0.c149",
|
||||
},
|
||||
{
|
||||
sourceRange: "s5-s15",
|
||||
targetRange: "s6-s7",
|
||||
expectedRange: "s6-s7",
|
||||
},
|
||||
{
|
||||
sourceRange: "s5:c50.c100-s15:c0.c149",
|
||||
targetRange: "s4-s4:c0.c1023",
|
||||
expectedErr: ErrIncomparable,
|
||||
},
|
||||
{
|
||||
sourceRange: "s4-s4:c0.c1023",
|
||||
targetRange: "s5:c50.c100-s15:c0.c149",
|
||||
expectedErr: ErrIncomparable,
|
||||
},
|
||||
{
|
||||
sourceRange: "s4-s4:c0.c1023.c10000",
|
||||
targetRange: "s5:c50.c100-s15:c0.c149",
|
||||
expectedErr: strconv.ErrSyntax,
|
||||
},
|
||||
{
|
||||
sourceRange: "s4-s4:c0.c1023.c10000-s4",
|
||||
targetRange: "s5:c50.c100-s15:c0.c149-s5",
|
||||
expectedErr: strconv.ErrSyntax,
|
||||
},
|
||||
{
|
||||
sourceRange: "4-4",
|
||||
targetRange: "s5:c50.c100-s15:c0.c149",
|
||||
expectedErr: ErrLevelSyntax,
|
||||
},
|
||||
{
|
||||
sourceRange: "t4-t4",
|
||||
targetRange: "s5:c50.c100-s15:c0.c149",
|
||||
expectedErr: ErrLevelSyntax,
|
||||
},
|
||||
{
|
||||
sourceRange: "s5:x50.x100-s15:c0.c149",
|
||||
targetRange: "s5:c50.c100-s15:c0.c149",
|
||||
expectedErr: ErrLevelSyntax,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, err := CalculateGlbLub(tt.sourceRange, tt.targetRange)
|
||||
if !errors.Is(err, tt.expectedErr) {
|
||||
// Go 1.13 strconv errors are not unwrappable,
|
||||
// so do that manually.
|
||||
// TODO remove this once we stop supporting Go 1.13.
|
||||
var numErr *strconv.NumError
|
||||
if errors.As(err, &numErr) && numErr.Err == tt.expectedErr { //nolint:errorlint // see above
|
||||
continue
|
||||
}
|
||||
t.Fatalf("want %q got %q: src: %q tgt: %q", tt.expectedErr, err, tt.sourceRange, tt.targetRange)
|
||||
}
|
||||
|
||||
if got != tt.expectedRange {
|
||||
t.Errorf("want %q got %q", tt.expectedRange, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextWithLevel(t *testing.T) {
|
||||
want := "bob:sysadm_r:sysadm_t:SystemLow-SystemHigh"
|
||||
|
||||
goodDefaultBuff := `
|
||||
foo_r:foo_t:s0 sysadm_r:sysadm_t:s0
|
||||
staff_r:staff_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0
|
||||
`
|
||||
|
||||
verifier := func(con string) error {
|
||||
if con != want {
|
||||
return fmt.Errorf("invalid context %s", con)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name, userBuff, defaultBuff string
|
||||
}{
|
||||
{
|
||||
name: "match exists in user context file",
|
||||
userBuff: `# COMMENT
|
||||
foo_r:foo_t:s0 sysadm_r:sysadm_t:s0
|
||||
|
||||
staff_r:staff_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0
|
||||
`,
|
||||
defaultBuff: goodDefaultBuff,
|
||||
},
|
||||
{
|
||||
name: "match exists in default context file, but not in user file",
|
||||
userBuff: `# COMMENT
|
||||
foo_r:foo_t:s0 sysadm_r:sysadm_t:s0
|
||||
fake_r:fake_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0
|
||||
`,
|
||||
defaultBuff: goodDefaultBuff,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := defaultSECtx{
|
||||
user: "bob",
|
||||
level: "SystemLow-SystemHigh",
|
||||
scon: "system_u:staff_r:staff_t:s0",
|
||||
userRdr: bytes.NewBufferString(tt.userBuff),
|
||||
defaultRdr: bytes.NewBufferString(tt.defaultBuff),
|
||||
verifier: verifier,
|
||||
}
|
||||
|
||||
got, err := getDefaultContextFromReaders(&c)
|
||||
if err != nil {
|
||||
t.Fatalf("err should not exist but is: %v", err)
|
||||
}
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("got context: %q but expected %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("no match in user or default context files", func(t *testing.T) {
|
||||
badUserBuff := ""
|
||||
|
||||
badDefaultBuff := `
|
||||
foo_r:foo_t:s0 sysadm_r:sysadm_t:s0
|
||||
dne_r:dne_t:s0 baz_r:baz_t:s0 sysadm_r:sysadm_t:s0
|
||||
`
|
||||
c := defaultSECtx{
|
||||
user: "bob",
|
||||
level: "SystemLow-SystemHigh",
|
||||
scon: "system_u:staff_r:staff_t:s0",
|
||||
userRdr: bytes.NewBufferString(badUserBuff),
|
||||
defaultRdr: bytes.NewBufferString(badDefaultBuff),
|
||||
verifier: verifier,
|
||||
}
|
||||
|
||||
_, err := getDefaultContextFromReaders(&c)
|
||||
if err == nil {
|
||||
t.Fatalf("err was expected")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkChcon(b *testing.B) {
|
||||
file, err := filepath.Abs(os.Args[0])
|
||||
if err != nil {
|
||||
b.Fatalf("filepath.Abs: %v", err)
|
||||
}
|
||||
dir := filepath.Dir(file)
|
||||
con, err := FileLabel(file)
|
||||
if err != nil {
|
||||
b.Fatalf("FileLabel(%q): %v", file, err)
|
||||
}
|
||||
b.Logf("Chcon(%q, %q)", dir, con)
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
if err := Chcon(dir, con, true); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCurrentLabel(b *testing.B) {
|
||||
var (
|
||||
l string
|
||||
err error
|
||||
)
|
||||
for n := 0; n < b.N; n++ {
|
||||
l, err = CurrentLabel()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.Log(l)
|
||||
}
|
||||
|
||||
func BenchmarkReadConfig(b *testing.B) {
|
||||
str := ""
|
||||
for n := 0; n < b.N; n++ {
|
||||
str = readConfig(selinuxTypeTag)
|
||||
}
|
||||
b.Log(str)
|
||||
}
|
||||
|
||||
func BenchmarkLoadLabels(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
loadLabels()
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package selinux
|
||||
|
||||
func attrPath(string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func readConThreadSelf(string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func writeConThreadSelf(string, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDisabled() {}
|
||||
|
||||
func getEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func classIndex(string) (int, error) {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func setFileLabel(string, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func lSetFileLabel(string, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileLabel(string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func lFileLabel(string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func setFSCreateLabel(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fsCreateLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func currentLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func pidLabel(int) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func execLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func canonicalizeContext(string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func computeCreateContext(string, string, string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func calculateGlbLub(string, string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func peerLabel(uintptr) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func setKeyLabel(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyLabel() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (c Context) get() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func newContext(string) (Context, error) {
|
||||
return Context{}, nil
|
||||
}
|
||||
|
||||
func clearLabels() {
|
||||
}
|
||||
|
||||
func reserveLabel(string) {
|
||||
}
|
||||
|
||||
func isMLSEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func enforceMode() int {
|
||||
return Disabled
|
||||
}
|
||||
|
||||
func setEnforceMode(int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultEnforceMode() int {
|
||||
return Disabled
|
||||
}
|
||||
|
||||
func releaseLabel(string) {
|
||||
}
|
||||
|
||||
func roFileLabel() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func kvmContainerLabels() (string, string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func initContainerLabels() (string, string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func containerLabels() (string, string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func securityCheckContext(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyLevel(string, string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func chcon(string, string, bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dupSecOpt(string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getDefaultContextWithLevel(string, string, string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func label(_ string) string {
|
||||
return ""
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package selinux
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testLabel = "foobar"
|
||||
|
||||
func TestSELinuxStubs(t *testing.T) {
|
||||
if GetEnabled() {
|
||||
t.Error("SELinux enabled on non-linux.")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
if _, err := FileLabel(tmpDir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := SetFileLabel(tmpDir, testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := LfileLabel(tmpDir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := LsetFileLabel(tmpDir, testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := SetFSCreateLabel(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := FSCreateLabel(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := CurrentLabel(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := PidLabel(0); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ClearLabels()
|
||||
|
||||
ReserveLabel(testLabel)
|
||||
ReleaseLabel(testLabel)
|
||||
if _, err := DupSecOpt(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if v := DisableSecOpt(); len(v) != 1 || v[0] != "disable" {
|
||||
t.Errorf(`expected "disabled", got %v`, v)
|
||||
}
|
||||
SetDisabled()
|
||||
if enabled := GetEnabled(); enabled {
|
||||
t.Error("Should not be enabled")
|
||||
}
|
||||
if err := SetExecLabel(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := SetTaskLabel(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := ExecLabel(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := CanonicalizeContext(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := ComputeCreateContext("foo", "bar", testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := SetSocketLabel(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := ClassIndex(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := SocketLabel(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := PeerLabel(0); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := SetKeyLabel(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := KeyLabel(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := SetExecLabel(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := ExecLabel(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
con, err := NewContext(testLabel)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
con.Get()
|
||||
if err = SetEnforceMode(1); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if v := DefaultEnforceMode(); v != Disabled {
|
||||
t.Errorf("expected %d, got %d", Disabled, v)
|
||||
}
|
||||
if v := EnforceMode(); v != Disabled {
|
||||
t.Errorf("expected %d, got %d", Disabled, v)
|
||||
}
|
||||
if v := ROFileLabel(); v != "" {
|
||||
t.Errorf(`expected "", got %q`, v)
|
||||
}
|
||||
if processLbl, fileLbl := ContainerLabels(); processLbl != "" || fileLbl != "" {
|
||||
t.Errorf(`expected fileLbl="", fileLbl="" got processLbl=%q, fileLbl=%q`, processLbl, fileLbl)
|
||||
}
|
||||
if err = SecurityCheckContext(testLabel); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err = CopyLevel("foo", "bar"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package selinux
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// lgetxattr returns a []byte slice containing the value of
|
||||
// an extended attribute attr set for path.
|
||||
func lgetxattr(path, attr string) ([]byte, error) {
|
||||
// Start with a 128 length byte array
|
||||
dest := make([]byte, 128)
|
||||
sz, errno := doLgetxattr(path, attr, dest)
|
||||
for errno == unix.ERANGE { //nolint:errorlint // unix errors are bare
|
||||
// Buffer too small, use zero-sized buffer to get the actual size
|
||||
sz, errno = doLgetxattr(path, attr, []byte{})
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
dest = make([]byte, sz)
|
||||
sz, errno = doLgetxattr(path, attr, dest)
|
||||
}
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return dest[:sz], nil
|
||||
}
|
||||
|
||||
// doLgetxattr is a wrapper that retries on EINTR
|
||||
func doLgetxattr(path, attr string, dest []byte) (int, error) {
|
||||
for {
|
||||
sz, err := unix.Lgetxattr(path, attr, dest)
|
||||
if err != unix.EINTR {
|
||||
return sz, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getxattr returns a []byte slice containing the value of
|
||||
// an extended attribute attr set for path.
|
||||
func getxattr(path, attr string) ([]byte, error) {
|
||||
// Start with a 128 length byte array
|
||||
dest := make([]byte, 128)
|
||||
sz, errno := dogetxattr(path, attr, dest)
|
||||
for errno == unix.ERANGE { //nolint:errorlint // unix errors are bare
|
||||
// Buffer too small, use zero-sized buffer to get the actual size
|
||||
sz, errno = dogetxattr(path, attr, []byte{})
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
dest = make([]byte, sz)
|
||||
sz, errno = dogetxattr(path, attr, dest)
|
||||
}
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return dest[:sz], nil
|
||||
}
|
||||
|
||||
// dogetxattr is a wrapper that retries on EINTR
|
||||
func dogetxattr(path, attr string, dest []byte) (int, error) {
|
||||
for {
|
||||
sz, err := unix.Getxattr(path, attr, dest)
|
||||
if err != unix.EINTR {
|
||||
return sz, err
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
module github.com/opencontainers/selinux
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/cyphar/filepath-securejoin v0.5.0
|
||||
golang.org/x/sys v0.18.0
|
||||
)
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
|
||||
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -1,52 +0,0 @@
|
||||
## pwalk: parallel implementation of filepath.Walk
|
||||
|
||||
This is a wrapper for [filepath.Walk](https://pkg.go.dev/path/filepath?tab=doc#Walk)
|
||||
which may speed it up by calling multiple callback functions (WalkFunc) in parallel,
|
||||
utilizing goroutines.
|
||||
|
||||
By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks.
|
||||
This can be changed by using WalkN function which has the additional
|
||||
parameter, specifying the number of goroutines (concurrency).
|
||||
|
||||
### pwalk vs pwalkdir
|
||||
|
||||
This package is deprecated in favor of
|
||||
[pwalkdir](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir),
|
||||
which is faster, but requires at least Go 1.16.
|
||||
|
||||
### Caveats
|
||||
|
||||
Please note the following limitations of this code:
|
||||
|
||||
* Unlike filepath.Walk, the order of calls is non-deterministic;
|
||||
|
||||
* Only primitive error handling is supported:
|
||||
|
||||
* filepath.SkipDir is not supported;
|
||||
|
||||
* ErrNotExist errors from filepath.Walk are silently ignored for any path
|
||||
except the top directory (Walk argument); any other error is returned to
|
||||
the caller of Walk;
|
||||
|
||||
* no errors are ever passed to WalkFunc;
|
||||
|
||||
* once any error is returned from any WalkFunc instance, no more new calls
|
||||
to WalkFunc are made, and the error is returned to the caller of Walk;
|
||||
|
||||
* if more than one walkFunc instance will return an error, only one
|
||||
of such errors will be propagated and returned by Walk, others
|
||||
will be silently discarded.
|
||||
|
||||
### Documentation
|
||||
|
||||
For the official documentation, see
|
||||
https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalk?tab=doc
|
||||
|
||||
### Benchmarks
|
||||
|
||||
For a WalkFunc that consists solely of the return statement, this
|
||||
implementation is about 10% slower than the standard library's
|
||||
filepath.Walk.
|
||||
|
||||
Otherwise (if a WalkFunc is doing something) this is usually faster,
|
||||
except when the WalkN(..., 1) is used.
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
package pwalk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// WalkFunc is the type of the function called by Walk to visit each
|
||||
// file or directory. It is an alias for [filepath.WalkFunc].
|
||||
//
|
||||
// Deprecated: use [github.com/opencontainers/selinux/pkg/pwalkdir] and [fs.WalkDirFunc].
|
||||
type WalkFunc = filepath.WalkFunc
|
||||
|
||||
// Walk is a wrapper for filepath.Walk which can call multiple walkFn
|
||||
// in parallel, allowing to handle each item concurrently. A maximum of
|
||||
// twice the runtime.NumCPU() walkFn will be called at any one time.
|
||||
// If you want to change the maximum, use WalkN instead.
|
||||
//
|
||||
// The order of calls is non-deterministic.
|
||||
//
|
||||
// Note that this implementation only supports primitive error handling:
|
||||
//
|
||||
// - no errors are ever passed to walkFn;
|
||||
//
|
||||
// - once a walkFn returns any error, all further processing stops
|
||||
// and the error is returned to the caller of Walk;
|
||||
//
|
||||
// - filepath.SkipDir is not supported;
|
||||
//
|
||||
// - if more than one walkFn instance will return an error, only one
|
||||
// of such errors will be propagated and returned by Walk, others
|
||||
// will be silently discarded.
|
||||
//
|
||||
// Deprecated: use [github.com/opencontainers/selinux/pkg/pwalkdir.Walk]
|
||||
func Walk(root string, walkFn WalkFunc) error {
|
||||
return WalkN(root, walkFn, runtime.NumCPU()*2)
|
||||
}
|
||||
|
||||
// WalkN is a wrapper for filepath.Walk which can call multiple walkFn
|
||||
// in parallel, allowing to handle each item concurrently. A maximum of
|
||||
// num walkFn will be called at any one time.
|
||||
//
|
||||
// Please see Walk documentation for caveats of using this function.
|
||||
//
|
||||
// Deprecated: use [github.com/opencontainers/selinux/pkg/pwalkdir.WalkN]
|
||||
func WalkN(root string, walkFn WalkFunc, num int) error {
|
||||
// make sure limit is sensible
|
||||
if num < 1 {
|
||||
return fmt.Errorf("walk(%q): num must be > 0", root)
|
||||
}
|
||||
|
||||
files := make(chan *walkArgs, 2*num)
|
||||
errCh := make(chan error, 1) // get the first error, ignore others
|
||||
|
||||
// Start walking a tree asap
|
||||
var (
|
||||
err error
|
||||
wg sync.WaitGroup
|
||||
|
||||
rootLen = len(root)
|
||||
rootEntry *walkArgs
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
err = filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// Walking a file tree can race with removal,
|
||||
// so ignore ENOENT, except for root.
|
||||
// https://github.com/opencontainers/selinux/issues/199.
|
||||
if errors.Is(err, os.ErrNotExist) && len(p) != rootLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
close(files)
|
||||
return err
|
||||
}
|
||||
if len(p) == rootLen {
|
||||
// Root entry is processed separately below.
|
||||
rootEntry = &walkArgs{path: p, info: &info}
|
||||
return nil
|
||||
}
|
||||
// add a file to the queue unless a callback sent an error
|
||||
select {
|
||||
case e := <-errCh:
|
||||
close(files)
|
||||
return e
|
||||
default:
|
||||
files <- &walkArgs{path: p, info: &info}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
if err == nil {
|
||||
close(files)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(num)
|
||||
for i := 0; i < num; i++ {
|
||||
go func() {
|
||||
for file := range files {
|
||||
if e := walkFn(file.path, *file.info, nil); e != nil {
|
||||
select {
|
||||
case errCh <- e: // sent ok
|
||||
default: // buffer full
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if err == nil {
|
||||
err = walkFn(rootEntry.path, *rootEntry.info, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// walkArgs holds the arguments that were passed to the Walk or WalkN
|
||||
// functions.
|
||||
type walkArgs struct {
|
||||
info *os.FileInfo
|
||||
path string
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
package pwalk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
var ac atomic.Uint32
|
||||
concurrency := runtime.NumCPU() * 2
|
||||
|
||||
dir, total := prepareTestSet(t, 3, 2, 1)
|
||||
|
||||
err := WalkN(dir,
|
||||
func(_ string, _ os.FileInfo, _ error) error {
|
||||
ac.Add(1)
|
||||
return nil
|
||||
},
|
||||
concurrency)
|
||||
if err != nil {
|
||||
t.Errorf("Walk failed: %v", err)
|
||||
}
|
||||
count := ac.Load()
|
||||
if count != total {
|
||||
t.Errorf("File count mismatch: found %d, expected %d", count, total)
|
||||
}
|
||||
|
||||
t.Logf("concurrency: %d, files found: %d", concurrency, count)
|
||||
}
|
||||
|
||||
func TestWalkTopLevelErrNotExistNotIgnored(t *testing.T) {
|
||||
if WalkN("non-existent-directory", cbEmpty, 8) == nil {
|
||||
t.Fatal("expected ErrNotExist, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/selinux/issues/199
|
||||
func TestWalkRaceWithRemoval(t *testing.T) {
|
||||
var ac atomic.Uint32
|
||||
concurrency := runtime.NumCPU() * 2
|
||||
// This test is still on a best-effort basis, meaning it can still pass
|
||||
// when there is a bug in the code, but the larger the test set is, the
|
||||
// higher the probability that this test fails (without a fix).
|
||||
//
|
||||
// With this set (4, 5, 6), and the fix commented out, it fails
|
||||
// 100 out of 100 runs on my machine.
|
||||
dir, total := prepareTestSet(t, 4, 5, 6)
|
||||
|
||||
// Race walk with removal.
|
||||
go os.RemoveAll(dir)
|
||||
err := WalkN(dir,
|
||||
func(_ string, _ os.FileInfo, _ error) error {
|
||||
ac.Add(1)
|
||||
return nil
|
||||
},
|
||||
concurrency)
|
||||
count := int(ac.Load())
|
||||
t.Logf("found %d of %d files", count, total)
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkDirManyErrors(t *testing.T) {
|
||||
var ac atomic.Uint32
|
||||
|
||||
dir, total := prepareTestSet(t, 3, 3, 2)
|
||||
|
||||
maxFiles := total / 2
|
||||
e42 := errors.New("42")
|
||||
err := Walk(dir,
|
||||
func(_ string, _ os.FileInfo, _ error) error {
|
||||
if ac.Add(1) > maxFiles {
|
||||
return e42
|
||||
}
|
||||
return nil
|
||||
})
|
||||
count := ac.Load()
|
||||
t.Logf("found %d of %d files", count, total)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Walk succeeded, but error is expected")
|
||||
if count != total {
|
||||
t.Errorf("File count mismatch: found %d, expected %d", count, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeManyDirs(prefix string, levels, dirs, files int) (count uint32, err error) {
|
||||
for d := 0; d < dirs; d++ {
|
||||
var dir string
|
||||
dir, err = os.MkdirTemp(prefix, "d-")
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
count++
|
||||
for f := 0; f < files; f++ {
|
||||
var fi *os.File
|
||||
fi, err = os.CreateTemp(dir, "f-")
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
_ = fi.Close()
|
||||
count++
|
||||
}
|
||||
if levels == 0 {
|
||||
continue
|
||||
}
|
||||
var c uint32
|
||||
if c, err = makeManyDirs(dir, levels-1, dirs, files); err != nil {
|
||||
return count, err
|
||||
}
|
||||
count += c
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// prepareTestSet() creates a directory tree of shallow files,
|
||||
// to be used for testing or benchmarking.
|
||||
//
|
||||
// Total dirs: dirs^levels + dirs^(levels-1) + ... + dirs^1
|
||||
// Total files: total_dirs * files
|
||||
func prepareTestSet(tb testing.TB, levels, dirs, files int) (dir string, total uint32) {
|
||||
tb.Helper()
|
||||
var err error
|
||||
|
||||
dir = tb.TempDir()
|
||||
total, err = makeManyDirs(dir, levels, dirs, files)
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
total++ // this dir
|
||||
|
||||
return dir, total
|
||||
}
|
||||
|
||||
type walkerFunc func(root string, walkFn WalkFunc) error
|
||||
|
||||
func genWalkN(n int) walkerFunc {
|
||||
return func(root string, walkFn WalkFunc) error {
|
||||
return WalkN(root, walkFn, n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWalk(b *testing.B) {
|
||||
const (
|
||||
levels = 5 // how deep
|
||||
dirs = 3 // dirs on each levels
|
||||
files = 8 // files on each levels
|
||||
)
|
||||
|
||||
benchmarks := []struct {
|
||||
walk filepath.WalkFunc
|
||||
name string
|
||||
}{
|
||||
{name: "Empty", walk: cbEmpty},
|
||||
{name: "ReadFile", walk: cbReadFile},
|
||||
{name: "ChownChmod", walk: cbChownChmod},
|
||||
{name: "RandomSleep", walk: cbRandomSleep},
|
||||
}
|
||||
|
||||
walkers := []struct {
|
||||
walker walkerFunc
|
||||
name string
|
||||
}{
|
||||
{name: "filepath.Walk", walker: filepath.Walk},
|
||||
{name: "pwalk.Walk", walker: Walk},
|
||||
// test WalkN with various values of N
|
||||
{name: "pwalk.Walk1", walker: genWalkN(1)},
|
||||
{name: "pwalk.Walk2", walker: genWalkN(2)},
|
||||
{name: "pwalk.Walk4", walker: genWalkN(4)},
|
||||
{name: "pwalk.Walk8", walker: genWalkN(8)},
|
||||
{name: "pwalk.Walk16", walker: genWalkN(16)},
|
||||
{name: "pwalk.Walk32", walker: genWalkN(32)},
|
||||
{name: "pwalk.Walk64", walker: genWalkN(64)},
|
||||
{name: "pwalk.Walk128", walker: genWalkN(128)},
|
||||
{name: "pwalk.Walk256", walker: genWalkN(256)},
|
||||
}
|
||||
|
||||
dir, total := prepareTestSet(b, levels, dirs, files)
|
||||
b.Logf("dataset: %d levels x %d dirs x %d files, total entries: %d", levels, dirs, files, total)
|
||||
|
||||
for _, bm := range benchmarks {
|
||||
for _, w := range walkers {
|
||||
walker := w.walker
|
||||
walkFn := bm.walk
|
||||
// preheat
|
||||
if err := w.walker(dir, bm.walk); err != nil {
|
||||
b.Errorf("walk failed: %v", err)
|
||||
}
|
||||
// benchmark
|
||||
b.Run(bm.name+"/"+w.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := walker(dir, walkFn); err != nil {
|
||||
b.Errorf("walk failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cbEmpty(_ string, _ os.FileInfo, _ error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cbChownChmod(path string, info os.FileInfo, _ error) error {
|
||||
_ = os.Chown(path, 0, 0)
|
||||
mode := os.FileMode(0o644)
|
||||
if info.Mode().IsDir() {
|
||||
mode = os.FileMode(0o755)
|
||||
}
|
||||
_ = os.Chmod(path, mode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cbReadFile(path string, info os.FileInfo, _ error) error {
|
||||
var err error
|
||||
if info.Mode().IsRegular() {
|
||||
_, err = os.ReadFile(path)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func cbRandomSleep(_ string, _ os.FileInfo, _ error) error {
|
||||
time.Sleep(time.Duration(rand.Intn(500)) * time.Microsecond) //nolint:gosec // ignore G404: Use of weak random number generator
|
||||
return nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
## pwalkdir: parallel implementation of filepath.WalkDir
|
||||
|
||||
This is a wrapper for [filepath.WalkDir](https://pkg.go.dev/path/filepath#WalkDir)
|
||||
which may speed it up by calling multiple callback functions (WalkDirFunc)
|
||||
in parallel, utilizing goroutines.
|
||||
|
||||
By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks.
|
||||
This can be changed by using WalkN function which has the additional
|
||||
parameter, specifying the number of goroutines (concurrency).
|
||||
|
||||
### pwalk vs pwalkdir
|
||||
|
||||
This package is very similar to
|
||||
[pwalk](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir),
|
||||
but utilizes `filepath.WalkDir` (added to Go 1.16), which does not call stat(2)
|
||||
on every entry and is therefore faster (up to 3x, depending on usage scenario).
|
||||
|
||||
Users who are OK with requiring Go 1.16+ should switch to this
|
||||
implementation.
|
||||
|
||||
### Caveats
|
||||
|
||||
Please note the following limitations of this code:
|
||||
|
||||
* Unlike filepath.WalkDir, the order of calls is non-deterministic;
|
||||
|
||||
* Only primitive error handling is supported:
|
||||
|
||||
* fs.SkipDir is not supported;
|
||||
|
||||
* ErrNotExist errors from filepath.WalkDir are silently ignored for any path
|
||||
except the top directory (WalkDir argument); any other error is returned to
|
||||
the caller of WalkDir;
|
||||
|
||||
* once any error is returned from any walkDirFunc instance, no more calls
|
||||
to WalkDirFunc are made, and the error is returned to the caller of WalkDir;
|
||||
|
||||
* if more than one WalkDirFunc instance will return an error, only one
|
||||
of such errors will be propagated to and returned by WalkDir, others
|
||||
will be silently discarded.
|
||||
|
||||
### Documentation
|
||||
|
||||
For the official documentation, see
|
||||
https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir
|
||||
|
||||
### Benchmarks
|
||||
|
||||
For a WalkDirFunc that consists solely of the return statement, this
|
||||
implementation is about 15% slower than the standard library's
|
||||
filepath.WalkDir.
|
||||
|
||||
Otherwise (if a WalkDirFunc is actually doing something) this is usually
|
||||
faster, except when the WalkDirN(..., 1) is used. Run `go test -bench .`
|
||||
to see how different operations can benefit from it, as well as how the
|
||||
level of parallelism affects the speed.
|
||||
@@ -1,123 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package pwalkdir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Walk is a wrapper for filepath.WalkDir which can call multiple walkFn
|
||||
// in parallel, allowing to handle each item concurrently. A maximum of
|
||||
// twice the runtime.NumCPU() walkFn will be called at any one time.
|
||||
// If you want to change the maximum, use WalkN instead.
|
||||
//
|
||||
// The order of calls is non-deterministic.
|
||||
//
|
||||
// Note that this implementation only supports primitive error handling:
|
||||
//
|
||||
// - no errors are ever passed to walkFn;
|
||||
//
|
||||
// - once a walkFn returns any error, all further processing stops
|
||||
// and the error is returned to the caller of Walk;
|
||||
//
|
||||
// - filepath.SkipDir is not supported;
|
||||
//
|
||||
// - if more than one walkFn instance will return an error, only one
|
||||
// of such errors will be propagated and returned by Walk, others
|
||||
// will be silently discarded.
|
||||
func Walk(root string, walkFn fs.WalkDirFunc) error {
|
||||
return WalkN(root, walkFn, runtime.NumCPU()*2)
|
||||
}
|
||||
|
||||
// WalkN is a wrapper for filepath.WalkDir which can call multiple walkFn
|
||||
// in parallel, allowing to handle each item concurrently. A maximum of
|
||||
// num walkFn will be called at any one time.
|
||||
//
|
||||
// Please see Walk documentation for caveats of using this function.
|
||||
func WalkN(root string, walkFn fs.WalkDirFunc, num int) error {
|
||||
// make sure limit is sensible
|
||||
if num < 1 {
|
||||
return fmt.Errorf("walk(%q): num must be > 0", root)
|
||||
}
|
||||
|
||||
files := make(chan *walkArgs, 2*num)
|
||||
errCh := make(chan error, 1) // Get the first error, ignore others.
|
||||
|
||||
// Start walking a tree asap.
|
||||
var (
|
||||
err error
|
||||
wg sync.WaitGroup
|
||||
|
||||
rootLen = len(root)
|
||||
rootEntry *walkArgs
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
err = filepath.WalkDir(root, func(p string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
// Walking a file tree can race with removal,
|
||||
// so ignore ENOENT, except for root.
|
||||
// https://github.com/opencontainers/selinux/issues/199.
|
||||
if errors.Is(err, fs.ErrNotExist) && len(p) != rootLen {
|
||||
return nil
|
||||
}
|
||||
close(files)
|
||||
return err
|
||||
}
|
||||
if len(p) == rootLen {
|
||||
// Root entry is processed separately below.
|
||||
rootEntry = &walkArgs{path: p, entry: entry}
|
||||
return nil
|
||||
}
|
||||
// Add a file to the queue unless a callback sent an error.
|
||||
select {
|
||||
case e := <-errCh:
|
||||
close(files)
|
||||
return e
|
||||
default:
|
||||
files <- &walkArgs{path: p, entry: entry}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
if err == nil {
|
||||
close(files)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(num)
|
||||
for i := 0; i < num; i++ {
|
||||
go func() {
|
||||
for file := range files {
|
||||
if e := walkFn(file.path, file.entry, nil); e != nil {
|
||||
select {
|
||||
case errCh <- e: // sent ok
|
||||
default: // buffer full
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if err == nil {
|
||||
err = walkFn(rootEntry.path, rootEntry.entry, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// walkArgs holds the arguments that were passed to the Walk or WalkN
|
||||
// functions.
|
||||
type walkArgs struct {
|
||||
entry fs.DirEntry
|
||||
path string
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package pwalkdir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWalkDir(t *testing.T) {
|
||||
var ac atomic.Uint32
|
||||
concurrency := runtime.NumCPU() * 2
|
||||
dir, total := prepareTestSet(t, 3, 2, 1)
|
||||
|
||||
err := WalkN(dir,
|
||||
func(_ string, _ fs.DirEntry, _ error) error {
|
||||
ac.Add(1)
|
||||
return nil
|
||||
},
|
||||
concurrency)
|
||||
if err != nil {
|
||||
t.Errorf("Walk failed: %v", err)
|
||||
}
|
||||
count := ac.Load()
|
||||
if count != total {
|
||||
t.Errorf("File count mismatch: found %d, expected %d", count, total)
|
||||
}
|
||||
|
||||
t.Logf("concurrency: %d, files found: %d", concurrency, count)
|
||||
}
|
||||
|
||||
func TestWalkDirTopLevelErrNotExistNotIgnored(t *testing.T) {
|
||||
err := WalkN("non-existent-directory", cbEmpty, 8)
|
||||
if err == nil {
|
||||
t.Fatal("expected ErrNotExist, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/selinux/issues/199
|
||||
func TestWalkDirRaceWithRemoval(t *testing.T) {
|
||||
var ac atomic.Uint32
|
||||
concurrency := runtime.NumCPU() * 2
|
||||
// This test is still on a best-effort basis, meaning it can still pass
|
||||
// when there is a bug in the code, but the larger the test set is, the
|
||||
// higher the probability that this test fails (without a fix).
|
||||
//
|
||||
// With this set (4, 5, 6), and the fix commented out, it fails
|
||||
// about 90 out of 100 runs on my machine.
|
||||
dir, total := prepareTestSet(t, 4, 5, 6)
|
||||
|
||||
// Make walk race with removal.
|
||||
go os.RemoveAll(dir)
|
||||
err := WalkN(dir,
|
||||
func(_ string, _ fs.DirEntry, _ error) error {
|
||||
ac.Add(1)
|
||||
return nil
|
||||
},
|
||||
concurrency)
|
||||
count := ac.Load()
|
||||
t.Logf("found %d of %d files", count, total)
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkDirManyErrors(t *testing.T) {
|
||||
var ac atomic.Uint32
|
||||
dir, total := prepareTestSet(t, 3, 3, 2)
|
||||
|
||||
maxFiles := total / 2
|
||||
e42 := errors.New("42")
|
||||
err := Walk(dir,
|
||||
func(_ string, _ fs.DirEntry, _ error) error {
|
||||
if ac.Add(1) > maxFiles {
|
||||
return e42
|
||||
}
|
||||
return nil
|
||||
})
|
||||
count := ac.Load()
|
||||
t.Logf("found %d of %d files", count, total)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Walk succeeded, but error is expected")
|
||||
if count != total {
|
||||
t.Errorf("File count mismatch: found %d, expected %d", count, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeManyDirs(prefix string, levels, dirs, files int) (count uint32, err error) {
|
||||
for d := 0; d < dirs; d++ {
|
||||
var dir string
|
||||
dir, err = os.MkdirTemp(prefix, "d-")
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
count++
|
||||
for f := 0; f < files; f++ {
|
||||
var fi *os.File
|
||||
fi, err = os.CreateTemp(dir, "f-")
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
fi.Close()
|
||||
count++
|
||||
}
|
||||
if levels == 0 {
|
||||
continue
|
||||
}
|
||||
var c uint32
|
||||
if c, err = makeManyDirs(dir, levels-1, dirs, files); err != nil {
|
||||
return count, err
|
||||
}
|
||||
count += c
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// prepareTestSet() creates a directory tree of shallow files,
|
||||
// to be used for testing or benchmarking.
|
||||
//
|
||||
// Total dirs: dirs^levels + dirs^(levels-1) + ... + dirs^1
|
||||
// Total files: total_dirs * files
|
||||
func prepareTestSet(tb testing.TB, levels, dirs, files int) (dir string, total uint32) {
|
||||
tb.Helper()
|
||||
var err error
|
||||
|
||||
dir = tb.TempDir()
|
||||
total, err = makeManyDirs(dir, levels, dirs, files)
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
total++ // this dir
|
||||
|
||||
return dir, total
|
||||
}
|
||||
|
||||
type walkerFunc func(root string, walkFn fs.WalkDirFunc) error
|
||||
|
||||
func genWalkN(n int) walkerFunc {
|
||||
return func(root string, walkFn fs.WalkDirFunc) error {
|
||||
return WalkN(root, walkFn, n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWalk(b *testing.B) {
|
||||
const (
|
||||
levels = 5 // how deep
|
||||
dirs = 3 // dirs on each levels
|
||||
files = 8 // files on each levels
|
||||
)
|
||||
|
||||
benchmarks := []struct {
|
||||
walk fs.WalkDirFunc
|
||||
name string
|
||||
}{
|
||||
{name: "Empty", walk: cbEmpty},
|
||||
{name: "ReadFile", walk: cbReadFile},
|
||||
{name: "ChownChmod", walk: cbChownChmod},
|
||||
{name: "RandomSleep", walk: cbRandomSleep},
|
||||
}
|
||||
|
||||
walkers := []struct {
|
||||
walker walkerFunc
|
||||
name string
|
||||
}{
|
||||
{name: "filepath.WalkDir", walker: filepath.WalkDir},
|
||||
{name: "pwalkdir.Walk", walker: Walk},
|
||||
// test WalkN with various values of N
|
||||
{name: "pwalkdir.Walk1", walker: genWalkN(1)},
|
||||
{name: "pwalkdir.Walk2", walker: genWalkN(2)},
|
||||
{name: "pwalkdir.Walk4", walker: genWalkN(4)},
|
||||
{name: "pwalkdir.Walk8", walker: genWalkN(8)},
|
||||
{name: "pwalkdir.Walk16", walker: genWalkN(16)},
|
||||
{name: "pwalkdir.Walk32", walker: genWalkN(32)},
|
||||
{name: "pwalkdir.Walk64", walker: genWalkN(64)},
|
||||
{name: "pwalkdir.Walk128", walker: genWalkN(128)},
|
||||
{name: "pwalkdir.Walk256", walker: genWalkN(256)},
|
||||
}
|
||||
|
||||
dir, total := prepareTestSet(b, levels, dirs, files)
|
||||
b.Logf("dataset: %d levels x %d dirs x %d files, total entries: %d", levels, dirs, files, total)
|
||||
|
||||
for _, bm := range benchmarks {
|
||||
for _, w := range walkers {
|
||||
walker := w.walker
|
||||
walkFn := bm.walk
|
||||
// preheat
|
||||
if err := w.walker(dir, bm.walk); err != nil {
|
||||
b.Errorf("walk failed: %v", err)
|
||||
}
|
||||
// benchmark
|
||||
b.Run(bm.name+"/"+w.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := walker(dir, walkFn); err != nil {
|
||||
b.Errorf("walk failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cbEmpty(_ string, _ fs.DirEntry, _ error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cbChownChmod(path string, e fs.DirEntry, _ error) error {
|
||||
_ = os.Chown(path, 0, 0)
|
||||
mode := os.FileMode(0o644)
|
||||
if e.IsDir() {
|
||||
mode = os.FileMode(0o755)
|
||||
}
|
||||
_ = os.Chmod(path, mode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cbReadFile(path string, e fs.DirEntry, _ error) error {
|
||||
var err error
|
||||
if e.Type().IsRegular() {
|
||||
_, err = os.ReadFile(path)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func cbRandomSleep(_ string, _ fs.DirEntry, _ error) error {
|
||||
time.Sleep(time.Duration(rand.Intn(500)) * time.Microsecond) //nolint:gosec // ignore G404: Use of weak random number generator
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user