Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/actions/build-libseccomp/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: build libseccomp
description: Build and install a specific version of libseccomp, and set up the environment to build and test against it.
inputs:
version:
description: libseccomp version (git tag/branch) to build. Use vX.X.X to use a release tarball.
required: true

runs:
using: composite
steps:
- name: build libseccomp ${{ inputs.version }}
shell: bash
run: |
set -x
VER="${{ inputs.version }}"
PREFIX="$(pwd)/seccomp-$VER"
LIBDIR="$PREFIX/lib"

if [[ "$VER" == v* ]]; then
# Released version: fetch and unpack the release tarball.
TAR_FILE="libseccomp-${VER#v}.tar.gz"
TAR_URL="https://github.com/seccomp/libseccomp/releases/download/$VER/$TAR_FILE"
mkdir libseccomp
curl -sSfL "$TAR_URL" | tar -xzf - -C libseccomp --strip-components=1
cd libseccomp
else
# Get it from git.
git clone https://github.com/seccomp/libseccomp
cd libseccomp
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
# 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.
if [ "$VER" == "HEAD" ]; then
VER=9.9.9
sed -i "/^AC_INIT(/s/0\.0\.0/$VER/" configure.ac
fi
./autogen.sh
fi
./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
66 changes: 27 additions & 39 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,56 +14,28 @@ jobs:
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.0", "HEAD"]
libseccomp: ["v2.3.3", "v2.4.4", "v2.5.6", "v2.6.1", "HEAD"]
os: [ubuntu-24.04, ubuntu-24.04-arm]

env:
LATEST_RELEASED_LIBSECCOMP: v2.6.1

runs-on: ${{ matrix.os }}

steps:

- name: checkout
uses: actions/checkout@v6

- name: build libseccomp ${{ matrix.libseccomp }}
- name: install libseccomp and its build deps
run: |
set -x
sudo apt -qq update
sudo apt -qq install gperf

PREFIX="$(pwd)/seccomp"
LIBDIR="$PREFIX/lib"
sudo apt -qq install libseccomp2 gperf

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 libseccomp ${{ matrix.libseccomp }}
uses: ./.github/actions/build-libseccomp
with:
version: ${{ matrix.libseccomp }}

- name: install go ${{ matrix.go-version }}
uses: actions/setup-go@v6
Expand All @@ -75,13 +47,29 @@ jobs:
go.sum
${{ env.PKG_CONFIG_LIBDIR }}/libseccomp.pc


- name: build
run: make check-build

- name: test
run: make test

- name: test with system libseccomp
run: |
unset LD_PRELOAD PKG_CONFIG_LIBDIR
export _EXPECTED_LIBSECCOMP_VERSION="$(dpkg-query -W -f='${Version}' libseccomp2 | sed -E 's/^[0-9]+://; s/-[^-]*$//')"
make test-run

- name: build the latest released libseccomp
if: matrix.libseccomp != env.LATEST_RELEASED_LIBSECCOMP
uses: ./.github/actions/build-libseccomp
with:
version: ${{ env.LATEST_RELEASED_LIBSECCOMP }}

- name: test with latest released libseccomp
if: matrix.libseccomp != env.LATEST_RELEASED_LIBSECCOMP
# Run the test (pre-built with other seccomp version above).
run: make test-run

all-done:
needs:
- test
Expand Down
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# libseccomp-golang

.PHONY: all check check-build check-syntax fix-syntax vet test lint
.PHONY: all check check-build check-syntax fix-syntax vet test test-build test-run lint

all: check-build

Expand All @@ -24,8 +24,13 @@ vet:
# be noticed earlier in the CI.
TEST_TIMEOUT=10s

test:
go test -v -timeout $(TEST_TIMEOUT)
test: test-build test-run

test-build:
go test -c .

test-run:
./libseccomp-golang.test -test.v -test.timeout $(TEST_TIMEOUT)

lint:
golangci-lint run .
18 changes: 18 additions & 0 deletions seccomp_api_compat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package seccomp

// -ldl is needed for the dlsym()-based fallback in seccomp_api_compat.h,
// used when this package is compiled against libseccomp headers older
// than v2.4.0. cgo directives can only be written as Go comments (they
// are parsed by the cgo tool itself, not the C compiler), so this can't
// live inside seccomp_api_compat.h; it's kept in its own file instead
// of seccomp_internal.go to stay next to what it's for.
//
// This flag is always passed, even when building against headers new
// enough that seccomp_api_compat.h isn't included, since cgo directives
// can't be conditioned on the C preprocessor macros that decide that.
// That's harmless: on any modern libc, -ldl either links a compatibility
// stub (the dl* functions have lived in libc itself since glibc 2.34) or
// a small, universally available library.

