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