Selaa lähdekoodia

Merge pull request #41515 from thaJeztah/seccomp_unmarshal

seccomp: implement marshal/unmarshall for MinVersion
Brian Goff 4 vuotta sitten
vanhempi
commit
7cf6dfcb9e

+ 1 - 1
profiles/seccomp/default_linux.go

@@ -389,7 +389,7 @@ func DefaultProfile() *Seccomp {
 			Names:  []string{"ptrace"},
 			Action: specs.ActAllow,
 			Includes: Filter{
-				MinKernel: "4.8",
+				MinKernel: &KernelVersion{4, 8},
 			},
 		},
 		{

+ 9 - 19
profiles/seccomp/kernel_linux.go

@@ -8,20 +8,14 @@ import (
 	"golang.org/x/sys/unix"
 )
 
-// kernelVersion holds information about the kernel.
-type kernelVersion struct {
-	kernel uint // Version of the kernel (i.e., the "4" in "4.1.2-generic")
-	major  uint // Major revision of the kernel (i.e., the "1" in "4.1.2-generic")
-}
-
 var (
-	currentKernelVersion *kernelVersion
+	currentKernelVersion *KernelVersion
 	kernelVersionError   error
 	once                 sync.Once
 )
 
 // getKernelVersion gets the current kernel version.
-func getKernelVersion() (*kernelVersion, error) {
+func getKernelVersion() (*KernelVersion, error) {
 	once.Do(func() {
 		var uts unix.Utsname
 		if err := unix.Uname(&uts); err != nil {
@@ -33,13 +27,13 @@ func getKernelVersion() (*kernelVersion, error) {
 	return currentKernelVersion, kernelVersionError
 }
 
-// parseRelease parses a string and creates a kernelVersion based on it.
-func parseRelease(release string) (*kernelVersion, error) {
-	var version = kernelVersion{}
+// parseRelease parses a string and creates a KernelVersion based on it.
+func parseRelease(release string) (*KernelVersion, error) {
+	var version = KernelVersion{}
 
 	// We're only make sure we get the "kernel" and "major revision". Sometimes we have
 	// 3.12.25-gentoo, but sometimes we just have 3.12-1-amd64.
-	_, err := fmt.Sscanf(release, "%d.%d", &version.kernel, &version.major)
+	_, err := fmt.Sscanf(release, "%d.%d", &version.Kernel, &version.Major)
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse kernel version %q: %w", release, err)
 	}
@@ -50,19 +44,15 @@ func parseRelease(release string) (*kernelVersion, error) {
 // equal to the given kernel version v. Only "kernel version" and "major revision"
 // can be specified (e.g., "3.12") and will be taken into account, which means
 // that 3.12.25-gentoo and 3.12-1-amd64 are considered equal (kernel: 3, major: 12).
-func kernelGreaterEqualThan(v string) (bool, error) {
-	minVersion, err := parseRelease(v)
-	if err != nil {
-		return false, err
-	}
+func kernelGreaterEqualThan(minVersion KernelVersion) (bool, error) {
 	kv, err := getKernelVersion()
 	if err != nil {
 		return false, err
 	}
-	if kv.kernel > minVersion.kernel {
+	if kv.Kernel > minVersion.Kernel {
 		return true, nil
 	}
-	if kv.kernel == minVersion.kernel && kv.major >= minVersion.major {
+	if kv.Kernel == minVersion.Kernel && kv.Major >= minVersion.Major {
 		return true, nil
 	}
 	return false, nil

+ 20 - 19
profiles/seccomp/kernel_linux_test.go

@@ -13,7 +13,7 @@ func TestGetKernelVersion(t *testing.T) {
 	if version == nil {
 		t.Fatal("version is nil")
 	}
-	if version.kernel == 0 {
+	if version.Kernel == 0 {
 		t.Fatal("no kernel version")
 	}
 }
@@ -22,18 +22,19 @@ func TestGetKernelVersion(t *testing.T) {
 func TestParseRelease(t *testing.T) {
 	tests := []struct {
 		in          string
-		out         kernelVersion
+		out         KernelVersion
 		expectedErr error
 	}{
-		{in: "3.8", out: kernelVersion{kernel: 3, major: 8}},
-		{in: "3.8.0", out: kernelVersion{kernel: 3, major: 8}},
-		{in: "3.8.0-19-generic", out: kernelVersion{kernel: 3, major: 8}},
-		{in: "3.4.54.longterm-1", out: kernelVersion{kernel: 3, major: 4}},
-		{in: "3.10.0-862.2.3.el7.x86_64", out: kernelVersion{kernel: 3, major: 10}},
-		{in: "3.12.8tag", out: kernelVersion{kernel: 3, major: 12}},
-		{in: "3.12-1-amd64", out: kernelVersion{kernel: 3, major: 12}},
-		{in: "3.12foobar", out: kernelVersion{kernel: 3, major: 12}},
-		{in: "99.999.999-19-generic", out: kernelVersion{kernel: 99, major: 999}},
+		{in: "3.8", out: KernelVersion{Kernel: 3, Major: 8}},
+		{in: "3.8.0", out: KernelVersion{Kernel: 3, Major: 8}},
+		{in: "3.8.0-19-generic", out: KernelVersion{Kernel: 3, Major: 8}},
+		{in: "3.4.54.longterm-1", out: KernelVersion{Kernel: 3, Major: 4}},
+		{in: "3.10.0-862.2.3.el7.x86_64", out: KernelVersion{Kernel: 3, Major: 10}},
+		{in: "3.12.8tag", out: KernelVersion{Kernel: 3, Major: 12}},
+		{in: "3.12-1-amd64", out: KernelVersion{Kernel: 3, Major: 12}},
+		{in: "3.12foobar", out: KernelVersion{Kernel: 3, Major: 12}},
+		{in: "99.999.999-19-generic", out: KernelVersion{Kernel: 99, Major: 999}},
+		{in: "", expectedErr: fmt.Errorf(`failed to parse kernel version "": EOF`)},
 		{in: "3", expectedErr: fmt.Errorf(`failed to parse kernel version "3": unexpected EOF`)},
 		{in: "3.", expectedErr: fmt.Errorf(`failed to parse kernel version "3.": EOF`)},
 		{in: "3a", expectedErr: fmt.Errorf(`failed to parse kernel version "3a": input does not match format`)},
@@ -66,8 +67,8 @@ func TestParseRelease(t *testing.T) {
 			if version == nil {
 				t.Fatal("version is nil")
 			}
-			if version.kernel != tc.out.kernel || version.major != tc.out.major {
-				t.Fatalf("expected: %d.%d, got: %d.%d", tc.out.kernel, tc.out.major, version.kernel, version.major)
+			if version.Kernel != tc.out.Kernel || version.Major != tc.out.Major {
+				t.Fatalf("expected: %d.%d, got: %d.%d", tc.out.Kernel, tc.out.Major, version.Kernel, version.Major)
 			}
 		})
 	}
@@ -82,33 +83,33 @@ func TestKernelGreaterEqualThan(t *testing.T) {
 
 	tests := []struct {
 		doc      string
-		in       string
+		in       KernelVersion
 		expected bool
 	}{
 		{
 			doc:      "same version",
-			in:       fmt.Sprintf("%d.%d", v.kernel, v.major),
+			in:       KernelVersion{v.Kernel, v.Major},
 			expected: true,
 		},
 		{
 			doc:      "kernel minus one",
-			in:       fmt.Sprintf("%d.%d", v.kernel-1, v.major),
+			in:       KernelVersion{v.Kernel - 1, v.Major},
 			expected: true,
 		},
 		{
 			doc:      "kernel plus one",
-			in:       fmt.Sprintf("%d.%d", v.kernel+1, v.major),
+			in:       KernelVersion{v.Kernel + 1, v.Major},
 			expected: false,
 		},
 		{
 			doc:      "major plus one",
-			in:       fmt.Sprintf("%d.%d", v.kernel, v.major+1),
+			in:       KernelVersion{v.Kernel, v.Major + 1},
 			expected: false,
 		},
 	}
 	for _, tc := range tests {
 		tc := tc
-		t.Run(tc.doc+": "+tc.in, func(t *testing.T) {
+		t.Run(tc.doc+": "+tc.in.String(), func(t *testing.T) {
 			ok, err := kernelGreaterEqualThan(tc.in)
 			if err != nil {
 				t.Fatal("unexpected error:", err)

+ 58 - 2
profiles/seccomp/seccomp.go

@@ -1,6 +1,13 @@
 package seccomp // import "github.com/docker/docker/profiles/seccomp"
 
-import "github.com/opencontainers/runtime-spec/specs-go"
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/opencontainers/runtime-spec/specs-go"
+)
 
 // Seccomp represents the config for a seccomp profile for syscall restriction.
 type Seccomp struct {
@@ -30,7 +37,7 @@ type Filter struct {
 	// When matching the kernel version of the host, minor revisions, and distro-
 	// specific suffixes are ignored, which means that "3.12.25-gentoo", "3.12-1-amd64",
 	// "3.12", and "3.12-rc5" are considered equal (kernel 3, major revision 12).
-	MinKernel string `json:"minKernel,omitempty"`
+	MinKernel *KernelVersion `json:"minKernel,omitempty"`
 }
 
 // Syscall is used to match a group of syscalls in Seccomp
@@ -43,3 +50,52 @@ type Syscall struct {
 	Includes Filter                   `json:"includes"`
 	Excludes Filter                   `json:"excludes"`
 }
+
+// KernelVersion holds information about the kernel.
+type KernelVersion struct {
+	Kernel uint64 // Version of the Kernel (i.e., the "4" in "4.1.2-generic")
+	Major  uint64 // Major revision of the Kernel (i.e., the "1" in "4.1.2-generic")
+}
+
+// String implements fmt.Stringer for KernelVersion
+func (k *KernelVersion) String() string {
+	if k.Kernel > 0 || k.Major > 0 {
+		return fmt.Sprintf("%d.%d", k.Kernel, k.Major)
+	}
+	return ""
+}
+
+// MarshalJSON implements json.Unmarshaler for KernelVersion
+func (k *KernelVersion) MarshalJSON() ([]byte, error) {
+	return json.Marshal(k.String())
+}
+
+// UnmarshalJSON implements json.Marshaler for KernelVersion
+func (k *KernelVersion) UnmarshalJSON(version []byte) error {
+	var (
+		ver string
+		err error
+	)
+
+	// make sure we have a string
+	if err = json.Unmarshal(version, &ver); err != nil {
+		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": %v`, string(version), err)
+	}
+	if ver == "" {
+		return nil
+	}
+	parts := strings.SplitN(ver, ".", 3)
+	if len(parts) != 2 {
+		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>"`, string(version))
+	}
+	if k.Kernel, err = strconv.ParseUint(parts[0], 10, 8); err != nil {
+		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": %v`, string(version), err)
+	}
+	if k.Major, err = strconv.ParseUint(parts[1], 10, 8); err != nil {
+		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": %v`, string(version), err)
+	}
+	if k.Kernel == 0 && k.Major == 0 {
+		return fmt.Errorf(`invalid kernel version: %s, expected "<kernel>.<major>": version cannot be 0.0`, string(version))
+	}
+	return nil
+}

+ 4 - 4
profiles/seccomp/seccomp_linux.go

@@ -123,8 +123,8 @@ Loop:
 				}
 			}
 		}
-		if call.Excludes.MinKernel != "" {
-			if ok, err := kernelGreaterEqualThan(call.Excludes.MinKernel); err != nil {
+		if call.Excludes.MinKernel != nil {
+			if ok, err := kernelGreaterEqualThan(*call.Excludes.MinKernel); err != nil {
 				return nil, err
 			} else if ok {
 				continue Loop
@@ -142,8 +142,8 @@ Loop:
 				}
 			}
 		}
-		if call.Includes.MinKernel != "" {
-			if ok, err := kernelGreaterEqualThan(call.Includes.MinKernel); err != nil {
+		if call.Includes.MinKernel != nil {
+			if ok, err := kernelGreaterEqualThan(*call.Includes.MinKernel); err != nil {
 				return nil, err
 			} else if !ok {
 				continue Loop

+ 53 - 0
profiles/seccomp/seccomp_test.go

@@ -5,6 +5,7 @@ package seccomp // import "github.com/docker/docker/profiles/seccomp"
 import (
 	"encoding/json"
 	"io/ioutil"
+	"strings"
 	"testing"
 
 	"github.com/opencontainers/runtime-spec/specs-go"
@@ -67,6 +68,58 @@ func TestUnmarshalDefaultProfile(t *testing.T) {
 	assert.DeepEqual(t, expected.Syscalls, profile.Syscalls)
 }
 
+func TestMarshalUnmarshalFilter(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		in    string
+		out   string
+		error bool
+	}{
+		{in: `{"arches":["s390x"],"minKernel":3}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":3.12}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":true}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":"0.0"}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":"3"}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":".3"}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":"3."}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":"true"}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":"3.12.1\""}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":"4.15abc"}`, error: true},
+		{in: `{"arches":["s390x"],"minKernel":null}`, out: `{"arches":["s390x"]}`},
+		{in: `{"arches":["s390x"],"minKernel":""}`, out: `{"arches":["s390x"],"minKernel":""}`}, // FIXME: try to fix omitempty for this
+		{in: `{"arches":["s390x"],"minKernel":"0.5"}`, out: `{"arches":["s390x"],"minKernel":"0.5"}`},
+		{in: `{"arches":["s390x"],"minKernel":"0.50"}`, out: `{"arches":["s390x"],"minKernel":"0.50"}`},
+		{in: `{"arches":["s390x"],"minKernel":"5.0"}`, out: `{"arches":["s390x"],"minKernel":"5.0"}`},
+		{in: `{"arches":["s390x"],"minKernel":"50.0"}`, out: `{"arches":["s390x"],"minKernel":"50.0"}`},
+		{in: `{"arches":["s390x"],"minKernel":"4.15"}`, out: `{"arches":["s390x"],"minKernel":"4.15"}`},
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.in, func(t *testing.T) {
+			var filter Filter
+			err := json.Unmarshal([]byte(tc.in), &filter)
+			if tc.error {
+				if err == nil {
+					t.Fatal("expected an error")
+				} else if !strings.Contains(err.Error(), "invalid kernel version") {
+					t.Fatal("unexpected error:", err)
+				}
+				return
+			}
+			if err != nil {
+				t.Fatal(err)
+			}
+			out, err := json.Marshal(filter)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if string(out) != tc.out {
+				t.Fatalf("expected %s, got %s", tc.out, string(out))
+			}
+		})
+	}
+}
+
 func TestLoadConditional(t *testing.T) {
 	f, err := ioutil.ReadFile("fixtures/conditional_include.json")
 	if err != nil {