github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/interfaces/apparmor/backend_test.go (about)

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