gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/apparmor/backend_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2020 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/ioutil" 26 "os" 27 "os/user" 28 "path/filepath" 29 "regexp" 30 "strings" 31 32 . "gopkg.in/check.v1" 33 34 "gitee.com/mysnapcore/mysnapd/dirs" 35 "gitee.com/mysnapcore/mysnapd/interfaces" 36 "gitee.com/mysnapcore/mysnapd/interfaces/apparmor" 37 "gitee.com/mysnapcore/mysnapd/interfaces/ifacetest" 38 "gitee.com/mysnapcore/mysnapd/logger" 39 "gitee.com/mysnapcore/mysnapd/osutil" 40 "gitee.com/mysnapcore/mysnapd/overlord/state" 41 "gitee.com/mysnapcore/mysnapd/release" 42 apparmor_sandbox "gitee.com/mysnapcore/mysnapd/sandbox/apparmor" 43 "gitee.com/mysnapcore/mysnapd/snap" 44 "gitee.com/mysnapcore/mysnapd/snap/snaptest" 45 "gitee.com/mysnapcore/mysnapd/testutil" 46 "gitee.com/mysnapcore/mysnapd/timings" 47 ) 48 49 type loadProfilesParams struct { 50 fnames []string 51 cacheDir string 52 flags apparmor_sandbox.AaParserFlags 53 } 54 55 type unloadProfilesParams struct { 56 fnames []string 57 cacheDir string 58 } 59 60 type backendSuite struct { 61 ifacetest.BackendSuite 62 63 perf *timings.Timings 64 meas *timings.Span 65 66 loadProfilesCalls []loadProfilesParams 67 loadProfilesReturn error 68 unloadProfilesCalls []unloadProfilesParams 69 unloadProfilesReturn error 70 } 71 72 var _ = Suite(&backendSuite{}) 73 74 var testedConfinementOpts = []interfaces.ConfinementOptions{ 75 {}, 76 {DevMode: true}, 77 {JailMode: true}, 78 {Classic: true}, 79 } 80 81 func (s *backendSuite) SetUpTest(c *C) { 82 s.Backend = &apparmor.Backend{} 83 s.BackendSuite.SetUpTest(c) 84 c.Assert(s.Repo.AddBackend(s.Backend), IsNil) 85 86 s.perf = timings.New(nil) 87 s.meas = s.perf.StartSpan("", "") 88 89 err := os.MkdirAll(apparmor_sandbox.CacheDir, 0700) 90 c.Assert(err, IsNil) 91 92 restore := release.MockReleaseInfo(&release.OS{ID: "ubuntu"}) 93 s.AddCleanup(restore) 94 95 restore = apparmor_sandbox.MockFeatures(nil, nil, nil, nil) 96 s.AddCleanup(restore) 97 98 s.loadProfilesCalls = nil 99 s.loadProfilesReturn = nil 100 s.unloadProfilesCalls = nil 101 s.unloadProfilesReturn = nil 102 restore = apparmor.MockLoadProfiles(func(fnames []string, cacheDir string, flags apparmor_sandbox.AaParserFlags) error { 103 // To simplify testing, ignore invocations with no profiles (as a 104 // matter of fact, the real implementation is doing the same) 105 if len(fnames) == 0 { 106 return nil 107 } 108 s.loadProfilesCalls = append(s.loadProfilesCalls, loadProfilesParams{fnames, cacheDir, flags}) 109 return s.loadProfilesReturn 110 }) 111 s.AddCleanup(restore) 112 restore = apparmor.MockUnloadProfiles(func(fnames []string, cacheDir string) error { 113 s.unloadProfilesCalls = append(s.unloadProfilesCalls, unloadProfilesParams{fnames, cacheDir}) 114 return s.unloadProfilesReturn 115 }) 116 s.AddCleanup(restore) 117 118 err = s.Backend.Initialize(ifacetest.DefaultInitializeOpts) 119 c.Assert(err, IsNil) 120 } 121 122 func (s *backendSuite) TearDownTest(c *C) { 123 s.BackendSuite.TearDownTest(c) 124 } 125 126 // Tests for Setup() and Remove() 127 128 func (s *backendSuite) TestName(c *C) { 129 c.Check(s.Backend.Name(), Equals, interfaces.SecurityAppArmor) 130 } 131 132 type expSnapConfineTransitionRules struct { 133 usrBinSnapRules bool 134 usrLibSnapdTarget string 135 coreSnapTarget string 136 snapdSnapTarget string 137 } 138 139 func checkProfileExtraRules(c *C, profile string, exp expSnapConfineTransitionRules) { 140 if exp.usrBinSnapRules { 141 c.Assert(profile, testutil.FileContains, " /usr/bin/snap ixr,") 142 c.Assert(profile, testutil.FileContains, " /snap/{snapd,core}/*/usr/bin/snap ixr,") 143 } else { 144 c.Assert(profile, Not(testutil.FileContains), "/usr/bin/snap") 145 } 146 147 if exp.usrLibSnapdTarget != "" { 148 rule := fmt.Sprintf("/usr/lib/snapd/snap-confine Pxr -> %s,", exp.usrLibSnapdTarget) 149 c.Assert(profile, testutil.FileContains, rule) 150 } else { 151 c.Assert(profile, Not(testutil.FileMatches), "/usr/lib/snapd/snap-confine") 152 } 153 154 if exp.coreSnapTarget != "" { 155 rule := fmt.Sprintf("/snap/core/*/usr/lib/snapd/snap-confine Pxr -> %s,", exp.coreSnapTarget) 156 c.Assert(profile, testutil.FileContains, rule) 157 } else { 158 c.Assert(profile, Not(testutil.FileMatches), `/snap/core/\*/usr/lib/snapd/snap-confine`) 159 } 160 161 if exp.snapdSnapTarget != "" { 162 rule := fmt.Sprintf("/snap/snapd/*/usr/lib/snapd/snap-confine Pxr -> %s,", exp.snapdSnapTarget) 163 c.Assert(profile, testutil.FileContains, rule) 164 } else { 165 c.Assert(profile, Not(testutil.FileMatches), `/snap/snapd/\*/usr/lib/snapd/snap-confine`) 166 } 167 } 168 169 func (s *backendSuite) TestInstallingDevmodeSnapCoreSnapOnlyExtraRules(c *C) { 170 // re-initialize with new options 171 backendOpts := &interfaces.SecurityBackendOptions{ 172 CoreSnapInfo: ifacetest.DefaultInitializeOpts.CoreSnapInfo, 173 } 174 err := s.Backend.Initialize(backendOpts) 175 c.Assert(err, IsNil) 176 177 devMode := interfaces.ConfinementOptions{DevMode: true} 178 s.InstallSnap(c, devMode, "", ifacetest.SambaYamlV1, 1) 179 180 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 181 182 checkProfileExtraRules(c, profile, expSnapConfineTransitionRules{ 183 usrBinSnapRules: true, 184 usrLibSnapdTarget: "/usr/lib/snapd/snap-confine", 185 coreSnapTarget: "/snap/core/123/usr/lib/snapd/snap-confine", 186 }) 187 } 188 189 func (s *backendSuite) TestInstallingDevmodeSnapSnapdSnapOnlyExtraRules(c *C) { 190 // re-initialize with new options 191 backendOpts := &interfaces.SecurityBackendOptions{ 192 SnapdSnapInfo: ifacetest.DefaultInitializeOpts.SnapdSnapInfo, 193 } 194 err := s.Backend.Initialize(backendOpts) 195 c.Assert(err, IsNil) 196 197 devMode := interfaces.ConfinementOptions{DevMode: true} 198 s.InstallSnap(c, devMode, "", ifacetest.SambaYamlV1, 1) 199 200 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 201 202 checkProfileExtraRules(c, profile, expSnapConfineTransitionRules{ 203 usrBinSnapRules: true, 204 usrLibSnapdTarget: "/snap/snapd/321/usr/lib/snapd/snap-confine", 205 snapdSnapTarget: "/snap/snapd/321/usr/lib/snapd/snap-confine", 206 }) 207 } 208 209 func (s *backendSuite) TestInstallingDevmodeSnapBothSnapdAndCoreSnapOnlyExtraRulesCore(c *C) { 210 r := release.MockOnClassic(false) 211 defer r() 212 213 // re-initialize with new options 214 backendOpts := &interfaces.SecurityBackendOptions{ 215 SnapdSnapInfo: ifacetest.DefaultInitializeOpts.SnapdSnapInfo, 216 CoreSnapInfo: ifacetest.DefaultInitializeOpts.CoreSnapInfo, 217 } 218 err := s.Backend.Initialize(backendOpts) 219 c.Assert(err, IsNil) 220 221 devMode := interfaces.ConfinementOptions{DevMode: true} 222 // snap base is core, but we are not on classic 223 s.InstallSnap(c, devMode, "", ifacetest.SambaYamlV1, 1) 224 225 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 226 checkProfileExtraRules(c, profile, expSnapConfineTransitionRules{ 227 usrBinSnapRules: true, 228 usrLibSnapdTarget: "/snap/snapd/321/usr/lib/snapd/snap-confine", 229 snapdSnapTarget: "/snap/snapd/321/usr/lib/snapd/snap-confine", 230 coreSnapTarget: "/snap/core/123/usr/lib/snapd/snap-confine", 231 }) 232 } 233 234 func (s *backendSuite) TestInstallingDevmodeSnapBothSnapdAndCoreSnapOnlyExtraRulesClassic(c *C) { 235 r := release.MockOnClassic(true) 236 defer r() 237 238 // re-initialize with new options 239 backendOpts := &interfaces.SecurityBackendOptions{ 240 SnapdSnapInfo: ifacetest.DefaultInitializeOpts.SnapdSnapInfo, 241 CoreSnapInfo: ifacetest.DefaultInitializeOpts.CoreSnapInfo, 242 } 243 err := s.Backend.Initialize(backendOpts) 244 c.Assert(err, IsNil) 245 246 devMode := interfaces.ConfinementOptions{DevMode: true} 247 // base core snap 248 s.InstallSnap(c, devMode, "", ifacetest.SambaYamlV1, 1) 249 250 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 251 checkProfileExtraRules(c, profile, expSnapConfineTransitionRules{ 252 usrBinSnapRules: true, 253 usrLibSnapdTarget: "/snap/core/123/usr/lib/snapd/snap-confine", 254 snapdSnapTarget: "/snap/snapd/321/usr/lib/snapd/snap-confine", 255 coreSnapTarget: "/snap/core/123/usr/lib/snapd/snap-confine", 256 }) 257 } 258 259 func (s *backendSuite) TestInstallingDevmodeSnapNonCoreBaseBothSnapdAndCoreSnapOnlyExtraRulesClassic(c *C) { 260 r := release.MockOnClassic(true) 261 defer r() 262 263 // re-initialize with new options 264 backendOpts := &interfaces.SecurityBackendOptions{ 265 SnapdSnapInfo: ifacetest.DefaultInitializeOpts.SnapdSnapInfo, 266 CoreSnapInfo: ifacetest.DefaultInitializeOpts.CoreSnapInfo, 267 } 268 err := s.Backend.Initialize(backendOpts) 269 c.Assert(err, IsNil) 270 271 devMode := interfaces.ConfinementOptions{DevMode: true} 272 // non-base core 273 s.InstallSnap(c, devMode, "", ifacetest.SambaYamlV1Core20Base, 1) 274 275 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 276 checkProfileExtraRules(c, profile, expSnapConfineTransitionRules{ 277 usrBinSnapRules: true, 278 usrLibSnapdTarget: "/snap/snapd/321/usr/lib/snapd/snap-confine", 279 snapdSnapTarget: "/snap/snapd/321/usr/lib/snapd/snap-confine", 280 coreSnapTarget: "/snap/core/123/usr/lib/snapd/snap-confine", 281 }) 282 } 283 284 func (s *backendSuite) TestInstallingDevmodeSnapNeitherSnapdNorCoreSnapInstalledPanicsLikeUC16InitialSeedWithDevmodeSnapInSeed(c *C) { 285 r := release.MockOnClassic(true) 286 defer r() 287 288 // neither snap is installed 289 backendOpts := &interfaces.SecurityBackendOptions{} 290 err := s.Backend.Initialize(backendOpts) 291 c.Assert(err, IsNil) 292 293 devMode := interfaces.ConfinementOptions{DevMode: true} 294 c.Assert(func() { 295 s.InstallSnap(c, devMode, "", ifacetest.SambaYamlV1, 1) 296 }, PanicMatches, "neither snapd nor core snap available while preparing apparmor profile for devmode snap samba, panicing to restart snapd to continue seeding") 297 } 298 299 func (s *backendSuite) TestInstallingSnapWritesAndLoadsProfiles(c *C) { 300 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 1) 301 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 302 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 303 // file called "snap.sambda.smbd" was created 304 _, err := os.Stat(profile) 305 c.Check(err, IsNil) 306 // apparmor_parser was used to load that file 307 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 308 {[]string{updateNSProfile, profile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.SkipReadCache}, 309 }) 310 } 311 312 func (s *backendSuite) TestInstallingSnapWithHookWritesAndLoadsProfiles(c *C) { 313 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.HookYaml, 1) 314 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.foo.hook.configure") 315 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.foo") 316 317 // Verify that profile "snap.foo.hook.configure" was created 318 _, err := os.Stat(profile) 319 c.Check(err, IsNil) 320 // apparmor_parser was used to load that file 321 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 322 {[]string{updateNSProfile, profile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.SkipReadCache}, 323 }) 324 } 325 326 const layoutYaml = `name: myapp 327 version: 1 328 apps: 329 myapp: 330 command: myapp 331 layout: 332 /usr/share/myapp: 333 bind: $SNAP/usr/share/myapp 334 ` 335 336 func (s *backendSuite) TestInstallingSnapWithLayoutWritesAndLoadsProfiles(c *C) { 337 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", layoutYaml, 1) 338 appProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.myapp.myapp") 339 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.myapp") 340 // both profiles were created 341 _, err := os.Stat(appProfile) 342 c.Check(err, IsNil) 343 _, err = os.Stat(updateNSProfile) 344 c.Check(err, IsNil) 345 // TODO: check for layout snippets inside the generated file once we have some snippets to check for. 346 // apparmor_parser was used to load them 347 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 348 {[]string{updateNSProfile, appProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.SkipReadCache}, 349 }) 350 } 351 352 const gadgetYaml = `name: mydevice 353 type: gadget 354 version: 1 355 ` 356 357 func (s *backendSuite) TestInstallingSnapWithoutAppsOrHooksDoesntAddProfiles(c *C) { 358 // Installing a snap that doesn't have either hooks or apps doesn't generate 359 // any apparmor profiles because there is no executable content that would need 360 // an execution environment and the corresponding mount namespace. 361 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", gadgetYaml, 1) 362 c.Check(s.loadProfilesCalls, HasLen, 0) 363 } 364 365 func (s *backendSuite) TestTimings(c *C) { 366 oldDurationThreshold := timings.DurationThreshold 367 defer func() { 368 timings.DurationThreshold = oldDurationThreshold 369 }() 370 timings.DurationThreshold = 0 371 372 for _, opts := range testedConfinementOpts { 373 perf := timings.New(nil) 374 meas := perf.StartSpan("", "") 375 376 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 377 c.Assert(s.Backend.Setup(snapInfo, opts, s.Repo, meas), IsNil) 378 379 st := state.New(nil) 380 st.Lock() 381 defer st.Unlock() 382 perf.Save(st) 383 384 var allTimings []map[string]interface{} 385 c.Assert(st.Get("timings", &allTimings), IsNil) 386 c.Assert(allTimings, HasLen, 1) 387 388 timings, ok := allTimings[0]["timings"] 389 c.Assert(ok, Equals, true) 390 391 c.Assert(timings, HasLen, 2) 392 timingsList, ok := timings.([]interface{}) 393 c.Assert(ok, Equals, true) 394 tm := timingsList[0].(map[string]interface{}) 395 c.Check(tm["label"], Equals, "load-profiles[changed]") 396 397 s.RemoveSnap(c, snapInfo) 398 } 399 } 400 401 func (s *backendSuite) TestProfilesAreAlwaysLoaded(c *C) { 402 for _, opts := range testedConfinementOpts { 403 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 404 s.loadProfilesCalls = nil 405 err := s.Backend.Setup(snapInfo, opts, s.Repo, s.meas) 406 c.Assert(err, IsNil) 407 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 408 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 409 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 410 {[]string{updateNSProfile, profile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 411 }) 412 s.RemoveSnap(c, snapInfo) 413 } 414 } 415 416 func (s *backendSuite) TestRemovingSnapRemovesAndUnloadsProfiles(c *C) { 417 for _, opts := range testedConfinementOpts { 418 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 419 s.unloadProfilesCalls = nil 420 s.RemoveSnap(c, snapInfo) 421 c.Check(s.unloadProfilesCalls, DeepEquals, []unloadProfilesParams{ 422 {[]string{"snap-update-ns.samba", "snap.samba.smbd"}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir)}, 423 }) 424 } 425 } 426 427 func (s *backendSuite) TestRemovingSnapWithHookRemovesAndUnloadsProfiles(c *C) { 428 for _, opts := range testedConfinementOpts { 429 snapInfo := s.InstallSnap(c, opts, "", ifacetest.HookYaml, 1) 430 s.unloadProfilesCalls = nil 431 s.RemoveSnap(c, snapInfo) 432 c.Check(s.unloadProfilesCalls, DeepEquals, []unloadProfilesParams{ 433 {[]string{"snap-update-ns.foo", "snap.foo.hook.configure"}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir)}, 434 }) 435 } 436 } 437 438 func (s *backendSuite) TestUpdatingSnapMakesNeccesaryChanges(c *C) { 439 for _, opts := range testedConfinementOpts { 440 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 441 s.loadProfilesCalls = nil 442 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 2) 443 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 444 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 445 // apparmor_parser was used to reload the profile because snap revision 446 // is inside the generated policy. 447 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 448 {[]string{profile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.SkipReadCache}, 449 {[]string{updateNSProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 450 }) 451 s.RemoveSnap(c, snapInfo) 452 } 453 } 454 455 func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) { 456 for _, opts := range testedConfinementOpts { 457 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 458 s.loadProfilesCalls = nil 459 // NOTE: the revision is kept the same to just test on the new application being added 460 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1WithNmbd, 1) 461 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 462 smbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 463 nmbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.nmbd") 464 // file called "snap.sambda.nmbd" was created 465 _, err := os.Stat(nmbdProfile) 466 c.Check(err, IsNil) 467 // apparmor_parser was used to load all the profiles, the nmbd profile is new so we force invalidate its cache (if any). 468 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 469 {[]string{nmbdProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.SkipReadCache}, 470 {[]string{updateNSProfile, smbdProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 471 }) 472 s.RemoveSnap(c, snapInfo) 473 } 474 } 475 476 func (s *backendSuite) TestUpdatingSnapToOneWithMoreHooks(c *C) { 477 for _, opts := range testedConfinementOpts { 478 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1WithNmbd, 1) 479 s.loadProfilesCalls = nil 480 // NOTE: the revision is kept the same to just test on the new application being added 481 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlWithHook, 1) 482 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 483 smbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 484 nmbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.nmbd") 485 hookProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.hook.configure") 486 487 // Verify that profile "snap.samba.hook.configure" was created 488 _, err := os.Stat(hookProfile) 489 c.Check(err, IsNil) 490 // apparmor_parser was used to load all the profiles, the hook profile has changed so we force invalidate its cache. 491 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 492 {[]string{hookProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.SkipReadCache}, 493 {[]string{updateNSProfile, nmbdProfile, smbdProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 494 }) 495 s.RemoveSnap(c, snapInfo) 496 } 497 } 498 499 func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) { 500 for _, opts := range testedConfinementOpts { 501 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1WithNmbd, 1) 502 s.loadProfilesCalls = nil 503 // NOTE: the revision is kept the same to just test on the application being removed 504 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 1) 505 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 506 smbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 507 nmbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.nmbd") 508 // file called "snap.sambda.nmbd" was removed 509 _, err := os.Stat(nmbdProfile) 510 c.Check(os.IsNotExist(err), Equals, true) 511 // apparmor_parser was used to remove the unused profile 512 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 513 {[]string{updateNSProfile, smbdProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 514 }) 515 s.RemoveSnap(c, snapInfo) 516 } 517 } 518 519 func (s *backendSuite) TestUpdatingSnapToOneWithFewerHooks(c *C) { 520 for _, opts := range testedConfinementOpts { 521 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlWithHook, 1) 522 s.loadProfilesCalls = nil 523 // NOTE: the revision is kept the same to just test on the application being removed 524 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1WithNmbd, 1) 525 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 526 smbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 527 nmbdProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.nmbd") 528 hookProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.hook.configure") 529 530 // Verify profile "snap.samba.hook.configure" was removed 531 _, err := os.Stat(hookProfile) 532 c.Check(os.IsNotExist(err), Equals, true) 533 // apparmor_parser was used to remove the unused profile 534 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 535 {[]string{updateNSProfile, nmbdProfile, smbdProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 536 }) 537 s.RemoveSnap(c, snapInfo) 538 } 539 } 540 541 // SetupMany tests 542 543 func (s *backendSuite) TestSetupManyProfilesAreAlwaysLoaded(c *C) { 544 for _, opts := range testedConfinementOpts { 545 snapInfo1 := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 546 snapInfo2 := s.InstallSnap(c, opts, "", ifacetest.SomeSnapYamlV1, 1) 547 s.loadProfilesCalls = nil 548 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 549 c.Assert(ok, Equals, true) 550 err := setupManyInterface.SetupMany([]*snap.Info{snapInfo1, snapInfo2}, func(snapName string) interfaces.ConfinementOptions { return opts }, s.Repo, s.meas) 551 c.Assert(err, IsNil) 552 snap1nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 553 snap1AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 554 snap2nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.some-snap") 555 snap2AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.some-snap.someapp") 556 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 557 {[]string{snap1nsProfile, snap1AAprofile, snap2nsProfile, snap2AAprofile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.ConserveCPU}, 558 }) 559 s.RemoveSnap(c, snapInfo1) 560 s.RemoveSnap(c, snapInfo2) 561 } 562 } 563 564 func (s *backendSuite) TestSetupManyProfilesWithChanged(c *C) { 565 for _, opts := range testedConfinementOpts { 566 snapInfo1 := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 567 snapInfo2 := s.InstallSnap(c, opts, "", ifacetest.SomeSnapYamlV1, 1) 568 s.loadProfilesCalls = nil 569 570 snap1nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 571 snap1AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 572 snap2nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.some-snap") 573 snap2AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.some-snap.someapp") 574 575 // simulate outdated profiles by changing their data on the disk 576 c.Assert(ioutil.WriteFile(snap1AAprofile, []byte("# an outdated profile"), 0644), IsNil) 577 c.Assert(ioutil.WriteFile(snap2AAprofile, []byte("# an outdated profile"), 0644), IsNil) 578 579 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 580 c.Assert(ok, Equals, true) 581 err := setupManyInterface.SetupMany([]*snap.Info{snapInfo1, snapInfo2}, func(snapName string) interfaces.ConfinementOptions { return opts }, s.Repo, s.meas) 582 c.Assert(err, IsNil) 583 584 // expect two batch executions - one for changed profiles, second for unchanged profiles. 585 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 586 {[]string{snap1AAprofile, snap2AAprofile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.SkipReadCache | apparmor_sandbox.ConserveCPU}, 587 {[]string{snap1nsProfile, snap2nsProfile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.ConserveCPU}, 588 }) 589 s.RemoveSnap(c, snapInfo1) 590 s.RemoveSnap(c, snapInfo2) 591 } 592 } 593 594 // helper for checking for apparmor parser calls where batch run is expected to fail and is followed by two separate runs for individual snaps. 595 func (s *backendSuite) checkSetupManyCallsWithFallback(c *C, invocations []loadProfilesParams) { 596 snap1nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 597 snap1AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 598 snap2nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.some-snap") 599 snap2AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.some-snap.someapp") 600 601 // We expect three calls to apparmor_parser due to the failure of batch run. First is the failed batch run, followed by succesfull fallback runs. 602 c.Check(invocations, DeepEquals, []loadProfilesParams{ 603 {[]string{snap1nsProfile, snap1AAprofile, snap2nsProfile, snap2AAprofile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), apparmor_sandbox.ConserveCPU}, 604 {[]string{snap1nsProfile, snap1AAprofile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 605 {[]string{snap2nsProfile, snap2AAprofile}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 606 }) 607 } 608 609 func (s *backendSuite) TestSetupManyApparmorBatchProcessingPermanentError(c *C) { 610 log, restore := logger.MockLogger() 611 defer restore() 612 613 for _, opts := range testedConfinementOpts { 614 log.Reset() 615 616 // note, InstallSnap here uses s.parserCmd which mocks happy apparmor_parser 617 snapInfo1 := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 618 snapInfo2 := s.InstallSnap(c, opts, "", ifacetest.SomeSnapYamlV1, 1) 619 s.loadProfilesCalls = nil 620 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 621 c.Assert(ok, Equals, true) 622 623 // mock apparmor_parser again with a failing one (and restore immediately for the next iteration of the test) 624 s.loadProfilesReturn = errors.New("apparmor_parser crash") 625 errs := setupManyInterface.SetupMany([]*snap.Info{snapInfo1, snapInfo2}, func(snapName string) interfaces.ConfinementOptions { return opts }, s.Repo, s.meas) 626 s.loadProfilesReturn = nil 627 628 s.checkSetupManyCallsWithFallback(c, s.loadProfilesCalls) 629 630 // two errors expected: SetupMany failure on multiple snaps falls back to one-by-one apparmor invocations. Both fail on apparmor_parser again and we only see 631 // individual failures. Error from batch run is only logged. 632 c.Assert(errs, HasLen, 2) 633 c.Check(errs[0], ErrorMatches, ".*cannot setup profiles for snap \"samba\": apparmor_parser crash") 634 c.Check(errs[1], ErrorMatches, ".*cannot setup profiles for snap \"some-snap\": apparmor_parser crash") 635 c.Check(log.String(), Matches, ".*failed to batch-reload unchanged profiles: apparmor_parser crash\n") 636 637 s.RemoveSnap(c, snapInfo1) 638 s.RemoveSnap(c, snapInfo2) 639 } 640 } 641 642 func (s *backendSuite) TestSetupManyApparmorBatchProcessingErrorWithFallbackOK(c *C) { 643 log, restore := logger.MockLogger() 644 defer restore() 645 646 for _, opts := range testedConfinementOpts { 647 log.Reset() 648 649 // note, InstallSnap here uses s.parserCmd which mocks happy apparmor_parser 650 snapInfo1 := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 651 snapInfo2 := s.InstallSnap(c, opts, "", ifacetest.SomeSnapYamlV1, 1) 652 s.loadProfilesCalls = nil 653 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 654 c.Assert(ok, Equals, true) 655 656 // mock apparmor_parser again with a failing one (and restore immediately for the next iteration of the test) 657 r := apparmor.MockLoadProfiles(func(fnames []string, cacheDir string, flags apparmor_sandbox.AaParserFlags) error { 658 if len(fnames) == 0 { 659 return nil 660 } 661 s.loadProfilesCalls = append(s.loadProfilesCalls, loadProfilesParams{fnames, cacheDir, flags}) 662 if len(fnames) > 3 { 663 return errors.New("some error") 664 } 665 return nil 666 }) 667 errs := setupManyInterface.SetupMany([]*snap.Info{snapInfo1, snapInfo2}, func(snapName string) interfaces.ConfinementOptions { return opts }, s.Repo, s.meas) 668 r() 669 670 s.checkSetupManyCallsWithFallback(c, s.loadProfilesCalls) 671 672 // no errors expected: error from batch run is only logged, but individual apparmor parser execution as part of the fallback are successful. 673 // note, tnis scenario is unlikely to happen in real life, because if a profile failed in a batch, it would fail when parsed alone too. It is 674 // tested here just to exercise various execution paths. 675 c.Assert(errs, HasLen, 0) 676 c.Check(log.String(), Matches, ".*failed to batch-reload unchanged profiles: some error\n") 677 678 s.RemoveSnap(c, snapInfo1) 679 s.RemoveSnap(c, snapInfo2) 680 } 681 } 682 683 func (s *backendSuite) TestSetupManyApparmorBatchProcessingErrorWithFallbackPartiallyOK(c *C) { 684 log, restore := logger.MockLogger() 685 defer restore() 686 687 for _, opts := range testedConfinementOpts { 688 log.Reset() 689 690 // note, InstallSnap here uses s.parserCmd which mocks happy apparmor_parser 691 snapInfo1 := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 692 snapInfo2 := s.InstallSnap(c, opts, "", ifacetest.SomeSnapYamlV1, 1) 693 s.loadProfilesCalls = nil 694 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 695 c.Assert(ok, Equals, true) 696 697 // mock apparmor_parser with a failing one 698 r := apparmor.MockLoadProfiles(func(fnames []string, cacheDir string, flags apparmor_sandbox.AaParserFlags) error { 699 if len(fnames) == 0 { 700 return nil 701 } 702 s.loadProfilesCalls = append(s.loadProfilesCalls, loadProfilesParams{fnames, cacheDir, flags}) 703 // If the profile list contains SAMBA, we fail 704 for _, profilePath := range fnames { 705 name := filepath.Base(profilePath) 706 if name == "snap.samba.smbd" { 707 return errors.New("fail on samba") 708 } 709 } 710 return nil 711 }) 712 errs := setupManyInterface.SetupMany([]*snap.Info{snapInfo1, snapInfo2}, func(snapName string) interfaces.ConfinementOptions { return opts }, s.Repo, s.meas) 713 r() 714 715 s.checkSetupManyCallsWithFallback(c, s.loadProfilesCalls) 716 717 // the batch reload fails because of snap.samba.smbd profile failing 718 c.Check(log.String(), Matches, ".* failed to batch-reload unchanged profiles: fail on samba\n") 719 // and we also fail when running that profile in fallback mode 720 c.Assert(errs, HasLen, 1) 721 c.Assert(errs[0], ErrorMatches, "cannot setup profiles for snap \"samba\": fail on samba") 722 723 s.RemoveSnap(c, snapInfo1) 724 s.RemoveSnap(c, snapInfo2) 725 } 726 } 727 728 const snapcraftPrYaml = `name: snapcraft-pr 729 version: 1 730 apps: 731 snapcraft-pr: 732 cmd: snapcraft-pr 733 ` 734 735 const snapcraftYaml = `name: snapcraft 736 version: 1 737 apps: 738 snapcraft: 739 cmd: snapcraft 740 ` 741 742 func (s *backendSuite) TestInstallingSnapDoesntBreakSnapsWithPrefixName(c *C) { 743 snapcraftProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft.snapcraft") 744 snapcraftPrProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft-pr.snapcraft-pr") 745 // Install snapcraft-pr and check that its profile was created. 746 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapcraftPrYaml, 1) 747 _, err := os.Stat(snapcraftPrProfile) 748 c.Check(err, IsNil) 749 750 // Install snapcraft (sans the -pr suffix) and check that its profile was created. 751 // Check that this didn't remove the profile of snapcraft-pr installed earlier. 752 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapcraftYaml, 1) 753 _, err = os.Stat(snapcraftProfile) 754 c.Check(err, IsNil) 755 _, err = os.Stat(snapcraftPrProfile) 756 c.Check(err, IsNil) 757 } 758 759 func (s *backendSuite) TestRemovingSnapDoesntBreakSnapsWIthPrefixName(c *C) { 760 snapcraftProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft.snapcraft") 761 snapcraftPrProfile := filepath.Join(dirs.SnapAppArmorDir, "snap.snapcraft-pr.snapcraft-pr") 762 763 // Install snapcraft-pr and check that its profile was created. 764 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapcraftPrYaml, 1) 765 _, err := os.Stat(snapcraftPrProfile) 766 c.Check(err, IsNil) 767 768 // Install snapcraft (sans the -pr suffix) and check that its profile was created. 769 // Check that this didn't remove the profile of snapcraft-pr installed earlier. 770 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapcraftYaml, 1) 771 _, err = os.Stat(snapcraftProfile) 772 c.Check(err, IsNil) 773 _, err = os.Stat(snapcraftPrProfile) 774 c.Check(err, IsNil) 775 776 // Remove snapcraft (sans the -pr suffix) and check that its profile was removed. 777 // Check that this didn't remove the profile of snapcraft-pr installed earlier. 778 s.RemoveSnap(c, snapInfo) 779 _, err = os.Stat(snapcraftProfile) 780 c.Check(os.IsNotExist(err), Equals, true) 781 _, err = os.Stat(snapcraftPrProfile) 782 c.Check(err, IsNil) 783 } 784 785 func (s *backendSuite) TestDefaultCoreRuntimesTemplateOnlyUsed(c *C) { 786 for _, base := range []string{ 787 "", 788 "base: core16", 789 "base: core18", 790 "base: core20", 791 "base: core22", 792 "base: core98", 793 } { 794 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 795 defer restore() 796 797 testYaml := ifacetest.SambaYamlV1 + base + "\n" 798 799 snapInfo := snaptest.MockInfo(c, testYaml, nil) 800 // NOTE: we don't call apparmor.MockTemplate() 801 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 802 c.Assert(err, IsNil) 803 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 804 data, err := ioutil.ReadFile(profile) 805 c.Assert(err, IsNil) 806 for _, line := range []string{ 807 // preamble 808 "#include <tunables/global>\n", 809 // footer 810 "}\n", 811 // templateCommon 812 "/etc/ld.so.preload r,\n", 813 "owner @{PROC}/@{pid}/maps k,\n", 814 "/tmp/ r,\n", 815 "/sys/class/ r,\n", 816 // defaultCoreRuntimeTemplateRules 817 "# Default rules for core base runtimes\n", 818 "/usr/share/terminfo/** k,\n", 819 } { 820 c.Assert(string(data), testutil.Contains, line) 821 } 822 for _, line := range []string{ 823 // defaultOtherBaseTemplateRules should not be present 824 "# Default rules for non-core base runtimes\n", 825 "/{,s}bin/** mrklix,\n", 826 } { 827 c.Assert(string(data), Not(testutil.Contains), line) 828 } 829 } 830 } 831 832 func (s *backendSuite) TestBaseDefaultTemplateOnlyUsed(c *C) { 833 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 834 defer restore() 835 836 testYaml := ifacetest.SambaYamlV1 + "base: other\n" 837 838 snapInfo := snaptest.MockInfo(c, testYaml, nil) 839 // NOTE: we don't call apparmor.MockTemplate() 840 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 841 c.Assert(err, IsNil) 842 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 843 data, err := ioutil.ReadFile(profile) 844 c.Assert(err, IsNil) 845 for _, line := range []string{ 846 // preamble 847 "#include <tunables/global>\n", 848 // footer 849 "}\n", 850 // templateCommon 851 "/etc/ld.so.preload r,\n", 852 "owner @{PROC}/@{pid}/maps k,\n", 853 "/tmp/ r,\n", 854 "/sys/class/ r,\n", 855 // defaultOtherBaseTemplateRules 856 "# Default rules for non-core base runtimes\n", 857 "/{,s}bin/** mrklix,\n", 858 } { 859 c.Assert(string(data), testutil.Contains, line) 860 } 861 for _, line := range []string{ 862 // defaultCoreRuntimeTemplateRules should not be present 863 "# Default rules for core base runtimes\n", 864 "/usr/share/terminfo/** k,\n", 865 "/{,usr/}bin/arch ixr,\n", 866 } { 867 c.Assert(string(data), Not(testutil.Contains), line) 868 } 869 } 870 871 func (s *backendSuite) TestTemplateRulesInCommon(c *C) { 872 // assume that we lstrip() the line 873 commonFiles := regexp.MustCompile(`^(audit +)?(deny +)?(owner +)?/((dev|etc|run|sys|tmp|{dev,run}|{,var/}run|usr/lib/snapd|var/lib/extrausers|var/lib/snapd)/|var/snap/{?@{SNAP_)`) 874 commonFilesVar := regexp.MustCompile(`^(audit +)?(deny +)?(owner +)?@{(HOME|HOMEDIRS|INSTALL_DIR|PROC)}/`) 875 commonOther := regexp.MustCompile(`^([^/@#]|#include +<)`) 876 877 // first, verify the regexes themselves 878 879 // Expected matches 880 for idx, tc := range []string{ 881 // abstraction 882 "#include <abstractions/base>", 883 // file 884 "/dev/{,u}random w,", 885 "/dev/{,u}random w, # test comment", 886 "/{dev,run}/shm/snap.@{SNAP_INSTANCE_NAME}.** mrwlkix,", 887 "/etc/ld.so.preload r,", 888 "@{INSTALL_DIR}/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/ r,", 889 "deny @{INSTALL_DIR}/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/**/__pycache__/*.pyc.[0-9]* w,", 890 "audit /dev/something r,", 891 "audit deny /dev/something r,", 892 "audit deny owner /dev/something r,", 893 "@{PROC}/ r,", 894 "owner @{PROC}/@{pid}/{,task/@{tid}}fd/[0-9]* rw,", 895 "/run/uuidd/request rw,", 896 "owner /run/user/[0-9]*/snap.@{SNAP_INSTANCE_NAME}/ rw,", 897 "/sys/devices/virtual/tty/{console,tty*}/active r,", 898 "/tmp/ r,", 899 "/{,var/}run/udev/tags/snappy-assign/ r,", 900 "/usr/lib/snapd/foo r,", 901 "/var/lib/extrausers/foo r,", 902 "/var/lib/snapd/foo r,", 903 "/var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/ r", 904 "/var/snap/@{SNAP_NAME}/ r", 905 // capability 906 "capability ipc_lock,", 907 // dbus - single line 908 "dbus (receive, send) peer=(label=snap.@{SNAP_INSTANCE_NAME}.*),", 909 // dbus - multiline 910 "dbus (send)", 911 "bus={session,system}", 912 "path=/org/freedesktop/DBus", 913 "interface=org.freedesktop.DBus.Introspectable", 914 "member=Introspect", 915 "peer=(label=unconfined),", 916 // mount 917 "mount,", 918 "remount,", 919 "umount,", 920 // network 921 "network,", 922 // pivot_root 923 "pivot_root,", 924 // ptrace 925 "ptrace,", 926 // signal 927 "signal peer=snap.@{SNAP_INSTANCE_NAME}.*,", 928 // unix 929 "unix peer=(label=snap.@{SNAP_INSTANCE_NAME}.*),", 930 } { 931 c.Logf("trying %d: %s", idx, tc) 932 cf := commonFiles.MatchString(tc) 933 cfv := commonFilesVar.MatchString(tc) 934 co := commonOther.MatchString(tc) 935 c.Check(cf || cfv || co, Equals, true) 936 } 937 938 // Expected no matches 939 for idx, tc := range []string{ 940 "/bin/ls", 941 "# some comment", 942 "deny /usr/lib/python3*/{,**/}__pycache__/ w,", 943 } { 944 c.Logf("trying %d: %s", idx, tc) 945 cf := commonFiles.MatchString(tc) 946 cfv := commonFilesVar.MatchString(tc) 947 co := commonOther.MatchString(tc) 948 c.Check(cf && cfv && co, Equals, false) 949 } 950 951 for _, raw := range strings.Split(apparmor.DefaultCoreRuntimeTemplateRules, "\n") { 952 line := strings.TrimLeft(raw, " \t") 953 cf := commonFiles.MatchString(line) 954 cfv := commonFilesVar.MatchString(line) 955 co := commonOther.MatchString(line) 956 res := cf || cfv || co 957 if res { 958 c.Logf("ERROR: found rule that should be in templateCommon (default template rules): %s", line) 959 } 960 c.Check(res, Equals, false) 961 } 962 963 for _, raw := range strings.Split(apparmor.DefaultOtherBaseTemplateRules, "\n") { 964 line := strings.TrimLeft(raw, " \t") 965 cf := commonFiles.MatchString(line) 966 cfv := commonFilesVar.MatchString(line) 967 co := commonOther.MatchString(line) 968 res := cf || cfv || co 969 if res { 970 c.Logf("ERROR: found rule that should be in templateCommon (default base template rules): %s", line) 971 } 972 c.Check(res, Equals, false) 973 } 974 } 975 976 type combineSnippetsScenario struct { 977 opts interfaces.ConfinementOptions 978 snippet string 979 content string 980 } 981 982 const commonPrefix = ` 983 # This is a snap name without the instance key 984 @{SNAP_NAME}="samba" 985 # This is a snap name with instance key 986 @{SNAP_INSTANCE_NAME}="samba" 987 @{SNAP_INSTANCE_DESKTOP}="samba" 988 @{SNAP_COMMAND_NAME}="smbd" 989 @{SNAP_REVISION}="1" 990 @{PROFILE_DBUS}="snap_2esamba_2esmbd" 991 @{INSTALL_DIR}="/{,var/lib/snapd/}snap"` 992 993 var combineSnippetsScenarios = []combineSnippetsScenario{{ 994 // By default apparmor is enforcing mode. 995 opts: interfaces.ConfinementOptions{}, 996 content: commonPrefix + "\nprofile \"snap.samba.smbd\" (attach_disconnected,mediate_deleted) {\n\n}\n", 997 }, { 998 // Snippets are injected in the space between "{" and "}" 999 opts: interfaces.ConfinementOptions{}, 1000 snippet: "snippet", 1001 content: commonPrefix + "\nprofile \"snap.samba.smbd\" (attach_disconnected,mediate_deleted) {\nsnippet\n}\n", 1002 }, { 1003 // DevMode switches apparmor to non-enforcing (complain) mode. 1004 opts: interfaces.ConfinementOptions{DevMode: true}, 1005 snippet: "snippet", 1006 content: commonPrefix + "\nprofile \"snap.samba.smbd\" (attach_disconnected,mediate_deleted,complain) {\nsnippet\n}\n", 1007 }, { 1008 // JailMode switches apparmor to enforcing mode even in the presence of DevMode. 1009 opts: interfaces.ConfinementOptions{DevMode: true}, 1010 snippet: "snippet", 1011 content: commonPrefix + "\nprofile \"snap.samba.smbd\" (attach_disconnected,mediate_deleted,complain) {\nsnippet\n}\n", 1012 }, { 1013 // Classic confinement (without jailmode) uses apparmor in complain mode by default and ignores all snippets. 1014 opts: interfaces.ConfinementOptions{Classic: true}, 1015 snippet: "snippet", 1016 content: "\n#classic" + commonPrefix + "\nprofile \"snap.samba.smbd\" (attach_disconnected,mediate_deleted,complain) {\n\n}\n", 1017 }, { 1018 // Classic confinement in JailMode uses enforcing apparmor. 1019 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 1020 snippet: "snippet", 1021 content: commonPrefix + ` 1022 profile "snap.samba.smbd" (attach_disconnected,mediate_deleted) { 1023 1024 # Read-only access to the core snap. 1025 @{INSTALL_DIR}/core/** r, 1026 # Read only access to the core snap to load libc from. 1027 # This is related to LP: #1666897 1028 @{INSTALL_DIR}/core/*/{,usr/}lib/@{multiarch}/{,**/}lib*.so* m, 1029 1030 # For snappy reexec on 4.8+ kernels 1031 @{INSTALL_DIR}/core/*/usr/lib/snapd/snap-exec m, 1032 1033 snippet 1034 } 1035 `, 1036 }} 1037 1038 func (s *backendSuite) TestCombineSnippets(c *C) { 1039 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1040 defer restore() 1041 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1042 defer restore() 1043 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1044 defer restore() 1045 1046 // NOTE: replace the real template with a shorter variant 1047 restoreTemplate := apparmor.MockTemplate("\n" + 1048 "###VAR###\n" + 1049 "###PROFILEATTACH### (attach_disconnected,mediate_deleted) {\n" + 1050 "###SNIPPETS###\n" + 1051 "}\n") 1052 defer restoreTemplate() 1053 restoreClassicTemplate := apparmor.MockClassicTemplate("\n" + 1054 "#classic\n" + 1055 "###VAR###\n" + 1056 "###PROFILEATTACH### (attach_disconnected,mediate_deleted) {\n" + 1057 "###SNIPPETS###\n" + 1058 "}\n") 1059 defer restoreClassicTemplate() 1060 for i, scenario := range combineSnippetsScenarios { 1061 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 1062 if scenario.snippet == "" { 1063 return nil 1064 } 1065 spec.AddSnippet(scenario.snippet) 1066 return nil 1067 } 1068 snapInfo := s.InstallSnap(c, scenario.opts, "", ifacetest.SambaYamlV1, 1) 1069 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 1070 c.Check(profile, testutil.FileEquals, scenario.content, Commentf("scenario %d: %#v", i, scenario)) 1071 stat, err := os.Stat(profile) 1072 c.Assert(err, IsNil) 1073 c.Check(stat.Mode(), Equals, os.FileMode(0644)) 1074 s.RemoveSnap(c, snapInfo) 1075 } 1076 } 1077 1078 func (s *backendSuite) TestCombineSnippetsChangeProfile(c *C) { 1079 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1080 defer restore() 1081 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1082 defer restore() 1083 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1084 defer restore() 1085 1086 restoreClassicTemplate := apparmor.MockClassicTemplate("###CHANGEPROFILE_RULE###") 1087 defer restoreClassicTemplate() 1088 1089 type changeProfileScenario struct { 1090 features []string 1091 expected string 1092 } 1093 1094 var changeProfileScenarios = []changeProfileScenario{{ 1095 features: []string{}, 1096 expected: "change_profile,", 1097 }, { 1098 features: []string{"unsafe"}, 1099 expected: "change_profile unsafe /**,", 1100 }} 1101 1102 for i, scenario := range changeProfileScenarios { 1103 restore = apparmor.MockParserFeatures(func() ([]string, error) { return scenario.features, nil }) 1104 defer restore() 1105 1106 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{Classic: true}, "", ifacetest.SambaYamlV1, 1) 1107 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 1108 c.Check(profile, testutil.FileEquals, scenario.expected, Commentf("scenario %d: %#v", i, scenario)) 1109 stat, err := os.Stat(profile) 1110 c.Assert(err, IsNil) 1111 c.Check(stat.Mode(), Equals, os.FileMode(0644)) 1112 s.RemoveSnap(c, snapInfo) 1113 } 1114 } 1115 1116 func (s *backendSuite) TestParallelInstallCombineSnippets(c *C) { 1117 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1118 defer restore() 1119 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1120 defer restore() 1121 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1122 defer restore() 1123 1124 // NOTE: replace the real template with a shorter variant 1125 restoreTemplate := apparmor.MockTemplate("\n" + 1126 "###VAR###\n" + 1127 "###PROFILEATTACH### (attach_disconnected,mediate_deleted) {\n" + 1128 "###SNIPPETS###\n" + 1129 "}\n") 1130 defer restoreTemplate() 1131 restoreClassicTemplate := apparmor.MockClassicTemplate("\n" + 1132 "#classic\n" + 1133 "###VAR###\n" + 1134 "###PROFILEATTACH### (attach_disconnected,mediate_deleted) {\n" + 1135 "###SNIPPETS###\n" + 1136 "}\n") 1137 defer restoreClassicTemplate() 1138 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 1139 return nil 1140 } 1141 expected := ` 1142 # This is a snap name without the instance key 1143 @{SNAP_NAME}="samba" 1144 # This is a snap name with instance key 1145 @{SNAP_INSTANCE_NAME}="samba_foo" 1146 @{SNAP_INSTANCE_DESKTOP}="samba+foo" 1147 @{SNAP_COMMAND_NAME}="smbd" 1148 @{SNAP_REVISION}="1" 1149 @{PROFILE_DBUS}="snap_2esamba_5ffoo_2esmbd" 1150 @{INSTALL_DIR}="/{,var/lib/snapd/}snap" 1151 profile "snap.samba_foo.smbd" (attach_disconnected,mediate_deleted) { 1152 1153 } 1154 ` 1155 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "samba_foo", ifacetest.SambaYamlV1, 1) 1156 c.Assert(snapInfo, NotNil) 1157 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba_foo.smbd") 1158 stat, err := os.Stat(profile) 1159 c.Assert(err, IsNil) 1160 c.Check(profile, testutil.FileEquals, expected) 1161 c.Check(stat.Mode(), Equals, os.FileMode(0644)) 1162 s.RemoveSnap(c, snapInfo) 1163 } 1164 1165 func (s *backendSuite) TestTemplateVarsWithHook(c *C) { 1166 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1167 defer restore() 1168 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1169 defer restore() 1170 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1171 defer restore() 1172 // NOTE: replace the real template with a shorter variant 1173 restoreTemplate := apparmor.MockTemplate("\n" + 1174 "###VAR###\n" + 1175 "###PROFILEATTACH### (attach_disconnected,mediate_deleted) {\n" + 1176 "###SNIPPETS###\n" + 1177 "}\n") 1178 defer restoreTemplate() 1179 1180 expected := ` 1181 # This is a snap name without the instance key 1182 @{SNAP_NAME}="foo" 1183 # This is a snap name with instance key 1184 @{SNAP_INSTANCE_NAME}="foo" 1185 @{SNAP_INSTANCE_DESKTOP}="foo" 1186 @{SNAP_COMMAND_NAME}="hook.configure" 1187 @{SNAP_REVISION}="1" 1188 @{PROFILE_DBUS}="snap_2efoo_2ehook_2econfigure" 1189 @{INSTALL_DIR}="/{,var/lib/snapd/}snap" 1190 profile "snap.foo.hook.configure" (attach_disconnected,mediate_deleted) { 1191 1192 } 1193 ` 1194 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.HookYaml, 1) 1195 c.Assert(snapInfo, NotNil) 1196 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.foo.hook.configure") 1197 stat, err := os.Stat(profile) 1198 c.Assert(err, IsNil) 1199 c.Check(profile, testutil.FileEquals, expected) 1200 c.Check(stat.Mode(), Equals, os.FileMode(0644)) 1201 s.RemoveSnap(c, snapInfo) 1202 } 1203 1204 const coreYaml = `name: core 1205 version: 1 1206 type: os 1207 ` 1208 1209 const snapdYaml = `name: snapd 1210 version: 1 1211 type: snapd 1212 ` 1213 1214 func (s *backendSuite) writeVanillaSnapConfineProfile(c *C, coreOrSnapdInfo *snap.Info) { 1215 vanillaProfilePath := filepath.Join(coreOrSnapdInfo.MountDir(), "/etc/apparmor.d/usr.lib.snapd.snap-confine.real") 1216 vanillaProfileText := []byte(`#include <tunables/global> 1217 /usr/lib/snapd/snap-confine (attach_disconnected) { 1218 # We run privileged, so be fanatical about what we include and don't use 1219 # any abstractions 1220 /etc/ld.so.cache r, 1221 } 1222 `) 1223 c.Assert(os.MkdirAll(filepath.Dir(vanillaProfilePath), 0755), IsNil) 1224 c.Assert(ioutil.WriteFile(vanillaProfilePath, vanillaProfileText, 0644), IsNil) 1225 } 1226 1227 func (s *backendSuite) TestSnapConfineProfile(c *C) { 1228 // Let's say we're working with the core snap at revision 111. 1229 coreInfo := snaptest.MockInfo(c, coreYaml, &snap.SideInfo{Revision: snap.R(111)}) 1230 s.writeVanillaSnapConfineProfile(c, coreInfo) 1231 // We expect to see the same profile, just anchored at a different directory. 1232 expectedProfileDir := filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/apparmor/profiles") 1233 expectedProfileName := "snap-confine.core.111" 1234 expectedProfileGlob := "snap-confine.core.*" 1235 expectedProfileText := fmt.Sprintf(`#include <tunables/global> 1236 %s/usr/lib/snapd/snap-confine (attach_disconnected) { 1237 # We run privileged, so be fanatical about what we include and don't use 1238 # any abstractions 1239 /etc/ld.so.cache r, 1240 } 1241 `, coreInfo.MountDir()) 1242 1243 c.Assert(expectedProfileName, testutil.Contains, coreInfo.Revision.String()) 1244 1245 // Compute the profile and see if it matches. 1246 dir, glob, content, err := apparmor.SnapConfineFromSnapProfile(coreInfo) 1247 c.Assert(err, IsNil) 1248 c.Assert(dir, Equals, expectedProfileDir) 1249 c.Assert(glob, Equals, expectedProfileGlob) 1250 c.Assert(content, DeepEquals, map[string]osutil.FileState{ 1251 expectedProfileName: &osutil.MemoryFileState{ 1252 Content: []byte(expectedProfileText), 1253 Mode: 0644, 1254 }, 1255 }) 1256 } 1257 1258 func (s *backendSuite) TestSnapConfineProfileFromSnapdSnap(c *C) { 1259 restore := release.MockOnClassic(false) 1260 defer restore() 1261 dirs.SetRootDir(s.RootDir) 1262 1263 snapdInfo := snaptest.MockInfo(c, snapdYaml, &snap.SideInfo{Revision: snap.R(222)}) 1264 s.writeVanillaSnapConfineProfile(c, snapdInfo) 1265 1266 // We expect to see the same profile, just anchored at a different directory. 1267 expectedProfileDir := filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/apparmor/profiles") 1268 expectedProfileName := "snap-confine.snapd.222" 1269 expectedProfileGlob := "snap-confine.snapd.222" 1270 expectedProfileText := fmt.Sprintf(`#include <tunables/global> 1271 %s/usr/lib/snapd/snap-confine (attach_disconnected) { 1272 # We run privileged, so be fanatical about what we include and don't use 1273 # any abstractions 1274 /etc/ld.so.cache r, 1275 } 1276 `, snapdInfo.MountDir()) 1277 1278 c.Assert(expectedProfileName, testutil.Contains, snapdInfo.Revision.String()) 1279 1280 // Compute the profile and see if it matches. 1281 dir, glob, content, err := apparmor.SnapConfineFromSnapProfile(snapdInfo) 1282 c.Assert(err, IsNil) 1283 c.Assert(dir, Equals, expectedProfileDir) 1284 c.Assert(glob, Equals, expectedProfileGlob) 1285 c.Assert(content, DeepEquals, map[string]osutil.FileState{ 1286 expectedProfileName: &osutil.MemoryFileState{ 1287 Content: []byte(expectedProfileText), 1288 Mode: 0644, 1289 }, 1290 }) 1291 } 1292 1293 func (s *backendSuite) TestSnapConfineFromSnapProfileCreatesAllDirs(c *C) { 1294 c.Assert(osutil.IsDirectory(dirs.SnapAppArmorDir), Equals, false) 1295 coreInfo := snaptest.MockInfo(c, coreYaml, &snap.SideInfo{Revision: snap.R(111)}) 1296 1297 s.writeVanillaSnapConfineProfile(c, coreInfo) 1298 1299 aa := &apparmor.Backend{} 1300 err := aa.SetupSnapConfineReexec(coreInfo) 1301 c.Assert(err, IsNil) 1302 c.Assert(osutil.IsDirectory(dirs.SnapAppArmorDir), Equals, true) 1303 } 1304 1305 func (s *backendSuite) TestSetupHostSnapConfineApparmorForReexecCleans(c *C) { 1306 restorer := release.MockOnClassic(true) 1307 defer restorer() 1308 restorer = apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1309 defer restorer() 1310 1311 coreInfo := snaptest.MockInfo(c, coreYaml, &snap.SideInfo{Revision: snap.R(111)}) 1312 s.writeVanillaSnapConfineProfile(c, coreInfo) 1313 1314 canaryName := "snap-confine.core.2718" 1315 canary := filepath.Join(dirs.SnapAppArmorDir, canaryName) 1316 err := os.MkdirAll(filepath.Dir(canary), 0755) 1317 c.Assert(err, IsNil) 1318 err = ioutil.WriteFile(canary, nil, 0644) 1319 c.Assert(err, IsNil) 1320 1321 // install the new core snap on classic triggers cleanup 1322 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", coreYaml, 111) 1323 1324 c.Check(canary, testutil.FileAbsent) 1325 } 1326 1327 func (s *backendSuite) TestSetupHostSnapConfineApparmorForReexecWritesNew(c *C) { 1328 restorer := release.MockOnClassic(true) 1329 defer restorer() 1330 restorer = apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1331 defer restorer() 1332 1333 coreInfo := snaptest.MockInfo(c, coreYaml, &snap.SideInfo{Revision: snap.R(111)}) 1334 s.writeVanillaSnapConfineProfile(c, coreInfo) 1335 1336 // Install the new core snap on classic triggers a new snap-confine 1337 // for this snap-confine on core 1338 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", coreYaml, 111) 1339 1340 newAA, err := filepath.Glob(filepath.Join(dirs.SnapAppArmorDir, "*")) 1341 c.Assert(err, IsNil) 1342 c.Assert(newAA, HasLen, 1) 1343 c.Check(newAA[0], Matches, `.*/var/lib/snapd/apparmor/profiles/snap-confine.core.111`) 1344 1345 // This is the key, rewriting "/usr/lib/snapd/snap-confine 1346 c.Check(newAA[0], testutil.FileContains, "/snap/core/111/usr/lib/snapd/snap-confine (attach_disconnected) {") 1347 // No other changes other than that to the input 1348 c.Check(newAA[0], testutil.FileEquals, fmt.Sprintf(`#include <tunables/global> 1349 %s/core/111/usr/lib/snapd/snap-confine (attach_disconnected) { 1350 # We run privileged, so be fanatical about what we include and don't use 1351 # any abstractions 1352 /etc/ld.so.cache r, 1353 } 1354 `, dirs.SnapMountDir)) 1355 1356 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 1357 {[]string{newAA[0]}, fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 0}, 1358 }) 1359 1360 // snap-confine directory was created 1361 _, err = os.Stat(dirs.SnapConfineAppArmorDir) 1362 c.Check(err, IsNil) 1363 } 1364 1365 func (s *backendSuite) TestSnapConfineProfileDiscardedLateSnapd(c *C) { 1366 restorer := release.MockOnClassic(false) 1367 defer restorer() 1368 restorer = apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1369 defer restorer() 1370 // snapd snap at revision 222. 1371 snapdInfo := snaptest.MockInfo(c, snapdYaml, &snap.SideInfo{Revision: snap.R(222)}) 1372 s.writeVanillaSnapConfineProfile(c, snapdInfo) 1373 err := s.Backend.Setup(snapdInfo, interfaces.ConfinementOptions{}, s.Repo, s.perf) 1374 c.Assert(err, IsNil) 1375 // precondition 1376 c.Assert(filepath.Join(dirs.SnapAppArmorDir, "snap-confine.snapd.222"), testutil.FilePresent) 1377 // place a canary 1378 c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapAppArmorDir, "snap-confine.snapd.111"), nil, 0644), IsNil) 1379 1380 // backed implements the right interface 1381 late, ok := s.Backend.(interfaces.SecurityBackendDiscardingLate) 1382 c.Assert(ok, Equals, true) 1383 err = late.RemoveLate(snapdInfo.InstanceName(), snapdInfo.Revision, snapdInfo.Type()) 1384 c.Assert(err, IsNil) 1385 c.Check(filepath.Join(dirs.SnapAppArmorDir, "snap-confine.snapd.222"), testutil.FileAbsent) 1386 // but the canary is still present 1387 c.Assert(filepath.Join(dirs.SnapAppArmorDir, "snap-confine.snapd.111"), testutil.FilePresent) 1388 } 1389 1390 func (s *backendSuite) TestCoreOnCoreCleansApparmorCache(c *C) { 1391 coreInfo := snaptest.MockInfo(c, coreYaml, &snap.SideInfo{Revision: snap.R(111)}) 1392 s.writeVanillaSnapConfineProfile(c, coreInfo) 1393 s.testCoreOrSnapdOnCoreCleansApparmorCache(c, coreYaml) 1394 } 1395 1396 func (s *backendSuite) TestSnapdOnCoreCleansApparmorCache(c *C) { 1397 snapdInfo := snaptest.MockInfo(c, snapdYaml, &snap.SideInfo{Revision: snap.R(111)}) 1398 s.writeVanillaSnapConfineProfile(c, snapdInfo) 1399 s.testCoreOrSnapdOnCoreCleansApparmorCache(c, snapdYaml) 1400 } 1401 1402 func (s *backendSuite) testCoreOrSnapdOnCoreCleansApparmorCache(c *C, coreOrSnapdYaml string) { 1403 restorer := release.MockOnClassic(false) 1404 defer restorer() 1405 1406 err := os.MkdirAll(apparmor_sandbox.SystemCacheDir, 0755) 1407 c.Assert(err, IsNil) 1408 // the canary file in the cache will be removed 1409 canaryPath := filepath.Join(apparmor_sandbox.SystemCacheDir, "meep") 1410 err = ioutil.WriteFile(canaryPath, nil, 0644) 1411 c.Assert(err, IsNil) 1412 // and the snap-confine profiles are removed 1413 scCanaryPath := filepath.Join(apparmor_sandbox.SystemCacheDir, "usr.lib.snapd.snap-confine.real") 1414 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1415 c.Assert(err, IsNil) 1416 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "usr.lib.snapd.snap-confine") 1417 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1418 c.Assert(err, IsNil) 1419 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "snap-confine.core.6405") 1420 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1421 c.Assert(err, IsNil) 1422 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "snap-confine.snapd.6405") 1423 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1424 c.Assert(err, IsNil) 1425 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "snap.core.4938.usr.lib.snapd.snap-confine") 1426 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1427 c.Assert(err, IsNil) 1428 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "var.lib.snapd.snap.core.1234.usr.lib.snapd.snap-confine") 1429 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1430 c.Assert(err, IsNil) 1431 // but non-regular entries in the cache dir are kept 1432 dirsAreKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "dir") 1433 err = os.MkdirAll(dirsAreKept, 0755) 1434 c.Assert(err, IsNil) 1435 symlinksAreKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "symlink") 1436 err = os.Symlink("some-sylink-target", symlinksAreKept) 1437 c.Assert(err, IsNil) 1438 // and the snap profiles are kept 1439 snapCanaryKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "snap.canary.meep") 1440 err = ioutil.WriteFile(snapCanaryKept, nil, 0644) 1441 c.Assert(err, IsNil) 1442 sunCanaryKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "snap-update-ns.canary") 1443 err = ioutil.WriteFile(sunCanaryKept, nil, 0644) 1444 c.Assert(err, IsNil) 1445 // and the .features file is kept 1446 dotKept := filepath.Join(apparmor_sandbox.SystemCacheDir, ".features") 1447 err = ioutil.WriteFile(dotKept, nil, 0644) 1448 c.Assert(err, IsNil) 1449 1450 // install the new core snap on classic triggers a new snap-confine 1451 // for this snap-confine on core 1452 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", coreOrSnapdYaml, 111) 1453 1454 l, err := filepath.Glob(filepath.Join(apparmor_sandbox.SystemCacheDir, "*")) 1455 c.Assert(err, IsNil) 1456 // canary is gone, extra stuff is kept 1457 c.Check(l, DeepEquals, []string{dotKept, dirsAreKept, sunCanaryKept, snapCanaryKept, symlinksAreKept}) 1458 } 1459 1460 // snap-confine policy when NFS is not used. 1461 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyNoNFS(c *C) { 1462 // Make it appear as if NFS was not used. 1463 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1464 defer restore() 1465 1466 // Make it appear as if overlay was not used. 1467 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1468 defer restore() 1469 1470 // Intercept interaction with apparmor_parser 1471 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1472 defer cmd.Restore() 1473 1474 // Setup generated policy for snap-confine. 1475 err := (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1476 c.Assert(err, IsNil) 1477 c.Assert(cmd.Calls(), HasLen, 0) 1478 1479 // Because NFS is not used there are no local policy files but the 1480 // directory was created. 1481 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1482 c.Assert(err, IsNil) 1483 c.Assert(files, HasLen, 0) 1484 1485 // The policy was not reloaded. 1486 c.Assert(cmd.Calls(), HasLen, 0) 1487 } 1488 1489 // Ensure that both names of the snap-confine apparmor profile are supported. 1490 1491 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFS1(c *C) { 1492 s.testSetupSnapConfineGeneratedPolicyWithNFS(c, "usr.lib.snapd.snap-confine") 1493 } 1494 1495 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFS2(c *C) { 1496 s.testSetupSnapConfineGeneratedPolicyWithNFS(c, "usr.lib.snapd.snap-confine.real") 1497 } 1498 1499 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFSNoProfileFiles(c *C) { 1500 // Make it appear as if NFS workaround was needed. 1501 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1502 defer restore() 1503 // Make it appear as if overlay was not used. 1504 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1505 defer restore() 1506 1507 // Intercept interaction with apparmor_parser 1508 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1509 defer cmd.Restore() 1510 // Set up apparmor profiles directory, but no profile for snap-confine 1511 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1512 1513 // The apparmor backend should not fail if the apparmor profile of 1514 // snap-confine is not present 1515 err := (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1516 c.Assert(err, IsNil) 1517 // Since there is no profile file, no call to apparmor were made 1518 c.Assert(cmd.Calls(), HasLen, 0) 1519 } 1520 1521 // snap-confine policy when NFS is used and snapd has not re-executed. 1522 func (s *backendSuite) testSetupSnapConfineGeneratedPolicyWithNFS(c *C, profileFname string) { 1523 // Make it appear as if NFS workaround was needed. 1524 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1525 defer restore() 1526 1527 // Make it appear as if overlay was not used. 1528 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1529 defer restore() 1530 1531 // Intercept the /proc/self/exe symlink and point it to the distribution 1532 // executable (the path doesn't matter as long as it is not from the 1533 // mounted core snap). This indicates that snapd is not re-executing 1534 // and that we should reload snap-confine profile. 1535 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1536 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1537 c.Assert(err, IsNil) 1538 restore = apparmor.MockProcSelfExe(fakeExe) 1539 defer restore() 1540 1541 profilePath := filepath.Join(apparmor_sandbox.ConfDir, profileFname) 1542 1543 // Create the directory where system apparmor profiles are stored and write 1544 // the system apparmor profile of snap-confine. 1545 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1546 c.Assert(ioutil.WriteFile(profilePath, []byte(""), 0644), IsNil) 1547 1548 // Setup generated policy for snap-confine. 1549 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1550 c.Assert(err, IsNil) 1551 1552 // Because NFS is being used, we have the extra policy file. 1553 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1554 c.Assert(err, IsNil) 1555 c.Assert(files, HasLen, 1) 1556 c.Assert(files[0].Name(), Equals, "nfs-support") 1557 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1558 c.Assert(files[0].IsDir(), Equals, false) 1559 1560 // The policy allows network access. 1561 fn := filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name()) 1562 c.Assert(fn, testutil.FileContains, "network inet,") 1563 c.Assert(fn, testutil.FileContains, "network inet6,") 1564 1565 // The system apparmor profile of snap-confine was reloaded. 1566 c.Assert(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{{ 1567 []string{profilePath}, 1568 apparmor_sandbox.SystemCacheDir, 1569 apparmor_sandbox.SkipReadCache, 1570 }}) 1571 } 1572 1573 // snap-confine policy when NFS is used and snapd has re-executed. 1574 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFSAndReExec(c *C) { 1575 // Make it appear as if NFS workaround was needed. 1576 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1577 defer restore() 1578 1579 // Make it appear as if overlay was not used. 1580 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1581 defer restore() 1582 1583 // Intercept interaction with apparmor_parser 1584 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1585 defer cmd.Restore() 1586 1587 // Intercept the /proc/self/exe symlink and point it to the snapd from the 1588 // mounted core snap. This indicates that snapd has re-executed and 1589 // should not reload snap-confine policy. 1590 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1591 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) 1592 c.Assert(err, IsNil) 1593 restore = apparmor.MockProcSelfExe(fakeExe) 1594 defer restore() 1595 1596 // Setup generated policy for snap-confine. 1597 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1598 c.Assert(err, IsNil) 1599 1600 // Because NFS is being used, we have the extra policy file. 1601 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1602 c.Assert(err, IsNil) 1603 c.Assert(files, HasLen, 1) 1604 c.Assert(files[0].Name(), Equals, "nfs-support") 1605 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1606 c.Assert(files[0].IsDir(), Equals, false) 1607 1608 // The policy allows network access. 1609 fn := filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name()) 1610 c.Assert(fn, testutil.FileContains, "network inet,") 1611 c.Assert(fn, testutil.FileContains, "network inet6,") 1612 1613 // The distribution policy was not reloaded because snap-confine executes 1614 // from core snap. This is handled separately by per-profile Setup. 1615 c.Assert(cmd.Calls(), HasLen, 0) 1616 } 1617 1618 // Test behavior when isHomeUsingNFS fails. 1619 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError1(c *C) { 1620 // Make it appear as if NFS detection was broken. 1621 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, fmt.Errorf("broken") }) 1622 defer restore() 1623 1624 // Make it appear as if overlay was not used. 1625 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1626 defer restore() 1627 1628 // Intercept interaction with apparmor_parser 1629 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1630 defer cmd.Restore() 1631 1632 // Intercept the /proc/self/exe symlink and point it to the snapd from the 1633 // distribution. This indicates that snapd has not re-executed and should 1634 // reload snap-confine policy. 1635 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1636 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/usr/lib/snapd/snapd"), fakeExe) 1637 c.Assert(err, IsNil) 1638 restore = apparmor.MockProcSelfExe(fakeExe) 1639 defer restore() 1640 1641 // Setup generated policy for snap-confine. 1642 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1643 // NOTE: Errors in determining NFS are non-fatal to prevent snapd from 1644 // failing to operate. A warning message is logged but system operates as 1645 // if NFS was not active. 1646 c.Assert(err, IsNil) 1647 1648 // While other stuff failed we created the policy directory and didn't 1649 // write any files to it. 1650 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1651 c.Assert(err, IsNil) 1652 c.Assert(files, HasLen, 0) 1653 1654 // We didn't reload the policy. 1655 c.Assert(cmd.Calls(), HasLen, 0) 1656 } 1657 1658 // Test behavior when os.Readlink "/proc/self/exe" fails. 1659 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError2(c *C) { 1660 // Make it appear as if NFS workaround was needed. 1661 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1662 defer restore() 1663 1664 // Make it appear as if overlay was not used. 1665 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1666 defer restore() 1667 1668 // Intercept interaction with apparmor_parser 1669 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1670 defer cmd.Restore() 1671 1672 // Intercept the /proc/self/exe symlink and make it point to something that 1673 // doesn't exist (break it). 1674 fakeExe := filepath.Join(s.RootDir, "corrupt-proc-self-exe") 1675 restore = apparmor.MockProcSelfExe(fakeExe) 1676 defer restore() 1677 1678 // Setup generated policy for snap-confine. 1679 err := (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1680 c.Assert(err, ErrorMatches, "cannot read .*corrupt-proc-self-exe: .*") 1681 1682 // We didn't create the policy file. 1683 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1684 c.Assert(err, IsNil) 1685 c.Assert(files, HasLen, 0) 1686 1687 // We didn't reload the policy though. 1688 c.Assert(cmd.Calls(), HasLen, 0) 1689 } 1690 1691 // Test behavior when exec.Command "apparmor_parser" fails 1692 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError3(c *C) { 1693 // Make it appear as if NFS workaround was needed. 1694 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1695 defer restore() 1696 1697 // Make it appear as if overlay was not used. 1698 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1699 defer restore() 1700 1701 // Intercept interaction with apparmor_parser and make it fail. 1702 s.loadProfilesReturn = errors.New("bad luck") 1703 1704 // Intercept the /proc/self/exe symlink. 1705 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1706 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1707 c.Assert(err, IsNil) 1708 restore = apparmor.MockProcSelfExe(fakeExe) 1709 defer restore() 1710 1711 // Create the directory where system apparmor profiles are stored and Write 1712 // the system apparmor profile of snap-confine. 1713 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1714 c.Assert(ioutil.WriteFile(filepath.Join(apparmor_sandbox.ConfDir, "usr.lib.snapd.snap-confine"), []byte(""), 0644), IsNil) 1715 1716 // Setup generated policy for snap-confine. 1717 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1718 c.Assert(err, ErrorMatches, "cannot reload snap-confine apparmor profile: bad luck") 1719 1720 // While created the policy file initially we also removed it so that 1721 // no side-effects remain. 1722 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1723 c.Assert(err, IsNil) 1724 c.Assert(files, HasLen, 0) 1725 1726 // We tried to reload the policy. 1727 c.Assert(s.loadProfilesCalls, HasLen, 1) 1728 } 1729 1730 // Test behavior when MkdirAll fails 1731 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError4(c *C) { 1732 // Create a file where we would expect to find the local policy. 1733 err := os.RemoveAll(filepath.Dir(dirs.SnapConfineAppArmorDir)) 1734 c.Assert(err, IsNil) 1735 err = os.MkdirAll(filepath.Dir(dirs.SnapConfineAppArmorDir), 0755) 1736 c.Assert(err, IsNil) 1737 err = ioutil.WriteFile(dirs.SnapConfineAppArmorDir, []byte(""), 0644) 1738 c.Assert(err, IsNil) 1739 1740 // Setup generated policy for snap-confine. 1741 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1742 c.Assert(err, ErrorMatches, "*.: not a directory") 1743 } 1744 1745 // Test behavior when EnsureDirState fails 1746 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError5(c *C) { 1747 // This test cannot run as root as root bypassed DAC checks. 1748 u, err := user.Current() 1749 c.Assert(err, IsNil) 1750 if u.Uid == "0" { 1751 c.Skip("this test cannot run as root") 1752 } 1753 1754 // Make it appear as if NFS workaround was not needed. 1755 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1756 defer restore() 1757 1758 // Make it appear as if overlay was not used. 1759 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1760 defer restore() 1761 1762 // Intercept interaction with apparmor_parser and make it fail. 1763 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1764 defer cmd.Restore() 1765 1766 // Intercept the /proc/self/exe symlink. 1767 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1768 err = os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1769 c.Assert(err, IsNil) 1770 restore = apparmor.MockProcSelfExe(fakeExe) 1771 defer restore() 1772 1773 // Create the snap-confine directory and put a file. Because the file name 1774 // matches the glob generated-* snapd will attempt to remove it but because 1775 // the directory is not writable, that operation will fail. 1776 err = os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755) 1777 c.Assert(err, IsNil) 1778 f := filepath.Join(dirs.SnapConfineAppArmorDir, "generated-test") 1779 err = ioutil.WriteFile(f, []byte("spurious content"), 0644) 1780 c.Assert(err, IsNil) 1781 err = os.Chmod(dirs.SnapConfineAppArmorDir, 0555) 1782 c.Assert(err, IsNil) 1783 1784 // Make the directory writable for cleanup. 1785 defer os.Chmod(dirs.SnapConfineAppArmorDir, 0755) 1786 1787 // Setup generated policy for snap-confine. 1788 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1789 c.Assert(err, ErrorMatches, `cannot synchronize snap-confine policy: remove .*/generated-test: permission denied`) 1790 1791 // The policy directory was unchanged. 1792 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1793 c.Assert(err, IsNil) 1794 c.Assert(files, HasLen, 1) 1795 1796 // We didn't try to reload the policy. 1797 c.Assert(cmd.Calls(), HasLen, 0) 1798 } 1799 1800 // snap-confine policy when overlay is not used. 1801 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyNoOverlay(c *C) { 1802 // Make it appear as if overlay was not used. 1803 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1804 defer restore() 1805 1806 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1807 defer restore() 1808 1809 // Intercept interaction with apparmor_parser 1810 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1811 defer cmd.Restore() 1812 1813 // Setup generated policy for snap-confine. 1814 err := (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1815 c.Assert(err, IsNil) 1816 c.Assert(cmd.Calls(), HasLen, 0) 1817 1818 // Because overlay is not used there are no local policy files but the 1819 // directory was created. 1820 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1821 c.Assert(err, IsNil) 1822 c.Assert(files, HasLen, 0) 1823 1824 // The policy was not reloaded. 1825 c.Assert(cmd.Calls(), HasLen, 0) 1826 } 1827 1828 // Ensure that both names of the snap-confine apparmor profile are supported. 1829 1830 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithOverlay1(c *C) { 1831 s.testSetupSnapConfineGeneratedPolicyWithOverlay(c, "usr.lib.snapd.snap-confine") 1832 } 1833 1834 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithOverlay2(c *C) { 1835 s.testSetupSnapConfineGeneratedPolicyWithOverlay(c, "usr.lib.snapd.snap-confine.real") 1836 } 1837 1838 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithOverlayNoProfileFiles(c *C) { 1839 // Make it appear as if overlay workaround was needed. 1840 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 1841 defer restore() 1842 // No NFS workaround 1843 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1844 defer restore() 1845 1846 // Intercept interaction with apparmor_parser 1847 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1848 defer cmd.Restore() 1849 // Set up apparmor profiles directory, but no profile for snap-confine 1850 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1851 1852 // The apparmor backend should not fail if the apparmor profile of 1853 // snap-confine is not present 1854 err := (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1855 c.Assert(err, IsNil) 1856 // Since there is no profile file, no call to apparmor were made 1857 c.Assert(cmd.Calls(), HasLen, 0) 1858 } 1859 1860 // snap-confine policy when overlay is used and snapd has not re-executed. 1861 func (s *backendSuite) testSetupSnapConfineGeneratedPolicyWithOverlay(c *C, profileFname string) { 1862 // Make it appear as if overlay workaround was needed. 1863 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 1864 defer restore() 1865 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1866 defer restore() 1867 1868 // Intercept the /proc/self/exe symlink and point it to the distribution 1869 // executable (the path doesn't matter as long as it is not from the 1870 // mounted core snap). This indicates that snapd is not re-executing 1871 // and that we should reload snap-confine profile. 1872 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1873 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1874 c.Assert(err, IsNil) 1875 restore = apparmor.MockProcSelfExe(fakeExe) 1876 defer restore() 1877 1878 profilePath := filepath.Join(apparmor_sandbox.ConfDir, profileFname) 1879 1880 // Create the directory where system apparmor profiles are stored and write 1881 // the system apparmor profile of snap-confine. 1882 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1883 c.Assert(ioutil.WriteFile(profilePath, []byte(""), 0644), IsNil) 1884 1885 // Setup generated policy for snap-confine. 1886 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1887 c.Assert(err, IsNil) 1888 1889 // Because overlay is being used, we have the extra policy file. 1890 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1891 c.Assert(err, IsNil) 1892 c.Assert(files, HasLen, 1) 1893 c.Assert(files[0].Name(), Equals, "overlay-root") 1894 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1895 c.Assert(files[0].IsDir(), Equals, false) 1896 1897 // The policy allows upperdir access. 1898 data, err := ioutil.ReadFile(filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name())) 1899 c.Assert(err, IsNil) 1900 c.Assert(string(data), testutil.Contains, "\"/upper/{,**/}\" r,") 1901 1902 // The system apparmor profile of snap-confine was reloaded. 1903 c.Assert(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{{ 1904 []string{profilePath}, 1905 apparmor_sandbox.SystemCacheDir, 1906 apparmor_sandbox.SkipReadCache, 1907 }}) 1908 } 1909 1910 // snap-confine policy when overlay is used and snapd has re-executed. 1911 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithOverlayAndReExec(c *C) { 1912 // Make it appear as if overlay workaround was needed. 1913 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 1914 defer restore() 1915 1916 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1917 defer restore() 1918 1919 // Intercept interaction with apparmor_parser 1920 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1921 defer cmd.Restore() 1922 1923 // Intercept the /proc/self/exe symlink and point it to the snapd from the 1924 // mounted core snap. This indicates that snapd has re-executed and 1925 // should not reload snap-confine policy. 1926 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1927 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) 1928 c.Assert(err, IsNil) 1929 restore = apparmor.MockProcSelfExe(fakeExe) 1930 defer restore() 1931 1932 // Setup generated policy for snap-confine. 1933 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1934 c.Assert(err, IsNil) 1935 1936 // Because overlay is being used, we have the extra policy file. 1937 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1938 c.Assert(err, IsNil) 1939 c.Assert(files, HasLen, 1) 1940 c.Assert(files[0].Name(), Equals, "overlay-root") 1941 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1942 c.Assert(files[0].IsDir(), Equals, false) 1943 1944 // The policy allows upperdir access 1945 data, err := ioutil.ReadFile(filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name())) 1946 c.Assert(err, IsNil) 1947 c.Assert(string(data), testutil.Contains, "\"/upper/{,**/}\" r,") 1948 1949 // The distribution policy was not reloaded because snap-confine executes 1950 // from core snap. This is handled separately by per-profile Setup. 1951 c.Assert(cmd.Calls(), HasLen, 0) 1952 } 1953 1954 func (s *backendSuite) testSetupSnapConfineGeneratedPolicyWithBPFCapability(c *C, reexec bool) { 1955 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1956 defer restore() 1957 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1958 defer restore() 1959 // Pretend apparmor_parser supports bpf capability 1960 apparmor_sandbox.MockFeatures(nil, nil, []string{"cap-bpf"}, nil) 1961 1962 // Hijack interaction with apparmor_parser 1963 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1964 defer cmd.Restore() 1965 1966 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1967 restore = apparmor.MockProcSelfExe(fakeExe) 1968 defer restore() 1969 if reexec { 1970 // Pretend snapd is reexecuted from the core snap 1971 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) 1972 c.Assert(err, IsNil) 1973 } else { 1974 // Pretend snapd is executing from the native package 1975 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1976 c.Assert(err, IsNil) 1977 } 1978 1979 profilePath := filepath.Join(apparmor_sandbox.ConfDir, "usr.lib.snapd.snap-confine") 1980 // Create the directory where system apparmor profiles are stored and write 1981 // the system apparmor profile of snap-confine. 1982 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1983 c.Assert(ioutil.WriteFile(profilePath, []byte(""), 0644), IsNil) 1984 1985 // Setup generated policy for snap-confine. 1986 err := (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 1987 c.Assert(err, IsNil) 1988 1989 // Capability bpf is supported by the parser, so an extra policy file 1990 // for snap-confine is present 1991 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1992 c.Assert(err, IsNil) 1993 c.Assert(files, HasLen, 1) 1994 c.Assert(files[0].Name(), Equals, "cap-bpf") 1995 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1996 c.Assert(files[0].IsDir(), Equals, false) 1997 1998 c.Assert(filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name()), 1999 testutil.FileContains, "capability bpf,") 2000 2001 if reexec { 2002 // The distribution policy was not reloaded because snap-confine executes 2003 // from core snap. This is handled separately by per-profile Setup. 2004 c.Assert(s.loadProfilesCalls, HasLen, 0) 2005 } else { 2006 c.Assert(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{{ 2007 []string{profilePath}, 2008 apparmor_sandbox.SystemCacheDir, 2009 apparmor_sandbox.SkipReadCache, 2010 }}) 2011 } 2012 } 2013 2014 // snap-confine policy when apparmor_parser supports BPF capability and snapd reexec 2015 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithBPFCapabilityReexec(c *C) { 2016 const reexecd = true 2017 s.testSetupSnapConfineGeneratedPolicyWithBPFCapability(c, reexecd) 2018 } 2019 2020 // snap-confine policy when apparmor_parser supports BPF capability but no reexec 2021 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithBPFCapabilityNoReexec(c *C) { 2022 const reexecd = false 2023 s.testSetupSnapConfineGeneratedPolicyWithBPFCapability(c, reexecd) 2024 } 2025 2026 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithBPFProbeError(c *C) { 2027 log, restore := logger.MockLogger() 2028 defer restore() 2029 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 2030 defer restore() 2031 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 2032 defer restore() 2033 // Probing for apparmor_parser features failed 2034 apparmor_sandbox.MockFeatures(nil, nil, nil, fmt.Errorf("mock probe error")) 2035 2036 // Hijack interaction with apparmor_parser 2037 cmd := testutil.MockCommand(c, "apparmor_parser", "") 2038 defer cmd.Restore() 2039 2040 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 2041 restore = apparmor.MockProcSelfExe(fakeExe) 2042 defer restore() 2043 // Pretend snapd is executing from the native package 2044 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 2045 c.Assert(err, IsNil) 2046 2047 profilePath := filepath.Join(apparmor_sandbox.ConfDir, "usr.lib.snapd.snap-confine") 2048 // Create the directory where system apparmor profiles are stored and write 2049 // the system apparmor profile of snap-confine. 2050 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 2051 c.Assert(ioutil.WriteFile(profilePath, []byte(""), 0644), IsNil) 2052 2053 // Setup generated policy for snap-confine. 2054 err = (&apparmor.Backend{}).Initialize(ifacetest.DefaultInitializeOpts) 2055 c.Assert(err, IsNil) 2056 2057 // Probing apparmor_parser capabilities failed, so nothing gets written 2058 // to the snap-confine policy directory 2059 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 2060 c.Assert(err, IsNil) 2061 c.Assert(files, HasLen, 0) 2062 2063 // No calls to apparmor_parser 2064 c.Assert(cmd.Calls(), HasLen, 0) 2065 2066 // But an error was logged 2067 c.Assert(log.String(), testutil.Contains, "cannot determine apparmor_parser features: mock probe error") 2068 } 2069 2070 type nfsAndOverlaySnippetsScenario struct { 2071 opts interfaces.ConfinementOptions 2072 overlaySnippet string 2073 nfsSnippet string 2074 } 2075 2076 var nfsAndOverlaySnippetsScenarios = []nfsAndOverlaySnippetsScenario{{ 2077 // By default apparmor is enforcing mode. 2078 opts: interfaces.ConfinementOptions{}, 2079 overlaySnippet: `"/upper/{,**/}" r,`, 2080 nfsSnippet: "network inet,\n network inet6,", 2081 }, { 2082 // DevMode switches apparmor to non-enforcing (complain) mode. 2083 opts: interfaces.ConfinementOptions{DevMode: true}, 2084 overlaySnippet: `"/upper/{,**/}" r,`, 2085 nfsSnippet: "network inet,\n network inet6,", 2086 }, { 2087 // JailMode switches apparmor to enforcing mode even in the presence of DevMode. 2088 opts: interfaces.ConfinementOptions{DevMode: true, JailMode: true}, 2089 overlaySnippet: `"/upper/{,**/}" r,`, 2090 nfsSnippet: "network inet,\n network inet6,", 2091 }, { 2092 // Classic confinement (without jailmode) uses apparmor in complain mode by default and ignores all snippets. 2093 opts: interfaces.ConfinementOptions{Classic: true}, 2094 overlaySnippet: "", 2095 nfsSnippet: "", 2096 }, { 2097 // Classic confinement in JailMode uses enforcing apparmor. 2098 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2099 // FIXME: logic in backend.addContent is wrong for this case 2100 //overlaySnippet: `"/upper/{,**/}" r,`, 2101 //nfsSnippet: "network inet,\n network inet6,", 2102 overlaySnippet: "", 2103 nfsSnippet: "", 2104 }} 2105 2106 func (s *backendSuite) TestNFSAndOverlaySnippets(c *C) { 2107 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2108 defer restore() 2109 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 2110 defer restore() 2111 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 2112 defer restore() 2113 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 2114 return nil 2115 } 2116 2117 for _, scenario := range nfsAndOverlaySnippetsScenarios { 2118 snapInfo := s.InstallSnap(c, scenario.opts, "", ifacetest.SambaYamlV1, 1) 2119 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2120 c.Check(profile, testutil.FileContains, scenario.overlaySnippet) 2121 c.Check(profile, testutil.FileContains, scenario.nfsSnippet) 2122 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 2123 c.Check(updateNSProfile, testutil.FileContains, scenario.overlaySnippet) 2124 s.RemoveSnap(c, snapInfo) 2125 } 2126 } 2127 2128 var casperOverlaySnippetsScenarios = []nfsAndOverlaySnippetsScenario{{ 2129 // By default apparmor is enforcing mode. 2130 opts: interfaces.ConfinementOptions{}, 2131 overlaySnippet: `"/upper/{,**/}" r,`, 2132 }, { 2133 // DevMode switches apparmor to non-enforcing (complain) mode. 2134 opts: interfaces.ConfinementOptions{DevMode: true}, 2135 overlaySnippet: `"/upper/{,**/}" r,`, 2136 }, { 2137 // JailMode switches apparmor to enforcing mode even in the presence of DevMode. 2138 opts: interfaces.ConfinementOptions{DevMode: true, JailMode: true}, 2139 overlaySnippet: `"/upper/{,**/}" r,`, 2140 }, { 2141 // Classic confinement (without jailmode) uses apparmor in complain mode by default and ignores all snippets. 2142 opts: interfaces.ConfinementOptions{Classic: true}, 2143 overlaySnippet: "", 2144 }, { 2145 // Classic confinement in JailMode uses enforcing apparmor. 2146 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2147 // FIXME: logic in backend.addContent is wrong for this case 2148 //overlaySnippet: `"/upper/{,**/}" r,`, 2149 overlaySnippet: "", 2150 }} 2151 2152 func (s *backendSuite) TestCasperOverlaySnippets(c *C) { 2153 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2154 defer restore() 2155 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 2156 defer restore() 2157 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 2158 defer restore() 2159 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 2160 return nil 2161 } 2162 2163 for _, scenario := range casperOverlaySnippetsScenarios { 2164 snapInfo := s.InstallSnap(c, scenario.opts, "", ifacetest.SambaYamlV1, 1) 2165 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2166 c.Check(profile, testutil.FileContains, scenario.overlaySnippet) 2167 s.RemoveSnap(c, snapInfo) 2168 } 2169 } 2170 2171 func (s *backendSuite) TestProfileGlobs(c *C) { 2172 globs := apparmor.ProfileGlobs("foo") 2173 c.Assert(globs, DeepEquals, []string{"snap.foo.*", "snap-update-ns.foo"}) 2174 } 2175 2176 func (s *backendSuite) TestNsProfile(c *C) { 2177 c.Assert(apparmor.NsProfile("foo"), Equals, "snap-update-ns.foo") 2178 } 2179 2180 func (s *backendSuite) TestSandboxFeatures(c *C) { 2181 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2182 defer restore() 2183 restore = apparmor.MockKernelFeatures(func() ([]string, error) { return []string{"foo", "bar"}, nil }) 2184 defer restore() 2185 restore = apparmor.MockParserFeatures(func() ([]string, error) { return []string{"baz", "norf"}, nil }) 2186 defer restore() 2187 2188 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "parser:baz", "parser:norf", "support-level:full", "policy:default"}) 2189 } 2190 2191 func (s *backendSuite) TestSandboxFeaturesPartial(c *C) { 2192 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Partial) 2193 defer restore() 2194 restore = release.MockReleaseInfo(&release.OS{ID: "opensuse-tumbleweed"}) 2195 defer restore() 2196 restore = osutil.MockKernelVersion("4.16.10-1-default") 2197 defer restore() 2198 restore = apparmor.MockKernelFeatures(func() ([]string, error) { return []string{"foo", "bar"}, nil }) 2199 defer restore() 2200 restore = apparmor.MockParserFeatures(func() ([]string, error) { return []string{"baz", "norf"}, nil }) 2201 defer restore() 2202 2203 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "parser:baz", "parser:norf", "support-level:partial", "policy:default"}) 2204 2205 restore = osutil.MockKernelVersion("4.14.1-default") 2206 defer restore() 2207 2208 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "parser:baz", "parser:norf", "support-level:partial", "policy:default"}) 2209 } 2210 2211 func (s *backendSuite) TestParallelInstanceSetupSnapUpdateNS(c *C) { 2212 dirs.SetRootDir(s.RootDir) 2213 2214 const trivialSnapYaml = `name: some-snap 2215 version: 1.0 2216 apps: 2217 app: 2218 command: app-command 2219 ` 2220 snapInfo := snaptest.MockInfo(c, trivialSnapYaml, &snap.SideInfo{Revision: snap.R(222)}) 2221 snapInfo.InstanceKey = "instance" 2222 2223 s.InstallSnap(c, interfaces.ConfinementOptions{}, "some-snap_instance", trivialSnapYaml, 1) 2224 profileUpdateNS := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.some-snap_instance") 2225 c.Check(profileUpdateNS, testutil.FileContains, `profile snap-update-ns.some-snap_instance (`) 2226 c.Check(profileUpdateNS, testutil.FileContains, ` 2227 # Allow parallel instance snap mount namespace adjustments 2228 mount options=(rw rbind) /snap/some-snap_instance/ -> /snap/some-snap/, 2229 mount options=(rw rbind) /var/snap/some-snap_instance/ -> /var/snap/some-snap/, 2230 `) 2231 } 2232 2233 func (s *backendSuite) TestPtraceTraceRule(c *C) { 2234 restoreTemplate := apparmor.MockTemplate("template\n###SNIPPETS###\n") 2235 defer restoreTemplate() 2236 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2237 defer restore() 2238 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 2239 defer restore() 2240 2241 needle := `deny ptrace (trace),` 2242 for _, tc := range []struct { 2243 opts interfaces.ConfinementOptions 2244 uses bool 2245 suppress bool 2246 expected bool 2247 }{ 2248 // strict, only suppress if suppress == true and uses == false 2249 { 2250 opts: interfaces.ConfinementOptions{}, 2251 uses: false, 2252 suppress: false, 2253 expected: false, 2254 }, 2255 { 2256 opts: interfaces.ConfinementOptions{}, 2257 uses: false, 2258 suppress: true, 2259 expected: true, 2260 }, 2261 { 2262 opts: interfaces.ConfinementOptions{}, 2263 uses: true, 2264 suppress: false, 2265 expected: false, 2266 }, 2267 { 2268 opts: interfaces.ConfinementOptions{}, 2269 uses: true, 2270 suppress: true, 2271 expected: false, 2272 }, 2273 // devmode, only suppress if suppress == true and uses == false 2274 { 2275 opts: interfaces.ConfinementOptions{DevMode: true}, 2276 uses: false, 2277 suppress: false, 2278 expected: false, 2279 }, 2280 { 2281 opts: interfaces.ConfinementOptions{DevMode: true}, 2282 uses: false, 2283 suppress: true, 2284 expected: true, 2285 }, 2286 { 2287 opts: interfaces.ConfinementOptions{DevMode: true}, 2288 uses: true, 2289 suppress: false, 2290 expected: false, 2291 }, 2292 { 2293 opts: interfaces.ConfinementOptions{DevMode: true}, 2294 uses: true, 2295 suppress: true, 2296 expected: false, 2297 }, 2298 // classic, never suppress 2299 { 2300 opts: interfaces.ConfinementOptions{Classic: true}, 2301 uses: false, 2302 suppress: false, 2303 expected: false, 2304 }, 2305 { 2306 opts: interfaces.ConfinementOptions{Classic: true}, 2307 uses: false, 2308 suppress: true, 2309 expected: false, 2310 }, 2311 { 2312 opts: interfaces.ConfinementOptions{Classic: true}, 2313 uses: true, 2314 suppress: false, 2315 expected: false, 2316 }, 2317 { 2318 opts: interfaces.ConfinementOptions{Classic: true}, 2319 uses: true, 2320 suppress: true, 2321 expected: false, 2322 }, 2323 // classic with jail, only suppress if suppress == true and uses == false 2324 { 2325 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2326 uses: false, 2327 suppress: false, 2328 expected: false, 2329 }, 2330 { 2331 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2332 uses: false, 2333 suppress: true, 2334 expected: true, 2335 }, 2336 { 2337 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2338 uses: true, 2339 suppress: false, 2340 expected: false, 2341 }, 2342 { 2343 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2344 uses: true, 2345 suppress: true, 2346 expected: false, 2347 }, 2348 } { 2349 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 2350 if tc.uses { 2351 spec.SetUsesPtraceTrace() 2352 } 2353 if tc.suppress { 2354 spec.SetSuppressPtraceTrace() 2355 } 2356 return nil 2357 } 2358 2359 snapInfo := s.InstallSnap(c, tc.opts, "", ifacetest.SambaYamlV1, 1) 2360 2361 err := s.Backend.Setup(snapInfo, tc.opts, s.Repo, s.meas) 2362 c.Assert(err, IsNil) 2363 2364 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2365 data, err := ioutil.ReadFile(profile) 2366 c.Assert(err, IsNil) 2367 2368 if tc.expected { 2369 c.Assert(string(data), testutil.Contains, needle) 2370 } else { 2371 c.Assert(string(data), Not(testutil.Contains), needle) 2372 } 2373 s.RemoveSnap(c, snapInfo) 2374 } 2375 } 2376 2377 func (s *backendSuite) TestHomeIxRule(c *C) { 2378 restoreTemplate := apparmor.MockTemplate("template\n###SNIPPETS###\nneedle rwkl###HOME_IX###,\n") 2379 defer restoreTemplate() 2380 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2381 defer restore() 2382 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 2383 defer restore() 2384 2385 for _, tc := range []struct { 2386 opts interfaces.ConfinementOptions 2387 suppress bool 2388 expected string 2389 }{ 2390 { 2391 opts: interfaces.ConfinementOptions{}, 2392 suppress: true, 2393 expected: "needle rwkl,", 2394 }, 2395 { 2396 opts: interfaces.ConfinementOptions{}, 2397 suppress: false, 2398 expected: "needle rwklix,", 2399 }, 2400 } { 2401 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 2402 if tc.suppress { 2403 spec.SetSuppressHomeIx() 2404 } 2405 spec.AddSnippet("needle rwkl###HOME_IX###,") 2406 return nil 2407 } 2408 2409 snapInfo := s.InstallSnap(c, tc.opts, "", ifacetest.SambaYamlV1, 1) 2410 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2411 data, err := ioutil.ReadFile(profile) 2412 c.Assert(err, IsNil) 2413 2414 c.Assert(string(data), testutil.Contains, tc.expected) 2415 s.RemoveSnap(c, snapInfo) 2416 } 2417 } 2418 2419 func (s *backendSuite) TestSystemUsernamesPolicy(c *C) { 2420 restoreTemplate := apparmor.MockTemplate("template\n###SNIPPETS###\n") 2421 defer restoreTemplate() 2422 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2423 defer restore() 2424 2425 snapYaml := ` 2426 name: app 2427 version: 0.1 2428 system-usernames: 2429 testid: shared 2430 apps: 2431 cmd: 2432 ` 2433 2434 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapYaml, 1) 2435 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.app.cmd") 2436 data, err := ioutil.ReadFile(profile) 2437 c.Assert(err, IsNil) 2438 c.Assert(string(data), testutil.Contains, "capability setuid,") 2439 c.Assert(string(data), testutil.Contains, "capability setgid,") 2440 c.Assert(string(data), testutil.Contains, "capability chown,") 2441 s.RemoveSnap(c, snapInfo) 2442 } 2443 2444 func (s *backendSuite) TestNoSystemUsernamesPolicy(c *C) { 2445 restoreTemplate := apparmor.MockTemplate("template\n###SNIPPETS###\n") 2446 defer restoreTemplate() 2447 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2448 defer restore() 2449 2450 snapYaml := ` 2451 name: app 2452 version: 0.1 2453 apps: 2454 cmd: 2455 ` 2456 2457 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapYaml, 1) 2458 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.app.cmd") 2459 data, err := ioutil.ReadFile(profile) 2460 c.Assert(err, IsNil) 2461 c.Assert(string(data), Not(testutil.Contains), "capability setuid,") 2462 c.Assert(string(data), Not(testutil.Contains), "capability setgid,") 2463 c.Assert(string(data), Not(testutil.Contains), "capability chown,") 2464 s.RemoveSnap(c, snapInfo) 2465 } 2466 2467 func (s *backendSuite) TestSetupManySmoke(c *C) { 2468 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 2469 c.Assert(ok, Equals, true) 2470 c.Assert(setupManyInterface, NotNil) 2471 } 2472 2473 func (s *backendSuite) TestInstallingSnapInPreseedMode(c *C) { 2474 // Intercept the /proc/self/exe symlink and point it to the snapd from the 2475 // mounted core snap. This indicates that snapd has re-executed and 2476 // should not reload snap-confine policy. 2477 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 2478 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) 2479 c.Assert(err, IsNil) 2480 restore := apparmor.MockProcSelfExe(fakeExe) 2481 defer restore() 2482 2483 aa, ok := s.Backend.(*apparmor.Backend) 2484 c.Assert(ok, Equals, true) 2485 2486 opts := interfaces.SecurityBackendOptions{Preseed: true} 2487 c.Assert(aa.Initialize(&opts), IsNil) 2488 2489 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 1) 2490 2491 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 2492 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2493 // file called "snap.sambda.smbd" was created 2494 _, err = os.Stat(profile) 2495 c.Check(err, IsNil) 2496 // apparmor_parser was used to load that file 2497 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 2498 { 2499 []string{updateNSProfile, profile}, 2500 fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 2501 apparmor_sandbox.SkipReadCache | apparmor_sandbox.SkipKernelLoad, 2502 }, 2503 }) 2504 } 2505 2506 func (s *backendSuite) TestSetupManyInPreseedMode(c *C) { 2507 aa, ok := s.Backend.(*apparmor.Backend) 2508 c.Assert(ok, Equals, true) 2509 2510 opts := interfaces.SecurityBackendOptions{ 2511 Preseed: true, 2512 CoreSnapInfo: ifacetest.DefaultInitializeOpts.CoreSnapInfo, 2513 } 2514 c.Assert(aa.Initialize(&opts), IsNil) 2515 2516 for _, opts := range testedConfinementOpts { 2517 snapInfo1 := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 2518 snapInfo2 := s.InstallSnap(c, opts, "", ifacetest.SomeSnapYamlV1, 1) 2519 s.loadProfilesCalls = nil 2520 2521 snap1nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 2522 snap1AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2523 snap2nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.some-snap") 2524 snap2AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.some-snap.someapp") 2525 2526 // simulate outdated profiles by changing their data on the disk 2527 c.Assert(ioutil.WriteFile(snap1AAprofile, []byte("# an outdated profile"), 0644), IsNil) 2528 c.Assert(ioutil.WriteFile(snap2AAprofile, []byte("# an outdated profile"), 0644), IsNil) 2529 2530 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 2531 c.Assert(ok, Equals, true) 2532 err := setupManyInterface.SetupMany([]*snap.Info{snapInfo1, snapInfo2}, func(snapName string) interfaces.ConfinementOptions { return opts }, s.Repo, s.meas) 2533 c.Assert(err, IsNil) 2534 2535 // expect two batch executions - one for changed profiles, second for unchanged profiles. 2536 c.Check(s.loadProfilesCalls, DeepEquals, []loadProfilesParams{ 2537 { 2538 []string{snap1AAprofile, snap2AAprofile}, 2539 fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 2540 apparmor_sandbox.SkipReadCache | apparmor_sandbox.ConserveCPU | apparmor_sandbox.SkipKernelLoad, 2541 }, 2542 { 2543 []string{snap1nsProfile, snap2nsProfile}, 2544 fmt.Sprintf("%s/var/cache/apparmor", s.RootDir), 2545 apparmor_sandbox.ConserveCPU | apparmor_sandbox.SkipKernelLoad, 2546 }, 2547 }) 2548 s.RemoveSnap(c, snapInfo1) 2549 s.RemoveSnap(c, snapInfo2) 2550 } 2551 } 2552 2553 func (s *backendSuite) TestCoreSnippetOnCoreSystem(c *C) { 2554 dirs.SetRootDir(s.RootDir) 2555 2556 // NOTE: replace the real template with a shorter variant 2557 restoreTemplate := apparmor.MockTemplate("\n" + 2558 "###SNIPPETS###\n" + 2559 "\n") 2560 defer restoreTemplate() 2561 2562 expectedContents := ` 2563 # Allow each snaps to access each their own folder on the 2564 # ubuntu-save partition, with write permissions. 2565 /var/lib/snapd/save/snap/@{SNAP_INSTANCE_NAME}/ rw, 2566 /var/lib/snapd/save/snap/@{SNAP_INSTANCE_NAME}/** mrwklix, 2567 ` 2568 2569 tests := []struct { 2570 onClassic bool 2571 classicConfinement bool 2572 jailMode bool 2573 shouldContainSnippet bool 2574 }{ 2575 // XXX: Is it possible for someone to make this nicer? 2576 {onClassic: false, classicConfinement: false, jailMode: false, shouldContainSnippet: true}, 2577 {onClassic: false, classicConfinement: false, jailMode: true, shouldContainSnippet: true}, 2578 2579 // Rest of the cases the core-specific snippet shouldn't turn up. 2580 {onClassic: false, classicConfinement: true, jailMode: false, shouldContainSnippet: false}, 2581 {onClassic: false, classicConfinement: true, jailMode: true, shouldContainSnippet: false}, 2582 {onClassic: true, classicConfinement: false, jailMode: false, shouldContainSnippet: false}, 2583 {onClassic: true, classicConfinement: true, jailMode: false, shouldContainSnippet: false}, 2584 {onClassic: true, classicConfinement: false, jailMode: true, shouldContainSnippet: false}, 2585 {onClassic: true, classicConfinement: true, jailMode: true, shouldContainSnippet: false}, 2586 } 2587 2588 for _, t := range tests { 2589 restore := release.MockOnClassic(t.onClassic) 2590 defer restore() 2591 2592 opts := interfaces.ConfinementOptions{ 2593 Classic: t.classicConfinement, 2594 JailMode: t.jailMode, 2595 } 2596 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 2597 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2598 if t.shouldContainSnippet { 2599 c.Check(profile, testutil.FileContains, expectedContents, Commentf("Classic %t, JailMode %t", t.onClassic, t.jailMode)) 2600 } else { 2601 c.Check(profile, Not(testutil.FileContains), expectedContents, Commentf("Classic %t, JailMode %t", t.onClassic, t.jailMode)) 2602 } 2603 stat, err := os.Stat(profile) 2604 c.Assert(err, IsNil) 2605 c.Check(stat.Mode(), Equals, os.FileMode(0644)) 2606 s.RemoveSnap(c, snapInfo) 2607 } 2608 }