github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/interfaces/apparmor/backend_test.go (about)

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