gitee.com/mysnapcore/mysnapd@v0.1.0/sandbox/apparmor/apparmor_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package apparmor_test 21 22 import ( 23 "errors" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 "testing" 30 31 . "gopkg.in/check.v1" 32 33 "gitee.com/mysnapcore/mysnapd/dirs" 34 "gitee.com/mysnapcore/mysnapd/osutil" 35 "gitee.com/mysnapcore/mysnapd/sandbox/apparmor" 36 "gitee.com/mysnapcore/mysnapd/testutil" 37 ) 38 39 func TestApparmor(t *testing.T) { 40 TestingT(t) 41 } 42 43 type apparmorSuite struct { 44 testutil.BaseTest 45 } 46 47 var _ = Suite(&apparmorSuite{}) 48 49 func (s *apparmorSuite) SetUpTest(c *C) { 50 s.BaseTest.SetUpTest(c) 51 s.AddCleanup(func() { 52 configFile := filepath.Join(dirs.GlobalRootDir, "/etc/apparmor.d/tunables/home.d/snapd") 53 if err := os.Remove(configFile); err != nil { 54 c.Assert(os.IsNotExist(err), Equals, true) 55 } 56 }) 57 } 58 59 func (*apparmorSuite) TestAppArmorLevelTypeStringer(c *C) { 60 c.Check(apparmor.Unknown.String(), Equals, "unknown") 61 c.Check(apparmor.Unsupported.String(), Equals, "none") 62 c.Check(apparmor.Unusable.String(), Equals, "unusable") 63 c.Check(apparmor.Partial.String(), Equals, "partial") 64 c.Check(apparmor.Full.String(), Equals, "full") 65 c.Check(apparmor.LevelType(42).String(), Equals, "AppArmorLevelType:42") 66 } 67 68 func (*apparmorSuite) TestAppArmorSystemCacheFallsback(c *C) { 69 // if we create the system cache dir under a new rootdir, then the 70 // SystemCacheDir should take that value 71 dir1 := c.MkDir() 72 systemCacheDir := filepath.Join(dir1, "/etc/apparmor.d/cache") 73 err := os.MkdirAll(systemCacheDir, 0755) 74 c.Assert(err, IsNil) 75 dirs.SetRootDir(dir1) 76 c.Assert(apparmor.SystemCacheDir, Equals, systemCacheDir) 77 78 // but if we set a new root dir without the system cache dir, now the var is 79 // set to the CacheDir 80 dir2 := c.MkDir() 81 dirs.SetRootDir(dir2) 82 c.Assert(apparmor.SystemCacheDir, Equals, apparmor.CacheDir) 83 84 // finally test that it's insufficient to just have the conf dir, we need 85 // specifically the cache dir 86 dir3 := c.MkDir() 87 err = os.MkdirAll(filepath.Join(dir3, "/etc/apparmor.d"), 0755) 88 c.Assert(err, IsNil) 89 dirs.SetRootDir(dir3) 90 c.Assert(apparmor.SystemCacheDir, Equals, apparmor.CacheDir) 91 } 92 93 func (*apparmorSuite) TestMockAppArmorLevel(c *C) { 94 for _, lvl := range []apparmor.LevelType{apparmor.Unsupported, apparmor.Unusable, apparmor.Partial, apparmor.Full} { 95 restore := apparmor.MockLevel(lvl) 96 c.Check(apparmor.ProbedLevel(), Equals, lvl) 97 c.Check(apparmor.Summary(), testutil.Contains, "mocked apparmor level: ") 98 features, err := apparmor.KernelFeatures() 99 c.Check(err, IsNil) 100 c.Check(features, DeepEquals, []string{"mocked-kernel-feature"}) 101 features, err = apparmor.ParserFeatures() 102 c.Check(err, IsNil) 103 c.Check(features, DeepEquals, []string{"mocked-parser-feature"}) 104 restore() 105 } 106 } 107 108 // Using MockAppArmorFeatures yields in apparmor assessment 109 func (*apparmorSuite) TestMockAppArmorFeatures(c *C) { 110 // No apparmor in the kernel, apparmor is disabled. 111 restore := apparmor.MockFeatures([]string{}, os.ErrNotExist, []string{}, nil) 112 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unsupported) 113 c.Check(apparmor.Summary(), Equals, "apparmor not enabled") 114 features, err := apparmor.KernelFeatures() 115 c.Assert(err, Equals, os.ErrNotExist) 116 c.Check(features, DeepEquals, []string{}) 117 features, err = apparmor.ParserFeatures() 118 c.Assert(err, IsNil) 119 c.Check(features, DeepEquals, []string{}) 120 restore() 121 122 // No apparmor_parser, apparmor is disabled. 123 restore = apparmor.MockFeatures([]string{}, nil, []string{}, os.ErrNotExist) 124 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unsupported) 125 c.Check(apparmor.Summary(), Equals, "apparmor_parser not found") 126 features, err = apparmor.KernelFeatures() 127 c.Assert(err, IsNil) 128 c.Check(features, DeepEquals, []string{}) 129 features, err = apparmor.ParserFeatures() 130 c.Assert(err, Equals, os.ErrNotExist) 131 c.Check(features, DeepEquals, []string{}) 132 restore() 133 134 // Complete kernel features but apparmor is unusable because of missing required parser features. 135 restore = apparmor.MockFeatures(apparmor.RequiredKernelFeatures, nil, []string{}, nil) 136 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unusable) 137 c.Check(apparmor.Summary(), Equals, "apparmor_parser is available but required parser features are missing: unsafe") 138 features, err = apparmor.KernelFeatures() 139 c.Assert(err, IsNil) 140 c.Check(features, DeepEquals, apparmor.RequiredKernelFeatures) 141 features, err = apparmor.ParserFeatures() 142 c.Assert(err, IsNil) 143 c.Check(features, DeepEquals, []string{}) 144 restore() 145 146 // Complete parser features but apparmor is unusable because of missing required kernel features. 147 // The test feature is there to pretend that apparmor in the kernel is not entirely disabled. 148 restore = apparmor.MockFeatures([]string{"test-feature"}, nil, apparmor.RequiredParserFeatures, nil) 149 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Unusable) 150 c.Check(apparmor.Summary(), Equals, "apparmor is enabled but required kernel features are missing: file") 151 features, err = apparmor.KernelFeatures() 152 c.Assert(err, IsNil) 153 c.Check(features, DeepEquals, []string{"test-feature"}) 154 features, err = apparmor.ParserFeatures() 155 c.Assert(err, IsNil) 156 c.Check(features, DeepEquals, apparmor.RequiredParserFeatures) 157 restore() 158 159 // Required kernel and parser features available, some optional features are missing though. 160 restore = apparmor.MockFeatures(apparmor.RequiredKernelFeatures, nil, apparmor.RequiredParserFeatures, nil) 161 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Partial) 162 c.Check(apparmor.Summary(), Equals, "apparmor is enabled but some kernel features are missing: caps, dbus, domain, mount, namespaces, network, ptrace, signal") 163 features, err = apparmor.KernelFeatures() 164 c.Assert(err, IsNil) 165 c.Check(features, DeepEquals, apparmor.RequiredKernelFeatures) 166 features, err = apparmor.ParserFeatures() 167 c.Assert(err, IsNil) 168 c.Check(features, DeepEquals, apparmor.RequiredParserFeatures) 169 restore() 170 171 // Preferred kernel and parser features available. 172 restore = apparmor.MockFeatures(apparmor.PreferredKernelFeatures, nil, apparmor.PreferredParserFeatures, nil) 173 c.Check(apparmor.ProbedLevel(), Equals, apparmor.Full) 174 c.Check(apparmor.Summary(), Equals, "apparmor is enabled and all features are available") 175 features, err = apparmor.KernelFeatures() 176 c.Assert(err, IsNil) 177 c.Check(features, DeepEquals, apparmor.PreferredKernelFeatures) 178 features, err = apparmor.ParserFeatures() 179 c.Assert(err, IsNil) 180 c.Check(features, DeepEquals, apparmor.PreferredParserFeatures) 181 restore() 182 } 183 184 const featuresSysPath = "sys/kernel/security/apparmor/features" 185 186 func (s *apparmorSuite) TestProbeAppArmorKernelFeatures(c *C) { 187 d := c.MkDir() 188 189 // Pretend that apparmor kernel features directory doesn't exist. 190 restore := apparmor.MockFsRootPath(d) 191 defer restore() 192 features, err := apparmor.ProbeKernelFeatures() 193 c.Assert(os.IsNotExist(err), Equals, true) 194 c.Check(features, DeepEquals, []string{}) 195 196 // Pretend that apparmor kernel features directory exists but is empty. 197 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath), 0755), IsNil) 198 features, err = apparmor.ProbeKernelFeatures() 199 c.Assert(err, IsNil) 200 c.Check(features, DeepEquals, []string{}) 201 202 // Pretend that apparmor kernel features directory contains some entries. 203 c.Assert(os.Mkdir(filepath.Join(d, featuresSysPath, "foo"), 0755), IsNil) 204 c.Assert(os.Mkdir(filepath.Join(d, featuresSysPath, "bar"), 0755), IsNil) 205 features, err = apparmor.ProbeKernelFeatures() 206 c.Assert(err, IsNil) 207 c.Check(features, DeepEquals, []string{"bar", "foo"}) 208 } 209 210 func (s *apparmorSuite) TestProbeAppArmorParserFeatures(c *C) { 211 212 var testcases = []struct { 213 exitCodes []int 214 expFeatures []string 215 }{ 216 { 217 exitCodes: []int{1, 1, 1, 1, 1}, 218 }, 219 { 220 exitCodes: []int{1, 0, 1, 1, 1}, 221 expFeatures: []string{"qipcrtr-socket"}, 222 }, 223 { 224 exitCodes: []int{0, 1, 1, 1, 1}, 225 expFeatures: []string{"unsafe"}, 226 }, 227 { 228 exitCodes: []int{1, 1, 1, 0, 1}, 229 expFeatures: []string{"cap-audit-read"}, 230 }, 231 { 232 exitCodes: []int{0, 0, 1, 1, 1}, 233 expFeatures: []string{"qipcrtr-socket", "unsafe"}, 234 }, 235 { 236 exitCodes: []int{0, 0, 0, 0, 0}, 237 expFeatures: []string{"cap-audit-read", "cap-bpf", "mqueue", "qipcrtr-socket", "unsafe"}, 238 }, 239 } 240 241 for _, t := range testcases { 242 d := c.MkDir() 243 contents := "" 244 for _, code := range t.exitCodes { 245 contents += fmt.Sprintf("%d ", code) 246 } 247 err := ioutil.WriteFile(filepath.Join(d, "codes"), []byte(contents), 0755) 248 c.Assert(err, IsNil) 249 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", fmt.Sprintf(` 250 cat >> %[1]s/stdin 251 echo "" >> %[1]s/stdin 252 253 read -r EXIT_CODE CODES_FOR_NEXT_CALLS < %[1]s/codes 254 echo "$CODES_FOR_NEXT_CALLS" > %[1]s/codes 255 256 exit "$EXIT_CODE" 257 `, d)) 258 defer mockParserCmd.Restore() 259 restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 260 defer restore() 261 262 features, err := apparmor.ProbeParserFeatures() 263 c.Assert(err, IsNil) 264 if len(t.expFeatures) == 0 { 265 c.Check(features, HasLen, 0) 266 } else { 267 c.Check(features, DeepEquals, t.expFeatures) 268 } 269 270 var expectedCalls [][]string 271 for range t.exitCodes { 272 expectedCalls = append(expectedCalls, []string{"apparmor_parser", "--preprocess"}) 273 } 274 c.Check(mockParserCmd.Calls(), DeepEquals, expectedCalls) 275 data, err := ioutil.ReadFile(filepath.Join(d, "stdin")) 276 c.Assert(err, IsNil) 277 c.Check(string(data), Equals, `profile snap-test { 278 change_profile unsafe /**, 279 } 280 profile snap-test { 281 network qipcrtr dgram, 282 } 283 profile snap-test { 284 capability bpf, 285 } 286 profile snap-test { 287 capability audit_read, 288 } 289 profile snap-test { 290 mqueue, 291 } 292 `) 293 } 294 295 // Pretend that we just don't have apparmor_parser at all. 296 restore := apparmor.MockParserSearchPath(c.MkDir()) 297 defer restore() 298 features, err := apparmor.ProbeParserFeatures() 299 c.Check(err, Equals, os.ErrNotExist) 300 c.Check(features, DeepEquals, []string{}) 301 } 302 303 func (s *apparmorSuite) TestInterfaceSystemKey(c *C) { 304 apparmor.FreshAppArmorAssessment() 305 306 d := c.MkDir() 307 restore := apparmor.MockFsRootPath(d) 308 defer restore() 309 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "policy"), 0755), IsNil) 310 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "network"), 0755), IsNil) 311 312 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "") 313 defer mockParserCmd.Restore() 314 restore = apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 315 defer restore() 316 317 apparmor.ProbedLevel() 318 319 features, err := apparmor.KernelFeatures() 320 c.Assert(err, IsNil) 321 c.Check(features, DeepEquals, []string{"network", "policy"}) 322 features, err = apparmor.ParserFeatures() 323 c.Assert(err, IsNil) 324 c.Check(features, DeepEquals, []string{"cap-audit-read", "cap-bpf", "mqueue", "qipcrtr-socket", "unsafe"}) 325 } 326 327 func (s *apparmorSuite) TestAppArmorParserMtime(c *C) { 328 // Pretend that we have apparmor_parser. 329 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "") 330 defer mockParserCmd.Restore() 331 restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 332 defer restore() 333 mtime := apparmor.ParserMtime() 334 fi, err := os.Stat(filepath.Join(mockParserCmd.BinDir(), "apparmor_parser")) 335 c.Assert(err, IsNil) 336 c.Check(mtime, Equals, fi.ModTime().Unix()) 337 338 // Pretend that we don't have apparmor_parser. 339 restore = apparmor.MockParserSearchPath(c.MkDir()) 340 defer restore() 341 mtime = apparmor.ParserMtime() 342 c.Check(mtime, Equals, int64(0)) 343 } 344 345 func (s *apparmorSuite) TestFeaturesProbedOnce(c *C) { 346 apparmor.FreshAppArmorAssessment() 347 348 d := c.MkDir() 349 restore := apparmor.MockFsRootPath(d) 350 defer restore() 351 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "policy"), 0755), IsNil) 352 c.Assert(os.MkdirAll(filepath.Join(d, featuresSysPath, "network"), 0755), IsNil) 353 354 mockParserCmd := testutil.MockCommand(c, "apparmor_parser", "") 355 defer mockParserCmd.Restore() 356 restore = apparmor.MockParserSearchPath(mockParserCmd.BinDir()) 357 defer restore() 358 359 features, err := apparmor.KernelFeatures() 360 c.Assert(err, IsNil) 361 c.Check(features, DeepEquals, []string{"network", "policy"}) 362 features, err = apparmor.ParserFeatures() 363 c.Assert(err, IsNil) 364 c.Check(features, DeepEquals, []string{"cap-audit-read", "cap-bpf", "mqueue", "qipcrtr-socket", "unsafe"}) 365 366 // this makes probing fails but is not done again 367 err = os.RemoveAll(d) 368 c.Assert(err, IsNil) 369 370 _, err = apparmor.KernelFeatures() 371 c.Assert(err, IsNil) 372 373 // this makes probing fails but is not done again 374 err = os.RemoveAll(mockParserCmd.BinDir()) 375 c.Assert(err, IsNil) 376 377 _, err = apparmor.ParserFeatures() 378 c.Assert(err, IsNil) 379 } 380 381 func (s *apparmorSuite) TestValidateFreeFromAAREUnhappy(c *C) { 382 var testCases = []string{"a?", "*b", "c[c", "dd]", "e{", "f}", "g^", `h"`, "f\000", "g\x00"} 383 384 for _, s := range testCases { 385 c.Check(apparmor.ValidateNoAppArmorRegexp(s), ErrorMatches, ".* contains a reserved apparmor char from .*", Commentf("%q is not raising an error", s)) 386 } 387 } 388 389 func (s *apparmorSuite) TestValidateFreeFromAAREhappy(c *C) { 390 var testCases = []string{"foo", "BaR", "b-z", "foo+bar", "b00m!", "be/ep", "a%b", "a&b", "a(b", "a)b", "a=b", "a#b", "a~b", "a'b", "a_b", "a,b", "a;b", "a>b", "a<b", "a|b"} 391 392 for _, s := range testCases { 393 c.Check(apparmor.ValidateNoAppArmorRegexp(s), IsNil, Commentf("%q raised an error but shouldn't", s)) 394 } 395 } 396 397 func (s *apparmorSuite) TestUpdateHomedirsTunableMkdirFail(c *C) { 398 restore := apparmor.MockMkdirAll(func(string, os.FileMode) error { 399 return errors.New("mkdir failure") 400 }) 401 defer restore() 402 403 err := apparmor.UpdateHomedirsTunable([]string{"does", "not", "matter"}) 404 c.Check(err, ErrorMatches, `cannot create AppArmor tunable directory: mkdir failure`) 405 } 406 407 func (s *apparmorSuite) TestUpdateHomedirsTunableWriteFail(c *C) { 408 restore := apparmor.MockMkdirAll(func(string, os.FileMode) error { 409 return nil 410 }) 411 defer restore() 412 413 restore = apparmor.MockAtomicWrite(func(string, io.Reader, os.FileMode, osutil.AtomicWriteFlags) error { 414 return errors.New("write failure") 415 }) 416 defer restore() 417 418 err := apparmor.UpdateHomedirsTunable([]string{"does", "not", "matter"}) 419 c.Check(err, ErrorMatches, `write failure`) 420 } 421 422 func (s *apparmorSuite) TestUpdateHomedirsTunableHappy(c *C) { 423 err := apparmor.UpdateHomedirsTunable([]string{"/home/a", "/dir2"}) 424 c.Assert(err, IsNil) 425 configFile := filepath.Join(dirs.GlobalRootDir, "/etc/apparmor.d/tunables/home.d/snapd") 426 fileContents, err := ioutil.ReadFile(configFile) 427 c.Assert(err, IsNil) 428 c.Check(string(fileContents), Equals, 429 `# Generated by snapd -- DO NOT EDIT!`+"\n"+`@{HOMEDIRS}+="/home/a" "/dir2"`) 430 } 431 432 func (s *apparmorSuite) TestUpdateHomedirsTunableHappyNoDirs(c *C) { 433 err := apparmor.UpdateHomedirsTunable([]string{}) 434 c.Check(err, IsNil) 435 configFile := filepath.Join(dirs.GlobalRootDir, "/etc/apparmor.d/tunables/home.d/snapd") 436 c.Check(osutil.FileExists(configFile), Equals, false) 437 }