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