diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee2b347..0ef80d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,23 +16,60 @@ jobs: fail-fast: false matrix: go-version: [1.15.x, 1.16.x, 1.17.x] + libseccomp: ["v2.3.3", "v2.4.3", "v2.5.2", "HEAD"] steps: - name: checkout uses: actions/checkout@v2 - - name: install deps - run: | - sudo apt -q update - sudo apt -q install libseccomp-dev - - name: install go ${{ matrix.go-version }} uses: actions/setup-go@v2 with: stable: '!contains(${{ matrix.go-version }}, "beta") && !contains(${{ matrix.go-version }}, "rc")' 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 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 64615e0..ebee592 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -24,4 +24,12 @@ jobs: # must be specified without patch version version: v1.41 - + codespell: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: install deps + # Version of codespell bundled with Ubuntu is way old, so use pip. + run: pip install codespell + - name: run codespell + run: codespell diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..7df8aa1 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,4 @@ +# For documentation, see https://golangci-lint.run/usage/configuration/ +linters: + enable: + - gofumpt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6862cb..c2fc80d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,31 +1,23 @@ -How to Submit Patches to the libseccomp Project +How 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 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 +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 -There are two possible tests you can run to verify your code. The first -test is used to check the formatting and coding style of your changes, you -can run the test with the following command: - - # make check-syntax - -... if there are any problems with your changes a diff/patch will be shown -which indicates the problems and how to fix them. - -The second possible test is used to ensure the sanity of your code changes -and to test these changes against the included tests. You can run the test -with the following command: +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 -... if there are any faults or errors they will be displayed. +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 diff --git a/README.md b/README.md index 6e15d60..6430f1c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ =============================================================================== https://github.com/seccomp/libseccomp-golang -[![Build Status](https://img.shields.io/travis/seccomp/libseccomp-golang/main.svg)](https://travis-ci.org/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 @@ -26,25 +28,14 @@ list. * https://groups.google.com/d/forum/libseccomp -Documentation is also available at: +Documentation for this package is also available at: -* https://godoc.org/github.com/seccomp/libseccomp-golang +* https://pkg.go.dev/github.com/seccomp/libseccomp-golang ## Installing the package -The libseccomp-golang bindings require at least Go v1.2.1 and GCC v4.8.4; -earlier versions may yield unpredictable results. If you meet these -requirements you can install this package using the command below: - # go get github.com/seccomp/libseccomp-golang -## Testing the Library - -A number of tests and lint related recipes are provided in the Makefile, if -you want to run the standard regression tests, you can excute the following: - - # make check - -In order to use it, the 'golangci-lint' tool is needed, which can be found at: +## Contributing -* https://github.com/golangci/golangci-lint +See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/go.sum b/go.sum index 72ae161..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +0,0 @@ -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 h1:gkI/wGGwpcG5W4hLCzZNGxA4wzWBGGDStRI1MrjDl2Q= -golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/seccomp.go b/seccomp.go index cd0d60f..ab14bac 100644 --- a/seccomp.go +++ b/seccomp.go @@ -1,5 +1,3 @@ -// +build linux - // Public API specification for libseccomp Go bindings // Contains public API for the bindings @@ -18,16 +16,6 @@ import ( "unsafe" ) -// C wrapping code - -// 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 import "C" @@ -37,9 +25,9 @@ import "C" // 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 - minVer string // minimally required libseccomp version - curAPI, minAPI uint // current and minimally required API versions + 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() { @@ -51,14 +39,13 @@ func init() { func (e VersionError) Error() string { if e.minAPI != 0 { - return fmt.Sprintf("%s requires libseccomp >= %s and API level >= %d "+ + return fmt.Sprintf("%s requires libseccomp >= %d.%d.%d and API level >= %d "+ "(current version: %d.%d.%d, API level: %d)", - e.op, e.minVer, e.minAPI, + e.op, e.major, e.minor, e.micro, e.minAPI, verMajor, verMinor, verMicro, e.curAPI) } - return fmt.Sprintf("%s requires libseccomp >= %s (current version: %d.%d.%d)", - e.op, e.minVer, verMajor, verMinor, verMicro) - + 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 @@ -147,46 +134,46 @@ const ( // variables are invalid ArchInvalid ScmpArch = iota // ArchNative is the native architecture of the kernel - ArchNative ScmpArch = iota + ArchNative // ArchX86 represents 32-bit x86 syscalls - ArchX86 ScmpArch = iota + ArchX86 // ArchAMD64 represents 64-bit x86-64 syscalls - ArchAMD64 ScmpArch = iota + ArchAMD64 // ArchX32 represents 64-bit x86-64 syscalls (32-bit pointers) - ArchX32 ScmpArch = iota + ArchX32 // ArchARM represents 32-bit ARM syscalls - ArchARM ScmpArch = iota + ArchARM // ArchARM64 represents 64-bit ARM syscalls - ArchARM64 ScmpArch = iota + ArchARM64 // ArchMIPS represents 32-bit MIPS syscalls - ArchMIPS ScmpArch = iota + ArchMIPS // ArchMIPS64 represents 64-bit MIPS syscalls - ArchMIPS64 ScmpArch = iota + ArchMIPS64 // ArchMIPS64N32 represents 64-bit MIPS syscalls (32-bit pointers) - ArchMIPS64N32 ScmpArch = iota + ArchMIPS64N32 // ArchMIPSEL represents 32-bit MIPS syscalls (little endian) - ArchMIPSEL ScmpArch = iota + ArchMIPSEL // ArchMIPSEL64 represents 64-bit MIPS syscalls (little endian) - ArchMIPSEL64 ScmpArch = iota + ArchMIPSEL64 // ArchMIPSEL64N32 represents 64-bit MIPS syscalls (little endian, // 32-bit pointers) - ArchMIPSEL64N32 ScmpArch = iota + ArchMIPSEL64N32 // ArchPPC represents 32-bit POWERPC syscalls - ArchPPC ScmpArch = iota + ArchPPC // ArchPPC64 represents 64-bit POWER syscalls (big endian) - ArchPPC64 ScmpArch = iota + ArchPPC64 // ArchPPC64LE represents 64-bit POWER syscalls (little endian) - ArchPPC64LE ScmpArch = iota + ArchPPC64LE // ArchS390 represents 31-bit System z/390 syscalls - ArchS390 ScmpArch = iota + ArchS390 // ArchS390X represents 64-bit System z/390 syscalls - ArchS390X ScmpArch = iota + ArchS390X // ArchPARISC represents 32-bit PA-RISC - ArchPARISC ScmpArch = iota + ArchPARISC // ArchPARISC64 represents 64-bit PA-RISC - ArchPARISC64 ScmpArch = iota + ArchPARISC64 // ArchRISCV64 represents RISCV64 - ArchRISCV64 ScmpArch = iota + ArchRISCV64 ) const ( @@ -197,32 +184,32 @@ const ( ActInvalid ScmpAction = iota // ActKill kills the thread that violated the rule. It is the same as ActKillThread. // All other threads from the same thread group will continue to execute. - ActKill ScmpAction = iota + ActKill // ActTrap throws SIGSYS - ActTrap ScmpAction = iota + ActTrap // ActNotify triggers a userspace notification. This action is only usable when // libseccomp API level 6 or higher is supported. - ActNotify ScmpAction = iota + ActNotify // ActErrno causes the syscall to return a negative error code. This // code can be set with the SetReturnCode method - ActErrno ScmpAction = iota + ActErrno // ActTrace causes the syscall to notify tracing processes with the // given error code. This code can be set with the SetReturnCode method - ActTrace ScmpAction = iota + ActTrace // ActAllow permits the syscall to continue execution - ActAllow ScmpAction = iota + 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 ScmpAction = iota + ActLog // ActKillThread kills the thread that violated the rule. It is the same as ActKill. // All other threads from the same thread group will continue to execute. - ActKillThread ScmpAction = iota + ActKillThread // 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 ScmpAction = iota + ActKillProcess ) const ( @@ -235,36 +222,34 @@ const ( CompareInvalid ScmpCompareOp = iota // CompareNotEqual returns true if the argument is not equal to the // given value - CompareNotEqual ScmpCompareOp = iota + CompareNotEqual // CompareLess returns true if the argument is less than the given value - CompareLess ScmpCompareOp = iota + CompareLess // CompareLessOrEqual returns true if the argument is less than or equal // to the given value - CompareLessOrEqual ScmpCompareOp = iota + CompareLessOrEqual // CompareEqual returns true if the argument is equal to the given value - CompareEqual ScmpCompareOp = iota + CompareEqual // CompareGreaterEqual returns true if the argument is greater than or // equal to the given value - CompareGreaterEqual ScmpCompareOp = iota + CompareGreaterEqual // CompareGreater returns true if the argument is greater than the given // value - CompareGreater ScmpCompareOp = iota + CompareGreater // CompareMaskedEqual returns true if the argument is equal to the given // value, when masked (bitwise &) against the second given value - CompareMaskedEqual ScmpCompareOp = iota + CompareMaskedEqual ) -var ( - // ErrSyscallDoesNotExist represents an error condition where - // libseccomp is unable to resolve the syscall - ErrSyscallDoesNotExist = fmt.Errorf("could not resolve syscall name") -) +// ErrSyscallDoesNotExist represents an error condition where +// libseccomp is unable to resolve the syscall +var ErrSyscallDoesNotExist = fmt.Errorf("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 notication + // call that triggered the notification. Must only be used when the notification // response's error is 0. NotifRespFlagContinue uint32 = 1 ) @@ -879,7 +864,7 @@ func (f *ScmpFilter) GetNoNewPrivsBit() (bool, error) { func (f *ScmpFilter) GetLogBit() (bool, error) { log, err := f.getFilterAttr(filterAttrLog) if err != nil { - if e := checkAPI("GetLogBit", 3, "2.4.0"); e != nil { + if e := checkAPI("GetLogBit", 3, 2, 4, 0); e != nil { err = e } @@ -902,7 +887,7 @@ func (f *ScmpFilter) GetLogBit() (bool, error) { func (f *ScmpFilter) GetSSB() (bool, error) { ssb, err := f.getFilterAttr(filterAttrSSB) if err != nil { - if e := checkAPI("GetSSB", 4, "2.5.0"); e != nil { + if e := checkAPI("GetSSB", 4, 2, 5, 0); e != nil { err = e } @@ -955,7 +940,7 @@ func (f *ScmpFilter) SetLogBit(state bool) error { err := f.setFilterAttr(filterAttrLog, toSet) if err != nil { - if e := checkAPI("SetLogBit", 3, "2.4.0"); e != nil { + if e := checkAPI("SetLogBit", 3, 2, 4, 0); e != nil { err = e } } @@ -976,7 +961,7 @@ func (f *ScmpFilter) SetSSB(state bool) error { err := f.setFilterAttr(filterAttrSSB, toSet) if err != nil { - if e := checkAPI("SetSSB", 4, "2.5.0"); e != nil { + if e := checkAPI("SetSSB", 4, 2, 5, 0); e != nil { err = e } } @@ -1028,9 +1013,6 @@ func (f *ScmpFilter) AddRuleExact(call ScmpSyscall, action ScmpAction) error { // 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. -// There is a bug in library versions below v2.2.1 which can, in some cases, -// cause conditions to be lost when more than one are used. Consequently, -// AddRuleConditional is disabled on library versions lower than v2.2.1 func (f *ScmpFilter) AddRuleConditional(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { return f.addRuleGeneric(call, action, false, conds) } @@ -1042,9 +1024,6 @@ func (f *ScmpFilter) AddRuleConditional(call ScmpSyscall, action ScmpAction, con // 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. -// There is a bug in library versions below v2.2.1 which can, in some cases, -// cause conditions to be lost when more than one are used. Consequently, -// AddRuleConditionalExact is disabled on library versions lower than v2.2.1 func (f *ScmpFilter) AddRuleConditionalExact(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { return f.addRuleGeneric(call, action, true, conds) } diff --git a/seccomp_internal.go b/seccomp_internal.go index 5c25e04..f008047 100644 --- a/seccomp_internal.go +++ b/seccomp_internal.go @@ -1,5 +1,3 @@ -// +build linux - // Internal functions for libseccomp Go bindings // No exported functions @@ -27,10 +25,10 @@ import ( #include #include -#if SCMP_VER_MAJOR < 2 -#error Minimum supported version of Libseccomp is v2.2.0 -#elif SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 2 -#error Minimum supported version of Libseccomp is v2.2.0 +#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 @@ -118,8 +116,7 @@ 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_MAJOR == 2 && SCMP_VER_MINOR < 4) +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4 #define SCMP_FLTATR_CTL_LOG _SCMP_FLTATR_MIN #endif #if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5 @@ -178,8 +175,7 @@ unsigned int get_micro_version() #endif // The libseccomp API level functions were added in v2.4.0 -#if (SCMP_VER_MAJOR < 2) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 4) +#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 @@ -222,8 +218,7 @@ void add_struct_arg_cmp( } // The seccomp notify API functions were added in v2.5.0 -#if (SCMP_VER_MAJOR < 2) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5) +#if SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5 struct seccomp_data { int nr; @@ -275,11 +270,11 @@ type scmpFilterAttr uint32 const ( filterAttrActDefault scmpFilterAttr = iota - filterAttrActBadArch scmpFilterAttr = iota - filterAttrNNP scmpFilterAttr = iota - filterAttrTsync scmpFilterAttr = iota - filterAttrLog scmpFilterAttr = iota - filterAttrSSB scmpFilterAttr = iota + filterAttrActBadArch + filterAttrNNP + filterAttrTsync + filterAttrLog + filterAttrSSB ) const ( @@ -307,10 +302,12 @@ var ( // Nonexported functions -// checkVersion returns an error if libseccomp version used during runtime -// is less than the one required by major, minor, and micro arguments. -// Argument op is arbitrary non-empty operation description, which -// may is used as a part of the error message returned. +// 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) || @@ -318,13 +315,15 @@ func checkVersion(op string, major, minor, micro uint) error { return nil } return &VersionError{ - op: op, - minVer: fmt.Sprintf("%d.%d.%d", major, minor, micro), + op: op, + major: major, + minor: minor, + micro: micro, } } func ensureSupportedVersion() error { - return checkVersion("seccomp", 2, 2, 0) + return checkVersion("seccomp", 2, 3, 1) } // Get the API level @@ -442,11 +441,6 @@ func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact b return err } } else { - // We don't support conditional filtering in library version v2.1 - if err := checkVersion("conditional filtering", 2, 2, 1); err != nil { - return err - } - argsArr := C.make_arg_cmp_array(C.uint(len(conds))) if argsArr == nil { return fmt.Errorf("error allocating memory for conditions") @@ -731,21 +725,24 @@ func (scmpResp *ScmpNotifResp) toNative(resp *C.struct_seccomp_notif_resp) { resp.flags = C.__u32(scmpResp.Flags) } -// checkAPI checks if API level is at least minLevel, and returns an error -// otherwise. Argument op is an arbitrary string description the operation, -// and minVersion is the minimally required libseccomp version. -// Both op and minVersion are only used in an error message. -func checkAPI(op string, minLevel uint, minVersion string) error { - // Ignore error from getAPI -- it returns level == 0 in case of error. +// 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 nil + return checkVersion(op, major, minor, micro) } return &VersionError{ op: op, curAPI: level, minAPI: minLevel, - minVer: minVersion, + major: major, + minor: minor, + micro: micro, } } @@ -753,7 +750,7 @@ func checkAPI(op string, minLevel uint, minVersion string) error { // Calls to C.seccomp_notify* hidden from seccomp.go func notifSupported() error { - return checkAPI("seccomp notification", 6, "2.5.0") + return checkAPI("seccomp notification", 6, 2, 5, 0) } func (f *ScmpFilter) getNotifFd() (ScmpFd, error) { @@ -817,7 +814,7 @@ func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error { return err } - // we only use the reponse here; the request is discarded + // we only use the response here; the request is discarded if retCode := C.seccomp_notify_alloc(&req, &resp); retCode != 0 { return errRc(retCode) } diff --git a/seccomp_test.go b/seccomp_test.go index f3480bf..52af6e8 100644 --- a/seccomp_test.go +++ b/seccomp_test.go @@ -1,5 +1,3 @@ -// +build linux - // Tests for public API of libseccomp Go bindings package seccomp @@ -16,7 +14,7 @@ import ( ) // execInSubprocess calls the go test binary again for the same test. -// This must be only top-level statment in the test function. Do not nest this. +// 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` @@ -32,45 +30,50 @@ func execInSubprocess(t *testing.T, f func(t *testing.T)) { cmd.Args = append(cmd.Args, arg) } } - cmd.Env = []string{subprocessEnvKey + "=1"} + cmd.Env = append(os.Environ(), + subprocessEnvKey+"=1", + ) cmd.Stdin = os.Stdin - var b strings.Builder - cmd.Stdout = &b - cmd.Stderr = &b - - err := cmd.Start() + out, err := cmd.CombinedOutput() + t.Logf("%s", out) if err != nil { - t.Logf("\n%s", b.String()) - t.Error("failed to spawn test in sub-process", err) - t.FailNow() + t.Fatal(err) } +} - err = cmd.Wait() - if err != nil { - t.Logf("\n%s", b.String()) - if err, ok := err.(*exec.ExitError); ok { - // err.ExitCode() not available in go1.11 - // https://github.com/golang/go/issues/26539 - t.Errorf("Test failed: %v", err.String()) - } - t.Error(`test failed`) - t.FailNow() +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) } - t.Logf("\n%s", b.String()) } // Type Function Tests func APILevelIsSupported() bool { return verMajor > 2 || - (verMajor == 2 && verMinor > 3) || - (verMajor == 2 && verMinor == 3 && verMicro >= 3) + (verMajor == 2 && verMinor >= 4) } func TestGetAPILevel(t *testing.T) { execInSubprocess(t, subprocessGetAPILevel) } + func subprocessGetAPILevel(t *testing.T) { api, err := GetAPI() if !APILevelIsSupported() { @@ -90,6 +93,7 @@ func subprocessGetAPILevel(t *testing.T) { func TestSetAPILevel(t *testing.T) { execInSubprocess(t, subprocessSetAPILevel) } + func subprocessSetAPILevel(t *testing.T) { const expectedAPI = uint(1) @@ -137,7 +141,7 @@ func TestSyscallGetName(t *testing.T) { _, err = callFail.GetName() if err == nil { - t.Errorf("Getting nonexistant syscall should error!") + t.Errorf("Getting nonexistent syscall should error!") } } @@ -360,10 +364,10 @@ func TestFilterArchFunctions(t *testing.T) { t.Errorf("Arch not added to filter is present") } - // Try removing the nonexistant arch - should succeed + // Try removing the nonexistent arch - should succeed err = filter.RemoveArch(prospectiveArch) if err != nil { - t.Errorf("Error removing nonexistant arch: %s", err) + t.Errorf("Error removing nonexistent arch: %s", err) } // Add an arch, see if it's in the filter @@ -420,7 +424,7 @@ func TestFilterAttributeGettersAndSetters(t *testing.T) { if err != nil { t.Errorf("Error getting bad arch action") } else if act != ActAllow { - t.Errorf("Bad arch action was not set correcly!") + t.Errorf("Bad arch action was not set correctly!") } err = filter.SetNoNewPrivsBit(false) @@ -562,6 +566,7 @@ func TestMergeFilters(t *testing.T) { func TestRuleAddAndLoad(t *testing.T) { execInSubprocess(t, subprocessRuleAddAndLoad) } + func subprocessRuleAddAndLoad(t *testing.T) { // Test #1: Add a trivial filter filter1, err := NewFilter(ActAllow) @@ -638,6 +643,7 @@ func subprocessRuleAddAndLoad(t *testing.T) { func TestLogAct(t *testing.T) { execInSubprocess(t, subprocessLogAct) } + func subprocessLogAct(t *testing.T) { expectedPid := syscall.Getpid() @@ -683,6 +689,7 @@ func subprocessLogAct(t *testing.T) { func TestCreateActKillThreadFilter(t *testing.T) { execInSubprocess(t, subprocessCreateActKillThreadFilter) } + func subprocessCreateActKillThreadFilter(t *testing.T) { filter, err := NewFilter(ActKillThread) if err != nil { @@ -697,6 +704,7 @@ func subprocessCreateActKillThreadFilter(t *testing.T) { func TestCreateActKillProcessFilter(t *testing.T) { execInSubprocess(t, subprocessCreateActKillProcessFilter) } + func subprocessCreateActKillProcessFilter(t *testing.T) { api, err := GetAPI() if err != nil { @@ -788,24 +796,10 @@ func notifHandler(ch chan error, fd ScmpFd, tests []notifTest) { func TestNotif(t *testing.T) { execInSubprocess(t, subprocessNotif) } -func subprocessNotif(t *testing.T) { - // seccomp notification requires API level >= 6 - api, err := GetAPI() - if err != nil { - if !APILevelIsSupported() { - t.Skipf("Skipping test: %s", err) - } - t.Errorf("Error getting API level: %s", err) - } else { - t.Logf("Got API level %v", api) - if api < 6 { - err = SetAPI(6) - if err != nil { - t.Skipf("Skipping test: API level %d is less than 6 and could not set it to 6", api) - return - } - } +func subprocessNotif(t *testing.T) { + if err := notifSupported(); err != nil { + t.Skip(err) } arch, err := GetNativeArch() @@ -955,16 +949,10 @@ L: func TestNotifUnsupported(t *testing.T) { execInSubprocess(t, subprocessNotifUnsupported) } + func subprocessNotifUnsupported(t *testing.T) { - // seccomp notification requires API level >= 6 - api := 0 - if APILevelIsSupported() { - api, err := GetAPI() - if err != nil { - t.Errorf("Error getting API level: %s", err) - } else if api >= 6 { - t.Skipf("Skipping test for old libseccomp support: API level %d is >= 6", api) - } + if err := notifSupported(); err == nil { + t.Skip("seccomp notification is supported") } filter, err := NewFilter(ActAllow) @@ -975,6 +963,6 @@ func subprocessNotifUnsupported(t *testing.T) { _, err = filter.GetNotifFd() if err == nil { - t.Errorf("Error: GetNotifFd was supposed to fail with API level %d", api) + t.Error("GetNotifFd: got nil, want error") } } diff --git a/seccomp_ver_test.go b/seccomp_ver_test.go index 55b2044..52d3d34 100644 --- a/seccomp_ver_test.go +++ b/seccomp_ver_test.go @@ -6,25 +6,26 @@ import ( func TestCheckVersion(t *testing.T) { for _, tc := range []struct { - // test input + // input op string x, y, z uint - // test output - res string // empty string if no error is expected + // expectations + isErr bool }{ - { - op: "frobnicate", x: 100, y: 99, z: 7, - res: "frobnicate requires libseccomp >= 100.99.7 (current version: ", - }, - { - op: "old-ver", x: 2, y: 2, z: 0, // 2.2.0 is guaranteed to succeed - }, + {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.res != "" { // error expected + if tc.isErr { if err == nil { - t.Errorf("case %s: expected %q-like error, got nil", tc.op, tc.res) + t.Errorf("case %s: expected error, got nil", tc.op) } continue } @@ -35,27 +36,30 @@ func TestCheckVersion(t *testing.T) { } func TestCheckAPI(t *testing.T) { + curAPI, _ := getAPI() for _, tc := range []struct { - // test input - op string - level uint - ver string - // test output - res string // empty string if no error is expected + // input + op string + level uint + x, y, z uint + // expectations + isErr bool }{ - { - op: "deviate", level: 99, ver: "100.99.88", - res: "frobnicate requires libseccomp >= 100.99.7 (current version: ", - }, - { - op: "api-0", level: 0, // API 0 will succeed - }, + {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.ver) + err := checkAPI(tc.op, tc.level, tc.x, tc.y, tc.z) t.Log(err) - if tc.res != "" { // error expected + if tc.isErr { if err == nil { - t.Errorf("case %s: expected %q-like error, got nil", tc.op, tc.res) + t.Errorf("case %s: expected error, got nil", tc.op) } continue }