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