diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 77973da..ce35e7d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,36 +9,45 @@ on: pull_request: jobs: - test: + build-libseccomp: + # Build every libseccomp version used below, once per architecture, and + # share the result as an artifact. Doing this in the test job instead + # would mean building the very same library in every matrix cell. strategy: fail-fast: false matrix: - go-version: [1.19.x, 1.25.x, 1.26.x] - libseccomp: ["v2.3.3", "v2.4.4", "v2.5.6", "v2.6.1", "HEAD"] - os: [ubuntu-24.04, ubuntu-24.04-arm, ubuntu-26.04, ubuntu-26.04-arm] + # v2.3.1 is the minimum version supported by this package. It is only + # used by the cross-version job below, not by the test job. + libseccomp: ["v2.3.1", "v2.3.3", "v2.4.4", "v2.5.6", "v2.6.1", "HEAD"] + os: [ubuntu-24.04, ubuntu-24.04-arm] + include: + - os: ubuntu-24.04 + arch: amd64 + - os: ubuntu-24.04-arm + arch: arm64 + exclude: + # v2.3.1 is only used by the cross-version job, which is amd64 only. + - libseccomp: "v2.3.1" + os: ubuntu-24.04-arm runs-on: ${{ matrix.os }} - steps: + env: + # sha256 checksums for libseccomp release tarballs. + SHA256_2_3_1: ff5bdd2168790f1979e24eaa498f8606c2f2d96f08a8dc4006a2e88affa4562b + SHA256_2_3_3: 7fc28f4294cc72e61c529bedf97e705c3acf9c479a8f1a3028d4cd2ca9f3b155 + SHA256_2_4_4: 4e79738d1ef3c9b7ca9769f1f8b8d84fc17143c2c1c432e53b9c64787e0ff3eb + SHA256_2_5_6: 04c37d72965dce218a0c94519b056e1775cf786b5260ee2b7992956c4ee38633 + SHA256_2_6_1: 501f66c667225d53791b97e1d7cf85ab764c297d04881f60f38f451c4b0ee1be - - name: checkout - uses: actions/checkout@v7 + steps: - name: build libseccomp ${{ matrix.libseccomp }} - env: - # sha256 checksums for libseccomp release tarballs. - SHA256_2_3_3: 7fc28f4294cc72e61c529bedf97e705c3acf9c479a8f1a3028d4cd2ca9f3b155 - SHA256_2_4_4: 4e79738d1ef3c9b7ca9769f1f8b8d84fc17143c2c1c432e53b9c64787e0ff3eb - SHA256_2_5_6: 04c37d72965dce218a0c94519b056e1775cf786b5260ee2b7992956c4ee38633 - SHA256_2_6_1: 501f66c667225d53791b97e1d7cf85ab764c297d04881f60f38f451c4b0ee1be run: | set -x sudo apt -qq update sudo apt -qq install gperf - PREFIX="$(pwd)/seccomp" - LIBDIR="$PREFIX/lib" - VER="${{ matrix.libseccomp }}" if [[ "$VER" == v* ]]; then # A specific release: fetch and verify the release tarball @@ -56,7 +65,7 @@ jobs: # Any other git tag/branch/sha: clone and build from the repo. git clone https://github.com/seccomp/libseccomp cd libseccomp - git checkout $VER + git checkout "$VER" # 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 @@ -66,20 +75,97 @@ jobs: # - 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. + # - the LIBFILE pattern in the test job expects single digits. VER=9.9.9 sed -i "/^AC_INIT(/s/0\.0\.0/$VER/" configure.ac ./autogen.sh fi - ./configure --prefix="$PREFIX" --libdir="$LIBDIR" - make + + # Install to a fixed absolute location, since libseccomp.pc records + # it, and the jobs below unpack this to the very same place. + PREFIX="/opt/seccomp/$VER" + ./configure --prefix="$PREFIX" --libdir="$PREFIX/lib" + make -j"$(nproc)" 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.?.?.?)" + # Pack it up, rather than uploading the tree as is, since GitHub + # artifacts preserve neither symlinks nor file modes. + tar -cf "seccomp-$VER.tar" -C / "opt/seccomp/$VER" + + - name: upload libseccomp ${{ matrix.libseccomp }} + uses: actions/upload-artifact@v7 + with: + name: seccomp-${{ matrix.libseccomp }}-${{ matrix.arch }} + path: seccomp-*.tar + retention-days: 1 + + + test: + needs: build-libseccomp + + strategy: + fail-fast: false + matrix: + # Every libseccomp version on every runner, with the latest Go. + go-version: [1.26.x] + libseccomp: ["v2.3.3", "v2.4.4", "v2.5.6", "v2.6.1", "HEAD"] + os: [ubuntu-24.04, ubuntu-24.04-arm, ubuntu-26.04, ubuntu-26.04-arm] + include: + # Which of the artifacts built above to use. + - os: ubuntu-24.04 + arch: amd64 + - os: ubuntu-24.04-arm + arch: arm64 + - os: ubuntu-26.04 + arch: amd64 + - os: ubuntu-26.04-arm + arch: arm64 + # The Go version does not interact with the libseccomp version, so + # the other supported Go versions get a cell each, rather than a + # full sweep. 1.19 is the minimum one (see go.mod). + # + # Unlike the entries above, these create new combinations rather + # than adding to the existing ones, so they have to spell out arch + # as well (an include entry is not applied to a combination another + # include entry has created). + - go-version: 1.19.x + libseccomp: "v2.6.1" + os: ubuntu-24.04 + arch: amd64 + - go-version: 1.25.x + libseccomp: "v2.6.1" + os: ubuntu-24.04 + arch: amd64 + + runs-on: ${{ matrix.os }} + + steps: + + - name: checkout + uses: actions/checkout@v7 + + - name: download libseccomp ${{ matrix.libseccomp }} + uses: actions/download-artifact@v8 + with: + name: seccomp-${{ matrix.libseccomp }}-${{ matrix.arch }} + + - name: install libseccomp ${{ matrix.libseccomp }} + run: | + set -x + sudo tar -C / -xf seccomp-*.tar + rm -f seccomp-*.tar + + # The tarball unpacks into /opt/seccomp/; for HEAD, that is + # the stand-in version it was built as. + set -- /opt/seccomp/* + [ $# -eq 1 ] || { echo "Error: expected a single version, got: $*"; exit 1; } + PREFIX="$1" + VER="${PREFIX##*/}" + + # For the next steps to build and execute with this library. + echo "PKG_CONFIG_LIBDIR=$PREFIX/lib/pkgconfig" >> $GITHUB_ENV + LIBFILE="$(echo $PREFIX/lib/libseccomp.so.?.?.?)" echo "LD_PRELOAD=$LIBFILE" >> $GITHUB_ENV # For TestExpectedSeccompVersion. echo "_EXPECTED_LIBSECCOMP_VERSION=$VER" >> $GITHUB_ENV @@ -88,22 +174,149 @@ jobs: uses: actions/setup-go@v7 with: go-version: ${{ matrix.go-version }} - # Add libseccomp.pc path so that setup-go adds this file hash to cache key. - # This way, we'll have different caches for different libseccomp versions. - cache-dependency-path: | - go.sum - ${{ env.PKG_CONFIG_LIBDIR }}/libseccomp.pc + # Caching doesn't really work across multiple libseccomp versions + # (see the cross-version job below), so don't bother. + cache: false - name: build run: make build + - name: check symbols + run: make check-symbols + - name: test run: make test + - name: test against system libseccomp + # Run the test binary built above against the distro-provided + # libseccomp, which is a different version than the one it was compiled + # against. This is exactly the scenario the weak references and the + # compile-time/run-time version checks are there for. + # + # Not done for HEAD, which is built as version 9.9.9, and so needs + # libseccomp.so.9, which no distro provides. + if: matrix.libseccomp != 'HEAD' + run: | + set -x + # Use distro-provided libseccomp.so. + unset LD_PRELOAD + + # Make sure it is indeed the distro-provided library, rather than the + # one installed above (which lives under /opt/seccomp). + LIB="$(ldd ./libseccomp-golang.test | awk '/libseccomp\.so\.2 /{print $3}')" + LIB="$(readlink -f "$LIB")" + case "$LIB" in + /opt/seccomp/*) echo "Error: not a system libseccomp: $LIB"; exit 1;; + esac + + # The version reported by the package is the lower of the compile-time + # and the run-time ones, which is what TestExpectedSeccompVersion has + # to expect here. + SYS_VER="${LIB##*/libseccomp.so.}" + case "$SYS_VER" in + [0-9]*.[0-9]*.[0-9]*) ;; + *) echo "Error: can not tell the version of $LIB"; exit 1;; + esac + _EXPECTED_LIBSECCOMP_VERSION="$(printf '%s\n%s\n' \ + "$_EXPECTED_LIBSECCOMP_VERSION" "$SYS_VER" | sort -V | head -1)" + export _EXPECTED_LIBSECCOMP_VERSION + + # Run the pre-built test. + make test-run + + + cross-version: + # Check that a test binary built against one libseccomp version works when + # used with another one, in both directions. In particular, this guards + # against a binary built against a newer libseccomp not even being able to + # start with an older one (see seccomp_compat.h for how that is avoided). + needs: build-libseccomp + + strategy: + fail-fast: false + matrix: + # The version to compile against. The resulting test binary is then + # run against every version built above. + libseccomp: ["v2.3.1", "v2.3.3", "v2.4.4", "v2.5.6", "v2.6.1"] + + runs-on: ubuntu-24.04 + + steps: + + - name: checkout + uses: actions/checkout@v7 + + - name: download libseccomp builds + uses: actions/download-artifact@v8 + with: + # Releases only: HEAD is built as 9.9.9, so it provides + # libseccomp.so.9, which none of these binaries ask for. + pattern: seccomp-v*-amd64 + merge-multiple: true + path: seccomp-tars + + - name: install libseccomp builds + run: | + set -x + for tar in seccomp-tars/*.tar; do + sudo tar -C / -xf "$tar" + done + rm -rf seccomp-tars + ls -d /opt/seccomp/* + + - name: install go + uses: actions/setup-go@v7 + with: + go-version: 1.26.x + # Caching doesn't really work across multiple libseccomp versions + # (same as the test job above), so don't bother. + cache: false + + - name: build test binary against libseccomp ${{ matrix.libseccomp }} + env: + COMPILE_VER: ${{ matrix.libseccomp }} + run: | + set -x + export PKG_CONFIG_LIBDIR="/opt/seccomp/${COMPILE_VER#v}/lib/pkgconfig" + pkg-config --modversion libseccomp + make test-build + make check-symbols + + - name: run it against every libseccomp version + env: + COMPILE_VER: ${{ matrix.libseccomp }} + run: | + compile="${COMPILE_VER#v}" + rc=0 + for dir in /opt/seccomp/*; do + run="${dir##*/}" + # The package reports the lower of the compile-time and the run-time + # version (see getMinVersion), which is what the tests expect. + expected="$(printf '%s\n%s\n' "$compile" "$run" | sort -V | head -1)" + echo "::group::compile-time $compile, run-time $run (expecting $expected)" + # Use LD_LIBRARY_PATH rather than LD_PRELOAD, so that the version + # being tested is the only libseccomp that can be loaded at all. + export LD_LIBRARY_PATH="$dir/lib" + # Make sure the above actually took effect. + ldd ./libseccomp-golang.test | grep -F "=> $dir/lib/libseccomp.so.2" || { + echo "Error: libseccomp.so.2 does not resolve to $dir/lib" + ldd ./libseccomp-golang.test + exit 1 + } + # Make the dynamic linker resolve all the symbols upfront, rather + # than on first use, so that a missing one fails here, rather than + # only for those users who call the affected functionality. + LD_BIND_NOW=1 _EXPECTED_LIBSECCOMP_VERSION="$expected" make test-run || rc=1 + echo "::endgroup::" + done + exit $rc + + all-done: needs: - test + - cross-version runs-on: ubuntu-24.04 steps: - run: echo "All jobs completed" diff --git a/Makefile b/Makefile index b893a48..359a05f 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ all: check .PHONY: check -check: lint build test +check: lint build test check-symbols # 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 @@ -11,8 +11,71 @@ check: lint build test TEST_TIMEOUT=10s .PHONY: test -test: - go test -v -timeout $(TEST_TIMEOUT) +test: test-build test-run + +.PHONY: test-build +test-build: + go test -c + +.PHONY: test-run +test-run: + ./libseccomp-golang.test -test.v -test.timeout $(TEST_TIMEOUT) + +# All libseccomp functions available in v2.3.1, the minimum version supported +# by this package. Since these are always present, they are the only ones that +# may be referenced strongly; anything added later has to be referenced weakly +# and called via a compat_* wrapper, so that a binary compiled against a newer +# libseccomp can still be loaded and used with an older run-time library. See +# seccomp_compat.h for details. +LIBSECCOMP_V231_SYMBOLS = \ + seccomp_arch_add \ + seccomp_arch_exist \ + seccomp_arch_native \ + seccomp_arch_remove \ + seccomp_attr_get \ + seccomp_attr_set \ + seccomp_export_bpf \ + seccomp_export_pfc \ + seccomp_init \ + seccomp_load \ + seccomp_merge \ + seccomp_release \ + seccomp_reset \ + seccomp_rule_add_array \ + seccomp_rule_add_exact_array \ + seccomp_syscall_priority \ + seccomp_syscall_resolve_name \ + seccomp_syscall_resolve_name_arch \ + seccomp_syscall_resolve_num_arch \ + seccomp_version + +.PHONY: check-symbols +check-symbols: test-build + @# A strong reference to a symbol the run-time library does not have makes + @# the binary fail to load, so anything post-v2.3.1 has to stay weak. + @command -v nm >/dev/null 2>&1 || { echo "nm not found; skipping symbol check"; exit 0; } + @strong=$$(nm -D --undefined-only ./libseccomp-golang.test \ + | awk '$$1 == "U" && $$2 ~ /^seccomp_/ { print $$2 }' \ + | grep -vx $(patsubst %,-e %,$(LIBSECCOMP_V231_SYMBOLS)) || true); \ + if [ -n "$$strong" ]; then \ + echo "Error: strong references to libseccomp symbols added after v2.3.1:"; \ + echo "$$strong" | sed 's/^/ /'; \ + echo "Declare them weak and call them via a compat_* wrapper (see seccomp_compat.h)."; \ + exit 1; \ + fi + @# A weak reference stays weak no matter who calls it, so the check above + @# can not see a direct call from Go, which would crash on NULL rather + @# than fail to load. Only seccomp_compat.c may call these directly, and + @# only after checking for NULL. + @direct=$$(grep -n -o 'C\.seccomp_[a-z0-9_]*' *.go \ + | sed 's/C\.//' \ + | grep -v $(patsubst %,-e ':%$$',$(LIBSECCOMP_V231_SYMBOLS)) || true); \ + if [ -n "$$direct" ]; then \ + echo "Error: direct calls to libseccomp symbols added after v2.3.1:"; \ + echo "$$direct" | sed 's/^/ /'; \ + echo "Call them via a compat_* wrapper instead (see seccomp_compat.h)."; \ + exit 1; \ + fi .PHONY: lint lint: diff --git a/seccomp.go b/seccomp.go index 6f09b33..63aabc4 100644 --- a/seccomp.go +++ b/seccomp.go @@ -18,26 +18,7 @@ import ( ) /* -#include -#include -#include - -// The following functions were added in libseccomp v2.6.0. -#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 6 -int seccomp_precompute(scmp_filter_ctx ctx) { - return -EOPNOTSUPP; -} -int seccomp_export_bpf_mem(const scmp_filter_ctx ctx, void *buf, size_t *len) { - return -EOPNOTSUPP; -} -int seccomp_transaction_start(const scmp_filter_ctx ctx) { - return -EOPNOTSUPP; -} -int seccomp_transaction_commit(const scmp_filter_ctx ctx) { - return -EOPNOTSUPP; -} -void seccomp_transaction_reject(const scmp_filter_ctx ctx) {} -#endif +#include "seccomp_compat.h" */ import "C" @@ -45,6 +26,10 @@ import "C" // VersionError represents an error when either the system libseccomp version // or the kernel version is too old to perform the operation requested. +// +// The libseccomp version it reports is the effective one, that is the lower of +// the compile-time and the run-time versions (see [GetLibraryVersion]), since +// an operation needs to be available in both. type VersionError struct { op string // operation that failed or would fail major, minor, micro uint // minimally required libseccomp version @@ -62,11 +47,11 @@ func init() { 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)", + "(effective 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)", + return fmt.Sprintf("%s requires libseccomp >= %d.%d.%d (effective version: %d.%d.%d)", e.op, e.major, e.minor, e.micro, verMajor, verMinor, verMicro) } @@ -469,14 +454,15 @@ func (a ScmpAction) GetReturnCode() int16 { // General utility functions -// GetLibraryVersion returns the version of the library the bindings are built -// against. +// GetLibraryVersion returns the version of the libseccomp library used, +// which is the lower of the compile-time and run-time versions. // 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. +// GetAPI returns the API level supported by the system, which is the lower of +// the compile-time and run-time supported levels. // 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: @@ -851,7 +837,11 @@ func (f *ScmpFilter) Precompute() error { return errBadFilter } - if retCode := C.seccomp_precompute(f.filterCtx); retCode != 0 { + if e := checkVersion("Precompute", 2, 6, 0); e != nil { + return e + } + + if retCode := C.compat_precompute(f.filterCtx); retCode != 0 { return errRc(retCode) } @@ -1262,14 +1252,18 @@ func (f *ScmpFilter) ExportBPFMem() ([]byte, error) { return nil, errBadFilter } + if e := checkVersion("ExportBPFMem", 2, 6, 0); e != nil { + return nil, e + } + var len C.size_t // Get the size required. - if retCode := C.seccomp_export_bpf_mem(f.filterCtx, unsafe.Pointer(nil), &len); retCode < 0 { + if retCode := C.compat_export_bpf_mem(f.filterCtx, unsafe.Pointer(nil), &len); retCode < 0 { return nil, errRc(retCode) } // Get the data. buf := make([]byte, int(len)) - if retCode := C.seccomp_export_bpf_mem(f.filterCtx, unsafe.Pointer(&buf[0]), &len); retCode < 0 { + if retCode := C.compat_export_bpf_mem(f.filterCtx, unsafe.Pointer(&buf[0]), &len); retCode < 0 { return nil, errRc(retCode) } @@ -1324,7 +1318,11 @@ func (f *ScmpFilter) TransactionStart() error { return errBadFilter } - if retCode := C.seccomp_transaction_start(f.filterCtx); retCode < 0 { + if e := checkVersion("TransactionStart", 2, 6, 0); e != nil { + return e + } + + if retCode := C.compat_transaction_start(f.filterCtx); retCode < 0 { return errRc(retCode) } @@ -1340,7 +1338,11 @@ func (f *ScmpFilter) TransactionReject() { return } - C.seccomp_transaction_reject(f.filterCtx) + if checkVersion("TransactionReject", 2, 6, 0) != nil { + return + } + + C.compat_transaction_reject(f.filterCtx) } // TransactionCommit commits a transaction started by [TransactionStart]. @@ -1352,7 +1354,11 @@ func (f *ScmpFilter) TransactionCommit() error { return errBadFilter } - if retCode := C.seccomp_transaction_commit(f.filterCtx); retCode < 0 { + if e := checkVersion("TransactionCommit", 2, 6, 0); e != nil { + return e + } + + if retCode := C.compat_transaction_commit(f.filterCtx); retCode < 0 { return errRc(retCode) } diff --git a/seccomp_compat.c b/seccomp_compat.c new file mode 100644 index 0000000..775fad9 --- /dev/null +++ b/seccomp_compat.c @@ -0,0 +1,121 @@ +// Wrappers around libseccomp functionality that may be missing from the +// run-time libseccomp library. The functions wrapped here are declared as weak +// references (see seccomp_compat.h), meaning they resolve to NULL when the +// run-time library is older than the version that added them, so they must not +// be called directly. + +#include "seccomp_compat.h" + +// The API level operations were added in libseccomp v2.4.0. + +unsigned int compat_api_get(void) +{ + // Return the "reserved" value of 0 to tell the caller that proper API + // level support is not available in libseccomp. + if (seccomp_api_get == NULL) + return 0; + + return seccomp_api_get(); +} + +int compat_api_set(unsigned int level) +{ + if (seccomp_api_set == NULL) + return -EOPNOTSUPP; + + return seccomp_api_set(level); +} + + +// The seccomp notify API was added in libseccomp v2.5.0. + +int compat_notify_alloc(struct seccomp_notif **req, struct seccomp_notif_resp **resp) +{ + if (seccomp_notify_alloc == NULL) + return -EOPNOTSUPP; + + return seccomp_notify_alloc(req, resp); +} + +int compat_notify_fd(const scmp_filter_ctx ctx) +{ + if (seccomp_notify_fd == NULL) + return -EOPNOTSUPP; + + return seccomp_notify_fd(ctx); +} + +void compat_notify_free(struct seccomp_notif *req, struct seccomp_notif_resp *resp) +{ + if (seccomp_notify_free == NULL) + return; + + seccomp_notify_free(req, resp); +} + +int compat_notify_id_valid(int fd, uint64_t id) +{ + if (seccomp_notify_id_valid == NULL) + return -EOPNOTSUPP; + + return seccomp_notify_id_valid(fd, id); +} + +int compat_notify_receive(int fd, struct seccomp_notif *req) +{ + if (seccomp_notify_receive == NULL) + return -EOPNOTSUPP; + + return seccomp_notify_receive(fd, req); +} + +int compat_notify_respond(int fd, struct seccomp_notif_resp *resp) +{ + if (seccomp_notify_respond == NULL) + return -EOPNOTSUPP; + + return seccomp_notify_respond(fd, resp); +} + + +// The following functions were added in libseccomp v2.6.0. + +int compat_precompute(scmp_filter_ctx ctx) +{ + if (seccomp_precompute == NULL) + return -EOPNOTSUPP; + + return seccomp_precompute(ctx); +} + +int compat_export_bpf_mem(const scmp_filter_ctx ctx, void *buf, size_t *len) +{ + if (seccomp_export_bpf_mem == NULL) + return -EOPNOTSUPP; + + return seccomp_export_bpf_mem(ctx, buf, len); +} + +int compat_transaction_start(const scmp_filter_ctx ctx) +{ + if (seccomp_transaction_start == NULL) + return -EOPNOTSUPP; + + return seccomp_transaction_start(ctx); +} + +int compat_transaction_commit(const scmp_filter_ctx ctx) +{ + if (seccomp_transaction_commit == NULL) + return -EOPNOTSUPP; + + return seccomp_transaction_commit(ctx); +} + +void compat_transaction_reject(const scmp_filter_ctx ctx) +{ + if (seccomp_transaction_reject == NULL) + return; + + seccomp_transaction_reject(ctx); +} diff --git a/seccomp_compat.h b/seccomp_compat.h new file mode 100644 index 0000000..d42419e --- /dev/null +++ b/seccomp_compat.h @@ -0,0 +1,231 @@ +#ifndef SECCOMP_COMPAT_H +#define SECCOMP_COMPAT_H + +#include +#include +#include + +// Minimally required version during compile time. +#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 + + +// Macros that are missing from older libseccomp headers. Being macros, they +// only need an #ifndef guard, so no version tracking is needed for them. + +#define ARCH_BAD ~0 + +#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 + +#ifndef SCMP_ARCH_LOONGARCH64 +#define SCMP_ARCH_LOONGARCH64 ARCH_BAD +#endif + +#ifndef SCMP_ARCH_M68K +#define SCMP_ARCH_M68K ARCH_BAD +#endif + +#ifndef SCMP_ARCH_SH +#define SCMP_ARCH_SH ARCH_BAD +#endif + +#ifndef SCMP_ARCH_SHEB +#define SCMP_ARCH_SHEB ARCH_BAD +#endif + +#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 + + +// Definitions that are only available in newer libseccomp headers. Unlike +// functions (see below), these can not be resolved at run time, so they have +// to be provided here when compiling against older headers. +// +// The SCMP_FLTATR_* ones are enum members rather than macros, so, unlike the +// above, the preprocessor can not tell whether they are already defined, and +// an explicit version check is needed instead. + +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4 + +// SCMP_FLTATR_CTL_LOG was added in v2.4.0. +#define SCMP_FLTATR_CTL_LOG 6 + +#endif // < 2.4.0 + + +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5 + +// The following SCMP_FLTATR_* were added in libseccomp v2.5.0. +#define SCMP_FLTATR_CTL_SSB 7 +#define SCMP_FLTATR_CTL_OPTIMIZE 8 +#define SCMP_FLTATR_API_SYSRAWRC 9 + +// The seccomp notify API structs were added in v2.5.0. Their layout is +// dictated by the kernel UAPI, so it is safe to define them here. + +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; +}; + +#endif // < 2.5.0 + + +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 6 + +#define SCMP_FLTATR_CTL_WAITKILL 10 + +#endif // < 2.6.0 + + +// Functions that were added after v2.3.1 are declared as weak references, so +// that a binary compiled against a newer libseccomp can still be loaded and +// used with an older run-time libseccomp. Without this, the dynamic linker +// fails to resolve the newer symbols, and the program does not even start. +// +// A weak reference to a symbol that is missing at run time resolves to NULL +// rather than being an error, so every such function must only be called via +// its compat_* wrapper below, which checks for availability first. +// +// Declaring these unconditionally serves a second purpose: when compiling +// against headers older than the version that added a function, the weak +// declaration is the only declaration, replacing the dummy implementations +// that used to be needed to make the package compile. + +#define SCMP_WEAK __attribute__((weak)) + +// Forward declarations, so that the notify prototypes below do not depend on +// which of seccomp.h, linux/seccomp.h, or the compat block above ends up +// defining these structs. Only pointers to them are used here. +struct seccomp_notif; +struct seccomp_notif_resp; + +// Added in v2.4.0. +SCMP_WEAK unsigned int seccomp_api_get(void); +SCMP_WEAK int seccomp_api_set(unsigned int level); + +// Added in v2.5.0. +SCMP_WEAK int seccomp_notify_alloc(struct seccomp_notif **req, struct seccomp_notif_resp **resp); +SCMP_WEAK int seccomp_notify_fd(const scmp_filter_ctx ctx); +SCMP_WEAK void seccomp_notify_free(struct seccomp_notif *req, struct seccomp_notif_resp *resp); +SCMP_WEAK int seccomp_notify_id_valid(int fd, uint64_t id); +SCMP_WEAK int seccomp_notify_receive(int fd, struct seccomp_notif *req); +SCMP_WEAK int seccomp_notify_respond(int fd, struct seccomp_notif_resp *resp); + +// Added in v2.6.0. +SCMP_WEAK int seccomp_precompute(scmp_filter_ctx ctx); +SCMP_WEAK int seccomp_export_bpf_mem(const scmp_filter_ctx ctx, void *buf, size_t *len); +SCMP_WEAK int seccomp_transaction_start(const scmp_filter_ctx ctx); +SCMP_WEAK int seccomp_transaction_commit(const scmp_filter_ctx ctx); +SCMP_WEAK void seccomp_transaction_reject(const scmp_filter_ctx ctx); + + +// Wrappers around the weakly referenced functions above; implemented in +// seccomp_compat.c. These are what the Go code calls. Normally, a missing +// function is already ruled out by the version and API level checks done in +// Go, and these wrappers are merely a safety net making sure a NULL pointer +// is never called. + +unsigned int compat_api_get(void); +int compat_api_set(unsigned int level); + +int compat_notify_alloc(struct seccomp_notif **req, struct seccomp_notif_resp **resp); +int compat_notify_fd(const scmp_filter_ctx ctx); +void compat_notify_free(struct seccomp_notif *req, struct seccomp_notif_resp *resp); +int compat_notify_id_valid(int fd, uint64_t id); +int compat_notify_receive(int fd, struct seccomp_notif *req); +int compat_notify_respond(int fd, struct seccomp_notif_resp *resp); + +int compat_precompute(scmp_filter_ctx ctx); +int compat_export_bpf_mem(const scmp_filter_ctx ctx, void *buf, size_t *len); +int compat_transaction_start(const scmp_filter_ctx ctx); +int compat_transaction_commit(const scmp_filter_ctx ctx); +void compat_transaction_reject(const scmp_filter_ctx ctx); + + +// The highest API level that can possibly be supported by the compile-time +// libseccomp headers, regardless of what the run-time library reports. This +// mirrors the version gates in the Go code, which use the lower of the +// compile-time and the run-time version: functionality added after the +// compile-time version can never be used, even if a newer library is loaded +// at run time. +// +// The API level to version mapping is documented in the seccomp_api_get(3) +// man page. +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4 +#define SCMP_COMPAT_MAX_API_LEVEL 2 +#elif SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 4 +#define SCMP_COMPAT_MAX_API_LEVEL 3 +#elif SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 5 +#define SCMP_COMPAT_MAX_API_LEVEL 6 +#else +// TODO: bump this (and add a branch above) whenever a new libseccomp +// version introduces a new API level. Until then, a newer libseccomp is +// capped to the highest level known here, which is the safe direction to +// err in, but does mean its new functionality stays unavailable. +#define SCMP_COMPAT_MAX_API_LEVEL 7 +#endif + +#endif // SECCOMP_COMPAT_H diff --git a/seccomp_internal.go b/seccomp_internal.go index cc90512..a36517b 100644 --- a/seccomp_internal.go +++ b/seccomp_internal.go @@ -22,68 +22,10 @@ import ( // #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 +#include "seccomp_compat.h" 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 - -#ifndef SCMP_ARCH_LOONGARCH64 -#define SCMP_ARCH_LOONGARCH64 ARCH_BAD -#endif - -#ifndef SCMP_ARCH_M68K -#define SCMP_ARCH_M68K ARCH_BAD -#endif - -#ifndef SCMP_ARCH_SH -#define SCMP_ARCH_SH ARCH_BAD -#endif - -#ifndef SCMP_ARCH_SHEB -#define SCMP_ARCH_SHEB 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; @@ -109,22 +51,6 @@ const uint32_t C_ARCH_M68K = SCMP_ARCH_M68K; const uint32_t C_ARCH_SH = SCMP_ARCH_SH; const uint32_t C_ARCH_SHEB = SCMP_ARCH_SHEB; -#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; @@ -135,24 +61,6 @@ 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 - -// Added in libseccomp v2.6.0. -#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 6 -#define SCMP_FLTATR_CTL_WAITKILL _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; @@ -171,39 +79,6 @@ 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; -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; -} - -// 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) @@ -229,49 +104,6 @@ void add_struct_arg_cmp( 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" @@ -310,24 +142,45 @@ 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()) + // errAPIUnsupported is returned when the libseccomp used lacks the API + // level operations, added in v2.4.0. + errAPIUnsupported = errors.New("API level operations are not supported") + // libseccomp major, minor, and micro version numbers. Used by checkVersion. + verMajor, verMinor, verMicro = getMinVersion() ) +// versionGE reports whether (major1,minor1,micro1) >= (major2,minor2,micro2). +func versionGE(major1, minor1, micro1, major2, minor2, micro2 uint) bool { + return (major1 > major2) || + (major1 == major2 && minor1 > minor2) || + (major1 == major2 && minor1 == minor2 && micro1 >= micro2) +} + +func getMinVersion() (uint, uint, uint) { + runVer := C.seccomp_version() + + cMajor, cMinor, cMicro := uint(C.SCMP_VER_MAJOR), uint(C.SCMP_VER_MINOR), uint(C.SCMP_VER_MICRO) + rMajor, rMinor, rMicro := uint(runVer.major), uint(runVer.minor), uint(runVer.micro) + if versionGE(rMajor, rMinor, rMicro, cMajor, cMinor, cMicro) { + return cMajor, cMinor, cMicro + } + return rMajor, rMinor, rMicro +} + // Nonexported functions -// checkVersion returns an error if the libseccomp version being used -// is less than the one specified by major, minor, and micro arguments. +// checkVersion returns an error if the libseccomp version is less than the one +// specified by major, minor, and micro arguments. +// +// Since this package may be compiled with one version of libseccomp and +// used with another, we use the older one to be on the safe side. +// // 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) { + if versionGE(verMajor, verMinor, verMicro, major, minor, micro) { return nil } return &VersionError{ @@ -342,22 +195,44 @@ func ensureSupportedVersion() error { return checkVersion("seccomp", 2, 3, 1) } -// Get the API level +// Get the API level, capped to the compile-time supported maximum (see +// getMinVersion for why this cap is needed). func getAPI() (uint, error) { - api := C.seccomp_api_get() + // The API level operations were added in libseccomp v2.4.0 and, like + // any other functionality, have to be available at both compile and + // run time. This is the one place that can not use checkAPI, the API + // level being the very thing the latter checks. + // + // Note that while the minimally required libseccomp v2.3.1 does support + // API level 2, the kernel may not support the API level 2 constructs + // (the seccomp() system call and the TSYNC filter flag), so no API + // level is assumed here. + if checkVersion("API level operations", 2, 4, 0) != nil { + return 0, errAPIUnsupported + } + + api := uint(C.compat_api_get()) if api == 0 { - return 0, errors.New("API level operations are not supported") + return 0, errAPIUnsupported + } + if maxAPI := uint(C.SCMP_COMPAT_MAX_API_LEVEL); api > maxAPI { + api = maxAPI } - return uint(api), nil + return api, nil } // Set the API level func setAPI(api uint) error { - if retCode := C.seccomp_api_set(C.uint(api)); retCode != 0 { + // See the comment in getAPI. + if checkVersion("API level operations", 2, 4, 0) != nil { + return errAPIUnsupported + } + + if retCode := C.compat_api_set(C.uint(api)); retCode != 0 { e := errRc(retCode) if e == syscall.EOPNOTSUPP { - return errors.New("API level operations are not supported") + return errAPIUnsupported } return fmt.Errorf("could not set API level: %w", e) @@ -784,7 +659,8 @@ func checkAPI(op string, minLevel uint, major, minor, micro uint) error { } // Userspace Notification API -// Calls to C.seccomp_notify* hidden from seccomp.go +// Calls to C.compat_notify* (thin wrappers around the libseccomp +// seccomp_notify* functions) hidden from seccomp.go func notifSupported() error { return checkAPI("seccomp notification", 6, 2, 5, 0) @@ -801,7 +677,7 @@ func (f *ScmpFilter) getNotifFd() (ScmpFd, error) { return -1, err } - fd := C.seccomp_notify_fd(f.filterCtx) + fd := C.compat_notify_fd(f.filterCtx) return ScmpFd(fd), nil } @@ -815,13 +691,13 @@ func notifReceive(fd ScmpFd) (*ScmpNotifReq, error) { } // we only use the request here; the response is unused - if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 { + if retCode := C.compat_notify_alloc(&req, &resp); retCode != 0 { return nil, errRc(retCode) } - defer C.seccomp_notify_free(req, resp) + defer C.compat_notify_free(req, resp) for { - retCode, errno := C.seccomp_notify_receive(C.int(fd), req) + retCode, errno := C.compat_notify_receive(C.int(fd), req) if retCode == 0 { break } @@ -849,15 +725,15 @@ func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error { } // we only use the response here; the request is discarded - if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 { + if retCode := C.compat_notify_alloc(&req, &resp); retCode != 0 { return errRc(retCode) } - defer C.seccomp_notify_free(req, resp) + defer C.compat_notify_free(req, resp) scmpResp.toNative(resp) for { - retCode, errno := C.seccomp_notify_respond(C.int(fd), resp) + retCode, errno := C.compat_notify_respond(C.int(fd), resp) if retCode == 0 { break } @@ -882,7 +758,7 @@ func notifIDValid(fd ScmpFd, id uint64) error { } for { - retCode, errno := C.seccomp_notify_id_valid(C.int(fd), C.uint64_t(id)) + retCode, errno := C.compat_notify_id_valid(C.int(fd), C.uint64_t(id)) if retCode == 0 { break } diff --git a/seccomp_test.go b/seccomp_test.go index 7524a85..7ad9ea3 100644 --- a/seccomp_test.go +++ b/seccomp_test.go @@ -4,7 +4,6 @@ package seccomp import ( "bytes" - "errors" "fmt" "io" "os" @@ -615,6 +614,21 @@ func subprocessRuleAddPrecomputeAndLoad(t *testing.T) { doSubprocessRuleAddAndLoad(t, true) } +// checkVersionedErr checks that err, as returned by the operation named by op, +// is nil if that operation is supported by the libseccomp used, and non-nil +// otherwise. +func checkVersionedErr(t *testing.T, op string, err error, major, minor, micro uint) { + t.Helper() + + if checkVersion(op, major, minor, micro) == nil { + if err != nil { + t.Errorf("%s: want nil, got %v", op, err) + } + } else if err == nil { + t.Errorf("%s: want error, got nil", op) + } +} + func doSubprocessRuleAddAndLoad(t *testing.T, precompute bool) { // Test #1: Add a trivial filter filter1, err := NewFilter(ActAllow) @@ -669,16 +683,9 @@ func doSubprocessRuleAddAndLoad(t *testing.T, precompute bool) { } if precompute { - expErr := error(nil) - // Precompute needs seccomp 2.6.0 and API level 7. - if checkAPI(t.Name(), 7, 2, 6, 0) != nil { - expErr = syscall.EOPNOTSUPP - } - err = filter1.Precompute() - if !errors.Is(err, expErr) { - t.Errorf("Precompute: want %v, got %v", expErr, err) - } + // Precompute needs seccomp 2.6.0. + checkVersionedErr(t, "Precompute", err, 2, 6, 0) } err = filter1.Load() @@ -816,15 +823,9 @@ func subprocessExportBPF(t *testing.T) { } t.Logf("ExportBPF: size %d", len(contents)) - expErr := error(nil) - // ExportBPFMem needs seccomp 2.6.0. - if checkAPI(t.Name(), 0, 2, 6, 0) != nil { - expErr = syscall.EOPNOTSUPP - } contentsMem, err := filter.ExportBPFMem() - if err != expErr { - t.Errorf("ExportBPFMem: want %v, got %v", expErr, err) - } + // ExportBPFMem needs seccomp 2.6.0. + checkVersionedErr(t, "ExportBPFMem", err, 2, 6, 0) if err == nil { t.Logf("ExportBPFMem: size %d", len(contents)) if !bytes.Equal(contents, contentsMem) { @@ -1078,7 +1079,7 @@ func TestTransaction(t *testing.T) { } func testTransaction(t *testing.T) { - if err := checkAPI("seccomp transaction support", 0, 2, 6, 0); err != nil { + if err := checkVersion("seccomp transaction support", 2, 6, 0); err != nil { t.Skip(err) } @@ -1112,7 +1113,7 @@ func TestTransactionUnsupported(t *testing.T) { } func testTransactionUnsupported(t *testing.T) { - if checkAPI("seccomp transaction support", 0, 2, 6, 0) == nil { + if checkVersion("seccomp transaction support", 2, 6, 0) == nil { t.Skip("seccomp transaction is supported") }