Skip to content

Commit ef01546

Browse files
kolyshkinpcmoore
authored andcommitted
all: unify libseccomp version / API level checks
This unifies all the code that is used to check for minimally required libseccomp version and API level and generate an error. For errors, VersionError is reused, which is now amended with API level. For checks, we now have checkVersion and checkAPI, which generate errors that can be returned as is. This removes some repetitive code and unifies error messages. As a side benefit, a user can now check if an error was caused by an old libseccomp and/or low API level, by using errors.As or checking if the type is VersionError. Signed-off-by: Kir Kolyshkin <[email protected]> Acked-by: Tom Hromatka <[email protected]> Signed-off-by: Paul Moore <[email protected]>
1 parent 3879420 commit ef01546

File tree

4 files changed

+134
-112
lines changed

4 files changed

+134
-112
lines changed

seccomp.go

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,32 +34,31 @@ import "C"
3434

3535
// Exported types
3636

37-
// VersionError denotes that the system libseccomp version is incompatible
38-
// with this package.
37+
// VersionError represents an error when either the system libseccomp version
38+
// or the kernel version is too old to perform the operation requested.
3939
type VersionError struct {
40-
message string
41-
minimum string
40+
op string // operation that failed or would fail
41+
minVer string // minimally required libseccomp version
42+
curAPI, minAPI uint // current and minimally required API versions
4243
}
4344

4445
func init() {
4546
// This forces the cgo libseccomp to initialize its internal API support state,
4647
// which is necessary on older versions of libseccomp in order to work
4748
// correctly.
48-
GetAPI()
49+
_, _ = getAPI()
4950
}
5051

