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