pax_global_header00006660000000000000000000000064142504540610014513gustar00rootroot0000000000000052 comment=879684a7b708c2ebc730e62fdae39ae9081b4680 libseccomp-golang-0.10.0/000077500000000000000000000000001425045406100151565ustar00rootroot00000000000000libseccomp-golang-0.10.0/.github/000077500000000000000000000000001425045406100165165ustar00rootroot00000000000000libseccomp-golang-0.10.0/.github/workflows/000077500000000000000000000000001425045406100205535ustar00rootroot00000000000000libseccomp-golang-0.10.0/.github/workflows/test.yml000066400000000000000000000041521425045406100222570ustar00rootroot00000000000000name: test on: push: tags: - v* branches: - main - master - release-* pull_request: jobs: test: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: go-version: [1.16.x, 1.17.x, 1.18.x] libseccomp: ["v2.3.3", "v2.4.3", "v2.5.4", "HEAD"] steps: - name: checkout uses: actions/checkout@v3 - name: install go ${{ matrix.go-version }} uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: build libseccomp ${{ matrix.libseccomp }} run: | set -x sudo apt -qq update sudo apt -qq install gperf PREFIX="$(pwd)/seccomp" LIBDIR="$PREFIX/lib" git clone https://github.com/seccomp/libseccomp cd libseccomp git checkout ${{ matrix.libseccomp }} # In main branch, configure.ac sets libseccomp version to 0.0.0, which # results in error when compiling libseccomp-golang. While 0.0.0 is # there for a reason, here we need to build and test against HEAD, so # set it to a suitable value. # # Version 9.9.9 is used because: # - version >= current is needed; # - chances are good such version won't ever exist; # - it is easy to spot in tests output; # - the LIBFILE pattern below expects single digits. VER="${{ matrix.libseccomp }}" if [ "$VER" == "HEAD" ]; then VER=9.9.9 sed -i "/^AC_INIT(/s/0\.0\.0/$VER/" configure.ac fi ./autogen.sh ./configure --prefix="$PREFIX" --libdir="$LIBDIR" make sudo make install cd - rm -rf libseccomp # For the next steps to build and execute with the compiled library. echo "PKG_CONFIG_LIBDIR=$LIBDIR/pkgconfig" >> $GITHUB_ENV LIBFILE="$(echo $LIBDIR/libseccomp.so.?.?.?)" echo "LD_PRELOAD=$LIBFILE" >> $GITHUB_ENV # For TestExpectedSeccompVersion. echo "_EXPECTED_LIBSECCOMP_VERSION=$VER" >> $GITHUB_ENV - name: build run: make check-build - name: test run: make test libseccomp-golang-0.10.0/.github/workflows/validate.yml000066400000000000000000000012421425045406100230660ustar00rootroot00000000000000name: validate on: push: tags: - v* branches: - master - main - release-* pull_request: jobs: lint: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - name: install deps run: | sudo apt -q update sudo apt -q install libseccomp-dev - uses: golangci/golangci-lint-action@v3 with: version: v1.45 codespell: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - name: install deps # Version of codespell bundled with Ubuntu is way old, so use pip. run: pip install codespell - name: run codespell run: codespell libseccomp-golang-0.10.0/.gitignore000066400000000000000000000000251425045406100171430ustar00rootroot00000000000000*~ *.swp *.orig tags libseccomp-golang-0.10.0/.golangci.yml000066400000000000000000000001511425045406100175370ustar00rootroot00000000000000# For documentation, see https://golangci-lint.run/usage/configuration/ linters: enable: - gofumpt libseccomp-golang-0.10.0/CHANGELOG000066400000000000000000000044221425045406100163720ustar00rootroot00000000000000libseccomp-golang: Releases =============================================================================== https://github.com/seccomp/libseccomp-golang * Version 0.10.0 - June 9, 2022 - Minimum supported version of libseccomp bumped to v2.3.1 - Add seccomp userspace notification API (ActNotify, filter.*Notif*) - Add filter.{Get,Set}SSB (to support SCMP_FLTATR_CTL_SSB) - Add filter.{Get,Set}Optimize (to support SCMP_FLTATR_CTL_OPTIMIZE) - Add filter.{Get,Set}RawRC (to support SCMP_FLTATR_API_SYSRAWRC) - Add ArchPARISC, ArchPARISC64, ArchRISCV64 - Add ActKillProcess and ActKillThread; deprecate ActKill - Add go module support - Return ErrSyscallDoesNotExist when unable to resolve a syscall - Fix some functions to check for both kernel level API and libseccomp version - Fix MakeCondition to use sanitizeCompareOp - Fix AddRule to handle EACCES (from libseccomp >= 2.5.0) - Updated the main docs and converted to README.md - Added CONTRIBUTING.md, SECURITY.md, and administrative docs under doc/admin - Add GitHub action CI, enable more linters - test: test against various libseccomp versions - test: fix and simplify execInSubprocess - test: fix APILevelIsSupported - Refactor the Errno(-1 * retCode) pattern - Refactor/unify libseccomp version / API level checks - Code cleanups (linter, formatting, spelling fixes) - Cleanup: use errors.New instead of fmt.Errorf where appropriate - Cleanup: remove duplicated cgo stuff, redundant linux build tag * Version 0.9.1 - May 21, 2019 - Minimum supported version of libseccomp bumped to v2.2.0 - Use Libseccomp's `seccomp_version` API to retrieve library version - Unconditionally set TSync attribute for filters, due to Go's heavily threaded nature - Fix CVE-2017-18367 - Multiple syscall arguments were incorrectly combined with logical-OR, instead of logical-AND - Fix a failure to build on Debian-based distributions due to CGo code - Fix unit test failures on 32-bit architectures - Improve several errors to be more verbose about their causes - Add support for SCMP_ACT_LOG (with libseccomp versions 2.4.x and higher), permitting syscalls but logging their execution - Add support for SCMP_FLTATR_CTL_LOG (with libseccomp versions 2.4.x and higher), logging not-allowed actions when they are denied * Version 0.9.0 - January 5, 2017 - Initial tagged release libseccomp-golang-0.10.0/CONTRIBUTING.md000066400000000000000000000124711425045406100174140ustar00rootroot00000000000000How to Submit Patches to the libseccomp-golang Project =============================================================================== https://github.com/seccomp/libseccomp-golang This document is intended to act as a guide to help you contribute to the libseccomp-golang project. It is not perfect, and there will always be exceptions to the rules described here, but by following the instructions below you should have a much easier time getting your work merged with the upstream project. ## Test Your Code Using Existing Tests A number of tests and lint related recipes are provided in the Makefile, if you want to run the standard regression tests, you can execute the following: # make check In order to use it, the 'golangci-lint' tool is needed, which can be found at: * https://github.com/golangci/golangci-lint ## Add New Tests for New Functionality Any submissions which add functionality, or significantly change the existing code, should include additional tests to verify the proper operation of the proposed changes. ## Explain Your Work At the top of every patch you should include a description of the problem you are trying to solve, how you solved it, and why you chose the solution you implemented. If you are submitting a bug fix, it is also incredibly helpful if you can describe/include a reproducer for the problem in the description as well as instructions on how to test for the bug and verify that it has been fixed. ## Sign Your Work The sign-off is a simple line at the end of the patch description, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. The "Developer's Certificate of Origin" pledge is taken from the Linux Kernel and the rules are pretty simple: 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 the bottom of your patch description, with your real name, saying: Signed-off-by: Random J Developer You can add this to your commit description in `git` with `git commit -s` ## Post Your Patches Upstream The libseccomp project accepts both GitHub pull requests and patches sent via the mailing list. GitHub pull requests are preferred. This sections below explain how to contribute via either method. Please read each step and perform all steps that apply to your chosen contribution method. ### Submitting via Email Depending on how you decided to work with the libseccomp code base and what tools you are using there are different ways to generate your patch(es). However, regardless of what tools you use, you should always generate your patches using the "unified" diff/patch format and the patches should always apply to the libseccomp source tree using the following command from the top directory of the libseccomp sources: # patch -p1 < changes.patch If you are not using git, stacked git (stgit), or some other tool which can generate patch files for you automatically, you may find the following command helpful in generating patches, where "libseccomp.orig/" is the unmodified source code directory and "libseccomp/" is the source code directory with your changes: # diff -purN libseccomp.orig/ libseccomp/ When in doubt please generate your patch and try applying it to an unmodified copy of the libseccomp sources; if it fails for you, it will fail for the rest of us. Finally, you will need to email your patches to the mailing list so they can be reviewed and potentially merged into the main libseccomp repository. When sending patches to the mailing list it is important to send your email in text form, no HTML mail please, and ensure that your email client does not mangle your patches. It should be possible to save your raw email to disk and apply it directly to the libseccomp source code; if that fails then you likely have a problem with your email client. When in doubt try a test first by sending yourself an email with your patch and attempting to apply the emailed patch to the libseccomp repository; if it fails for you, it will fail for the rest of us trying to test your patch and include it in the main libseccomp repository. ### Submitting via GitHub See [this guide](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) if you've never done this before. libseccomp-golang-0.10.0/LICENSE000066400000000000000000000025211425045406100161630ustar00rootroot00000000000000Copyright (c) 2015 Matthew Heon Copyright (c) 2015 Paul Moore All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libseccomp-golang-0.10.0/Makefile000066400000000000000000000011041425045406100166120ustar00rootroot00000000000000# libseccomp-golang .PHONY: all check check-build check-syntax fix-syntax vet test lint all: check-build check: lint test check-build: go build check-syntax: gofmt -d . fix-syntax: gofmt -w . vet: go vet -v ./... # Previous bugs have made the tests freeze until the timeout. Golang default # timeout for tests is 10 minutes, which is too long, considering current tests # can be executed in less than 1 second. Reduce the timeout, so problems can # be noticed earlier in the CI. TEST_TIMEOUT=10s test: go test -v -timeout $(TEST_TIMEOUT) lint: golangci-lint run . libseccomp-golang-0.10.0/README.md000066400000000000000000000046451425045406100164460ustar00rootroot00000000000000![libseccomp Golang Bindings](https://github.com/seccomp/libseccomp-artwork/blob/main/logo/libseccomp-color_text.png) =============================================================================== https://github.com/seccomp/libseccomp-golang [![Go Reference](https://pkg.go.dev/badge/github.com/seccomp/libseccomp-golang.svg)](https://pkg.go.dev/github.com/seccomp/libseccomp-golang) [![validate](https://github.com/seccomp/libseccomp-golang/actions/workflows/validate.yml/badge.svg)](https://github.com/seccomp/libseccomp-golang/actions/workflows/validate.yml) [![test](https://github.com/seccomp/libseccomp-golang/actions/workflows/test.yml/badge.svg)](https://github.com/seccomp/libseccomp-golang/actions/workflows/test.yml) The libseccomp library provides an easy to use, platform independent, interface to the Linux Kernel's syscall filtering mechanism. The libseccomp API is designed to abstract away the underlying BPF based syscall filter language and present a more conventional function-call based filtering interface that should be familiar to, and easily adopted by, application developers. The libseccomp-golang library provides a Go based interface to the libseccomp library. ## Online Resources The library source repository currently lives on GitHub at the following URLs: * https://github.com/seccomp/libseccomp-golang * https://github.com/seccomp/libseccomp Documentation for this package is also available at: * https://pkg.go.dev/github.com/seccomp/libseccomp-golang ## Verifying Releases Starting with libseccomp-golang v0.10.0, the git tag corresponding to each release should be signed by one of the libseccomp-golang maintainers. It is recommended that before use you verify the release tags using the following command: % git tag -v At present, only the following keys, specified via the fingerprints below, are authorized to sign official libseccomp-golang release tags: Paul Moore 7100 AADF AE6E 6E94 0D2E 0AD6 55E4 5A5A E8CA 7C8A Tom Hromatka 47A6 8FCE 37C7 D702 4FD6 5E11 356C E62C 2B52 4099 Kir Kolyshkin C242 8CD7 5720 FACD CF76 B6EA 17DE 5ECB 75A1 100E More information on GnuPG and git tag verification can be found at their respective websites: https://git-scm.com/docs/git and https://gnupg.org. ## Installing the package % go get github.com/seccomp/libseccomp-golang ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). libseccomp-golang-0.10.0/SECURITY.md000066400000000000000000000046001425045406100167470ustar00rootroot00000000000000The libseccomp-golang Security Vulnerability Handling Process =============================================================================== https://github.com/seccomp/libseccomp-golang This document document attempts to describe the processes through which sensitive security relevant bugs can be responsibly disclosed to the libseccomp-golang project and how the project maintainers should handle these reports. Just like the other libseccomp-golang process documents, this document should be treated as a guiding document and not a hard, unyielding set of regulations; the bug reporters and project maintainers are encouraged to work together to address the issues as best they can, in a manner which works best for all parties involved. ### Reporting Problems Problems with the libseccomp-golang library that are not suitable for immediate public disclosure should be emailed to the current libseccomp-golang maintainers, the list is below. We typically request at most a 90 day time period to address the issue before it is made public, but we will make every effort to address the issue as quickly as possible and shorten the disclosure window. * Paul Moore, paul@paul-moore.com * Tom Hromatka, tom.hromatka@oracle.com * Kir Kolyshkin, kolyshkin@gmail.com ### Resolving Sensitive Security Issues Upon disclosure of a bug, the maintainers should work together to investigate the problem and decide on a solution. In order to prevent an early disclosure of the problem, those working on the solution should do so privately and outside of the traditional libseccomp-golang development practices. One possible solution to this is to leverage the GitHub "Security" functionality to create a private development fork that can be shared among the maintainers, and optionally the reporter. A placeholder GitHub issue may be created, but details should remain extremely limited until such time as the problem has been fixed and responsibly disclosed. If a CVE, or other tag, has been assigned to the problem, the GitHub issue title should include the vulnerability tag once the problem has been disclosed. ### Public Disclosure Whenever possible, responsible reporting and patching practices should be followed, including notification to the linux-distros and oss-security mailing lists. * https://oss-security.openwall.org/wiki/mailing-lists/distros * https://oss-security.openwall.org/wiki/mailing-lists/oss-security libseccomp-golang-0.10.0/doc/000077500000000000000000000000001425045406100157235ustar00rootroot00000000000000libseccomp-golang-0.10.0/doc/admin/000077500000000000000000000000001425045406100170135ustar00rootroot00000000000000libseccomp-golang-0.10.0/doc/admin/MAINTAINER_PROCESS.md000066400000000000000000000110471425045406100221650ustar00rootroot00000000000000The libseccomp-golang Maintainer Process =============================================================================== https://github.com/seccomp/libseccomp-golang This document attempts to describe the processes that should be followed by the various libseccomp-golang maintainers. It is not intended as a hard requirement, but rather as a guiding document intended to make it easier for multiple co-maintainers to manage the libseccomp-golang project. We recognize this document, like all other parts of the libseccomp-golang project, is not perfect. If changes need to be made, they should be made following the guidelines described here. ### Reviewing and Merging Patches In a perfect world each patch would be independently reviewed and ACK'd by each maintainer, but we recognize that is not likely to be practical for each patch. Under normal circumstances, each patch should be ACK'd by a simple majority of maintainers (in the case of an even number of maintainers, N/2+1) before being merged into the repository. Maintainers should ACK patches using a format similar to the Linux Kernel, for example: ``` Acked-by: John Smith ``` The maintainer which merged the patch into the repository should add their sign-off after ensuring that it is correct to do so (see the documentation on submitting patches); if it is not correct for the maintainer to add their sign-off, it is likely the patch should not be merged. The maintainer should add their sign-off using the standard format at the end of the patch's metadata, for example: ``` Signed-off-by: Jane Smith ``` The maintainers are encouraged to communicate with each other for many reasons, one of which is to let the others when one is going to be unreachable for an extended period of time. If a patch is being held due to a lack of ACKs and the other maintainers are not responding after a reasonable period of time (for example, a delay of over two weeks), as long as there are no outstanding NACKs the patch can be merged without a simple majority. ### Managing Sensitive Vulnerability Reports The libseccomp-golang vulnerability reporting process is documented in the SECURITY.md document. The maintainers should work together with the reporter to asses the validity and seriousness of the reported vulnerability. ### Managing the GitHub Issue Tracker We use the GitHub issue tracker to track bugs, feature requests, and sometimes unanswered questions. The conventions here are intended to help distinguish between the different uses, and prioritize within those categories. Feature requests MUST have a "RFE:" prefix added to the issue name and use the "enhancement" label. Bug reports MUST a "BUG:" prefix added to the issue name and use the "bug" label. Issues SHOULD be prioritized using the "priority/high", "priority/medium", and "priority/low" labels. The meaning should hopefully be obvious. Issues CAN be additionally labeled with the "pending/info", "pending/review", and "pending/revision" labels to indicate that additional information is needed, the issue/patch is pending review, and/or the patch requires changes. ### Managing the GitHub Release Milestones There should be at least two GitHub milestones at any point in time: one for the next major/minor release, and one for the next patch release. As issues are entered into the system, they can be added to the milestones at the discretion of the maintainers. ### Handling Inappropriate Community Behavior The libseccomp-golang project community is relatively small, and almost always respectful and considerate. However, there have been some limited cases of inappropriate behavior and it is the responsibility of the maintainers to deal with it accordingly. As mentioned above, the maintainers are encouraged to communicate with each other, and this communication is very important in this case. When inappropriate behavior is identified in the project (e.g. mailing list, GitHub, etc.) the maintainers should talk with each other as well as the responsible individual to try and correct the behavior. If the individual continues to act inappropriately the maintainers can block the individual from the project using whatever means are available. This should only be done as a last resort, and with the agreement of all the maintainers. In cases where a quick response is necessary, a maintainer can unilaterally block an individual, but the block should be reviewed by all the other maintainers soon afterwards. ### New Project Releases The libseccomp-golang release process is documented in the RELEASE_PROCESS.md document. libseccomp-golang-0.10.0/doc/admin/RELEASE_PROCESS.md000066400000000000000000000027421425045406100216200ustar00rootroot00000000000000The libseccomp-golang Release Process =============================================================================== https://github.com/seccomp/libseccomp-golang This is the process that should be followed when creating a new libseccomp-golang release. #### 1. Verify that all issues assigned to the release milestone have been resolved * https://github.com/seccomp/libseccomp-golang/milestones #### 2. Verify that the syntax/style meets the guidelines % make check-syntax #### 3. Verify that the bundled tests run without error % make vet % make check #### 4. If any problems were found up to this point that resulted in code changes, restart the process #### 5. Update the CHANGELOG file with significant changes since the last release #### 6. If this is a new major/minor release, create new 'release-X.Y' branch % stg branch -c "release-X.Y" ... or ... % git branch "release-X.Y" #### 7. Tag the release in the local repository with a signed tag % git tag -s -m "version X.Y.Z" vX.Y.Z #### 8. Push the release tag to the main GitHub repository % git push vX.Y.Z #### 9. Create a new GitHub release using the associated tag, add the relevant section from the CHANGELOG file #### 19. Update the GitHub release notes for older releases which are now unsupported The following Markdown text is suggested at the top of the release note, see old GitHub releases for examples. ``` ***This release is no longer supported upstream, please use a more recent release*** ``` libseccomp-golang-0.10.0/go.mod000066400000000000000000000000651425045406100162650ustar00rootroot00000000000000module github.com/seccomp/libseccomp-golang go 1.14 libseccomp-golang-0.10.0/go.sum000066400000000000000000000000001425045406100162770ustar00rootroot00000000000000libseccomp-golang-0.10.0/seccomp.go000066400000000000000000001102561425045406100171430ustar00rootroot00000000000000// Public API specification for libseccomp Go bindings // Contains public API for the bindings // Package seccomp provides bindings for libseccomp, a library wrapping the Linux // seccomp syscall. Seccomp enables an application to restrict system call use // for itself and its children. package seccomp import ( "errors" "fmt" "os" "runtime" "strings" "sync" "syscall" "unsafe" ) // #include // #include import "C" // Exported types // VersionError represents an error when either the system libseccomp version // or the kernel version is too old to perform the operation requested. type VersionError struct { op string // operation that failed or would fail major, minor, micro uint // minimally required libseccomp version curAPI, minAPI uint // current and minimally required API versions } func init() { // This forces the cgo libseccomp to initialize its internal API support state, // which is necessary on older versions of libseccomp in order to work // correctly. _, _ = getAPI() } func (e VersionError) Error() string { if e.minAPI != 0 { return fmt.Sprintf("%s requires libseccomp >= %d.%d.%d and API level >= %d "+ "(current version: %d.%d.%d, API level: %d)", e.op, e.major, e.minor, e.micro, e.minAPI, verMajor, verMinor, verMicro, e.curAPI) } return fmt.Sprintf("%s requires libseccomp >= %d.%d.%d (current version: %d.%d.%d)", e.op, e.major, e.minor, e.micro, verMajor, verMinor, verMicro) } // ScmpArch represents a CPU architecture. Seccomp can restrict syscalls on a // per-architecture basis. type ScmpArch uint // ScmpAction represents an action to be taken on a filter rule match in // libseccomp type ScmpAction uint // ScmpCompareOp represents a comparison operator which can be used in a filter // rule type ScmpCompareOp uint // ScmpCondition represents a rule in a libseccomp filter context type ScmpCondition struct { Argument uint `json:"argument,omitempty"` Op ScmpCompareOp `json:"operator,omitempty"` Operand1 uint64 `json:"operand_one,omitempty"` Operand2 uint64 `json:"operand_two,omitempty"` } // Seccomp userspace notification structures associated with filters that use the ActNotify action. // ScmpSyscall identifies a Linux System Call by its number. type ScmpSyscall int32 // ScmpFd represents a file-descriptor used for seccomp userspace notifications. type ScmpFd int32 // ScmpNotifData describes the system call context that triggered a notification. // // Syscall: the syscall number // Arch: the filter architecture // InstrPointer: address of the instruction that triggered a notification // Args: arguments (up to 6) for the syscall // type ScmpNotifData struct { Syscall ScmpSyscall `json:"syscall,omitempty"` Arch ScmpArch `json:"arch,omitempty"` InstrPointer uint64 `json:"instr_pointer,omitempty"` Args []uint64 `json:"args,omitempty"` } // ScmpNotifReq represents a seccomp userspace notification. See NotifReceive() for // info on how to pull such a notification. // // ID: notification ID // Pid: process that triggered the notification event // Flags: filter flags (see seccomp(2)) // Data: system call context that triggered the notification // type ScmpNotifReq struct { ID uint64 `json:"id,omitempty"` Pid uint32 `json:"pid,omitempty"` Flags uint32 `json:"flags,omitempty"` Data ScmpNotifData `json:"data,omitempty"` } // ScmpNotifResp represents a seccomp userspace notification response. See NotifRespond() // for info on how to push such a response. // // ID: notification ID (must match the corresponding ScmpNotifReq ID) // Error: must be 0 if no error occurred, or an error constant from package // syscall (e.g., syscall.EPERM, etc). In the latter case, it's used // as an error return from the syscall that created the notification. // Val: return value for the syscall that created the notification. Only // relevant if Error is 0. // Flags: userspace notification response flag (e.g., NotifRespFlagContinue) // type ScmpNotifResp struct { ID uint64 `json:"id,omitempty"` Error int32 `json:"error,omitempty"` Val uint64 `json:"val,omitempty"` Flags uint32 `json:"flags,omitempty"` } // Exported Constants const ( // Valid architectures recognized by libseccomp // PowerPC and S390(x) architectures are unavailable below library version // v2.3.0 and will returns errors if used with incompatible libraries // ArchInvalid is a placeholder to ensure uninitialized ScmpArch // variables are invalid ArchInvalid ScmpArch = iota // ArchNative is the native architecture of the kernel ArchNative // ArchX86 represents 32-bit x86 syscalls ArchX86 // ArchAMD64 represents 64-bit x86-64 syscalls ArchAMD64 // ArchX32 represents 64-bit x86-64 syscalls (32-bit pointers) ArchX32 // ArchARM represents 32-bit ARM syscalls ArchARM // ArchARM64 represents 64-bit ARM syscalls ArchARM64 // ArchMIPS represents 32-bit MIPS syscalls ArchMIPS // ArchMIPS64 represents 64-bit MIPS syscalls ArchMIPS64 // ArchMIPS64N32 represents 64-bit MIPS syscalls (32-bit pointers) ArchMIPS64N32 // ArchMIPSEL represents 32-bit MIPS syscalls (little endian) ArchMIPSEL // ArchMIPSEL64 represents 64-bit MIPS syscalls (little endian) ArchMIPSEL64 // ArchMIPSEL64N32 represents 64-bit MIPS syscalls (little endian, // 32-bit pointers) ArchMIPSEL64N32 // ArchPPC represents 32-bit POWERPC syscalls ArchPPC // ArchPPC64 represents 64-bit POWER syscalls (big endian) ArchPPC64 // ArchPPC64LE represents 64-bit POWER syscalls (little endian) ArchPPC64LE // ArchS390 represents 31-bit System z/390 syscalls ArchS390 // ArchS390X represents 64-bit System z/390 syscalls ArchS390X // ArchPARISC represents 32-bit PA-RISC ArchPARISC // ArchPARISC64 represents 64-bit PA-RISC ArchPARISC64 // ArchRISCV64 represents RISCV64 ArchRISCV64 ) const ( // Supported actions on filter match // ActInvalid is a placeholder to ensure uninitialized ScmpAction // variables are invalid ActInvalid ScmpAction = iota // ActKillThread kills the thread that violated the rule. // All other threads from the same thread group will continue to execute. ActKillThread // ActTrap throws SIGSYS ActTrap // ActNotify triggers a userspace notification. This action is only usable when // libseccomp API level 6 or higher is supported. ActNotify // ActErrno causes the syscall to return a negative error code. This // code can be set with the SetReturnCode method ActErrno // ActTrace causes the syscall to notify tracing processes with the // given error code. This code can be set with the SetReturnCode method ActTrace // ActAllow permits the syscall to continue execution ActAllow // ActLog permits the syscall to continue execution after logging it. // This action is only usable when libseccomp API level 3 or higher is // supported. ActLog // ActKillProcess kills the process that violated the rule. // All threads in the thread group are also terminated. // This action is only usable when libseccomp API level 3 or higher is // supported. ActKillProcess // ActKill kills the thread that violated the rule. // All other threads from the same thread group will continue to execute. // // Deprecated: use ActKillThread ActKill = ActKillThread ) const ( // These are comparison operators used in conditional seccomp rules // They are used to compare the value of a single argument of a syscall // against a user-defined constant // CompareInvalid is a placeholder to ensure uninitialized ScmpCompareOp // variables are invalid CompareInvalid ScmpCompareOp = iota // CompareNotEqual returns true if the argument is not equal to the // given value CompareNotEqual // CompareLess returns true if the argument is less than the given value CompareLess // CompareLessOrEqual returns true if the argument is less than or equal // to the given value CompareLessOrEqual // CompareEqual returns true if the argument is equal to the given value CompareEqual // CompareGreaterEqual returns true if the argument is greater than or // equal to the given value CompareGreaterEqual // CompareGreater returns true if the argument is greater than the given // value CompareGreater // CompareMaskedEqual returns true if the masked argument value is // equal to the masked datum value. Mask is the first argument, and // datum is the second one. CompareMaskedEqual ) // ErrSyscallDoesNotExist represents an error condition where // libseccomp is unable to resolve the syscall. var ErrSyscallDoesNotExist = errors.New("could not resolve syscall name") const ( // Userspace notification response flags // NotifRespFlagContinue tells the kernel to continue executing the system // call that triggered the notification. Must only be used when the notification // response's error is 0. NotifRespFlagContinue uint32 = 1 ) // Helpers for types // GetArchFromString returns an ScmpArch constant from a string representing an // architecture func GetArchFromString(arch string) (ScmpArch, error) { if err := ensureSupportedVersion(); err != nil { return ArchInvalid, err } switch strings.ToLower(arch) { case "x86": return ArchX86, nil case "amd64", "x86-64", "x86_64", "x64": return ArchAMD64, nil case "x32": return ArchX32, nil case "arm": return ArchARM, nil case "arm64", "aarch64": return ArchARM64, nil case "mips": return ArchMIPS, nil case "mips64": return ArchMIPS64, nil case "mips64n32": return ArchMIPS64N32, nil case "mipsel": return ArchMIPSEL, nil case "mipsel64": return ArchMIPSEL64, nil case "mipsel64n32": return ArchMIPSEL64N32, nil case "ppc": return ArchPPC, nil case "ppc64": return ArchPPC64, nil case "ppc64le": return ArchPPC64LE, nil case "s390": return ArchS390, nil case "s390x": return ArchS390X, nil case "parisc": return ArchPARISC, nil case "parisc64": return ArchPARISC64, nil case "riscv64": return ArchRISCV64, nil default: return ArchInvalid, fmt.Errorf("cannot convert unrecognized string %q", arch) } } // String returns a string representation of an architecture constant func (a ScmpArch) String() string { switch a { case ArchX86: return "x86" case ArchAMD64: return "amd64" case ArchX32: return "x32" case ArchARM: return "arm" case ArchARM64: return "arm64" case ArchMIPS: return "mips" case ArchMIPS64: return "mips64" case ArchMIPS64N32: return "mips64n32" case ArchMIPSEL: return "mipsel" case ArchMIPSEL64: return "mipsel64" case ArchMIPSEL64N32: return "mipsel64n32" case ArchPPC: return "ppc" case ArchPPC64: return "ppc64" case ArchPPC64LE: return "ppc64le" case ArchS390: return "s390" case ArchS390X: return "s390x" case ArchPARISC: return "parisc" case ArchPARISC64: return "parisc64" case ArchRISCV64: return "riscv64" case ArchNative: return "native" case ArchInvalid: return "Invalid architecture" default: return fmt.Sprintf("Unknown architecture %#x", uint(a)) } } // String returns a string representation of a comparison operator constant func (a ScmpCompareOp) String() string { switch a { case CompareNotEqual: return "Not equal" case CompareLess: return "Less than" case CompareLessOrEqual: return "Less than or equal to" case CompareEqual: return "Equal" case CompareGreaterEqual: return "Greater than or equal to" case CompareGreater: return "Greater than" case CompareMaskedEqual: return "Masked equality" case CompareInvalid: return "Invalid comparison operator" default: return fmt.Sprintf("Unrecognized comparison operator %#x", uint(a)) } } // String returns a string representation of a seccomp match action func (a ScmpAction) String() string { switch a & 0xFFFF { case ActKillThread: return "Action: Kill thread" case ActKillProcess: return "Action: Kill process" case ActTrap: return "Action: Send SIGSYS" case ActErrno: return fmt.Sprintf("Action: Return error code %d", (a >> 16)) case ActTrace: return fmt.Sprintf("Action: Notify tracing processes with code %d", (a >> 16)) case ActNotify: return "Action: Notify userspace" case ActLog: return "Action: Log system call" case ActAllow: return "Action: Allow system call" default: return fmt.Sprintf("Unrecognized Action %#x", uint(a)) } } // SetReturnCode adds a return code to a supporting ScmpAction, clearing any // existing code Only valid on ActErrno and ActTrace. Takes no action otherwise. // Accepts 16-bit return code as argument. // Returns a valid ScmpAction of the original type with the new error code set. func (a ScmpAction) SetReturnCode(code int16) ScmpAction { aTmp := a & 0x0000FFFF if aTmp == ActErrno || aTmp == ActTrace { return (aTmp | (ScmpAction(code)&0xFFFF)<<16) } return a } // GetReturnCode returns the return code of an ScmpAction func (a ScmpAction) GetReturnCode() int16 { return int16(a >> 16) } // General utility functions // GetLibraryVersion returns the version of the library the bindings are built // against. // The version is formatted as follows: Major.Minor.Micro func GetLibraryVersion() (major, minor, micro uint) { return verMajor, verMinor, verMicro } // GetAPI returns the API level supported by the system. // Returns a positive int containing the API level, or 0 with an error if the // API level could not be detected due to the library being older than v2.4.0. // See the seccomp_api_get(3) man page for details on available API levels: // https://github.com/seccomp/libseccomp/blob/main/doc/man/man3/seccomp_api_get.3 func GetAPI() (uint, error) { return getAPI() } // SetAPI forcibly sets the API level. General use of this function is strongly // discouraged. // Returns an error if the API level could not be set. An error is always // returned if the library is older than v2.4.0 // See the seccomp_api_get(3) man page for details on available API levels: // https://github.com/seccomp/libseccomp/blob/main/doc/man/man3/seccomp_api_get.3 func SetAPI(api uint) error { return setAPI(api) } // Syscall functions // GetName retrieves the name of a syscall from its number. // Acts on any syscall number. // Returns either a string containing the name of the syscall, or an error. func (s ScmpSyscall) GetName() (string, error) { return s.GetNameByArch(ArchNative) } // GetNameByArch retrieves the name of a syscall from its number for a given // architecture. // Acts on any syscall number. // Accepts a valid architecture constant. // Returns either a string containing the name of the syscall, or an error. // if the syscall is unrecognized or an issue occurred. func (s ScmpSyscall) GetNameByArch(arch ScmpArch) (string, error) { if err := sanitizeArch(arch); err != nil { return "", err } cString := C.seccomp_syscall_resolve_num_arch(arch.toNative(), C.int(s)) if cString == nil { return "", ErrSyscallDoesNotExist } defer C.free(unsafe.Pointer(cString)) finalStr := C.GoString(cString) return finalStr, nil } // GetSyscallFromName returns the number of a syscall by name on the kernel's // native architecture. // Accepts a string containing the name of a syscall. // Returns the number of the syscall, or an error if no syscall with that name // was found. func GetSyscallFromName(name string) (ScmpSyscall, error) { if err := ensureSupportedVersion(); err != nil { return 0, err } cString := C.CString(name) defer C.free(unsafe.Pointer(cString)) result := C.seccomp_syscall_resolve_name(cString) if result == scmpError { return 0, ErrSyscallDoesNotExist } return ScmpSyscall(result), nil } // GetSyscallFromNameByArch returns the number of a syscall by name for a given // architecture's ABI. // Accepts the name of a syscall and an architecture constant. // Returns the number of the syscall, or an error if an invalid architecture is // passed or a syscall with that name was not found. func GetSyscallFromNameByArch(name string, arch ScmpArch) (ScmpSyscall, error) { if err := ensureSupportedVersion(); err != nil { return 0, err } if err := sanitizeArch(arch); err != nil { return 0, err } cString := C.CString(name) defer C.free(unsafe.Pointer(cString)) result := C.seccomp_syscall_resolve_name_arch(arch.toNative(), cString) if result == scmpError { return 0, ErrSyscallDoesNotExist } return ScmpSyscall(result), nil } // MakeCondition creates and returns a new condition to attach to a filter rule. // Associated rules will only match if this condition is true. // Accepts the number the argument we are checking, and a comparison operator // and value to compare to. // The rule will match if argument $arg (zero-indexed) of the syscall is // $COMPARE_OP the provided comparison value. // Some comparison operators accept two values. Masked equals, for example, // will mask $arg of the syscall with the second value provided (via bitwise // AND) and then compare against the first value provided. // For example, in the less than or equal case, if the syscall argument was // 0 and the value provided was 1, the condition would match, as 0 is less // than or equal to 1. // Return either an error on bad argument or a valid ScmpCondition struct. func MakeCondition(arg uint, comparison ScmpCompareOp, values ...uint64) (ScmpCondition, error) { var condStruct ScmpCondition if err := ensureSupportedVersion(); err != nil { return condStruct, err } if err := sanitizeCompareOp(comparison); err != nil { return condStruct, err } else if arg > 5 { return condStruct, fmt.Errorf("syscalls only have up to 6 arguments (%d given)", arg) } else if len(values) > 2 { return condStruct, fmt.Errorf("conditions can have at most 2 arguments (%d given)", len(values)) } else if len(values) == 0 { return condStruct, errors.New("must provide at least one value to compare against") } condStruct.Argument = arg condStruct.Op = comparison condStruct.Operand1 = values[0] if len(values) == 2 { condStruct.Operand2 = values[1] } else { condStruct.Operand2 = 0 // Unused } return condStruct, nil } // Utility Functions // GetNativeArch returns architecture token representing the native kernel // architecture func GetNativeArch() (ScmpArch, error) { if err := ensureSupportedVersion(); err != nil { return ArchInvalid, err } arch := C.seccomp_arch_native() return archFromNative(arch) } // Public Filter API // ScmpFilter represents a filter context in libseccomp. // A filter context is initially empty. Rules can be added to it, and it can // then be loaded into the kernel. type ScmpFilter struct { filterCtx C.scmp_filter_ctx valid bool lock sync.Mutex } // NewFilter creates and returns a new filter context. Accepts a default action to be // taken for syscalls which match no rules in the filter. // Returns a reference to a valid filter context, or nil and an error // if the filter context could not be created or an invalid default action was given. func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) { if err := ensureSupportedVersion(); err != nil { return nil, err } if err := sanitizeAction(defaultAction); err != nil { return nil, err } fPtr := C.seccomp_init(defaultAction.toNative()) if fPtr == nil { return nil, errors.New("could not create filter") } filter := new(ScmpFilter) filter.filterCtx = fPtr filter.valid = true runtime.SetFinalizer(filter, filterFinalizer) // Enable TSync so all goroutines will receive the same rules. // If the kernel does not support TSYNC, allow us to continue without error. if err := filter.setFilterAttr(filterAttrTsync, 0x1); err != nil && err != syscall.ENOTSUP { filter.Release() return nil, fmt.Errorf("could not create filter: error setting tsync bit: %w", err) } return filter, nil } // IsValid determines whether a filter context is valid to use. // Some operations (Release and Merge) render filter contexts invalid and // consequently prevent further use. func (f *ScmpFilter) IsValid() bool { f.lock.Lock() defer f.lock.Unlock() return f.valid } // Reset resets a filter context, removing all its existing state. // Accepts a new default action to be taken for syscalls which do not match. // Returns an error if the filter or action provided are invalid. func (f *ScmpFilter) Reset(defaultAction ScmpAction) error { f.lock.Lock() defer f.lock.Unlock() if err := sanitizeAction(defaultAction); err != nil { return err } else if !f.valid { return errBadFilter } if retCode := C.seccomp_reset(f.filterCtx, defaultAction.toNative()); retCode != 0 { return errRc(retCode) } return nil } // Release releases a filter context, freeing its memory. Should be called after // loading into the kernel, when the filter is no longer needed. // After calling this function, the given filter is no longer valid and cannot // be used. // Release() will be invoked automatically when a filter context is garbage // collected, but can also be called manually to free memory. func (f *ScmpFilter) Release() { f.lock.Lock() defer f.lock.Unlock() if !f.valid { return } f.valid = false C.seccomp_release(f.filterCtx) } // Merge merges two filter contexts. // The source filter src will be released as part of the process, and will no // longer be usable or valid after this call. // To be merged, filters must NOT share any architectures, and all their // attributes (Default Action, Bad Arch Action, and No New Privs bools) // must match. // The filter src will be merged into the filter this is called on. // The architectures of the src filter not present in the destination, and all // associated rules, will be added to the destination. // Returns an error if merging the filters failed. func (f *ScmpFilter) Merge(src *ScmpFilter) error { f.lock.Lock() defer f.lock.Unlock() src.lock.Lock() defer src.lock.Unlock() if !src.valid || !f.valid { return errors.New("one or more of the filter contexts is invalid or uninitialized") } // Merge the filters if retCode := C.seccomp_merge(f.filterCtx, src.filterCtx); retCode != 0 { e := errRc(retCode) if e == syscall.EINVAL { return fmt.Errorf("filters could not be merged due to a mismatch in attributes or invalid filter: %w", e) } return e } src.valid = false return nil } // IsArchPresent checks if an architecture is present in a filter. // If a filter contains an architecture, it uses its default action for // syscalls which do not match rules in it, and its rules can match syscalls // for that ABI. // If a filter does not contain an architecture, all syscalls made to that // kernel ABI will fail with the filter's default Bad Architecture Action // (by default, killing the process). // Accepts an architecture constant. // Returns true if the architecture is present in the filter, false otherwise, // and an error on an invalid filter context, architecture constant, or an // issue with the call to libseccomp. func (f *ScmpFilter) IsArchPresent(arch ScmpArch) (bool, error) { f.lock.Lock() defer f.lock.Unlock() if err := sanitizeArch(arch); err != nil { return false, err } else if !f.valid { return false, errBadFilter } if retCode := C.seccomp_arch_exist(f.filterCtx, arch.toNative()); retCode != 0 { e := errRc(retCode) if e == syscall.EEXIST { // -EEXIST is "arch not present" return false, nil } return false, e } return true, nil } // AddArch adds an architecture to the filter. // Accepts an architecture constant. // Returns an error on invalid filter context or architecture token, or an // issue with the call to libseccomp. func (f *ScmpFilter) AddArch(arch ScmpArch) error { f.lock.Lock() defer f.lock.Unlock() if err := sanitizeArch(arch); err != nil { return err } else if !f.valid { return errBadFilter } // Libseccomp returns -EEXIST if the specified architecture is already // present. Succeed silently in this case, as it's not fatal, and the // architecture is present already. if retCode := C.seccomp_arch_add(f.filterCtx, arch.toNative()); retCode != 0 { if e := errRc(retCode); e != syscall.EEXIST { return e } } return nil } // RemoveArch removes an architecture from the filter. // Accepts an architecture constant. // Returns an error on invalid filter context or architecture token, or an // issue with the call to libseccomp. func (f *ScmpFilter) RemoveArch(arch ScmpArch) error { f.lock.Lock() defer f.lock.Unlock() if err := sanitizeArch(arch); err != nil { return err } else if !f.valid { return errBadFilter } // Similar to AddArch, -EEXIST is returned if the arch is not present // Succeed silently in that case, this is not fatal and the architecture // is not present in the filter after RemoveArch if retCode := C.seccomp_arch_remove(f.filterCtx, arch.toNative()); retCode != 0 { if e := errRc(retCode); e != syscall.EEXIST { return e } } return nil } // Load loads a filter context into the kernel. // Returns an error if the filter context is invalid or the syscall failed. func (f *ScmpFilter) Load() error { f.lock.Lock() defer f.lock.Unlock() if !f.valid { return errBadFilter } if retCode := C.seccomp_load(f.filterCtx); retCode != 0 { return errRc(retCode) } return nil } // GetDefaultAction returns the default action taken on a syscall which does not // match a rule in the filter, or an error if an issue was encountered // retrieving the value. func (f *ScmpFilter) GetDefaultAction() (ScmpAction, error) { action, err := f.getFilterAttr(filterAttrActDefault) if err != nil { return 0x0, err } return actionFromNative(action) } // GetBadArchAction returns the default action taken on a syscall for an // architecture not in the filter, or an error if an issue was encountered // retrieving the value. func (f *ScmpFilter) GetBadArchAction() (ScmpAction, error) { action, err := f.getFilterAttr(filterAttrActBadArch) if err != nil { return 0x0, err } return actionFromNative(action) } // GetNoNewPrivsBit returns the current state the No New Privileges bit will be set // to on the filter being loaded, or an error if an issue was encountered // retrieving the value. // The No New Privileges bit tells the kernel that new processes run with exec() // cannot gain more privileges than the process that ran exec(). // For example, a process with No New Privileges set would be unable to exec // setuid/setgid executables. func (f *ScmpFilter) GetNoNewPrivsBit() (bool, error) { noNewPrivs, err := f.getFilterAttr(filterAttrNNP) if err != nil { return false, err } if noNewPrivs == 0 { return false, nil } return true, nil } // GetLogBit returns the current state the Log bit will be set to on the filter // being loaded, or an error if an issue was encountered retrieving the value. // The Log bit tells the kernel that all actions taken by the filter, with the // exception of ActAllow, should be logged. // The Log bit is only usable when libseccomp API level 3 or higher is // supported. func (f *ScmpFilter) GetLogBit() (bool, error) { log, err := f.getFilterAttr(filterAttrLog) if err != nil { if e := checkAPI("GetLogBit", 3, 2, 4, 0); e != nil { err = e } return false, err } if log == 0 { return false, nil } return true, nil } // GetSSB returns the current state the SSB bit will be set to on the filter // being loaded, or an error if an issue was encountered retrieving the value. // The SSB bit tells the kernel that a seccomp user is not interested in enabling // Speculative Store Bypass mitigation. // The SSB bit is only usable when libseccomp API level 4 or higher is // supported. func (f *ScmpFilter) GetSSB() (bool, error) { ssb, err := f.getFilterAttr(filterAttrSSB) if err != nil { if e := checkAPI("GetSSB", 4, 2, 5, 0); e != nil { err = e } return false, err } if ssb == 0 { return false, nil } return true, nil } // GetOptimize returns the current optimization level of the filter, // or an error if an issue was encountered retrieving the value. // See SetOptimize for more details. func (f *ScmpFilter) GetOptimize() (int, error) { level, err := f.getFilterAttr(filterAttrOptimize) if err != nil { if e := checkAPI("GetOptimize", 4, 2, 5, 0); e != nil { err = e } return 0, err } return int(level), nil } // GetRawRC returns the current state of RawRC flag, or an error // if an issue was encountered retrieving the value. // See SetRawRC for more details. func (f *ScmpFilter) GetRawRC() (bool, error) { rawrc, err := f.getFilterAttr(filterAttrRawRC) if err != nil { if e := checkAPI("GetRawRC", 4, 2, 5, 0); e != nil { err = e } return false, err } if rawrc == 0 { return false, nil } return true, nil } // SetBadArchAction sets the default action taken on a syscall for an // architecture not in the filter, or an error if an issue was encountered // setting the value. func (f *ScmpFilter) SetBadArchAction(action ScmpAction) error { if err := sanitizeAction(action); err != nil { return err } return f.setFilterAttr(filterAttrActBadArch, action.toNative()) } // SetNoNewPrivsBit sets the state of the No New Privileges bit, which will be // applied on filter load, or an error if an issue was encountered setting the // value. // Filters with No New Privileges set to 0 can only be loaded if the process // has the CAP_SYS_ADMIN capability. func (f *ScmpFilter) SetNoNewPrivsBit(state bool) error { var toSet C.uint32_t = 0x0 if state { toSet = 0x1 } return f.setFilterAttr(filterAttrNNP, toSet) } // SetLogBit sets the state of the Log bit, which will be applied on filter // load, or an error if an issue was encountered setting the value. // The Log bit is only usable when libseccomp API level 3 or higher is // supported. func (f *ScmpFilter) SetLogBit(state bool) error { var toSet C.uint32_t = 0x0 if state { toSet = 0x1 } err := f.setFilterAttr(filterAttrLog, toSet) if err != nil { if e := checkAPI("SetLogBit", 3, 2, 4, 0); e != nil { err = e } } return err } // SetSSB sets the state of the SSB bit, which will be applied on filter // load, or an error if an issue was encountered setting the value. // The SSB bit is only usable when libseccomp API level 4 or higher is // supported. func (f *ScmpFilter) SetSSB(state bool) error { var toSet C.uint32_t = 0x0 if state { toSet = 0x1 } err := f.setFilterAttr(filterAttrSSB, toSet) if err != nil { if e := checkAPI("SetSSB", 4, 2, 5, 0); e != nil { err = e } } return err } // SetOptimize sets optimization level of the seccomp filter. By default // libseccomp generates a set of sequential "if" statements for each rule in // the filter. SetSyscallPriority can be used to prioritize the order for the // default cause. The binary tree optimization sorts by syscall numbers and // generates consistent O(log n) filter traversal for every rule in the filter. // The binary tree may be advantageous for large filters. Note that // SetSyscallPriority is ignored when level == 2. // // The different optimization levels are: // 0: Reserved value, not currently used. // 1: Rules sorted by priority and complexity (DEFAULT). // 2: Binary tree sorted by syscall number. func (f *ScmpFilter) SetOptimize(level int) error { cLevel := C.uint32_t(level) err := f.setFilterAttr(filterAttrOptimize, cLevel) if err != nil { if e := checkAPI("SetOptimize", 4, 2, 5, 0); e != nil { err = e } } return err } // SetRawRC sets whether libseccomp should pass system error codes back to the // caller, instead of the default ECANCELED. Defaults to false. func (f *ScmpFilter) SetRawRC(state bool) error { var toSet C.uint32_t = 0x0 if state { toSet = 0x1 } err := f.setFilterAttr(filterAttrRawRC, toSet) if err != nil { if e := checkAPI("SetRawRC", 4, 2, 5, 0); e != nil { err = e } } return err } // SetSyscallPriority sets a syscall's priority. // This provides a hint to the filter generator in libseccomp about the // importance of this syscall. High-priority syscalls are placed // first in the filter code, and incur less overhead (at the expense of // lower-priority syscalls). func (f *ScmpFilter) SetSyscallPriority(call ScmpSyscall, priority uint8) error { f.lock.Lock() defer f.lock.Unlock() if !f.valid { return errBadFilter } if retCode := C.seccomp_syscall_priority(f.filterCtx, C.int(call), C.uint8_t(priority)); retCode != 0 { return errRc(retCode) } return nil } // AddRule adds a single rule for an unconditional action on a syscall. // Accepts the number of the syscall and the action to be taken on the call // being made. // Returns an error if an issue was encountered adding the rule. func (f *ScmpFilter) AddRule(call ScmpSyscall, action ScmpAction) error { return f.addRuleGeneric(call, action, false, nil) } // AddRuleExact adds a single rule for an unconditional action on a syscall. // Accepts the number of the syscall and the action to be taken on the call // being made. // No modifications will be made to the rule, and it will fail to add if it // cannot be applied to the current architecture without modification. // The rule will function exactly as described, but it may not function identically // (or be able to be applied to) all architectures. // Returns an error if an issue was encountered adding the rule. func (f *ScmpFilter) AddRuleExact(call ScmpSyscall, action ScmpAction) error { return f.addRuleGeneric(call, action, true, nil) } // AddRuleConditional adds a single rule for a conditional action on a syscall. // Returns an error if an issue was encountered adding the rule. // All conditions must match for the rule to match. func (f *ScmpFilter) AddRuleConditional(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { return f.addRuleGeneric(call, action, false, conds) } // AddRuleConditionalExact adds a single rule for a conditional action on a // syscall. // No modifications will be made to the rule, and it will fail to add if it // cannot be applied to the current architecture without modification. // The rule will function exactly as described, but it may not function identically // (or be able to be applied to) all architectures. // Returns an error if an issue was encountered adding the rule. func (f *ScmpFilter) AddRuleConditionalExact(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { return f.addRuleGeneric(call, action, true, conds) } // ExportPFC output PFC-formatted, human-readable dump of a filter context's // rules to a file. // Accepts file to write to (must be open for writing). // Returns an error if writing to the file fails. func (f *ScmpFilter) ExportPFC(file *os.File) error { f.lock.Lock() defer f.lock.Unlock() fd := file.Fd() if !f.valid { return errBadFilter } if retCode := C.seccomp_export_pfc(f.filterCtx, C.int(fd)); retCode != 0 { return errRc(retCode) } return nil } // ExportBPF outputs Berkeley Packet Filter-formatted, kernel-readable dump of a // filter context's rules to a file. // Accepts file to write to (must be open for writing). // Returns an error if writing to the file fails. func (f *ScmpFilter) ExportBPF(file *os.File) error { f.lock.Lock() defer f.lock.Unlock() fd := file.Fd() if !f.valid { return errBadFilter } if retCode := C.seccomp_export_bpf(f.filterCtx, C.int(fd)); retCode != 0 { return errRc(retCode) } return nil } // Userspace Notification API // GetNotifFd returns the userspace notification file descriptor associated with the given // filter context. Such a file descriptor is only valid after the filter has been loaded // and only when the filter uses the ActNotify action. The file descriptor can be used to // retrieve and respond to notifications associated with the filter (see NotifReceive(), // NotifRespond(), and NotifIDValid()). func (f *ScmpFilter) GetNotifFd() (ScmpFd, error) { return f.getNotifFd() } // NotifReceive retrieves a seccomp userspace notification from a filter whose ActNotify // action has triggered. The caller is expected to process the notification and return a // response via NotifRespond(). Each invocation of this function returns one // notification. As multiple notifications may be pending at any time, this function is // normally called within a polling loop. func NotifReceive(fd ScmpFd) (*ScmpNotifReq, error) { return notifReceive(fd) } // NotifRespond responds to a notification retrieved via NotifReceive(). The response Id // must match that of the corresponding notification retrieved via NotifReceive(). func NotifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error { return notifRespond(fd, scmpResp) } // NotifIDValid checks if a notification is still valid. An return value of nil means the // notification is still valid. Otherwise the notification is not valid. This can be used // to mitigate time-of-check-time-of-use (TOCTOU) attacks as described in seccomp_notify_id_valid(2). func NotifIDValid(fd ScmpFd, id uint64) error { return notifIDValid(fd, id) } libseccomp-golang-0.10.0/seccomp_internal.go000066400000000000000000000526201425045406100210370ustar00rootroot00000000000000// Internal functions for libseccomp Go bindings // No exported functions package seccomp import ( "errors" "fmt" "syscall" ) // Unexported C wrapping code - provides the C-Golang interface // Get the seccomp header in scope // Need stdlib.h for free() on cstrings // To compile libseccomp-golang against a specific version of libseccomp: // cd ../libseccomp && mkdir -p prefix // ./configure --prefix=$PWD/prefix && make && make install // cd ../libseccomp-golang // PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make // LD_PRELOAD=$PWD/../libseccomp/prefix/lib/libseccomp.so.2.5.0 PKG_CONFIG_PATH=$PWD/../libseccomp/prefix/lib/pkgconfig/ make test // #cgo pkg-config: libseccomp /* #include #include #include #if (SCMP_VER_MAJOR < 2) || \ (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 3) || \ (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 3 && SCMP_VER_MICRO < 1) #error This package requires libseccomp >= v2.3.1 #endif #define ARCH_BAD ~0 const uint32_t C_ARCH_BAD = ARCH_BAD; #ifndef SCMP_ARCH_PPC #define SCMP_ARCH_PPC ARCH_BAD #endif #ifndef SCMP_ARCH_PPC64 #define SCMP_ARCH_PPC64 ARCH_BAD #endif #ifndef SCMP_ARCH_PPC64LE #define SCMP_ARCH_PPC64LE ARCH_BAD #endif #ifndef SCMP_ARCH_S390 #define SCMP_ARCH_S390 ARCH_BAD #endif #ifndef SCMP_ARCH_S390X #define SCMP_ARCH_S390X ARCH_BAD #endif #ifndef SCMP_ARCH_PARISC #define SCMP_ARCH_PARISC ARCH_BAD #endif #ifndef SCMP_ARCH_PARISC64 #define SCMP_ARCH_PARISC64 ARCH_BAD #endif #ifndef SCMP_ARCH_RISCV64 #define SCMP_ARCH_RISCV64 ARCH_BAD #endif const uint32_t C_ARCH_NATIVE = SCMP_ARCH_NATIVE; const uint32_t C_ARCH_X86 = SCMP_ARCH_X86; const uint32_t C_ARCH_X86_64 = SCMP_ARCH_X86_64; const uint32_t C_ARCH_X32 = SCMP_ARCH_X32; const uint32_t C_ARCH_ARM = SCMP_ARCH_ARM; const uint32_t C_ARCH_AARCH64 = SCMP_ARCH_AARCH64; const uint32_t C_ARCH_MIPS = SCMP_ARCH_MIPS; const uint32_t C_ARCH_MIPS64 = SCMP_ARCH_MIPS64; const uint32_t C_ARCH_MIPS64N32 = SCMP_ARCH_MIPS64N32; const uint32_t C_ARCH_MIPSEL = SCMP_ARCH_MIPSEL; const uint32_t C_ARCH_MIPSEL64 = SCMP_ARCH_MIPSEL64; const uint32_t C_ARCH_MIPSEL64N32 = SCMP_ARCH_MIPSEL64N32; const uint32_t C_ARCH_PPC = SCMP_ARCH_PPC; const uint32_t C_ARCH_PPC64 = SCMP_ARCH_PPC64; const uint32_t C_ARCH_PPC64LE = SCMP_ARCH_PPC64LE; const uint32_t C_ARCH_S390 = SCMP_ARCH_S390; const uint32_t C_ARCH_S390X = SCMP_ARCH_S390X; const uint32_t C_ARCH_PARISC = SCMP_ARCH_PARISC; const uint32_t C_ARCH_PARISC64 = SCMP_ARCH_PARISC64; const uint32_t C_ARCH_RISCV64 = SCMP_ARCH_RISCV64; #ifndef SCMP_ACT_LOG #define SCMP_ACT_LOG 0x7ffc0000U #endif #ifndef SCMP_ACT_KILL_PROCESS #define SCMP_ACT_KILL_PROCESS 0x80000000U #endif #ifndef SCMP_ACT_KILL_THREAD #define SCMP_ACT_KILL_THREAD 0x00000000U #endif #ifndef SCMP_ACT_NOTIFY #define SCMP_ACT_NOTIFY 0x7fc00000U #endif const uint32_t C_ACT_KILL = SCMP_ACT_KILL; const uint32_t C_ACT_KILL_PROCESS = SCMP_ACT_KILL_PROCESS; const uint32_t C_ACT_KILL_THREAD = SCMP_ACT_KILL_THREAD; const uint32_t C_ACT_TRAP = SCMP_ACT_TRAP; const uint32_t C_ACT_ERRNO = SCMP_ACT_ERRNO(0); const uint32_t C_ACT_TRACE = SCMP_ACT_TRACE(0); const uint32_t C_ACT_LOG = SCMP_ACT_LOG; const uint32_t C_ACT_ALLOW = SCMP_ACT_ALLOW; const uint32_t C_ACT_NOTIFY = SCMP_ACT_NOTIFY; // The libseccomp SCMP_FLTATR_CTL_LOG member of the scmp_filter_attr enum was // added in v2.4.0 #if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4 #define SCMP_FLTATR_CTL_LOG _SCMP_FLTATR_MIN #endif // The following SCMP_FLTATR_* were added in libseccomp v2.5.0. #if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5 #define SCMP_FLTATR_CTL_SSB _SCMP_FLTATR_MIN #define SCMP_FLTATR_CTL_OPTIMIZE _SCMP_FLTATR_MIN #define SCMP_FLTATR_API_SYSRAWRC _SCMP_FLTATR_MIN #endif const uint32_t C_ATTRIBUTE_DEFAULT = (uint32_t)SCMP_FLTATR_ACT_DEFAULT; const uint32_t C_ATTRIBUTE_BADARCH = (uint32_t)SCMP_FLTATR_ACT_BADARCH; const uint32_t C_ATTRIBUTE_NNP = (uint32_t)SCMP_FLTATR_CTL_NNP; const uint32_t C_ATTRIBUTE_TSYNC = (uint32_t)SCMP_FLTATR_CTL_TSYNC; const uint32_t C_ATTRIBUTE_LOG = (uint32_t)SCMP_FLTATR_CTL_LOG; const uint32_t C_ATTRIBUTE_SSB = (uint32_t)SCMP_FLTATR_CTL_SSB; const uint32_t C_ATTRIBUTE_OPTIMIZE = (uint32_t)SCMP_FLTATR_CTL_OPTIMIZE; const uint32_t C_ATTRIBUTE_SYSRAWRC = (uint32_t)SCMP_FLTATR_API_SYSRAWRC; const int C_CMP_NE = (int)SCMP_CMP_NE; const int C_CMP_LT = (int)SCMP_CMP_LT; const int C_CMP_LE = (int)SCMP_CMP_LE; const int C_CMP_EQ = (int)SCMP_CMP_EQ; const int C_CMP_GE = (int)SCMP_CMP_GE; const int C_CMP_GT = (int)SCMP_CMP_GT; const int C_CMP_MASKED_EQ = (int)SCMP_CMP_MASKED_EQ; const int C_VERSION_MAJOR = SCMP_VER_MAJOR; const int C_VERSION_MINOR = SCMP_VER_MINOR; const int C_VERSION_MICRO = SCMP_VER_MICRO; #if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR >= 3 unsigned int get_major_version() { return seccomp_version()->major; } unsigned int get_minor_version() { return seccomp_version()->minor; } unsigned int get_micro_version() { return seccomp_version()->micro; } #else unsigned int get_major_version() { return (unsigned int)C_VERSION_MAJOR; } unsigned int get_minor_version() { return (unsigned int)C_VERSION_MINOR; } unsigned int get_micro_version() { return (unsigned int)C_VERSION_MICRO; } #endif // The libseccomp API level functions were added in v2.4.0 #if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4 const unsigned int seccomp_api_get(void) { // libseccomp-golang requires libseccomp v2.2.0, at a minimum, which // supported API level 2. However, the kernel may not support API level // 2 constructs which are the seccomp() system call and the TSYNC // filter flag. Return the "reserved" value of 0 here to indicate that // proper API level support is not available in libseccomp. return 0; } int seccomp_api_set(unsigned int level) { return -EOPNOTSUPP; } #endif typedef struct scmp_arg_cmp* scmp_cast_t; void* make_arg_cmp_array(unsigned int length) { return calloc(length, sizeof(struct scmp_arg_cmp)); } // Wrapper to add an scmp_arg_cmp struct to an existing arg_cmp array void add_struct_arg_cmp( struct scmp_arg_cmp* arr, unsigned int pos, unsigned int arg, int compare, uint64_t a, uint64_t b ) { arr[pos].arg = arg; arr[pos].op = compare; arr[pos].datum_a = a; arr[pos].datum_b = b; return; } // The seccomp notify API functions were added in v2.5.0 #if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5 struct seccomp_data { int nr; __u32 arch; __u64 instruction_pointer; __u64 args[6]; }; struct seccomp_notif { __u64 id; __u32 pid; __u32 flags; struct seccomp_data data; }; struct seccomp_notif_resp { __u64 id; __s64 val; __s32 error; __u32 flags; }; int seccomp_notify_alloc(struct seccomp_notif **req, struct seccomp_notif_resp **resp) { return -EOPNOTSUPP; } int seccomp_notify_fd(const scmp_filter_ctx ctx) { return -EOPNOTSUPP; } void seccomp_notify_free(struct seccomp_notif *req, struct seccomp_notif_resp *resp) { } int seccomp_notify_id_valid(int fd, uint64_t id) { return -EOPNOTSUPP; } int seccomp_notify_receive(int fd, struct seccomp_notif *req) { return -EOPNOTSUPP; } int seccomp_notify_respond(int fd, struct seccomp_notif_resp *resp) { return -EOPNOTSUPP; } #endif */ import "C" // Nonexported types type scmpFilterAttr uint32 // Nonexported constants const ( filterAttrActDefault scmpFilterAttr = iota filterAttrActBadArch filterAttrNNP filterAttrTsync filterAttrLog filterAttrSSB filterAttrOptimize filterAttrRawRC ) const ( // An error return from certain libseccomp functions scmpError C.int = -1 // Comparison boundaries to check for architecture validity archStart ScmpArch = ArchNative archEnd ScmpArch = ArchRISCV64 // Comparison boundaries to check for action validity actionStart ScmpAction = ActKillThread actionEnd ScmpAction = ActKillProcess // Comparison boundaries to check for comparison operator validity compareOpStart ScmpCompareOp = CompareNotEqual compareOpEnd ScmpCompareOp = CompareMaskedEqual ) var ( // errBadFilter is thrown on bad filter context. errBadFilter = errors.New("filter is invalid or uninitialized") errDefAction = errors.New("requested action matches default action of filter") // Constants representing library major, minor, and micro versions verMajor = uint(C.get_major_version()) verMinor = uint(C.get_minor_version()) verMicro = uint(C.get_micro_version()) ) // Nonexported functions // checkVersion returns an error if the libseccomp version being used // is less than the one specified by major, minor, and micro arguments. // Argument op is an arbitrary non-empty operation description, which // is used as a part of the error message returned. // // Most users should use checkAPI instead. func checkVersion(op string, major, minor, micro uint) error { if (verMajor > major) || (verMajor == major && verMinor > minor) || (verMajor == major && verMinor == minor && verMicro >= micro) { return nil } return &VersionError{ op: op, major: major, minor: minor, micro: micro, } } func ensureSupportedVersion() error { return checkVersion("seccomp", 2, 3, 1) } // Get the API level func getAPI() (uint, error) { api := C.seccomp_api_get() if api == 0 { return 0, errors.New("API level operations are not supported") } return uint(api), nil } // Set the API level func setAPI(api uint) error { if retCode := C.seccomp_api_set(C.uint(api)); retCode != 0 { e := errRc(retCode) if e == syscall.EOPNOTSUPP { return errors.New("API level operations are not supported") } return fmt.Errorf("could not set API level: %w", e) } return nil } // Filter helpers // Filter finalizer - ensure that kernel context for filters is freed func filterFinalizer(f *ScmpFilter) { f.Release() } func errRc(rc C.int) error { return syscall.Errno(-1 * rc) } // Get a raw filter attribute func (f *ScmpFilter) getFilterAttr(attr scmpFilterAttr) (C.uint32_t, error) { f.lock.Lock() defer f.lock.Unlock() if !f.valid { return 0x0, errBadFilter } var attribute C.uint32_t retCode := C.seccomp_attr_get(f.filterCtx, attr.toNative(), &attribute) if retCode != 0 { return 0x0, errRc(retCode) } return attribute, nil } // Set a raw filter attribute func (f *ScmpFilter) setFilterAttr(attr scmpFilterAttr, value C.uint32_t) error { f.lock.Lock() defer f.lock.Unlock() if !f.valid { return errBadFilter } retCode := C.seccomp_attr_set(f.filterCtx, attr.toNative(), value) if retCode != 0 { return errRc(retCode) } return nil } // DOES NOT LOCK OR CHECK VALIDITY // Assumes caller has already done this // Wrapper for seccomp_rule_add_... functions func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, length C.uint, cond C.scmp_cast_t) error { if length != 0 && cond == nil { return errors.New("null conditions list, but length is nonzero") } var retCode C.int if exact { retCode = C.seccomp_rule_add_exact_array(f.filterCtx, action.toNative(), C.int(call), length, cond) } else { retCode = C.seccomp_rule_add_array(f.filterCtx, action.toNative(), C.int(call), length, cond) } if retCode != 0 { switch e := errRc(retCode); e { case syscall.EFAULT: return fmt.Errorf("unrecognized syscall %#x", int32(call)) // libseccomp >= v2.5.0 returns EACCES, older versions return EPERM. // TODO: remove EPERM once libseccomp < v2.5.0 is not supported. case syscall.EPERM, syscall.EACCES: return errDefAction case syscall.EINVAL: return errors.New("two checks on same syscall argument") default: return e } } return nil } // Generic add function for filter rules func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact bool, conds []ScmpCondition) error { f.lock.Lock() defer f.lock.Unlock() if !f.valid { return errBadFilter } if len(conds) == 0 { if err := f.addRuleWrapper(call, action, exact, 0, nil); err != nil { return err } } else { argsArr := C.make_arg_cmp_array(C.uint(len(conds))) if argsArr == nil { return errors.New("error allocating memory for conditions") } defer C.free(argsArr) for i, cond := range conds { C.add_struct_arg_cmp(C.scmp_cast_t(argsArr), C.uint(i), C.uint(cond.Argument), cond.Op.toNative(), C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2)) } if err := f.addRuleWrapper(call, action, exact, C.uint(len(conds)), C.scmp_cast_t(argsArr)); err != nil { return err } } return nil } // Generic Helpers // Helper - Sanitize Arch token input func sanitizeArch(in ScmpArch) error { if in < archStart || in > archEnd { return fmt.Errorf("unrecognized architecture %#x", uint(in)) } if in.toNative() == C.C_ARCH_BAD { return fmt.Errorf("architecture %v is not supported on this version of the library", in) } return nil } func sanitizeAction(in ScmpAction) error { inTmp := in & 0x0000FFFF if inTmp < actionStart || inTmp > actionEnd { return fmt.Errorf("unrecognized action %#x", uint(inTmp)) } if inTmp != ActTrace && inTmp != ActErrno && (in&0xFFFF0000) != 0 { return errors.New("highest 16 bits must be zeroed except for Trace and Errno") } return nil } func sanitizeCompareOp(in ScmpCompareOp) error { if in < compareOpStart || in > compareOpEnd { return fmt.Errorf("unrecognized comparison operator %#x", uint(in)) } return nil } func archFromNative(a C.uint32_t) (ScmpArch, error) { switch a { case C.C_ARCH_X86: return ArchX86, nil case C.C_ARCH_X86_64: return ArchAMD64, nil case C.C_ARCH_X32: return ArchX32, nil case C.C_ARCH_ARM: return ArchARM, nil case C.C_ARCH_NATIVE: return ArchNative, nil case C.C_ARCH_AARCH64: return ArchARM64, nil case C.C_ARCH_MIPS: return ArchMIPS, nil case C.C_ARCH_MIPS64: return ArchMIPS64, nil case C.C_ARCH_MIPS64N32: return ArchMIPS64N32, nil case C.C_ARCH_MIPSEL: return ArchMIPSEL, nil case C.C_ARCH_MIPSEL64: return ArchMIPSEL64, nil case C.C_ARCH_MIPSEL64N32: return ArchMIPSEL64N32, nil case C.C_ARCH_PPC: return ArchPPC, nil case C.C_ARCH_PPC64: return ArchPPC64, nil case C.C_ARCH_PPC64LE: return ArchPPC64LE, nil case C.C_ARCH_S390: return ArchS390, nil case C.C_ARCH_S390X: return ArchS390X, nil case C.C_ARCH_PARISC: return ArchPARISC, nil case C.C_ARCH_PARISC64: return ArchPARISC64, nil case C.C_ARCH_RISCV64: return ArchRISCV64, nil default: return 0x0, fmt.Errorf("unrecognized architecture %#x", uint32(a)) } } // Only use with sanitized arches, no error handling func (a ScmpArch) toNative() C.uint32_t { switch a { case ArchX86: return C.C_ARCH_X86 case ArchAMD64: return C.C_ARCH_X86_64 case ArchX32: return C.C_ARCH_X32 case ArchARM: return C.C_ARCH_ARM case ArchARM64: return C.C_ARCH_AARCH64 case ArchMIPS: return C.C_ARCH_MIPS case ArchMIPS64: return C.C_ARCH_MIPS64 case ArchMIPS64N32: return C.C_ARCH_MIPS64N32 case ArchMIPSEL: return C.C_ARCH_MIPSEL case ArchMIPSEL64: return C.C_ARCH_MIPSEL64 case ArchMIPSEL64N32: return C.C_ARCH_MIPSEL64N32 case ArchPPC: return C.C_ARCH_PPC case ArchPPC64: return C.C_ARCH_PPC64 case ArchPPC64LE: return C.C_ARCH_PPC64LE case ArchS390: return C.C_ARCH_S390 case ArchS390X: return C.C_ARCH_S390X case ArchPARISC: return C.C_ARCH_PARISC case ArchPARISC64: return C.C_ARCH_PARISC64 case ArchRISCV64: return C.C_ARCH_RISCV64 case ArchNative: return C.C_ARCH_NATIVE default: return 0x0 } } // Only use with sanitized ops, no error handling func (a ScmpCompareOp) toNative() C.int { switch a { case CompareNotEqual: return C.C_CMP_NE case CompareLess: return C.C_CMP_LT case CompareLessOrEqual: return C.C_CMP_LE case CompareEqual: return C.C_CMP_EQ case CompareGreaterEqual: return C.C_CMP_GE case CompareGreater: return C.C_CMP_GT case CompareMaskedEqual: return C.C_CMP_MASKED_EQ default: return 0x0 } } func actionFromNative(a C.uint32_t) (ScmpAction, error) { aTmp := a & 0xFFFF switch a & 0xFFFF0000 { case C.C_ACT_KILL_PROCESS: return ActKillProcess, nil case C.C_ACT_KILL_THREAD: return ActKillThread, nil case C.C_ACT_TRAP: return ActTrap, nil case C.C_ACT_ERRNO: return ActErrno.SetReturnCode(int16(aTmp)), nil case C.C_ACT_TRACE: return ActTrace.SetReturnCode(int16(aTmp)), nil case C.C_ACT_LOG: return ActLog, nil case C.C_ACT_ALLOW: return ActAllow, nil case C.C_ACT_NOTIFY: return ActNotify, nil default: return 0x0, fmt.Errorf("unrecognized action %#x", uint32(a)) } } // Only use with sanitized actions, no error handling func (a ScmpAction) toNative() C.uint32_t { switch a & 0xFFFF { case ActKillProcess: return C.C_ACT_KILL_PROCESS case ActKillThread: return C.C_ACT_KILL_THREAD case ActTrap: return C.C_ACT_TRAP case ActErrno: return C.C_ACT_ERRNO | (C.uint32_t(a) >> 16) case ActTrace: return C.C_ACT_TRACE | (C.uint32_t(a) >> 16) case ActLog: return C.C_ACT_LOG case ActAllow: return C.C_ACT_ALLOW case ActNotify: return C.C_ACT_NOTIFY default: return 0x0 } } // Internal only, assumes safe attribute func (a scmpFilterAttr) toNative() uint32 { switch a { case filterAttrActDefault: return uint32(C.C_ATTRIBUTE_DEFAULT) case filterAttrActBadArch: return uint32(C.C_ATTRIBUTE_BADARCH) case filterAttrNNP: return uint32(C.C_ATTRIBUTE_NNP) case filterAttrTsync: return uint32(C.C_ATTRIBUTE_TSYNC) case filterAttrLog: return uint32(C.C_ATTRIBUTE_LOG) case filterAttrSSB: return uint32(C.C_ATTRIBUTE_SSB) case filterAttrOptimize: return uint32(C.C_ATTRIBUTE_OPTIMIZE) case filterAttrRawRC: return uint32(C.C_ATTRIBUTE_SYSRAWRC) default: return 0x0 } } func syscallFromNative(a C.int) ScmpSyscall { return ScmpSyscall(a) } func notifReqFromNative(req *C.struct_seccomp_notif) (*ScmpNotifReq, error) { scmpArgs := make([]uint64, 6) for i := 0; i < len(scmpArgs); i++ { scmpArgs[i] = uint64(req.data.args[i]) } arch, err := archFromNative(req.data.arch) if err != nil { return nil, err } scmpData := ScmpNotifData{ Syscall: syscallFromNative(req.data.nr), Arch: arch, InstrPointer: uint64(req.data.instruction_pointer), Args: scmpArgs, } scmpReq := &ScmpNotifReq{ ID: uint64(req.id), Pid: uint32(req.pid), Flags: uint32(req.flags), Data: scmpData, } return scmpReq, nil } func (scmpResp *ScmpNotifResp) toNative(resp *C.struct_seccomp_notif_resp) { resp.id = C.__u64(scmpResp.ID) resp.val = C.__s64(scmpResp.Val) resp.error = (C.__s32(scmpResp.Error) * -1) // kernel requires a negated value resp.flags = C.__u32(scmpResp.Flags) } // checkAPI checks that both the API level and the seccomp version is equal to // or greater than the specified minLevel and major, minor, micro, // respectively, and returns an error otherwise. Argument op is an arbitrary // non-empty operation description, used as a part of the error message // returned. func checkAPI(op string, minLevel uint, major, minor, micro uint) error { // Ignore error from getAPI, as it returns level == 0 in case of error. level, _ := getAPI() if level >= minLevel { return checkVersion(op, major, minor, micro) } return &VersionError{ op: op, curAPI: level, minAPI: minLevel, major: major, minor: minor, micro: micro, } } // Userspace Notification API // Calls to C.seccomp_notify* hidden from seccomp.go func notifSupported() error { return checkAPI("seccomp notification", 6, 2, 5, 0) } func (f *ScmpFilter) getNotifFd() (ScmpFd, error) { f.lock.Lock() defer f.lock.Unlock() if !f.valid { return -1, errBadFilter } if err := notifSupported(); err != nil { return -1, err } fd := C.seccomp_notify_fd(f.filterCtx) return ScmpFd(fd), nil } func notifReceive(fd ScmpFd) (*ScmpNotifReq, error) { var req *C.struct_seccomp_notif var resp *C.struct_seccomp_notif_resp if err := notifSupported(); err != nil { return nil, err } // we only use the request here; the response is unused if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 { return nil, errRc(retCode) } defer func() { C.seccomp_notify_free(req, resp) }() for { retCode, errno := C.seccomp_notify_receive(C.int(fd), req) if retCode == 0 { break } if errno == syscall.EINTR { continue } if errno == syscall.ENOENT { return nil, errno } return nil, errRc(retCode) } return notifReqFromNative(req) } func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error { var req *C.struct_seccomp_notif var resp *C.struct_seccomp_notif_resp if err := notifSupported(); err != nil { return err } // we only use the response here; the request is discarded if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 { return errRc(retCode) } defer func() { C.seccomp_notify_free(req, resp) }() scmpResp.toNative(resp) for { retCode, errno := C.seccomp_notify_respond(C.int(fd), resp) if retCode == 0 { break } if errno == syscall.EINTR { continue } if errno == syscall.ENOENT { return errno } return errRc(retCode) } return nil } func notifIDValid(fd ScmpFd, id uint64) error { if err := notifSupported(); err != nil { return err } for { retCode, errno := C.seccomp_notify_id_valid(C.int(fd), C.uint64_t(id)) if retCode == 0 { break } if errno == syscall.EINTR { continue } if errno == syscall.ENOENT { return errno } return errRc(retCode) } return nil } libseccomp-golang-0.10.0/seccomp_test.go000066400000000000000000000603301425045406100201770ustar00rootroot00000000000000// Tests for public API of libseccomp Go bindings package seccomp import ( "fmt" "os" "os/exec" "strings" "syscall" "testing" "time" "unsafe" ) // execInSubprocess calls the go test binary again for the same test. // This must be only top-level statement in the test function. Do not nest this. // It will slightly defect the test log output as the test is entered twice func execInSubprocess(t *testing.T, f func(t *testing.T)) { const subprocessEnvKey = `GO_SUBPROCESS_KEY` if testIDString, ok := os.LookupEnv(subprocessEnvKey); ok && testIDString == "1" { t.Run(`subprocess`, f) return } cmd := exec.Command(os.Args[0]) cmd.Args = []string{os.Args[0], "-test.run=" + t.Name() + "$", "-test.v=true"} for _, arg := range os.Args { if strings.HasPrefix(arg, `-test.testlogfile=`) { cmd.Args = append(cmd.Args, arg) } } cmd.Env = append(os.Environ(), subprocessEnvKey+"=1", ) cmd.Stdin = os.Stdin out, err := cmd.CombinedOutput() t.Logf("%s", out) if err != nil { t.Fatal(err) } } func TestExpectedSeccompVersion(t *testing.T) { execInSubprocess(t, subprocessExpectedSeccompVersion) } func subprocessExpectedSeccompVersion(t *testing.T) { // This environment variable can be set by CI. const name = "_EXPECTED_LIBSECCOMP_VERSION" expVer := os.Getenv(name) if expVer == "" { t.Skip(name, "not set") } expVer = strings.TrimPrefix(expVer, "v") curVer := fmt.Sprintf("%d.%d.%d", verMajor, verMinor, verMicro) t.Logf("testing against libseccomp %s", curVer) if curVer != expVer { t.Fatalf("libseccomp version mismatch: must be %s, got %s", expVer, curVer) } } // Type Function Tests func APILevelIsSupported() bool { return verMajor > 2 || (verMajor == 2 && verMinor >= 4) } func TestGetAPILevel(t *testing.T) { execInSubprocess(t, subprocessGetAPILevel) } func subprocessGetAPILevel(t *testing.T) { api, err := GetAPI() if !APILevelIsSupported() { if api != 0 { t.Errorf("API level returned despite lack of support: %v", api) } else if err == nil { t.Errorf("No error returned despite lack of API level support") } t.Skipf("Skipping test: %s", err) } else if err != nil { t.Errorf("Error getting API level: %s", err) } t.Logf("Got API level of %v\n", api) } func TestSetAPILevel(t *testing.T) { execInSubprocess(t, subprocessSetAPILevel) } func subprocessSetAPILevel(t *testing.T) { const expectedAPI = uint(1) err := SetAPI(expectedAPI) if !APILevelIsSupported() { if err == nil { t.Errorf("No error returned despite lack of API level support") } t.Skipf("Skipping test: %s", err) } else if err != nil { t.Errorf("Error setting API level: %s", err) } api, err := GetAPI() if err != nil { t.Errorf("Error getting API level: %s", err) } else if api != expectedAPI { t.Errorf("Got API level %v: expected %v", api, expectedAPI) } } func TestActionSetReturnCode(t *testing.T) { if ActInvalid.SetReturnCode(0x0010) != ActInvalid { t.Errorf("Able to set a return code on invalid action!") } codeSet := ActErrno.SetReturnCode(0x0001) if codeSet == ActErrno || codeSet.GetReturnCode() != 0x0001 { t.Errorf("Could not set return code on ActErrno") } } func TestSyscallGetName(t *testing.T) { call1 := ScmpSyscall(0x1) callFail := ScmpSyscall(0x999) name, err := call1.GetName() if err != nil { t.Errorf("Error getting syscall name for number 0x1") } else if len(name) == 0 { t.Errorf("Empty name returned for syscall 0x1") } t.Logf("Got name of syscall 0x1 on native arch as %s\n", name) _, err = callFail.GetName() if err == nil { t.Errorf("Getting nonexistent syscall should error!") } } func TestSyscallGetNameByArch(t *testing.T) { call1 := ScmpSyscall(0x1) callInvalid := ScmpSyscall(0x999) archGood := ArchAMD64 archBad := ArchInvalid name, err := call1.GetNameByArch(archGood) if err != nil { t.Errorf("Error getting syscall name for number 0x1 and arch AMD64") } else if name != "write" { t.Errorf("Got incorrect name for syscall 0x1 - expected write, got %s", name) } _, err = call1.GetNameByArch(archBad) if err == nil { t.Errorf("Bad architecture GetNameByArch() should error!") } _, err = callInvalid.GetNameByArch(archGood) if err == nil { t.Errorf("Bad syscall GetNameByArch() should error!") } _, err = callInvalid.GetNameByArch(archBad) if err == nil { t.Errorf("Bad syscall and bad arch GetNameByArch() should error!") } } func TestGetSyscallFromName(t *testing.T) { name1 := "write" nameInval := "NOTASYSCALL" syscall, err := GetSyscallFromName(name1) if err != nil { t.Errorf("Error getting syscall number of write: %s", err) } t.Logf("Got syscall number of write on native arch as %d\n", syscall) _, err = GetSyscallFromName(nameInval) if err == nil { t.Errorf("Getting an invalid syscall should error!") } } func TestGetSyscallFromNameByArch(t *testing.T) { name1 := "write" nameInval := "NOTASYSCALL" arch1 := ArchAMD64 archInval := ArchInvalid syscall, err := GetSyscallFromNameByArch(name1, arch1) if err != nil { t.Errorf("Error getting syscall number of write on AMD64: %s", err) } t.Logf("Got syscall number of write on AMD64 as %d\n", syscall) _, err = GetSyscallFromNameByArch(nameInval, arch1) if err == nil { t.Errorf("Getting invalid syscall with valid arch should error") } _, err = GetSyscallFromNameByArch(name1, archInval) if err == nil { t.Errorf("Getting valid syscall for invalid arch should error") } _, err = GetSyscallFromNameByArch(nameInval, archInval) if err == nil { t.Errorf("Getting invalid syscall for invalid arch should error") } } func TestMakeCondition(t *testing.T) { condition, err := MakeCondition(3, CompareNotEqual, 0x10) if err != nil { t.Errorf("Error making condition struct: %s", err) } else if condition.Argument != 3 || condition.Operand1 != 0x10 || condition.Operand2 != 0 || condition.Op != CompareNotEqual { t.Errorf("Condition struct was filled incorrectly") } condition, err = MakeCondition(3, CompareMaskedEqual, 0x10, 0x20) if err != nil { t.Errorf("Error making condition struct: %s", err) } else if condition.Argument != 3 || condition.Operand1 != 0x10 || condition.Operand2 != 0x20 || condition.Op != CompareMaskedEqual { t.Errorf("Condition struct was filled incorrectly") } _, err = MakeCondition(7, CompareNotEqual, 0x10) if err == nil { t.Errorf("Condition struct with bad syscall argument number should error") } _, err = MakeCondition(3, CompareInvalid, 0x10) if err == nil { t.Errorf("Condition struct with bad comparison operator should error") } _, err = MakeCondition(3, CompareMaskedEqual, 0x10, 0x20, 0x30) if err == nil { t.Errorf("MakeCondition with more than 2 arguments should fail") } _, err = MakeCondition(3, CompareMaskedEqual) if err == nil { t.Errorf("MakeCondition with no arguments should fail") } } // Utility Function Tests func TestGetNativeArch(t *testing.T) { arch, err := GetNativeArch() if err != nil { t.Errorf("GetNativeArch should not error!") } t.Logf("Got native arch of system as %s\n", arch.String()) } // Filter Tests func TestFilterCreateRelease(t *testing.T) { _, err := NewFilter(ActInvalid) if err == nil { t.Errorf("Can create filter with invalid action") } filter, err := NewFilter(ActKillThread) if err != nil { t.Errorf("Error creating filter: %s", err) } if !filter.IsValid() { t.Errorf("Filter created by NewFilter was not valid") } filter.Release() if filter.IsValid() { t.Errorf("Filter is valid after being released") } } func TestFilterReset(t *testing.T) { filter, err := NewFilter(ActKillThread) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter.Release() // Ensure the default action is ActKill action, err := filter.GetDefaultAction() if err != nil { t.Errorf("Error getting default action of filter") } else if action != ActKillThread { t.Errorf("Default action of filter was set incorrectly!") } // Reset with a different default action err = filter.Reset(ActAllow) if err != nil { t.Errorf("Error resetting filter!") } valid := filter.IsValid() if !valid { t.Errorf("Filter is no longer valid after reset!") } // The default action should no longer be ActKill action, err = filter.GetDefaultAction() if err != nil { t.Errorf("Error getting default action of filter") } else if action != ActAllow { t.Errorf("Default action of filter was set incorrectly!") } } func TestFilterArchFunctions(t *testing.T) { filter, err := NewFilter(ActKillThread) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter.Release() arch, err := GetNativeArch() if err != nil { t.Errorf("Error getting native architecture: %s", err) } present, err := filter.IsArchPresent(arch) if err != nil { t.Errorf("Error retrieving arch from filter: %s", err) } else if !present { t.Errorf("Filter does not contain native architecture by default") } // Adding the native arch again should succeed, as it's already present err = filter.AddArch(arch) if err != nil { t.Errorf("Adding arch to filter already containing it should succeed") } // Make sure we don't add the native arch again prospectiveArch := ArchX86 if arch == ArchX86 { prospectiveArch = ArchAMD64 } // Check to make sure this other arch isn't in the filter present, err = filter.IsArchPresent(prospectiveArch) if err != nil { t.Errorf("Error retrieving arch from filter: %s", err) } else if present { t.Errorf("Arch not added to filter is present") } // Try removing the nonexistent arch - should succeed err = filter.RemoveArch(prospectiveArch) if err != nil { t.Errorf("Error removing nonexistent arch: %s", err) } // Add an arch, see if it's in the filter err = filter.AddArch(prospectiveArch) if err != nil { t.Errorf("Could not add arch %s to filter: %s", prospectiveArch.String(), err) } present, err = filter.IsArchPresent(prospectiveArch) if err != nil { t.Errorf("Error retrieving arch from filter: %s", err) } else if !present { t.Errorf("Filter does not contain architecture %s after it was added", prospectiveArch.String()) } // Remove the arch again, make sure it's not in the filter err = filter.RemoveArch(prospectiveArch) if err != nil { t.Errorf("Could not remove arch %s from filter: %s", prospectiveArch.String(), err) } present, err = filter.IsArchPresent(prospectiveArch) if err != nil { t.Errorf("Error retrieving arch from filter: %s", err) } else if present { t.Errorf("Filter contains architecture %s after it was removed", prospectiveArch.String()) } } func TestFilterAttributeGettersAndSetters(t *testing.T) { filter, err := NewFilter(ActKillThread) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter.Release() act, err := filter.GetDefaultAction() if err != nil { t.Errorf("Error getting default action: %s", err) } else if act != ActKillThread { t.Errorf("Default action was set incorrectly") } err = filter.SetBadArchAction(ActAllow) if err != nil { t.Errorf("Error setting bad arch action: %s", err) } act, err = filter.GetBadArchAction() if err != nil { t.Errorf("Error getting bad arch action") } else if act != ActAllow { t.Errorf("Bad arch action was not set correctly!") } err = filter.SetBadArchAction(ActInvalid) if err == nil { t.Errorf("Setting bad arch action to an invalid action should error") } err = filter.SetNoNewPrivsBit(false) if err != nil { t.Errorf("Error setting no new privileges bit") } privs, err := filter.GetNoNewPrivsBit() if err != nil { t.Errorf("Error getting no new privileges bit!") } else if privs != false { t.Errorf("No new privileges bit was not set correctly") } // Checks that require API level >= 3 and libseccomp >= 2.4.0. if err := checkAPI(t.Name(), 3, 2, 4, 0); err != nil { t.Logf("Skipping the rest of the test: %v", err) return } err = filter.SetLogBit(true) if err != nil { t.Errorf("Error setting log bit: %v", err) } log, err := filter.GetLogBit() if err != nil { t.Errorf("Error getting log bit: %v", err) } else if log != true { t.Error("Log bit was not set correctly") } // Checks that require API level >= 4 and libseccomp >= 2.5.0. if err := checkAPI(t.Name(), 4, 2, 5, 0); err != nil { t.Logf("Skipping the rest of the test: %v", err) return } err = filter.SetSSB(true) if err != nil { t.Errorf("Error setting SSB bit: %v", err) } ssb, err := filter.GetSSB() if err != nil { t.Errorf("Error getting SSB bit: %v", err) } else if ssb != true { t.Error("SSB bit was not set correctly") } err = filter.SetOptimize(2) if err != nil { t.Errorf("Error setting optimize level: %v", err) } level, err := filter.GetOptimize() if err != nil { t.Errorf("Error getting optimize level: %v", err) } else if level != 2 { t.Error("Optimize level was not set correctly") } err = filter.SetRawRC(true) if err != nil { t.Errorf("Error setting RawRC flag: %v", err) } rawrc, err := filter.GetRawRC() if err != nil { t.Errorf("Error getting RawRC flag: %v", err) } else if rawrc != true { t.Error("RawRC flag was not set correctly") } } func TestMergeFilters(t *testing.T) { filter1, err := NewFilter(ActAllow) if err != nil { t.Errorf("Error creating filter: %s", err) } filter2, err := NewFilter(ActAllow) if err != nil { t.Errorf("Error creating filter: %s", err) } // Need to remove the native arch and add another to the second filter // Filters must NOT share architectures to be successfully merged nativeArch, err := GetNativeArch() if err != nil { t.Errorf("Error getting native arch: %s", err) } prospectiveArch := ArchAMD64 if nativeArch == ArchAMD64 { prospectiveArch = ArchX86 } err = filter2.AddArch(prospectiveArch) if err != nil { t.Errorf("Error adding architecture to filter: %s", err) } err = filter2.RemoveArch(nativeArch) if err != nil { t.Errorf("Error removing architecture from filter: %s", err) } err = filter1.Merge(filter2) if err != nil { t.Errorf("Error merging filters: %s", err) } if filter2.IsValid() { t.Errorf("Source filter should not be valid after merging") } filter3, err := NewFilter(ActKillThread) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter3.Release() err = filter1.Merge(filter3) if err == nil { t.Errorf("Attributes should have to match to merge filters") } } func TestAddRuleErrors(t *testing.T) { execInSubprocess(t, subprocessAddRuleErrors) } func subprocessAddRuleErrors(t *testing.T) { filter, err := NewFilter(ActAllow) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter.Release() err = filter.AddRule(ScmpSyscall(0x1), ActAllow) if err == nil { t.Error("expected error, got nil") } else if err != errDefAction { t.Errorf("expected error %v, got %v", errDefAction, err) } } func TestRuleAddAndLoad(t *testing.T) { execInSubprocess(t, subprocessRuleAddAndLoad) } func subprocessRuleAddAndLoad(t *testing.T) { // Test #1: Add a trivial filter filter1, err := NewFilter(ActAllow) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter1.Release() call, err := GetSyscallFromName("getpid") if err != nil { t.Errorf("Error getting syscall number of getpid: %s", err) } call2, err := GetSyscallFromName("setreuid") if err != nil { t.Errorf("Error getting syscall number of setreuid: %s", err) } call3, err := GetSyscallFromName("setreuid32") if err != nil { t.Errorf("Error getting syscall number of setreuid32: %s", err) } uid := syscall.Getuid() euid := syscall.Geteuid() err = filter1.AddRule(call, ActErrno.SetReturnCode(0x1)) if err != nil { t.Errorf("Error adding rule to restrict syscall: %s", err) } cond, err := MakeCondition(1, CompareEqual, uint64(euid)) if err != nil { t.Errorf("Error making rule to restrict syscall: %s", err) } cond2, err := MakeCondition(0, CompareEqual, uint64(uid)) if err != nil { t.Errorf("Error making rule to restrict syscall: %s", err) } conditions := []ScmpCondition{cond, cond2} err = filter1.AddRuleConditional(call2, ActErrno.SetReturnCode(0x2), conditions) if err != nil { t.Errorf("Error adding conditional rule: %s", err) } err = filter1.AddRuleConditional(call3, ActErrno.SetReturnCode(0x3), conditions) if err != nil { t.Errorf("Error adding second conditional rule: %s", err) } err = filter1.Load() if err != nil { t.Errorf("Error loading filter: %s", err) } // Try making a simple syscall, it should error pid := syscall.Getpid() if pid != -1 { t.Errorf("Syscall should have returned error code!") } // Try making a Geteuid syscall that should normally succeed err = syscall.Setreuid(uid, euid) if err == nil { t.Errorf("Syscall should have returned error code!") } else if err != syscall.Errno(2) && err != syscall.Errno(3) { t.Errorf("Syscall returned incorrect error code - likely not blocked by Seccomp!") } } func TestLogAct(t *testing.T) { execInSubprocess(t, subprocessLogAct) } func subprocessLogAct(t *testing.T) { // ActLog requires API >=3 and libseccomp >= 2.4.0. if err := checkAPI(t.Name(), 3, 2, 4, 0); err != nil { t.Skip(err) } expectedPid := syscall.Getpid() filter, err := NewFilter(ActAllow) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter.Release() call, err := GetSyscallFromName("getpid") if err != nil { t.Errorf("Error getting syscall number of getpid: %s", err) } err = filter.AddRule(call, ActLog) if err != nil { t.Errorf("Error adding rule to log syscall: %s", err) } err = filter.Load() if err != nil { t.Errorf("Error loading filter: %s", err) } // Try making a simple syscall, it should succeed pid := syscall.Getpid() if pid != expectedPid { t.Errorf("Syscall should have returned expected pid (%d != %d)", pid, expectedPid) } } func TestCreateActKillThreadFilter(t *testing.T) { execInSubprocess(t, subprocessCreateActKillThreadFilter) } func subprocessCreateActKillThreadFilter(t *testing.T) { filter, err := NewFilter(ActKillThread) if err != nil { t.Errorf("Error creating filter: %s", err) } if !filter.IsValid() { t.Errorf("Filter created by NewFilter was not valid") } } func TestCreateActKillProcessFilter(t *testing.T) { execInSubprocess(t, subprocessCreateActKillProcessFilter) } func subprocessCreateActKillProcessFilter(t *testing.T) { // Requires API level >= 3 and libseccomp >= 2.4.0 if err := checkAPI(t.Name(), 3, 2, 4, 0); err != nil { t.Skip(err) } filter, err := NewFilter(ActKillThread) if err != nil { t.Errorf("Error creating filter: %s", err) } if !filter.IsValid() { t.Errorf("Filter created by NewFilter was not valid") } } // // Seccomp notification tests // // notifTest describes a seccomp notification test type notifTest struct { syscall ScmpSyscall args [6]uintptr arch ScmpArch respErr int32 respVal uint64 respFlags uint32 expectedErr error expectedVal uint64 } // notifHandler handles seccomp notifications and responses func notifHandler(ch chan error, fd ScmpFd, tests []notifTest) { for _, test := range tests { req, err := NotifReceive(fd) if err != nil { ch <- fmt.Errorf("Error in NotifReceive(): %s", err) return } if req.Data.Syscall != test.syscall { want, _ := test.syscall.GetName() got, _ := req.Data.Syscall.GetName() ch <- fmt.Errorf("Error in notification request syscall: got %s, want %s", got, want) return } if req.Data.Arch != test.arch { ch <- fmt.Errorf("Error in notification request arch: got %s, want %s", req.Data.Arch, test.arch) return } for i, arg := range test.args { if arg != uintptr(req.Data.Args[i]) { ch <- fmt.Errorf("Error in syscall arg[%d]: got 0x%x, want 0x%x", i, req.Data.Args[i], arg) return } } // TOCTOU check if err := NotifIDValid(fd, req.ID); err != nil { ch <- fmt.Errorf("TOCTOU check failed: req.ID is no longer valid: %s", err) return } resp := &ScmpNotifResp{ ID: req.ID, Error: test.respErr, Val: test.respVal, Flags: test.respFlags, } if err = NotifRespond(fd, resp); err != nil { ch <- fmt.Errorf("Error in notification response: %s", err) return } ch <- nil } } func TestNotif(t *testing.T) { execInSubprocess(t, subprocessNotif) } func subprocessNotif(t *testing.T) { if err := notifSupported(); err != nil { t.Skip(err) } arch, err := GetNativeArch() if err != nil { t.Errorf("Error in GetNativeArch(): %s", err) return } cwd, err := os.Getwd() if err != nil { t.Errorf("Error in Getwd(): %s", err) return } // Create a filter that only notifies on chdir. This way, while the // seccomp filter applies to all threads, we can run the target and // handling in different go routines with no problem as only the target // goroutine uses chdir. filter, err := NewFilter(ActAllow) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter.Release() call, err := GetSyscallFromName("chdir") if err != nil { t.Errorf("Error getting syscall number: %s", err) } err = filter.AddRule(call, ActNotify) if err != nil { t.Errorf("Error adding rule to log syscall: %s", err) } nonExistentPath, err := syscall.BytePtrFromString("/non-existent-path") if err != nil { t.Errorf("Error converting string: %s", err) } currentWorkingDirectory, err := syscall.BytePtrFromString(cwd) if err != nil { t.Errorf("Error converting string: %s", err) } tests := []notifTest{ { syscall: call, args: [6]uintptr{uintptr(unsafe.Pointer(nonExistentPath)), 0, 0, 0, 0, 0}, arch: arch, respErr: 0, respVal: 0, respFlags: NotifRespFlagContinue, expectedErr: syscall.ENOENT, expectedVal: ^uint64(0), // -1 }, { syscall: call, args: [6]uintptr{uintptr(unsafe.Pointer(currentWorkingDirectory)), 0, 0, 0, 0, 0}, arch: arch, respErr: 0, respVal: 0, respFlags: NotifRespFlagContinue, expectedErr: syscall.Errno(0), expectedVal: 0, }, { syscall: call, args: [6]uintptr{uintptr(unsafe.Pointer(nonExistentPath)), 0, 0, 0, 0, 0}, arch: arch, respErr: int32(syscall.ENOMEDIUM), respVal: ^uint64(0), // -1 respFlags: 0, expectedErr: syscall.ENOMEDIUM, expectedVal: ^uint64(0), // -1 }, { syscall: call, args: [6]uintptr{uintptr(unsafe.Pointer(currentWorkingDirectory)), 0, 0, 0, 0, 0}, arch: arch, respErr: int32(syscall.EPIPE), respVal: ^uint64(0), // -1 respFlags: 0, expectedErr: syscall.EPIPE, expectedVal: ^uint64(0), // -1 }, } seccompFdChan := make(chan ScmpFd) errorChan := make(chan error, 2) infoChan := make(chan string) done := make(chan struct{}) go func() { err = filter.Load() if err != nil { t.Errorf("Error loading filter: %s", err) } fd, err := filter.GetNotifFd() if err != nil { t.Errorf("Error getting filter notification fd: %s", err) } if fd < 3 { t.Errorf("Error in notification fd: want >=3, got %v", fd) } seccompFdChan <- fd for i, test := range tests { infoChan <- fmt.Sprintf("Starting test %d", i) r1, r2, err := syscall.Syscall6(syscall.SYS_CHDIR, test.args[0], test.args[1], test.args[2], test.args[3], test.args[4], test.args[5]) if err != test.expectedErr || uint64(r1) != test.expectedVal { errorChan <- fmt.Errorf("test #%d: error in syscall: want \"%s\", got \"%s\" (want %v, got r1=%v, r2=%v)", i, test.expectedErr, err, test.expectedVal, r1, r2) } infoChan <- fmt.Sprintf("Test %d completed", i) } done <- struct{}{} }() seccompFd := <-seccompFdChan go notifHandler(errorChan, seccompFd, tests) L: for { select { case <-done: t.Logf("Tests completed") break L case msg := <-infoChan: t.Logf("%s", msg) case err = <-errorChan: if err != nil { t.Errorf("Received error: %s", err.Error()) break L } case <-time.After(5 * time.Second): t.Errorf("Timeout during tests") break L } } } // TestNotifUnsupported is checking that the user notify API correctly returns // an error when we don't have the proper api level, for example when linking // with libseccomp < 2.5.0. func TestNotifUnsupported(t *testing.T) { execInSubprocess(t, subprocessNotifUnsupported) } func subprocessNotifUnsupported(t *testing.T) { if err := notifSupported(); err == nil { t.Skip("seccomp notification is supported") } filter, err := NewFilter(ActAllow) if err != nil { t.Errorf("Error creating filter: %s", err) } defer filter.Release() _, err = filter.GetNotifFd() if err == nil { t.Error("GetNotifFd: got nil, want error") } } libseccomp-golang-0.10.0/seccomp_ver_test.go000066400000000000000000000033321425045406100210520ustar00rootroot00000000000000package seccomp import ( "testing" ) func TestCheckVersion(t *testing.T) { for _, tc := range []struct { // input op string x, y, z uint // expectations isErr bool }{ {op: "verNew", x: 100, y: 99, z: 7, isErr: true}, {op: "verMajor+1", x: verMajor + 1, isErr: true}, {op: "verMinor+1", x: verMajor, y: verMinor + 1, isErr: true}, {op: "verMicro+1", x: verMajor, y: verMinor, z: verMicro + 1, isErr: true}, // Current version is guaranteed to succeed. {op: "verCur", x: verMajor, y: verMinor, z: verMicro}, // 2.2.0 is guaranteed to succeed. {op: "verOld", x: 2, y: 2, z: 0}, } { err := checkVersion(tc.op, tc.x, tc.y, tc.z) t.Log(err) if tc.isErr { if err == nil { t.Errorf("case %s: expected error, got nil", tc.op) } continue } if err != nil { t.Errorf("case %s: expected no error, got %s", tc.op, err) } } } func TestCheckAPI(t *testing.T) { curAPI, _ := getAPI() for _, tc := range []struct { // input op string level uint x, y, z uint // expectations isErr bool }{ {op: "apiHigh", level: 99, isErr: true}, {op: "api+1", level: curAPI + 1, isErr: true}, // Cases that should succeed. {op: "apiCur", level: curAPI}, {op: "api0", level: 0}, {op: "apiCur_verCur", level: curAPI, x: verMajor, y: verMinor, z: verMicro}, // Adequate API level but version is too high. {op: "verHigh", level: 0, x: 99, isErr: true}, // Other cases with version are checked by testCheckVersion. } { err := checkAPI(tc.op, tc.level, tc.x, tc.y, tc.z) t.Log(err) if tc.isErr { if err == nil { t.Errorf("case %s: expected error, got nil", tc.op) } continue } if err != nil { t.Errorf("case %s: expected no error, got %s", tc.op, err) } } }