github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/interfaces/apparmor/backend_test.go (about)

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