From 7e015ff77e9c57a22db779d9fadd4737e1cbb07c Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Wed, 25 Oct 2017 05:44:14 +0000 Subject: [PATCH 1/5] golang: Add API level bindings Add bindings for getting and setting the libseccomp API level which was added in the following libseccomp commit: commit e89d18205c7dcd7582f41051cd6389c9b12dfccf Author: Paul Moore Date: Thu Sep 21 10:27:38 2017 -0400 api: create an API level construct as part of the supported API The added tests for getting and setting the API level detect if API level support is available in libseccomp and tests the behavior of GetApi() and SetApi() accordingly. Signed-off-by: Tyler Hicks --- seccomp.go | 19 +++++++++++++++++++ seccomp_internal.go | 43 +++++++++++++++++++++++++++++++++++++++++++ seccomp_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/seccomp.go b/seccomp.go index a627418..ce99595 100644 --- a/seccomp.go +++ b/seccomp.go @@ -328,6 +328,25 @@ 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/master/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/master/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. diff --git a/seccomp_internal.go b/seccomp_internal.go index dbac0cc..fd8f32b 100644 --- a/seccomp_internal.go +++ b/seccomp_internal.go @@ -16,6 +16,7 @@ import ( // #cgo pkg-config: libseccomp /* +#include #include #include @@ -122,6 +123,25 @@ 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) +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) @@ -201,6 +221,29 @@ func ensureSupportedVersion() error { return nil } +// Get the API level +func getApi() (uint, error) { + api := C.seccomp_api_get() + if api == 0 { + return 0, fmt.Errorf("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 { + if syscall.Errno(-1*retCode) == syscall.EOPNOTSUPP { + return fmt.Errorf("API level operations are not supported") + } + + return fmt.Errorf("could not set API level: %v", retCode) + } + + return nil +} + // Filter helpers // Filter finalizer - ensure that kernel context for filters is freed diff --git a/seccomp_test.go b/seccomp_test.go index 52174a0..896a2d5 100644 --- a/seccomp_test.go +++ b/seccomp_test.go @@ -64,6 +64,51 @@ func TestVersionError(t *testing.T) { } } +func ApiLevelIsSupported() bool { + return verMajor > 2 || + (verMajor == 2 && verMinor > 3) || + (verMajor == 2 && verMinor == 3 && verMicro >= 3) +} + +func TestGetApiLevel(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) + } + fmt.Printf("Got API level of %v\n", api) +} + +func TestSetApiLevel(t *testing.T) { + var expectedApi uint + + expectedApi = 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!") From 05ae4c0d98bc0adbba1dd09f922f8ebaae71addb Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Thu, 21 Sep 2017 03:10:46 +0000 Subject: [PATCH 2/5] golang: Add support for SCMP_FLTATR_CTL_LOG Create a new scmpFilterAttr, filterAttrLog, to represent libseccomp's SCMP_FLTATR_CTL_LOG. A new set of getter and setter functions are created to set the log filter attribute. They are named GetLogBit() and SetLogBit(). Signed-off-by: Tyler Hicks --- seccomp.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ seccomp_internal.go | 11 +++++++++++ 2 files changed, 57 insertions(+) diff --git a/seccomp.go b/seccomp.go index ce99595..660e9c5 100644 --- a/seccomp.go +++ b/seccomp.go @@ -749,6 +749,30 @@ func (f *ScmpFilter) GetNoNewPrivsBit() (bool, error) { 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 { + api, apiErr := getApi() + if (apiErr != nil && api == 0) || (apiErr == nil && api < 3) { + return false, fmt.Errorf("getting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher") + } + + return false, err + } + + if log == 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. @@ -775,6 +799,28 @@ func (f *ScmpFilter) SetNoNewPrivsBit(state bool) error { 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 { + api, apiErr := getApi() + if (apiErr != nil && api == 0) || (apiErr == nil && api < 3) { + return fmt.Errorf("setting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher") + } + } + + 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 diff --git a/seccomp_internal.go b/seccomp_internal.go index fd8f32b..ebee98b 100644 --- a/seccomp_internal.go +++ b/seccomp_internal.go @@ -74,10 +74,18 @@ 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_ALLOW = SCMP_ACT_ALLOW; +// 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) +#define SCMP_FLTATR_CTL_LOG _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 int C_CMP_NE = (int)SCMP_CMP_NE; const int C_CMP_LT = (int)SCMP_CMP_LT; @@ -179,6 +187,7 @@ const ( filterAttrActBadArch scmpFilterAttr = iota filterAttrNNP scmpFilterAttr = iota filterAttrTsync scmpFilterAttr = iota + filterAttrLog scmpFilterAttr = iota ) const ( @@ -545,6 +554,8 @@ func (a scmpFilterAttr) toNative() uint32 { return uint32(C.C_ATTRIBUTE_NNP) case filterAttrTsync: return uint32(C.C_ATTRIBUTE_TSYNC) + case filterAttrLog: + return uint32(C.C_ATTRIBUTE_LOG) default: return 0x0 } From 503d08cae23030128d124763b30af72a629be43f Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Thu, 21 Sep 2017 03:13:54 +0000 Subject: [PATCH 3/5] golang: Add filterAttrLog getter/setters test Update TestFilterAttributeGettersAndSetters() to set the log filter flag and then verify its value. The log filter flag is not tested when libseccomp is not new enough to support API level operations. Signed-off-by: Tyler Hicks --- seccomp_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/seccomp_test.go b/seccomp_test.go index 896a2d5..78263d5 100644 --- a/seccomp_test.go +++ b/seccomp_test.go @@ -432,6 +432,38 @@ func TestFilterAttributeGettersAndSetters(t *testing.T) { t.Errorf("No new privileges bit was not set correctly") } + if ApiLevelIsSupported() { + api, err := GetApi() + if err != nil { + t.Errorf("Error getting API level: %s", err) + } else if api < 3 { + err = SetApi(3) + if err != nil { + t.Errorf("Error setting API level: %s", err) + } + } + } + + err = filter.SetLogBit(true) + if err != nil { + if !ApiLevelIsSupported() { + t.Logf("Ignoring failure: %s\n", err) + } else { + t.Errorf("Error setting log bit") + } + } + + log, err := filter.GetLogBit() + if err != nil { + if !ApiLevelIsSupported() { + t.Logf("Ignoring failure: %s\n", err) + } else { + t.Errorf("Error getting log bit") + } + } else if log != true { + t.Errorf("Log bit was not set correctly") + } + err = filter.SetBadArchAction(ActInvalid) if err == nil { t.Errorf("Setting bad arch action to an invalid action should error") From 97bab4bb915c5f6267d435335adae5198ced6547 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Thu, 21 Sep 2017 03:14:51 +0000 Subject: [PATCH 4/5] golang: Add support for SCMP_ACT_LOG Represent libseccomp's SCMP_ACT_LOG action with ActLog. This action logs before allowing the syscall. Signed-off-by: Tyler Hicks --- seccomp.go | 6 ++++++ seccomp_internal.go | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/seccomp.go b/seccomp.go index 660e9c5..a3cc538 100644 --- a/seccomp.go +++ b/seccomp.go @@ -137,6 +137,10 @@ const ( ActTrace ScmpAction = iota // ActAllow permits the syscall to continue execution ActAllow ScmpAction = iota + // 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 ) const ( @@ -295,6 +299,8 @@ func (a ScmpAction) String() string { case ActTrace: return fmt.Sprintf("Action: Notify tracing processes with code %d", (a >> 16)) + case ActLog: + return "Action: Log system call" case ActAllow: return "Action: Allow system call" default: diff --git a/seccomp_internal.go b/seccomp_internal.go index ebee98b..4e36b27 100644 --- a/seccomp_internal.go +++ b/seccomp_internal.go @@ -68,10 +68,15 @@ 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; +#ifndef SCMP_ACT_LOG +#define SCMP_ACT_LOG 0x7ffc0000U +#endif + const uint32_t C_ACT_KILL = SCMP_ACT_KILL; 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; // The libseccomp SCMP_FLTATR_CTL_LOG member of the scmp_filter_attr enum was @@ -198,7 +203,7 @@ const ( archEnd ScmpArch = ArchS390X // Comparison boundaries to check for action validity actionStart ScmpAction = ActKill - actionEnd ScmpAction = ActAllow + actionEnd ScmpAction = ActLog // Comparison boundaries to check for comparison operator validity compareOpStart ScmpCompareOp = CompareNotEqual compareOpEnd ScmpCompareOp = CompareMaskedEqual @@ -518,6 +523,8 @@ func actionFromNative(a C.uint32_t) (ScmpAction, error) { 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 default: @@ -536,6 +543,8 @@ func (a ScmpAction) toNative() C.uint32_t { 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 default: From a722f4488b91c71ceac6f9a3c9b8d52c3c491f77 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Thu, 21 Sep 2017 03:16:16 +0000 Subject: [PATCH 5/5] golang: Add ActLog test Add a new test that defaults to ActErrno, allows the syscalls needed to carry out a basic test, and sets the getpid() syscall to ActLog. The getpid() syscall is called before the filter is loaded and once again after the filter is loaded. The test is successful if the return values match. The test is skipped when libseccomp is not new enough to support API level operations or the API level is less than 3. Signed-off-by: Tyler Hicks --- seccomp_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/seccomp_test.go b/seccomp_test.go index 78263d5..f56970e 100644 --- a/seccomp_test.go +++ b/seccomp_test.go @@ -596,3 +596,75 @@ func TestRuleAddAndLoad(t *testing.T) { t.Errorf("Syscall returned incorrect error code - likely not blocked by Seccomp!") } } + +func TestLogAct(t *testing.T) { + expectedPid := syscall.Getpid() + + api, err := GetApi() + if err != nil { + if !ApiLevelIsSupported() { + t.Skipf("Skipping test: %s", err) + } + + t.Errorf("Error getting API level: %s", err) + } else if api < 3 { + t.Skipf("Skipping test: API level %d is less than 3", api) + } + + filter, err := NewFilter(ActErrno.SetReturnCode(0x0001)) + 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) + } + + call1, err := GetSyscallFromName("write") + if err != nil { + t.Errorf("Error getting syscall number of write: %s", err) + } + + call2, err := GetSyscallFromName("futex") + if err != nil { + t.Errorf("Error getting syscall number of futex: %s", err) + } + + call3, err := GetSyscallFromName("exit_group") + if err != nil { + t.Errorf("Error getting syscall number of exit_group: %s", err) + } + + err = filter.AddRule(call, ActLog) + if err != nil { + t.Errorf("Error adding rule to log syscall: %s", err) + } + + err = filter.AddRule(call1, ActAllow) + if err != nil { + t.Errorf("Error adding rule to allow write syscall: %s", err) + } + + err = filter.AddRule(call2, ActAllow) + if err != nil { + t.Errorf("Error adding rule to allow futex syscall: %s", err) + } + + err = filter.AddRule(call3, ActAllow) + if err != nil { + t.Errorf("Error adding rule to allow exit_group 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) + } +}