5152
func (e VersionError) Error() string {
52-
messageStr := ""
53-
if e.message != "" {
54-
messageStr = e.message + ": "
53+
if e.minAPI != 0 {
54+
return fmt.Sprintf("%s requires libseccomp >= %s and API level >= %d "+
55+
"(current version: %d.%d.%d, API level: %d)",
56+
e.op, e.minVer, e.minAPI,
57+
verMajor, verMinor, verMicro, e.curAPI)
5558
}
56-
minimumStr := ""
57-
if e.minimum != "" {
58-
minimumStr = e.minimum
59-
} else {
60-
minimumStr = "2.2.0"
61-
}
62-
return fmt.Sprintf("Libseccomp version too low: %sminimum supported is %s: detected %d.%d.%d", messageStr, minimumStr, verMajor, verMinor, verMicro)
59+
return fmt.Sprintf("%s requires libseccomp >= %s (current version: %d.%d.%d)",
60+
e.op, e.minVer, verMajor, verMinor, verMicro)
61+
6362
}
6463

6564
// ScmpArch represents a CPU architecture. Seccomp can restrict syscalls on a
@@ -874,10 +873,8 @@ func (f *ScmpFilter) GetNoNewPrivsBit() (bool, error) {
874873
func (f *ScmpFilter) GetLogBit() (bool, error) {
875874
log, err := f.getFilterAttr(filterAttrLog)
876875
if err != nil {
877-
// Ignore error, if not supported returns apiLevel == 0
878-
apiLevel, _ := GetAPI()
879-
if apiLevel < 3 {
880-
return false, fmt.Errorf("getting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher")
876+
if e := checkAPI("GetLogBit", 3, "2.4.0"); e != nil {
877+
err = e
881878
}
882879

883880
return false, err
@@ -899,9 +896,8 @@ func (f *ScmpFilter) GetLogBit() (bool, error) {
899896
func (f *ScmpFilter) GetSSB() (bool, error) {
900897
ssb, err := f.getFilterAttr(filterAttrSSB)
901898
if err != nil {
902-
api, apiErr := getAPI()
903-
if (apiErr != nil && api == 0) || (apiErr == nil && api < 4) {
904-
return false, fmt.Errorf("getting the SSB flag is only supported in libseccomp 2.5.0 and newer with API level 4 or higher")
899+
if e := checkAPI("GetSSB", 4, "2.5.0"); e != nil {
900+
err = e
905901
}
906902

907903
return false, err
@@ -953,10 +949,8 @@ func (f *ScmpFilter) SetLogBit(state bool) error {
953949

954950
err := f.setFilterAttr(filterAttrLog, toSet)
955951
if err != nil {
956-
// Ignore error, if not supported returns apiLevel == 0
957-
apiLevel, _ := GetAPI()
958-
if apiLevel < 3 {
959-
return fmt.Errorf("setting the log bit is only supported in libseccomp 2.4.0 and newer with API level 3 or higher")
952+
if e := checkAPI("SetLogBit", 3, "2.4.0"); e != nil {
953+
err = e
960954
}
961955
}
962956

@@ -976,9 +970,8 @@ func (f *ScmpFilter) SetSSB(state bool) error {
976970

977971
err := f.setFilterAttr(filterAttrSSB, toSet)
978972
if err != nil {
979-
api, apiErr := getAPI()
980-
if (apiErr != nil && api == 0) || (apiErr == nil && api < 4) {
981-
return fmt.Errorf("setting the SSB flag is only supported in libseccomp 2.5.0 and newer with API level 4 or higher")
973+
if e := checkAPI("SetSSB", 4, "2.5.0"); e != nil {
974+
err = e
982975
}
983976
}
984977

seccomp_internal.go

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -302,19 +302,24 @@ var (
302302

303303
// Nonexported functions
304304

305-
// Check if library version is greater than or equal to the given one
306-
func checkVersionAbove(major, minor, micro uint) bool {
307-
return (verMajor > major) ||
305+
// checkVersion returns an error if libseccomp version used during runtime
306+
// is less than the one required by major, minor, and micro arguments.
307+
// Argument op is arbitrary non-empty operation description, which
308+
// may is used as a part of the error message returned.
309+
func checkVersion(op string, major, minor, micro uint) error {
310+
if (verMajor > major) ||
308311
(verMajor == major && verMinor > minor) ||
309-
(verMajor == major && verMinor == minor && verMicro >= micro)
312+
(verMajor == major && verMinor == minor && verMicro >= micro) {
313+
return nil
314+
}
315+
return &VersionError{
316+
op: op,
317+
minVer: fmt.Sprintf("%d.%d.%d", major, minor, micro),
318+
}
310319
}
311320

312-
// Ensure that the library is supported, i.e. >= 2.2.0.
313321
func ensureSupportedVersion() error {
314-
if !checkVersionAbove(2, 2, 0) {
315-
return VersionError{}
316-
}
317-
return nil
322+
return checkVersion("seccomp", 2, 2, 0)
318323
}
319324

320325
// Get the API level
@@ -433,11 +438,8 @@ func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact b
433438
}
434439
} else {
435440
// We don't support conditional filtering in library version v2.1
436-
if !checkVersionAbove(2, 2, 1) {
437-
return VersionError{
438-
message: "conditional filtering is not supported",
439-
minimum: "2.2.1",
440-
}
441+
if err := checkVersion("conditional filtering", 2, 2, 1); err != nil {
442+
return err
441443
}
442444

443445
argsArr := C.make_arg_cmp_array(C.uint(len(conds)))
@@ -724,21 +726,40 @@ func (scmpResp *ScmpNotifResp) toNative(resp *C.struct_seccomp_notif_resp) {
724726
resp.flags = C.__u32(scmpResp.Flags)
725727
}
726728

729+
// checkAPI checks if API level is at least minLevel, and returns an error
730+
// otherwise. Argument op is an arbitrary string description the operation,
731+
// and minVersion is the minimally required libseccomp version.
732+
// Both op and minVersion are only used in an error message.
733+
func checkAPI(op string, minLevel uint, minVersion string) error {
734+
// Ignore error from getAPI -- it returns level == 0 in case of error.
735+
level, _ := getAPI()
736+
if level >= minLevel {
737+
return nil
738+
}
739+
return &VersionError{
740+
op: op,
741+
curAPI: level,
742+
minAPI: minLevel,
743+
minVer: minVersion,
744+
}
745+
}
746+
727747
// Userspace Notification API
728748
// Calls to C.seccomp_notify* hidden from seccomp.go
729749

750+
func notifSupported() error {
751+
return checkAPI("seccomp notification", 6, "2.5.0")
752+
}
753+
730754
func (f *ScmpFilter) getNotifFd() (ScmpFd, error) {
731755
f.lock.Lock()
732756
defer f.lock.Unlock()
733757

734758
if !f.valid {
735759
return -1, errBadFilter
736760
}
737-
738-
// Ignore error, if not supported returns apiLevel == 0
739-
apiLevel, _ := GetAPI()
740-
if apiLevel < 6 {
741-
return -1, fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
761+
if err := notifSupported(); err != nil {
762+
return -1, err
742763
}
743764

744765
fd := C.seccomp_notify_fd(f.filterCtx)
@@ -750,10 +771,8 @@ func notifReceive(fd ScmpFd) (*ScmpNotifReq, error) {
750771
var req *C.struct_seccomp_notif
751772
var resp *C.struct_seccomp_notif_resp
752773

753-
// Ignore error, if not supported returns apiLevel == 0
754-
apiLevel, _ := GetAPI()
755-
if apiLevel < 6 {
756-
return nil, fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
774+
if err := notifSupported(); err != nil {
775+
return nil, err
757776
}
758777

759778
// we only use the request here; the response is unused
@@ -789,10 +808,8 @@ func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error {
789808
var req *C.struct_seccomp_notif
790809
var resp *C.struct_seccomp_notif_resp
791810

792-
// Ignore error, if not supported returns apiLevel == 0
793-
apiLevel, _ := GetAPI()
794-
if apiLevel < 6 {
795-
return fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
811+
if err := notifSupported(); err != nil {
812+
return err
796813
}
797814

798815
// we only use the reponse here; the request is discarded
@@ -827,10 +844,8 @@ func notifRespond(fd ScmpFd, scmpResp *ScmpNotifResp) error {
827844
}
828845

829846
func notifIDValid(fd ScmpFd, id uint64) error {
830-
// Ignore error, if not supported returns apiLevel == 0
831-
apiLevel, _ := GetAPI()
832-
if apiLevel < 6 {
833-
return fmt.Errorf("seccomp notification requires API level >= 6; current level = %d", apiLevel)
847+
if err := notifSupported(); err != nil {
848+
return err
834849
}
835850

836851
for {

seccomp_test.go

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -62,58 +62,6 @@ func execInSubprocess(t *testing.T, f func(t *testing.T)) {
6262

6363
// Type Function Tests
6464

65-
type versionErrorTest struct {
66-
err VersionError
67-
str string
68-
}
69-
70-
var versionStr = fmt.Sprintf("%d.%d.%d", verMajor, verMinor, verMicro)
71-
72-
var versionErrorTests = []versionErrorTest{
73-
{
74-
VersionError{
75-
"deadbeef",
76-
"x.y.z",
77-
},
78-
"Libseccomp version too low: deadbeef: " +
79-
"minimum supported is x.y.z: detected " + versionStr,
80-
},
81-
{
82-
VersionError{
83-
"",
84-
"x.y.z",
85-
},
86-
"Libseccomp version too low: minimum supported is x.y.z: " +
87-
"detected " + versionStr,
88-
},
89-
{
90-
VersionError{
91-
"deadbeef",
92-
"",
93-
},
94-
"Libseccomp version too low: " +
95-
"deadbeef: minimum supported is 2.2.0: " +
96-
"detected " + versionStr,
97-
},
98-
{
99-
VersionError{
100-
"",
101-
"",
102-
},
103-
"Libseccomp version too low: minimum supported is 2.2.0: " +
104-
"detected " + versionStr,
105-
},
106-
}
107-
108-
func TestVersionError(t *testing.T) {
109-
for i, test := range versionErrorTests {
110-
str := test.err.Error()
111-
if str != test.str {
112-
t.Errorf("VersionError %d: got %q: expected %q", i, str, test.str)
113-
}
114-
}
115-
}
116-
11765
func APILevelIsSupported() bool {
11866
return verMajor > 2 ||
11967
(verMajor == 2 && verMinor > 3) ||

seccomp_ver_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package seccomp
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestCheckVersion(t *testing.T) {
8+
for _, tc := range []struct {
9+
// test input
10+
op string
11+
x, y, z uint
12+
// test output
13+
res string // empty string if no error is expected
14+
}{
15+
{
16+
op: "frobnicate", x: 100, y: 99, z: 7,
17+
res: "frobnicate requires libseccomp >= 100.99.7 (current version: ",
18+
},
19+
{
20+
op: "old-ver", x: 2, y: 2, z: 0, // 2.2.0 is guaranteed to succeed
21+
},
22+
} {
23+
err := checkVersion(tc.op, tc.x, tc.y, tc.z)
24+
t.Log(err)
25+
if tc.res != "" { // error expected
26+
if err == nil {
27+
t.Errorf("case %s: expected %q-like error, got nil", tc.op, tc.res)
28+
}
29+
continue
30+
}
31+
if err != nil {
32+
t.Errorf("case %s: expected no error, got %s", tc.op, err)
33+
}
34+
}
35+
}
36+
37+
func TestCheckAPI(t *testing.T) {
38+
for _, tc := range []struct {
39+
// test input
40+
op string
41+
level uint
42+
ver string
43+
// test output
44+
res string // empty string if no error is expected
45+
}{
46+
{
47+
op: "deviate", level: 99, ver: "100.99.88",
48+
res: "frobnicate requires libseccomp >= 100.99.7 (current version: ",
49+
},
50+
{
51+
op: "api-0", level: 0, // API 0 will succeed
52+
},
53+
} {
54+
err := checkAPI(tc.op, tc.level, tc.ver)
55+
t.Log(err)
56+
if tc.res != "" { // error expected
57+
if err == nil {
58+
t.Errorf("case %s: expected %q-like error, got nil", tc.op, tc.res)
59+
}
60+
continue
61+
}
62+
if err != nil {
63+
t.Errorf("case %s: expected no error, got %s", tc.op, err)
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)