github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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.*" 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(osutil.FileExists(canary), Equals, false) 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) TestCoreOnCoreCleansApparmorCache(c *C) { 1271 coreInfo := snaptest.MockInfo(c, coreYaml, &snap.SideInfo{Revision: snap.R(111)}) 1272 s.writeVanillaSnapConfineProfile(c, coreInfo) 1273 s.testCoreOrSnapdOnCoreCleansApparmorCache(c, coreYaml) 1274 } 1275 1276 func (s *backendSuite) TestSnapdOnCoreCleansApparmorCache(c *C) { 1277 snapdInfo := snaptest.MockInfo(c, snapdYaml, &snap.SideInfo{Revision: snap.R(111)}) 1278 s.writeVanillaSnapConfineProfile(c, snapdInfo) 1279 s.testCoreOrSnapdOnCoreCleansApparmorCache(c, snapdYaml) 1280 } 1281 1282 func (s *backendSuite) testCoreOrSnapdOnCoreCleansApparmorCache(c *C, coreOrSnapdYaml string) { 1283 restorer := release.MockOnClassic(false) 1284 defer restorer() 1285 1286 err := os.MkdirAll(apparmor_sandbox.SystemCacheDir, 0755) 1287 c.Assert(err, IsNil) 1288 // the canary file in the cache will be removed 1289 canaryPath := filepath.Join(apparmor_sandbox.SystemCacheDir, "meep") 1290 err = ioutil.WriteFile(canaryPath, nil, 0644) 1291 c.Assert(err, IsNil) 1292 // and the snap-confine profiles are removed 1293 scCanaryPath := filepath.Join(apparmor_sandbox.SystemCacheDir, "usr.lib.snapd.snap-confine.real") 1294 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1295 c.Assert(err, IsNil) 1296 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "usr.lib.snapd.snap-confine") 1297 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1298 c.Assert(err, IsNil) 1299 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "snap-confine.core.6405") 1300 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1301 c.Assert(err, IsNil) 1302 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "snap-confine.snapd.6405") 1303 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1304 c.Assert(err, IsNil) 1305 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "snap.core.4938.usr.lib.snapd.snap-confine") 1306 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1307 c.Assert(err, IsNil) 1308 scCanaryPath = filepath.Join(apparmor_sandbox.SystemCacheDir, "var.lib.snapd.snap.core.1234.usr.lib.snapd.snap-confine") 1309 err = ioutil.WriteFile(scCanaryPath, nil, 0644) 1310 c.Assert(err, IsNil) 1311 // but non-regular entries in the cache dir are kept 1312 dirsAreKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "dir") 1313 err = os.MkdirAll(dirsAreKept, 0755) 1314 c.Assert(err, IsNil) 1315 symlinksAreKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "symlink") 1316 err = os.Symlink("some-sylink-target", symlinksAreKept) 1317 c.Assert(err, IsNil) 1318 // and the snap profiles are kept 1319 snapCanaryKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "snap.canary.meep") 1320 err = ioutil.WriteFile(snapCanaryKept, nil, 0644) 1321 c.Assert(err, IsNil) 1322 sunCanaryKept := filepath.Join(apparmor_sandbox.SystemCacheDir, "snap-update-ns.canary") 1323 err = ioutil.WriteFile(sunCanaryKept, nil, 0644) 1324 c.Assert(err, IsNil) 1325 // and the .features file is kept 1326 dotKept := filepath.Join(apparmor_sandbox.SystemCacheDir, ".features") 1327 err = ioutil.WriteFile(dotKept, nil, 0644) 1328 c.Assert(err, IsNil) 1329 1330 // install the new core snap on classic triggers a new snap-confine 1331 // for this snap-confine on core 1332 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", coreOrSnapdYaml, 111) 1333 1334 l, err := filepath.Glob(filepath.Join(apparmor_sandbox.SystemCacheDir, "*")) 1335 c.Assert(err, IsNil) 1336 // canary is gone, extra stuff is kept 1337 c.Check(l, DeepEquals, []string{dotKept, dirsAreKept, sunCanaryKept, snapCanaryKept, symlinksAreKept}) 1338 } 1339 1340 // snap-confine policy when NFS is not used. 1341 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyNoNFS(c *C) { 1342 // Make it appear as if NFS was not used. 1343 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1344 defer restore() 1345 1346 // Make it appear as if overlay was not used. 1347 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1348 defer restore() 1349 1350 // Intercept interaction with apparmor_parser 1351 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1352 defer cmd.Restore() 1353 1354 // Setup generated policy for snap-confine. 1355 err := (&apparmor.Backend{}).Initialize(nil) 1356 c.Assert(err, IsNil) 1357 c.Assert(cmd.Calls(), HasLen, 0) 1358 1359 // Because NFS is not used there are no local policy files but the 1360 // directory was created. 1361 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1362 c.Assert(err, IsNil) 1363 c.Assert(files, HasLen, 0) 1364 1365 // The policy was not reloaded. 1366 c.Assert(cmd.Calls(), HasLen, 0) 1367 } 1368 1369 // Ensure that both names of the snap-confine apparmor profile are supported. 1370 1371 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFS1(c *C) { 1372 s.testSetupSnapConfineGeneratedPolicyWithNFS(c, "usr.lib.snapd.snap-confine") 1373 } 1374 1375 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFS2(c *C) { 1376 s.testSetupSnapConfineGeneratedPolicyWithNFS(c, "usr.lib.snapd.snap-confine.real") 1377 } 1378 1379 // snap-confine policy when NFS is used and snapd has not re-executed. 1380 func (s *backendSuite) testSetupSnapConfineGeneratedPolicyWithNFS(c *C, profileFname string) { 1381 // Make it appear as if NFS workaround was needed. 1382 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1383 defer restore() 1384 1385 // Make it appear as if overlay was not used. 1386 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1387 defer restore() 1388 1389 // Intercept interaction with apparmor_parser 1390 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1391 defer cmd.Restore() 1392 1393 // Intercept the /proc/self/exe symlink and point it to the distribution 1394 // executable (the path doesn't matter as long as it is not from the 1395 // mounted core snap). This indicates that snapd is not re-executing 1396 // and that we should reload snap-confine profile. 1397 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1398 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1399 c.Assert(err, IsNil) 1400 restore = apparmor.MockProcSelfExe(fakeExe) 1401 defer restore() 1402 1403 profilePath := filepath.Join(apparmor_sandbox.ConfDir, profileFname) 1404 1405 // Create the directory where system apparmor profiles are stored and write 1406 // the system apparmor profile of snap-confine. 1407 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1408 c.Assert(ioutil.WriteFile(profilePath, []byte(""), 0644), IsNil) 1409 1410 // Setup generated policy for snap-confine. 1411 err = (&apparmor.Backend{}).Initialize(nil) 1412 c.Assert(err, IsNil) 1413 1414 // Because NFS is being used, we have the extra policy file. 1415 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1416 c.Assert(err, IsNil) 1417 c.Assert(files, HasLen, 1) 1418 c.Assert(files[0].Name(), Equals, "nfs-support") 1419 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1420 c.Assert(files[0].IsDir(), Equals, false) 1421 1422 // The policy allows network access. 1423 fn := filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name()) 1424 c.Assert(fn, testutil.FileContains, "network inet,") 1425 c.Assert(fn, testutil.FileContains, "network inet6,") 1426 1427 // The system apparmor profile of snap-confine was reloaded. 1428 c.Assert(cmd.Calls(), HasLen, 1) 1429 c.Assert(cmd.Calls(), DeepEquals, [][]string{{ 1430 "apparmor_parser", "--replace", 1431 "--write-cache", 1432 "-O", "no-expr-simplify", 1433 "--cache-loc=" + apparmor_sandbox.SystemCacheDir, 1434 "--skip-read-cache", 1435 "--quiet", 1436 profilePath, 1437 }}) 1438 } 1439 1440 // snap-confine policy when NFS is used and snapd has re-executed. 1441 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFSAndReExec(c *C) { 1442 // Make it appear as if NFS workaround was needed. 1443 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1444 defer restore() 1445 1446 // Make it appear as if overlay was not used. 1447 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1448 defer restore() 1449 1450 // Intercept interaction with apparmor_parser 1451 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1452 defer cmd.Restore() 1453 1454 // Intercept the /proc/self/exe symlink and point it to the snapd from the 1455 // mounted core snap. This indicates that snapd has re-executed and 1456 // should not reload snap-confine policy. 1457 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1458 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) 1459 c.Assert(err, IsNil) 1460 restore = apparmor.MockProcSelfExe(fakeExe) 1461 defer restore() 1462 1463 // Setup generated policy for snap-confine. 1464 err = (&apparmor.Backend{}).Initialize(nil) 1465 c.Assert(err, IsNil) 1466 1467 // Because NFS is being used, we have the extra policy file. 1468 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1469 c.Assert(err, IsNil) 1470 c.Assert(files, HasLen, 1) 1471 c.Assert(files[0].Name(), Equals, "nfs-support") 1472 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1473 c.Assert(files[0].IsDir(), Equals, false) 1474 1475 // The policy allows network access. 1476 fn := filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name()) 1477 c.Assert(fn, testutil.FileContains, "network inet,") 1478 c.Assert(fn, testutil.FileContains, "network inet6,") 1479 1480 // The distribution policy was not reloaded because snap-confine executes 1481 // from core snap. This is handled separately by per-profile Setup. 1482 c.Assert(cmd.Calls(), HasLen, 0) 1483 } 1484 1485 // Test behavior when isHomeUsingNFS fails. 1486 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError1(c *C) { 1487 // Make it appear as if NFS detection was broken. 1488 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, fmt.Errorf("broken") }) 1489 defer restore() 1490 1491 // Make it appear as if overlay was not used. 1492 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1493 defer restore() 1494 1495 // Intercept interaction with apparmor_parser 1496 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1497 defer cmd.Restore() 1498 1499 // Intercept the /proc/self/exe symlink and point it to the snapd from the 1500 // distribution. This indicates that snapd has not re-executed and should 1501 // reload snap-confine policy. 1502 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1503 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/usr/lib/snapd/snapd"), fakeExe) 1504 c.Assert(err, IsNil) 1505 restore = apparmor.MockProcSelfExe(fakeExe) 1506 defer restore() 1507 1508 // Setup generated policy for snap-confine. 1509 err = (&apparmor.Backend{}).Initialize(nil) 1510 // NOTE: Errors in determining NFS are non-fatal to prevent snapd from 1511 // failing to operate. A warning message is logged but system operates as 1512 // if NFS was not active. 1513 c.Assert(err, IsNil) 1514 1515 // While other stuff failed we created the policy directory and didn't 1516 // write any files to it. 1517 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1518 c.Assert(err, IsNil) 1519 c.Assert(files, HasLen, 0) 1520 1521 // We didn't reload the policy. 1522 c.Assert(cmd.Calls(), HasLen, 0) 1523 } 1524 1525 // Test behavior when os.Readlink "/proc/self/exe" fails. 1526 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError2(c *C) { 1527 // Make it appear as if NFS workaround was needed. 1528 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1529 defer restore() 1530 1531 // Make it appear as if overlay was not used. 1532 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1533 defer restore() 1534 1535 // Intercept interaction with apparmor_parser 1536 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1537 defer cmd.Restore() 1538 1539 // Intercept the /proc/self/exe symlink and make it point to something that 1540 // doesn't exist (break it). 1541 fakeExe := filepath.Join(s.RootDir, "corrupt-proc-self-exe") 1542 restore = apparmor.MockProcSelfExe(fakeExe) 1543 defer restore() 1544 1545 // Setup generated policy for snap-confine. 1546 err := (&apparmor.Backend{}).Initialize(nil) 1547 c.Assert(err, ErrorMatches, "cannot read .*corrupt-proc-self-exe: .*") 1548 1549 // We didn't create the policy file. 1550 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1551 c.Assert(err, IsNil) 1552 c.Assert(files, HasLen, 0) 1553 1554 // We didn't reload the policy though. 1555 c.Assert(cmd.Calls(), HasLen, 0) 1556 } 1557 1558 // Test behavior when exec.Command "apparmor_parser" fails 1559 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError3(c *C) { 1560 // Make it appear as if NFS workaround was needed. 1561 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1562 defer restore() 1563 1564 // Make it appear as if overlay was not used. 1565 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1566 defer restore() 1567 1568 // Intercept interaction with apparmor_parser and make it fail. 1569 cmd := testutil.MockCommand(c, "apparmor_parser", "echo testing; exit 1") 1570 defer cmd.Restore() 1571 1572 // Intercept the /proc/self/exe symlink. 1573 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1574 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1575 c.Assert(err, IsNil) 1576 restore = apparmor.MockProcSelfExe(fakeExe) 1577 defer restore() 1578 1579 // Create the directory where system apparmor profiles are stored and Write 1580 // the system apparmor profile of snap-confine. 1581 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1582 c.Assert(ioutil.WriteFile(filepath.Join(apparmor_sandbox.ConfDir, "usr.lib.snapd.snap-confine"), []byte(""), 0644), IsNil) 1583 1584 // Setup generated policy for snap-confine. 1585 err = (&apparmor.Backend{}).Initialize(nil) 1586 c.Assert(err, ErrorMatches, "cannot reload snap-confine apparmor profile: .*\n.*\ntesting\n") 1587 1588 // While created the policy file initially we also removed it so that 1589 // no side-effects remain. 1590 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1591 c.Assert(err, IsNil) 1592 c.Assert(files, HasLen, 0) 1593 1594 // We tried to reload the policy. 1595 c.Assert(cmd.Calls(), HasLen, 1) 1596 } 1597 1598 // Test behavior when MkdirAll fails 1599 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError4(c *C) { 1600 // Create a file where we would expect to find the local policy. 1601 err := os.MkdirAll(filepath.Dir(dirs.SnapConfineAppArmorDir), 0755) 1602 c.Assert(err, IsNil) 1603 err = ioutil.WriteFile(dirs.SnapConfineAppArmorDir, []byte(""), 0644) 1604 c.Assert(err, IsNil) 1605 1606 // Setup generated policy for snap-confine. 1607 err = (&apparmor.Backend{}).Initialize(nil) 1608 c.Assert(err, ErrorMatches, "*.: not a directory") 1609 } 1610 1611 // Test behavior when EnsureDirState fails 1612 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError5(c *C) { 1613 // This test cannot run as root as root bypassed DAC checks. 1614 u, err := user.Current() 1615 c.Assert(err, IsNil) 1616 if u.Uid == "0" { 1617 c.Skip("this test cannot run as root") 1618 } 1619 1620 // Make it appear as if NFS workaround was not needed. 1621 restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1622 defer restore() 1623 1624 // Make it appear as if overlay was not used. 1625 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1626 defer restore() 1627 1628 // Intercept interaction with apparmor_parser and make it fail. 1629 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1630 defer cmd.Restore() 1631 1632 // Intercept the /proc/self/exe symlink. 1633 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1634 err = os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1635 c.Assert(err, IsNil) 1636 restore = apparmor.MockProcSelfExe(fakeExe) 1637 defer restore() 1638 1639 // Create the snap-confine directory and put a file. Because the file name 1640 // matches the glob generated-* snapd will attempt to remove it but because 1641 // the directory is not writable, that operation will fail. 1642 err = os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755) 1643 c.Assert(err, IsNil) 1644 f := filepath.Join(dirs.SnapConfineAppArmorDir, "generated-test") 1645 err = ioutil.WriteFile(f, []byte("spurious content"), 0644) 1646 c.Assert(err, IsNil) 1647 err = os.Chmod(dirs.SnapConfineAppArmorDir, 0555) 1648 c.Assert(err, IsNil) 1649 1650 // Make the directory writable for cleanup. 1651 defer os.Chmod(dirs.SnapConfineAppArmorDir, 0755) 1652 1653 // Setup generated policy for snap-confine. 1654 err = (&apparmor.Backend{}).Initialize(nil) 1655 c.Assert(err, ErrorMatches, `cannot synchronize snap-confine policy: remove .*/generated-test: permission denied`) 1656 1657 // The policy directory was unchanged. 1658 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1659 c.Assert(err, IsNil) 1660 c.Assert(files, HasLen, 1) 1661 1662 // We didn't try to reload the policy. 1663 c.Assert(cmd.Calls(), HasLen, 0) 1664 } 1665 1666 // snap-confine policy when overlay is not used. 1667 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyNoOverlay(c *C) { 1668 // Make it appear as if overlay was not used. 1669 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) 1670 defer restore() 1671 1672 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1673 defer restore() 1674 1675 // Intercept interaction with apparmor_parser 1676 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1677 defer cmd.Restore() 1678 1679 // Setup generated policy for snap-confine. 1680 err := (&apparmor.Backend{}).Initialize(nil) 1681 c.Assert(err, IsNil) 1682 c.Assert(cmd.Calls(), HasLen, 0) 1683 1684 // Because overlay is not used there are no local policy files but the 1685 // directory was created. 1686 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1687 c.Assert(err, IsNil) 1688 c.Assert(files, HasLen, 0) 1689 1690 // The policy was not reloaded. 1691 c.Assert(cmd.Calls(), HasLen, 0) 1692 } 1693 1694 // Ensure that both names of the snap-confine apparmor profile are supported. 1695 1696 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithOverlay1(c *C) { 1697 s.testSetupSnapConfineGeneratedPolicyWithOverlay(c, "usr.lib.snapd.snap-confine") 1698 } 1699 1700 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithOverlay2(c *C) { 1701 s.testSetupSnapConfineGeneratedPolicyWithOverlay(c, "usr.lib.snapd.snap-confine.real") 1702 } 1703 1704 // snap-confine policy when overlay is used and snapd has not re-executed. 1705 func (s *backendSuite) testSetupSnapConfineGeneratedPolicyWithOverlay(c *C, profileFname string) { 1706 // Make it appear as if overlay workaround was needed. 1707 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 1708 defer restore() 1709 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1710 defer restore() 1711 // Intercept interaction with apparmor_parser 1712 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1713 defer cmd.Restore() 1714 1715 // Intercept the /proc/self/exe symlink and point it to the distribution 1716 // executable (the path doesn't matter as long as it is not from the 1717 // mounted core snap). This indicates that snapd is not re-executing 1718 // and that we should reload snap-confine profile. 1719 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1720 err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) 1721 c.Assert(err, IsNil) 1722 restore = apparmor.MockProcSelfExe(fakeExe) 1723 defer restore() 1724 1725 profilePath := filepath.Join(apparmor_sandbox.ConfDir, profileFname) 1726 1727 // Create the directory where system apparmor profiles are stored and write 1728 // the system apparmor profile of snap-confine. 1729 c.Assert(os.MkdirAll(apparmor_sandbox.ConfDir, 0755), IsNil) 1730 c.Assert(ioutil.WriteFile(profilePath, []byte(""), 0644), IsNil) 1731 1732 // Setup generated policy for snap-confine. 1733 err = (&apparmor.Backend{}).Initialize(nil) 1734 c.Assert(err, IsNil) 1735 1736 // Because overlay is being used, we have the extra policy file. 1737 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1738 c.Assert(err, IsNil) 1739 c.Assert(files, HasLen, 1) 1740 c.Assert(files[0].Name(), Equals, "overlay-root") 1741 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1742 c.Assert(files[0].IsDir(), Equals, false) 1743 1744 // The policy allows upperdir access. 1745 data, err := ioutil.ReadFile(filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name())) 1746 c.Assert(err, IsNil) 1747 c.Assert(string(data), testutil.Contains, "\"/upper/{,**/}\" r,") 1748 1749 // The system apparmor profile of snap-confine was reloaded. 1750 c.Assert(cmd.Calls(), HasLen, 1) 1751 c.Assert(cmd.Calls(), DeepEquals, [][]string{{ 1752 "apparmor_parser", "--replace", 1753 "--write-cache", 1754 "-O", "no-expr-simplify", 1755 "--cache-loc=" + apparmor_sandbox.SystemCacheDir, 1756 "--skip-read-cache", 1757 "--quiet", 1758 profilePath, 1759 }}) 1760 } 1761 1762 // snap-confine policy when overlay is used and snapd has re-executed. 1763 func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithOverlayAndReExec(c *C) { 1764 // Make it appear as if overlay workaround was needed. 1765 restore := apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 1766 defer restore() 1767 1768 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1769 defer restore() 1770 1771 // Intercept interaction with apparmor_parser 1772 cmd := testutil.MockCommand(c, "apparmor_parser", "") 1773 defer cmd.Restore() 1774 1775 // Intercept the /proc/self/exe symlink and point it to the snapd from the 1776 // mounted core snap. This indicates that snapd has re-executed and 1777 // should not reload snap-confine policy. 1778 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 1779 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) 1780 c.Assert(err, IsNil) 1781 restore = apparmor.MockProcSelfExe(fakeExe) 1782 defer restore() 1783 1784 // Setup generated policy for snap-confine. 1785 err = (&apparmor.Backend{}).Initialize(nil) 1786 c.Assert(err, IsNil) 1787 1788 // Because overlay is being used, we have the extra policy file. 1789 files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) 1790 c.Assert(err, IsNil) 1791 c.Assert(files, HasLen, 1) 1792 c.Assert(files[0].Name(), Equals, "overlay-root") 1793 c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) 1794 c.Assert(files[0].IsDir(), Equals, false) 1795 1796 // The policy allows upperdir access 1797 data, err := ioutil.ReadFile(filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name())) 1798 c.Assert(err, IsNil) 1799 c.Assert(string(data), testutil.Contains, "\"/upper/{,**/}\" r,") 1800 1801 // The distribution policy was not reloaded because snap-confine executes 1802 // from core snap. This is handled separately by per-profile Setup. 1803 c.Assert(cmd.Calls(), HasLen, 0) 1804 } 1805 1806 type nfsAndOverlaySnippetsScenario struct { 1807 opts interfaces.ConfinementOptions 1808 overlaySnippet string 1809 nfsSnippet string 1810 } 1811 1812 var nfsAndOverlaySnippetsScenarios = []nfsAndOverlaySnippetsScenario{{ 1813 // By default apparmor is enforcing mode. 1814 opts: interfaces.ConfinementOptions{}, 1815 overlaySnippet: `"/upper/{,**/}" r,`, 1816 nfsSnippet: "network inet,\n network inet6,", 1817 }, { 1818 // DevMode switches apparmor to non-enforcing (complain) mode. 1819 opts: interfaces.ConfinementOptions{DevMode: true}, 1820 overlaySnippet: `"/upper/{,**/}" r,`, 1821 nfsSnippet: "network inet,\n network inet6,", 1822 }, { 1823 // JailMode switches apparmor to enforcing mode even in the presence of DevMode. 1824 opts: interfaces.ConfinementOptions{DevMode: true, JailMode: true}, 1825 overlaySnippet: `"/upper/{,**/}" r,`, 1826 nfsSnippet: "network inet,\n network inet6,", 1827 }, { 1828 // Classic confinement (without jailmode) uses apparmor in complain mode by default and ignores all snippets. 1829 opts: interfaces.ConfinementOptions{Classic: true}, 1830 overlaySnippet: "", 1831 nfsSnippet: "", 1832 }, { 1833 // Classic confinement in JailMode uses enforcing apparmor. 1834 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 1835 // FIXME: logic in backend.addContent is wrong for this case 1836 //overlaySnippet: `"/upper/{,**/}" r,`, 1837 //nfsSnippet: "network inet,\n network inet6,", 1838 overlaySnippet: "", 1839 nfsSnippet: "", 1840 }} 1841 1842 func (s *backendSuite) TestNFSAndOverlaySnippets(c *C) { 1843 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1844 defer restore() 1845 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) 1846 defer restore() 1847 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 1848 defer restore() 1849 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 1850 return nil 1851 } 1852 1853 for _, scenario := range nfsAndOverlaySnippetsScenarios { 1854 snapInfo := s.InstallSnap(c, scenario.opts, "", ifacetest.SambaYamlV1, 1) 1855 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 1856 c.Check(profile, testutil.FileContains, scenario.overlaySnippet) 1857 c.Check(profile, testutil.FileContains, scenario.nfsSnippet) 1858 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 1859 c.Check(updateNSProfile, testutil.FileContains, scenario.overlaySnippet) 1860 s.RemoveSnap(c, snapInfo) 1861 } 1862 } 1863 1864 var casperOverlaySnippetsScenarios = []nfsAndOverlaySnippetsScenario{{ 1865 // By default apparmor is enforcing mode. 1866 opts: interfaces.ConfinementOptions{}, 1867 overlaySnippet: `"/upper/{,**/}" r,`, 1868 }, { 1869 // DevMode switches apparmor to non-enforcing (complain) mode. 1870 opts: interfaces.ConfinementOptions{DevMode: true}, 1871 overlaySnippet: `"/upper/{,**/}" r,`, 1872 }, { 1873 // JailMode switches apparmor to enforcing mode even in the presence of DevMode. 1874 opts: interfaces.ConfinementOptions{DevMode: true, JailMode: true}, 1875 overlaySnippet: `"/upper/{,**/}" r,`, 1876 }, { 1877 // Classic confinement (without jailmode) uses apparmor in complain mode by default and ignores all snippets. 1878 opts: interfaces.ConfinementOptions{Classic: true}, 1879 overlaySnippet: "", 1880 }, { 1881 // Classic confinement in JailMode uses enforcing apparmor. 1882 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 1883 // FIXME: logic in backend.addContent is wrong for this case 1884 //overlaySnippet: `"/upper/{,**/}" r,`, 1885 overlaySnippet: "", 1886 }} 1887 1888 func (s *backendSuite) TestCasperOverlaySnippets(c *C) { 1889 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1890 defer restore() 1891 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1892 defer restore() 1893 restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "/upper", nil }) 1894 defer restore() 1895 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 1896 return nil 1897 } 1898 1899 for _, scenario := range casperOverlaySnippetsScenarios { 1900 snapInfo := s.InstallSnap(c, scenario.opts, "", ifacetest.SambaYamlV1, 1) 1901 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 1902 c.Check(profile, testutil.FileContains, scenario.overlaySnippet) 1903 s.RemoveSnap(c, snapInfo) 1904 } 1905 } 1906 1907 func (s *backendSuite) TestProfileGlobs(c *C) { 1908 globs := apparmor.ProfileGlobs("foo") 1909 c.Assert(globs, DeepEquals, []string{"snap.foo.*", "snap-update-ns.foo"}) 1910 } 1911 1912 func (s *backendSuite) TestNsProfile(c *C) { 1913 c.Assert(apparmor.NsProfile("foo"), Equals, "snap-update-ns.foo") 1914 } 1915 1916 func (s *backendSuite) TestSandboxFeatures(c *C) { 1917 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1918 defer restore() 1919 restore = apparmor.MockKernelFeatures(func() ([]string, error) { return []string{"foo", "bar"}, nil }) 1920 defer restore() 1921 restore = apparmor.MockParserFeatures(func() ([]string, error) { return []string{"baz", "norf"}, nil }) 1922 defer restore() 1923 1924 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "parser:baz", "parser:norf", "support-level:full", "policy:default"}) 1925 } 1926 1927 func (s *backendSuite) TestSandboxFeaturesPartial(c *C) { 1928 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Partial) 1929 defer restore() 1930 restore = release.MockReleaseInfo(&release.OS{ID: "opensuse-tumbleweed"}) 1931 defer restore() 1932 restore = osutil.MockKernelVersion("4.16.10-1-default") 1933 defer restore() 1934 restore = apparmor.MockKernelFeatures(func() ([]string, error) { return []string{"foo", "bar"}, nil }) 1935 defer restore() 1936 restore = apparmor.MockParserFeatures(func() ([]string, error) { return []string{"baz", "norf"}, nil }) 1937 defer restore() 1938 1939 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "parser:baz", "parser:norf", "support-level:partial", "policy:default"}) 1940 1941 restore = osutil.MockKernelVersion("4.14.1-default") 1942 defer restore() 1943 1944 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "parser:baz", "parser:norf", "support-level:partial", "policy:default"}) 1945 } 1946 1947 func (s *backendSuite) TestParallelInstanceSetupSnapUpdateNS(c *C) { 1948 dirs.SetRootDir(s.RootDir) 1949 1950 const trivialSnapYaml = `name: some-snap 1951 version: 1.0 1952 apps: 1953 app: 1954 command: app-command 1955 ` 1956 snapInfo := snaptest.MockInfo(c, trivialSnapYaml, &snap.SideInfo{Revision: snap.R(222)}) 1957 snapInfo.InstanceKey = "instance" 1958 1959 s.InstallSnap(c, interfaces.ConfinementOptions{}, "some-snap_instance", trivialSnapYaml, 1) 1960 profileUpdateNS := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.some-snap_instance") 1961 c.Check(profileUpdateNS, testutil.FileContains, `profile snap-update-ns.some-snap_instance (`) 1962 c.Check(profileUpdateNS, testutil.FileContains, ` 1963 # Allow parallel instance snap mount namespace adjustments 1964 mount options=(rw rbind) /snap/some-snap_instance/ -> /snap/some-snap/, 1965 mount options=(rw rbind) /var/snap/some-snap_instance/ -> /var/snap/some-snap/, 1966 `) 1967 } 1968 1969 func (s *backendSuite) TestPtraceTraceRule(c *C) { 1970 restoreTemplate := apparmor.MockTemplate("template\n###SNIPPETS###\n") 1971 defer restoreTemplate() 1972 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 1973 defer restore() 1974 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 1975 defer restore() 1976 1977 needle := `deny ptrace (trace),` 1978 for _, tc := range []struct { 1979 opts interfaces.ConfinementOptions 1980 uses bool 1981 suppress bool 1982 expected bool 1983 }{ 1984 // strict, only suppress if suppress == true and uses == false 1985 { 1986 opts: interfaces.ConfinementOptions{}, 1987 uses: false, 1988 suppress: false, 1989 expected: false, 1990 }, 1991 { 1992 opts: interfaces.ConfinementOptions{}, 1993 uses: false, 1994 suppress: true, 1995 expected: true, 1996 }, 1997 { 1998 opts: interfaces.ConfinementOptions{}, 1999 uses: true, 2000 suppress: false, 2001 expected: false, 2002 }, 2003 { 2004 opts: interfaces.ConfinementOptions{}, 2005 uses: true, 2006 suppress: true, 2007 expected: false, 2008 }, 2009 // devmode, only suppress if suppress == true and uses == false 2010 { 2011 opts: interfaces.ConfinementOptions{DevMode: true}, 2012 uses: false, 2013 suppress: false, 2014 expected: false, 2015 }, 2016 { 2017 opts: interfaces.ConfinementOptions{DevMode: true}, 2018 uses: false, 2019 suppress: true, 2020 expected: true, 2021 }, 2022 { 2023 opts: interfaces.ConfinementOptions{DevMode: true}, 2024 uses: true, 2025 suppress: false, 2026 expected: false, 2027 }, 2028 { 2029 opts: interfaces.ConfinementOptions{DevMode: true}, 2030 uses: true, 2031 suppress: true, 2032 expected: false, 2033 }, 2034 // classic, never suppress 2035 { 2036 opts: interfaces.ConfinementOptions{Classic: true}, 2037 uses: false, 2038 suppress: false, 2039 expected: false, 2040 }, 2041 { 2042 opts: interfaces.ConfinementOptions{Classic: true}, 2043 uses: false, 2044 suppress: true, 2045 expected: false, 2046 }, 2047 { 2048 opts: interfaces.ConfinementOptions{Classic: true}, 2049 uses: true, 2050 suppress: false, 2051 expected: false, 2052 }, 2053 { 2054 opts: interfaces.ConfinementOptions{Classic: true}, 2055 uses: true, 2056 suppress: true, 2057 expected: false, 2058 }, 2059 // classic with jail, only suppress if suppress == true and uses == false 2060 { 2061 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2062 uses: false, 2063 suppress: false, 2064 expected: false, 2065 }, 2066 { 2067 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2068 uses: false, 2069 suppress: true, 2070 expected: true, 2071 }, 2072 { 2073 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2074 uses: true, 2075 suppress: false, 2076 expected: false, 2077 }, 2078 { 2079 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 2080 uses: true, 2081 suppress: true, 2082 expected: false, 2083 }, 2084 } { 2085 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 2086 if tc.uses { 2087 spec.SetUsesPtraceTrace() 2088 } 2089 if tc.suppress { 2090 spec.SetSuppressPtraceTrace() 2091 } 2092 return nil 2093 } 2094 2095 snapInfo := s.InstallSnap(c, tc.opts, "", ifacetest.SambaYamlV1, 1) 2096 s.parserCmd.ForgetCalls() 2097 2098 err := s.Backend.Setup(snapInfo, tc.opts, s.Repo, s.meas) 2099 c.Assert(err, IsNil) 2100 2101 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2102 data, err := ioutil.ReadFile(profile) 2103 c.Assert(err, IsNil) 2104 2105 if tc.expected { 2106 c.Assert(string(data), testutil.Contains, needle) 2107 } else { 2108 c.Assert(string(data), Not(testutil.Contains), needle) 2109 } 2110 s.RemoveSnap(c, snapInfo) 2111 } 2112 } 2113 2114 func (s *backendSuite) TestHomeIxRule(c *C) { 2115 restoreTemplate := apparmor.MockTemplate("template\n###SNIPPETS###\nneedle rwkl###HOME_IX###,\n") 2116 defer restoreTemplate() 2117 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2118 defer restore() 2119 restore = apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) 2120 defer restore() 2121 2122 for _, tc := range []struct { 2123 opts interfaces.ConfinementOptions 2124 suppress bool 2125 expected string 2126 }{ 2127 { 2128 opts: interfaces.ConfinementOptions{}, 2129 suppress: true, 2130 expected: "needle rwkl,", 2131 }, 2132 { 2133 opts: interfaces.ConfinementOptions{}, 2134 suppress: false, 2135 expected: "needle rwklix,", 2136 }, 2137 } { 2138 s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { 2139 if tc.suppress { 2140 spec.SetSuppressHomeIx() 2141 } 2142 spec.AddSnippet("needle rwkl###HOME_IX###,") 2143 return nil 2144 } 2145 2146 snapInfo := s.InstallSnap(c, tc.opts, "", ifacetest.SambaYamlV1, 1) 2147 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2148 data, err := ioutil.ReadFile(profile) 2149 c.Assert(err, IsNil) 2150 2151 c.Assert(string(data), testutil.Contains, tc.expected) 2152 s.RemoveSnap(c, snapInfo) 2153 } 2154 } 2155 2156 func (s *backendSuite) TestSystemUsernamesPolicy(c *C) { 2157 restoreTemplate := apparmor.MockTemplate("template\n###SNIPPETS###\n") 2158 defer restoreTemplate() 2159 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 2160 defer restore() 2161 2162 snapYaml := ` 2163 name: app 2164 version: 0.1 2165 system-usernames: 2166 testid: shared 2167 apps: 2168 cmd: 2169 ` 2170 2171 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapYaml, 1) 2172 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.app.cmd") 2173 data, err := ioutil.ReadFile(profile) 2174 c.Assert(err, IsNil) 2175 c.Assert(string(data), testutil.Contains, "capability setuid,") 2176 c.Assert(string(data), testutil.Contains, "capability setgid,") 2177 c.Assert(string(data), testutil.Contains, "capability chown,") 2178 s.RemoveSnap(c, snapInfo) 2179 } 2180 2181 func (s *backendSuite) TestNoSystemUsernamesPolicy(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 apps: 2191 cmd: 2192 ` 2193 2194 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapYaml, 1) 2195 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.app.cmd") 2196 data, err := ioutil.ReadFile(profile) 2197 c.Assert(err, IsNil) 2198 c.Assert(string(data), Not(testutil.Contains), "capability setuid,") 2199 c.Assert(string(data), Not(testutil.Contains), "capability setgid,") 2200 c.Assert(string(data), Not(testutil.Contains), "capability chown,") 2201 s.RemoveSnap(c, snapInfo) 2202 } 2203 2204 func (s *backendSuite) TestSetupManySmoke(c *C) { 2205 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 2206 c.Assert(ok, Equals, true) 2207 c.Assert(setupManyInterface, NotNil) 2208 } 2209 2210 func (s *backendSuite) TestInstallingSnapInPreseedMode(c *C) { 2211 // Intercept the /proc/self/exe symlink and point it to the snapd from the 2212 // mounted core snap. This indicates that snapd has re-executed and 2213 // should not reload snap-confine policy. 2214 fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") 2215 err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) 2216 c.Assert(err, IsNil) 2217 restore := apparmor.MockProcSelfExe(fakeExe) 2218 defer restore() 2219 2220 aa, ok := s.Backend.(*apparmor.Backend) 2221 c.Assert(ok, Equals, true) 2222 2223 opts := interfaces.SecurityBackendOptions{Preseed: true} 2224 c.Assert(aa.Initialize(&opts), IsNil) 2225 2226 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 1) 2227 2228 updateNSProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 2229 profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2230 // file called "snap.sambda.smbd" was created 2231 _, err = os.Stat(profile) 2232 c.Check(err, IsNil) 2233 // apparmor_parser was used to load that file 2234 c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ 2235 {"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}, 2236 }) 2237 } 2238 2239 func (s *backendSuite) TestSetupManyInPreseedMode(c *C) { 2240 aa, ok := s.Backend.(*apparmor.Backend) 2241 c.Assert(ok, Equals, true) 2242 2243 opts := interfaces.SecurityBackendOptions{Preseed: true} 2244 c.Assert(aa.Initialize(&opts), IsNil) 2245 2246 for _, opts := range testedConfinementOpts { 2247 snapInfo1 := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 1) 2248 snapInfo2 := s.InstallSnap(c, opts, "", ifacetest.SomeSnapYamlV1, 1) 2249 s.parserCmd.ForgetCalls() 2250 2251 snap1nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.samba") 2252 snap1AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") 2253 snap2nsProfile := filepath.Join(dirs.SnapAppArmorDir, "snap-update-ns.some-snap") 2254 snap2AAprofile := filepath.Join(dirs.SnapAppArmorDir, "snap.some-snap.someapp") 2255 2256 // simulate outdated profiles by changing their data on the disk 2257 c.Assert(ioutil.WriteFile(snap1AAprofile, []byte("# an outdated profile"), 0644), IsNil) 2258 c.Assert(ioutil.WriteFile(snap2AAprofile, []byte("# an outdated profile"), 0644), IsNil) 2259 2260 setupManyInterface, ok := s.Backend.(interfaces.SecurityBackendSetupMany) 2261 c.Assert(ok, Equals, true) 2262 err := setupManyInterface.SetupMany([]*snap.Info{snapInfo1, snapInfo2}, func(snapName string) interfaces.ConfinementOptions { return opts }, s.Repo, s.meas) 2263 c.Assert(err, IsNil) 2264 2265 // expect two batch executions - one for changed profiles, second for unchanged profiles. 2266 c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ 2267 {"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}, 2268 {"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}, 2269 }) 2270 s.RemoveSnap(c, snapInfo1) 2271 s.RemoveSnap(c, snapInfo2) 2272 } 2273 }