-
Notifications
You must be signed in to change notification settings - Fork 59
expose improved kernel logging features through libseccomp-golang #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7e015ff
05ae4c0
503d08c
97bab4b
a722f44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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: | ||
|
|
@@ -328,6 +334,25 @@ func GetLibraryVersion() (major, minor, micro uint) { | |
| return verMajor, verMinor, verMicro | ||
| } | ||
|
|
||
| // GetApi returns the API level supported by the system. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give a brief description of what API levels there are in the comments for this and SetAPI?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I sure can. My reasoning for not including API level descriptions in libseccomp-golang was that they may become outdated because developers could easily forget to keep the descriptions updated when new API levels are added. The
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be fine with directing people to a manpage. A link to the libseccomp github (if it's documented anywhere on there) would also be fine. I'd just like to give people a pointer as to what valid API levels look like. |
||
| // 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see us calling this internally (and if we do, the public version has no locking). As such, I don't think we need the internal helper - you can just move the logic out into here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! That means that I'd have to move the cgo glue code to handle old versions of libseccomp into seccomp.go. The seccomp.go file doesn't have any cgo glue code like that currently which is why I opted to stick it into seccomp_internal.go. I'll move everything to seccomp.go.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is an excellent reason to keep it exactly where it is. Didn't look closely enough, sorry! If it depends on CGo in seccomp_internal.go I'm fine with keeping the helpers. |
||
| } | ||
|
|
||
| // Syscall functions | ||
|
|
||
| // GetName retrieves the name of a syscall from its number. | ||
|
|
@@ -730,6 +755,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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add something in the comment regarding a minimum required kernel version for this working?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is a good idea. There's, unfortunately, now a little uncertainty around which kernel release it'll be present in due to an unrelated issue. I'll update the comment as soon as there's some clarity.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the update to this PR, I've added a comment about the minimum API level required (3). |
||
| // 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. | ||
|
|
@@ -756,6 +805,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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, please add something about minimum kernel version where this will take effect. And/or minimum libseccomp version.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the update to this PR, I've added a comment about the minimum API level required (3). |
||
| // 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import ( | |
|
|
||
| // #cgo pkg-config: libseccomp | ||
| /* | ||
| #include <errno.h> | ||
| #include <stdlib.h> | ||
| #include <seccomp.h> | ||
|
|
||
|
|
@@ -67,16 +68,29 @@ 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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the right thing to do here? This is to allow a new libseccomp-golang to build with an older libseccomp which doesn't have SCMP_ACT_LOG support. Is it acceptable to fail the build if the installed libseccomp is not new enough or should I leave this #define here?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This matches what we're been doing elsewhere - we need to support building against older libseccomp, so defining if it's undefined is necessary. So yes, this is the correct thing to do, though it's not very convenient or good practice in general. |
||
|
|
||
| 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 | ||
| // 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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar question here regarding what to do if the installed libseccomp is not new enough to know about SCMP_FLTATR_CTL_LOG.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should also be correct.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This ended up not working since SCMP_FLTATR_CTL_LOG is a member of an enum and the C preprocessor isn't able to handle enum members in ifndef conditionals. In the update to this PR, I conditionalized based on the (upcoming) libseccomp version of 2.3.3. |
||
|
|
||
| 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; | ||
|
|
@@ -122,6 +136,25 @@ unsigned int get_micro_version() | |
| } | ||
| #endif | ||
|
|
||
| // The libseccomp API level functions were added in v2.4.0 | ||
| #if (SCMP_VER_MAJOR < 2) || \ | ||
tyhicks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| (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) | ||
|
|
@@ -159,6 +192,7 @@ const ( | |
| filterAttrActBadArch scmpFilterAttr = iota | ||
| filterAttrNNP scmpFilterAttr = iota | ||
| filterAttrTsync scmpFilterAttr = iota | ||
| filterAttrLog scmpFilterAttr = iota | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -169,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 | ||
|
|
@@ -201,6 +235,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 | ||
|
|
@@ -466,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: | ||
|
|
@@ -484,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: | ||
|
|
@@ -502,6 +563,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 | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you put this after ActAllow? By placing it between ActTrace and ActAllow, ActAllow's constant is renumbered versus current versions of the library