Browse code

apparmor: fix version checks to work properly

Using {{if major}}{{if minor}} doesn't work as expected when the major
version changes. In addition, this didn't support patch levels (which is
necessary in some cases when distributions ship apparmor weirdly).

Signed-off-by: Aleksa Sarai <asarai@suse.com>

Aleksa Sarai authored on 2016/02/14 16:04:16
Showing 6 changed files
... ...
@@ -11,8 +11,7 @@ import (
11 11
 )
12 12
 
13 13
 type profileData struct {
14
-	MajorVersion int
15
-	MinorVersion int
14
+	Version int
16 15
 }
17 16
 
18 17
 func main() {
... ...
@@ -23,13 +22,12 @@ func main() {
23 23
 	// parse the arg
24 24
 	apparmorProfilePath := os.Args[1]
25 25
 
26
-	majorVersion, minorVersion, err := aaparser.GetVersion()
26
+	version, err := aaparser.GetVersion()
27 27
 	if err != nil {
28 28
 		log.Fatal(err)
29 29
 	}
30 30
 	data := profileData{
31
-		MajorVersion: majorVersion,
32
-		MinorVersion: minorVersion,
31
+		Version: version,
33 32
 	}
34 33
 	fmt.Printf("apparmor_parser is of version %+v\n", data)
35 34
 
... ...
@@ -20,11 +20,11 @@ profile /usr/bin/docker (attach_disconnected, complain) {
20 20
 
21 21
   umount,
22 22
   pivot_root,
23
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
23
+{{if ge .Version 209000}}
24 24
   signal (receive) peer=@{profile_name},
25 25
   signal (receive) peer=unconfined,
26 26
   signal (send),
27
-{{end}}{{end}}
27
+{{end}}
28 28
   network,
29 29
   capability,
30 30
   owner /** rw,
... ...
@@ -46,12 +46,12 @@ profile /usr/bin/docker (attach_disconnected, complain) {
46 46
   /etc/ld.so.cache r,
47 47
   /etc/passwd r,
48 48
 
49
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
49
+{{if ge .Version 209000}}
50 50
   ptrace peer=@{profile_name},
51 51
   ptrace (read) peer=docker-default,
52 52
   deny ptrace (trace) peer=docker-default,
53 53
   deny ptrace peer=/usr/bin/docker///bin/ps,
54
-{{end}}{{end}}
54
+{{end}}
55 55
 
56 56
   /usr/lib/** rm,
57 57
   /lib/** rm,
... ...
@@ -72,11 +72,11 @@ profile /usr/bin/docker (attach_disconnected, complain) {
72 72
   /sbin/zfs rCx,
73 73
   /sbin/apparmor_parser rCx,
74 74
 
75
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
75
+{{if ge .Version 209000}}
76 76
   # Transitions
77 77
   change_profile -> docker-*,
78 78
   change_profile -> unconfined,
79
-{{end}}{{end}}
79
+{{end}}
80 80
 
81 81
   profile /bin/cat (complain) {
82 82
     /etc/ld.so.cache r,
... ...
@@ -98,10 +98,10 @@ profile /usr/bin/docker (attach_disconnected, complain) {
98 98
     /dev/null rw,
99 99
     /bin/ps mr,
100 100
 
101
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
101
+{{if ge .Version 209000}}
102 102
     # We don't need ptrace so we'll deny and ignore the error.
103 103
     deny ptrace (read, trace),
104
-{{end}}{{end}}
104
+{{end}}
105 105
 
106 106
     # Quiet dac_override denials
107 107
     deny capability dac_override,
... ...
@@ -119,15 +119,15 @@ profile /usr/bin/docker (attach_disconnected, complain) {
119 119
     /proc/tty/drivers r,
120 120
   }
121 121
   profile /sbin/iptables (complain) {
122
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
122
+{{if ge .Version 209000}}
123 123
     signal (receive) peer=/usr/bin/docker,
124
-{{end}}{{end}}
124
+{{end}}
125 125
     capability net_admin,
126 126
   }
127 127
   profile /sbin/auplink flags=(attach_disconnected, complain) {
128
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
128
+{{if ge .Version 209000}}
129 129
     signal (receive) peer=/usr/bin/docker,
130
-{{end}}{{end}}
130
+{{end}}
131 131
     capability sys_admin,
132 132
     capability dac_override,
133 133
 
... ...
@@ -146,9 +146,9 @@ profile /usr/bin/docker (attach_disconnected, complain) {
146 146
     /proc/[0-9]*/mounts rw,
147 147
   }
148 148
   profile /sbin/modprobe /bin/kmod (complain) {
149
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
149
+{{if ge .Version 209000}}
150 150
     signal (receive) peer=/usr/bin/docker,
151
-{{end}}{{end}}
151
+{{end}}
152 152
     capability sys_module,
153 153
     /etc/ld.so.cache r,
154 154
     /lib/** rm,
... ...
@@ -162,9 +162,9 @@ profile /usr/bin/docker (attach_disconnected, complain) {
162 162
   }
163 163
   # xz works via pipes, so we do not need access to the filesystem.
164 164
   profile /usr/bin/xz (complain) {
165
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
165
+{{if ge .Version 209000}}
166 166
     signal (receive) peer=/usr/bin/docker,
167
-{{end}}{{end}}
167
+{{end}}
168 168
     /etc/ld.so.cache r,
169 169
     /lib/** rm,
170 170
     /usr/bin/xz rm,
... ...
@@ -14,13 +14,13 @@ const (
14 14
 )
15 15
 
16 16
 // GetVersion returns the major and minor version of apparmor_parser.
17
-func GetVersion() (int, int, error) {
17
+func GetVersion() (int, error) {
18 18
 	output, err := cmd("", "--version")
19 19
 	if err != nil {
20
-		return -1, -1, err
20
+		return -1, err
21 21
 	}
22 22
 
23
-	return parseVersion(string(output))
23
+	return parseVersion(output)
24 24
 }
25 25
 
26 26
 // LoadProfile runs `apparmor_parser -r -W` on a specified apparmor profile to
... ...
@@ -47,30 +47,46 @@ func cmd(dir string, arg ...string) (string, error) {
47 47
 }
48 48
 
49 49
 // parseVersion takes the output from `apparmor_parser --version` and returns
50
-// the major and minor version for `apparor_parser`.
51
-func parseVersion(output string) (int, int, error) {
50
+// a representation of the {major, minor, patch} version as a single number of
51
+// the form MMmmPPP {major, minor, patch}.
52
+func parseVersion(output string) (int, error) {
52 53
 	// output is in the form of the following:
53 54
 	// AppArmor parser version 2.9.1
54 55
 	// Copyright (C) 1999-2008 Novell Inc.
55 56
 	// Copyright 2009-2012 Canonical Ltd.
57
+
56 58
 	lines := strings.SplitN(output, "\n", 2)
57 59
 	words := strings.Split(lines[0], " ")
58 60
 	version := words[len(words)-1]
59 61
 
60 62
 	// split by major minor version
61 63
 	v := strings.Split(version, ".")
62
-	if len(v) < 2 {
63
-		return -1, -1, fmt.Errorf("parsing major minor version failed for output: `%s`", output)
64
+	if len(v) == 0 || len(v) > 3 {
65
+		return -1, fmt.Errorf("parsing version failed for output: `%s`", output)
64 66
 	}
65 67
 
68
+	// Default the versions to 0.
69
+	var majorVersion, minorVersion, patchLevel int
70
+
66 71
 	majorVersion, err := strconv.Atoi(v[0])
67 72
 	if err != nil {
68
-		return -1, -1, err
73
+		return -1, err
69 74
 	}
70
-	minorVersion, err := strconv.Atoi(v[1])
71
-	if err != nil {
72
-		return -1, -1, err
75
+
76
+	if len(v) > 1 {
77
+		minorVersion, err = strconv.Atoi(v[1])
78
+		if err != nil {
79
+			return -1, err
80
+		}
81
+	}
82
+	if len(v) > 2 {
83
+		patchLevel, err = strconv.Atoi(v[2])
84
+		if err != nil {
85
+			return -1, err
86
+		}
73 87
 	}
74 88
 
75
-	return majorVersion, minorVersion, nil
89
+	// major*10^5 + minor*10^3 + patch*10^0
90
+	numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel
91
+	return numericVersion, nil
76 92
 }
... ...
@@ -5,9 +5,8 @@ import (
5 5
 )
6 6
 
7 7
 type versionExpected struct {
8
-	output string
9
-	major  int
10
-	minor  int
8
+	output  string
9
+	version int
11 10
 }
12 11
 
13 12
 func TestParseVersion(t *testing.T) {
... ...
@@ -18,8 +17,7 @@ Copyright (C) 1999-2008 Novell Inc.
18 18
 Copyright 2009-2012 Canonical Ltd.
19 19
 
20 20
 `,
21
-			major: 2,
22
-			minor: 10,
21
+			version: 210000,
23 22
 		},
24 23
 		{
25 24
 			output: `AppArmor parser version 2.8
... ...
@@ -27,8 +25,7 @@ Copyright (C) 1999-2008 Novell Inc.
27 27
 Copyright 2009-2012 Canonical Ltd.
28 28
 
29 29
 `,
30
-			major: 2,
31
-			minor: 8,
30
+			version: 208000,
32 31
 		},
33 32
 		{
34 33
 			output: `AppArmor parser version 2.20
... ...
@@ -36,8 +33,7 @@ Copyright (C) 1999-2008 Novell Inc.
36 36
 Copyright 2009-2012 Canonical Ltd.
37 37
 
38 38
 `,
39
-			major: 2,
40
-			minor: 20,
39
+			version: 220000,
41 40
 		},
42 41
 		{
43 42
 			output: `AppArmor parser version 2.05
... ...
@@ -45,21 +41,33 @@ Copyright (C) 1999-2008 Novell Inc.
45 45
 Copyright 2009-2012 Canonical Ltd.
46 46
 
47 47
 `,
48
-			major: 2,
49
-			minor: 5,
48
+			version: 205000,
49
+		},
50
+		{
51
+			output: `AppArmor parser version 2.9.95
52
+Copyright (C) 1999-2008 Novell Inc.
53
+Copyright 2009-2012 Canonical Ltd.
54
+
55
+`,
56
+			version: 209095,
57
+		},
58
+		{
59
+			output: `AppArmor parser version 3.14.159
60
+Copyright (C) 1999-2008 Novell Inc.
61
+Copyright 2009-2012 Canonical Ltd.
62
+
63
+`,
64
+			version: 314159,
50 65
 		},
51 66
 	}
52 67
 
53 68
 	for _, v := range versions {
54
-		major, minor, err := parseVersion(v.output)
69
+		version, err := parseVersion(v.output)
55 70
 		if err != nil {
56 71
 			t.Fatalf("expected error to be nil for %#v, got: %v", v, err)
57 72
 		}
58
-		if major != v.major {
59
-			t.Fatalf("expected major version to be %d, was %d, for: %#v\n", v.major, major, v)
60
-		}
61
-		if minor != v.minor {
62
-			t.Fatalf("expected minor version to be %d, was %d, for: %#v\n", v.minor, minor, v)
73
+		if version != v.version {
74
+			t.Fatalf("expected version to be %d, was %d, for: %#v\n", v.version, version, v)
63 75
 		}
64 76
 	}
65 77
 }
... ...
@@ -30,10 +30,8 @@ type profileData struct {
30 30
 	Imports []string
31 31
 	// InnerImports defines the apparmor functions to import in the profile.
32 32
 	InnerImports []string
33
-	// MajorVersion is the apparmor_parser major version.
34
-	MajorVersion int
35
-	// MinorVersion is the apparmor_parser minor version.
36
-	MinorVersion int
33
+	// Version is the {major, minor, patch} version of apparmor_parser as a single number.
34
+	Version int
37 35
 }
38 36
 
39 37
 // generateDefault creates an apparmor profile from ProfileData.
... ...
@@ -38,13 +38,13 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
38 38
   deny /sys/firmware/efi/efivars/** rwklx,
39 39
   deny /sys/kernel/security/** rwklx,
40 40
 
41
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 8}}
41
+{{if ge .Version 208000}}
42 42
   # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
43 43
   ptrace (trace,read) peer=docker-default,
44
-{{end}}{{end}}
45
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
44
+{{end}}
45
+{{if ge .Version 209000}}
46 46
   # docker daemon confinement requires explict allow rule for signal
47 47
   signal (receive) set=(kill,term) peer={{.ExecPath}},
48
-{{end}}{{end}}
48
+{{end}}
49 49
 }
50 50
 `