// #cgo LDFLAGS: -ldl
import "C"
64 changes: 64 additions & 0 deletions seccomp_api_compat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// This file provides fallback definitions of seccomp_api_get() and
// seccomp_api_set(), which were added to libseccomp in v2.4.0. It is
// only included (from seccomp_internal.go) when this package is being
// compiled against libseccomp headers older than that, which don't
// declare these two functions at all.
//
// The libseccomp shared library actually loaded at runtime may still
// be v2.4.0 or newer (and thus provide real implementations of these
// functions) even though the headers used to compile this package are
// older -- for example, this package built against v2.3.3 headers, but
// run against a system libseccomp v2.5.5 or newer. A naive fallback
// that just always returns "unsupported" would permanently hide the
// real API level support in that case: cgo places the whole preamble
// (which is where this file gets included) into the same translation
// unit as the code that calls these functions, so a statically
// compiled-in fallback would be resolved directly at compile time and
// would always run, regardless of what's available at runtime.
//
// So instead, look the real symbols up in whatever shared library
// provides them at runtime via dlsym(), and only fall back to the
// "unsupported" behavior if that lookup fails. The lookup itself is
// only ever done once, in a constructor that runs at load time (before
// any Go code, so before any goroutines or extra OS threads exist),
// which lets it be a simple unsynchronized global write instead of a
// dlsym() call on every invocation guarded by a lock or an atomic
// check-and-set.

#include <dlfcn.h>
#include <errno.h>
#include <stddef.h>

typedef unsigned int (*seccomp_api_get_func)(void);
typedef int (*seccomp_api_set_func)(unsigned int);

static seccomp_api_get_func real_seccomp_api_get;
static seccomp_api_set_func real_seccomp_api_set;

__attribute__((constructor))
static void resolve_real_seccomp_api_funcs(void)
{
real_seccomp_api_get = (seccomp_api_get_func)dlsym(RTLD_DEFAULT, "seccomp_api_get");
real_seccomp_api_set = (seccomp_api_set_func)dlsym(RTLD_DEFAULT, "seccomp_api_set");
}

static unsigned int seccomp_api_get(void)
{
if (real_seccomp_api_get != NULL)
return real_seccomp_api_get();

// 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;
}

static int seccomp_api_set(unsigned int level)
{
if (real_seccomp_api_set != NULL)
return real_seccomp_api_set(level);

return -EOPNOTSUPP;
}
35 changes: 18 additions & 17 deletions seccomp_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,22 +186,12 @@ unsigned int get_micro_version()
return seccomp_version()->micro;
}

// The libseccomp API level functions were added in v2.4.0
// The libseccomp API level functions were added in v2.4.0; headers
// older than that don't declare seccomp_api_get()/seccomp_api_set() at
// all. See seccomp_api_compat.h for why the fallback used in that case
// lives in its own file instead of being defined here directly.
#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;
}
#include "seccomp_api_compat.h"
#endif

typedef struct scmp_arg_cmp* scmp_cast_t;
Expand Down Expand Up @@ -271,6 +261,10 @@ int seccomp_notify_respond(int fd, struct seccomp_notif_resp *resp) {
return -EOPNOTSUPP;
}

#define HAVE_NOTIFY 0

#else // seccomp >= 2.5.0
#define HAVE_NOTIFY 1
#endif
*/
import "C"
Expand Down Expand Up @@ -304,12 +298,16 @@ const (
// Comparison boundaries to check for comparison operator validity
compareOpStart ScmpCompareOp = CompareNotEqual
compareOpEnd ScmpCompareOp = CompareMaskedEqual
// Whether this package was compiled against libseccomp headers
// new enough to provide real seccomp_notify_* functions.
notifyAvailable = C.HAVE_NOTIFY != 0
)

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")
errBadFilter = errors.New("filter is invalid or uninitialized")
errDefAction = errors.New("requested action matches default action of filter")
errNotifUnsup = errors.New("seccomp notification requires libseccomp >= 2.5.0 at build time")
// Constants representing library major, minor, and micro versions
verMajor = uint(C.get_major_version())
verMinor = uint(C.get_minor_version())
Expand Down Expand Up @@ -787,6 +785,9 @@ func checkAPI(op string, minLevel uint, major, minor, micro uint) error {
// Calls to C.seccomp_notify* hidden from seccomp.go

func notifSupported() error {
if !notifyAvailable {
return errNotifUnsup
}
return checkAPI("seccomp notification", 6, 2, 5, 0)
}

Expand Down
Loading