github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/seccomp/backend_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2018 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 seccomp_test
    21  
    22  import (
    23  	"bytes"
    24  	"errors"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/cmd"
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/interfaces"
    34  	"github.com/snapcore/snapd/interfaces/ifacetest"
    35  	"github.com/snapcore/snapd/interfaces/seccomp"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/release"
    38  	seccomp_compiler "github.com/snapcore/snapd/sandbox/seccomp"
    39  	"github.com/snapcore/snapd/snap"
    40  	"github.com/snapcore/snapd/snap/snaptest"
    41  	"github.com/snapcore/snapd/testutil"
    42  	"github.com/snapcore/snapd/timings"
    43  )
    44  
    45  type backendSuite struct {
    46  	ifacetest.BackendSuite
    47  
    48  	snapSeccomp   *testutil.MockCmd
    49  	profileHeader string
    50  	meas          *timings.Span
    51  
    52  	restoreReadlink func()
    53  }
    54  
    55  var _ = Suite(&backendSuite{})
    56  
    57  var testedConfinementOpts = []interfaces.ConfinementOptions{
    58  	{},
    59  	{DevMode: true},
    60  	{JailMode: true},
    61  	{Classic: true},
    62  }
    63  
    64  func (s *backendSuite) SetUpTest(c *C) {
    65  	s.Backend = &seccomp.Backend{}
    66  	s.BackendSuite.SetUpTest(c)
    67  	c.Assert(s.Repo.AddBackend(s.Backend), IsNil)
    68  
    69  	// Prepare a directory for seccomp profiles.
    70  	// NOTE: Normally this is a part of the OS snap.
    71  	err := os.MkdirAll(dirs.SnapSeccompDir, 0700)
    72  	c.Assert(err, IsNil)
    73  
    74  	s.restoreReadlink = cmd.MockOsReadlink(func(string) (string, error) {
    75  		// pretend that snapd is run from distro libexecdir
    76  		return filepath.Join(dirs.DistroLibExecDir, "snapd"), nil
    77  	})
    78  	snapSeccompPath := filepath.Join(dirs.DistroLibExecDir, "snap-seccomp")
    79  	err = os.MkdirAll(filepath.Dir(snapSeccompPath), 0755)
    80  	c.Assert(err, IsNil)
    81  	s.snapSeccomp = testutil.MockCommand(c, snapSeccompPath, `
    82  if [ "$1" = "version-info" ]; then
    83      echo "abcdef 1.2.3 1234abcd -"
    84  fi`)
    85  
    86  	s.Backend.Initialize()
    87  	s.profileHeader = `# snap-seccomp version information:
    88  # abcdef 1.2.3 1234abcd -
    89  `
    90  	// make sure initialize called version-info
    91  	c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{
    92  		{"snap-seccomp", "version-info"},
    93  	})
    94  	s.snapSeccomp.ForgetCalls()
    95  
    96  	perf := timings.New(nil)
    97  	s.meas = perf.StartSpan("", "")
    98  }
    99  
   100  func (s *backendSuite) TearDownTest(c *C) {
   101  	s.BackendSuite.TearDownTest(c)
   102  
   103  	s.snapSeccomp.Restore()
   104  	s.restoreReadlink()
   105  }
   106  
   107  func (s *backendSuite) TestInitialize(c *C) {
   108  	err := s.Backend.Initialize()
   109  	c.Assert(err, IsNil)
   110  	fname := filepath.Join(dirs.SnapSeccompDir, "global.bin")
   111  	if seccomp.IsBigEndian() {
   112  		c.Check(fname, testutil.FileEquals, seccomp.GlobalProfileBE)
   113  	} else {
   114  		c.Check(fname, testutil.FileEquals, seccomp.GlobalProfileLE)
   115  	}
   116  }
   117  
   118  // Tests for Setup() and Remove()
   119  func (s *backendSuite) TestName(c *C) {
   120  	c.Check(s.Backend.Name(), Equals, interfaces.SecuritySecComp)
   121  }
   122  
   123  func (s *backendSuite) TestInstallingSnapWritesProfiles(c *C) {
   124  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 0)
   125  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   126  	// file called "snap.sambda.smbd" was created
   127  	_, err := os.Stat(profile + ".src")
   128  	c.Check(err, IsNil)
   129  	// and got compiled
   130  	c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{
   131  		{"snap-seccomp", "compile", profile + ".src", profile + ".bin"},
   132  	})
   133  }
   134  
   135  func (s *backendSuite) TestInstallingSnapWritesHookProfiles(c *C) {
   136  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.HookYaml, 0)
   137  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.hook.configure")
   138  
   139  	// Verify that profile named "snap.foo.hook.configure" was created.
   140  	_, err := os.Stat(profile + ".src")
   141  	c.Check(err, IsNil)
   142  	// and got compiled
   143  	c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{
   144  		{"snap-seccomp", "compile", profile + ".src", profile + ".bin"},
   145  	})
   146  }
   147  
   148  func (s *backendSuite) TestInstallingSnapWritesProfilesWithReexec(c *C) {
   149  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   150  		// simulate that we run snapd from core
   151  		return filepath.Join(dirs.SnapMountDir, "core/42/usr/lib/snapd/snapd"), nil
   152  	})
   153  	defer restore()
   154  
   155  	// ensure we have a mocked snap-seccomp on core
   156  	snapSeccompOnCorePath := filepath.Join(dirs.SnapMountDir, "core/42/usr/lib/snapd/snap-seccomp")
   157  	err := os.MkdirAll(filepath.Dir(snapSeccompOnCorePath), 0755)
   158  	c.Assert(err, IsNil)
   159  	snapSeccompOnCore := testutil.MockCommand(c, snapSeccompOnCorePath, `if [ "$1" = "version-info" ]; then
   160  echo "2345cdef 2.3.4 2345cdef -"
   161  fi`)
   162  	defer snapSeccompOnCore.Restore()
   163  
   164  	// rerun initialization
   165  	err = s.Backend.Initialize()
   166  	c.Assert(err, IsNil)
   167  
   168  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 0)
   169  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   170  	// file called "snap.sambda.smbd" was created
   171  	_, err = os.Stat(profile + ".src")
   172  	c.Check(err, IsNil)
   173  	// ensure the snap-seccomp from the regular path was *not* used
   174  	c.Check(s.snapSeccomp.Calls(), HasLen, 0)
   175  	// ensure the snap-seccomp from the core snap was used instead
   176  	c.Check(snapSeccompOnCore.Calls(), DeepEquals, [][]string{
   177  		{"snap-seccomp", "version-info"},
   178  		{"snap-seccomp", "compile", profile + ".src", profile + ".bin"},
   179  	})
   180  	raw, err := ioutil.ReadFile(profile + ".src")
   181  	c.Assert(err, IsNil)
   182  	c.Assert(bytes.HasPrefix(raw, []byte(`# snap-seccomp version information:
   183  # 2345cdef 2.3.4 2345cdef -
   184  `)), Equals, true)
   185  }
   186  
   187  func (s *backendSuite) TestRemovingSnapRemovesProfiles(c *C) {
   188  	for _, opts := range testedConfinementOpts {
   189  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   190  		s.RemoveSnap(c, snapInfo)
   191  		profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   192  		// file called "snap.sambda.smbd" was removed
   193  		_, err := os.Stat(profile + ".src")
   194  		c.Check(os.IsNotExist(err), Equals, true)
   195  	}
   196  }
   197  
   198  func (s *backendSuite) TestRemovingSnapRemovesHookProfiles(c *C) {
   199  	for _, opts := range testedConfinementOpts {
   200  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.HookYaml, 0)
   201  		s.RemoveSnap(c, snapInfo)
   202  		profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.hook.configure")
   203  
   204  		// Verify that profile "snap.foo.hook.configure" was removed.
   205  		_, err := os.Stat(profile + ".src")
   206  		c.Check(os.IsNotExist(err), Equals, true)
   207  	}
   208  }
   209  
   210  func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) {
   211  	for _, opts := range testedConfinementOpts {
   212  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   213  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1WithNmbd, 0)
   214  		profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.nmbd")
   215  		_, err := os.Stat(profile + ".src")
   216  		// file called "snap.sambda.nmbd" was created
   217  		c.Check(err, IsNil)
   218  		// and got compiled
   219  		c.Check(s.snapSeccomp.Calls(), testutil.DeepContains, []string{"snap-seccomp", "compile", profile + ".src", profile + ".bin"})
   220  		s.snapSeccomp.ForgetCalls()
   221  
   222  		s.RemoveSnap(c, snapInfo)
   223  	}
   224  }
   225  
   226  func (s *backendSuite) TestUpdatingSnapToOneWithHooks(c *C) {
   227  	for _, opts := range testedConfinementOpts {
   228  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   229  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlWithHook, 0)
   230  		profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.hook.configure")
   231  
   232  		_, err := os.Stat(profile + ".src")
   233  		// Verify that profile "snap.samba.hook.configure" was created.
   234  		c.Check(err, IsNil)
   235  		// and got compiled
   236  		c.Check(s.snapSeccomp.Calls(), testutil.DeepContains, []string{"snap-seccomp", "compile", profile + ".src", profile + ".bin"})
   237  		s.snapSeccomp.ForgetCalls()
   238  
   239  		s.RemoveSnap(c, snapInfo)
   240  	}
   241  }
   242  
   243  func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) {
   244  	for _, opts := range testedConfinementOpts {
   245  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1WithNmbd, 0)
   246  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 0)
   247  		profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.nmbd")
   248  		// file called "snap.sambda.nmbd" was removed
   249  		_, err := os.Stat(profile + ".src")
   250  		c.Check(os.IsNotExist(err), Equals, true)
   251  		s.RemoveSnap(c, snapInfo)
   252  	}
   253  }
   254  
   255  func (s *backendSuite) TestUpdatingSnapToOneWithNoHooks(c *C) {
   256  	for _, opts := range testedConfinementOpts {
   257  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlWithHook, 0)
   258  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 0)
   259  		profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.hook.configure")
   260  
   261  		// Verify that profile snap.samba.hook.configure was removed.
   262  		_, err := os.Stat(profile + ".src")
   263  		c.Check(os.IsNotExist(err), Equals, true)
   264  		s.RemoveSnap(c, snapInfo)
   265  	}
   266  }
   267  
   268  func (s *backendSuite) TestRealDefaultTemplateIsNormallyUsed(c *C) {
   269  	snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil)
   270  	// NOTE: we don't call seccomp.MockTemplate()
   271  	err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   272  	c.Assert(err, IsNil)
   273  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   274  	data, err := ioutil.ReadFile(profile + ".src")
   275  	c.Assert(err, IsNil)
   276  	for _, line := range []string{
   277  		// NOTE: a few randomly picked lines from the real profile.  Comments
   278  		// and empty lines are avoided as those can be discarded in the future.
   279  		"# - create_module, init_module, finit_module, delete_module (kernel modules)\n",
   280  		"open\n",
   281  		"getuid\n",
   282  		"setresuid\n", // this is not random
   283  	} {
   284  		c.Assert(string(data), testutil.Contains, line)
   285  	}
   286  }
   287  
   288  type combineSnippetsScenario struct {
   289  	opts    interfaces.ConfinementOptions
   290  	snippet string
   291  	content string
   292  }
   293  
   294  var combineSnippetsScenarios = []combineSnippetsScenario{{
   295  	opts:    interfaces.ConfinementOptions{},
   296  	content: "default\n",
   297  }, {
   298  	opts:    interfaces.ConfinementOptions{},
   299  	snippet: "snippet",
   300  	content: "default\nsnippet\n",
   301  }, {
   302  	opts:    interfaces.ConfinementOptions{DevMode: true},
   303  	content: "@complain\ndefault\n",
   304  }, {
   305  	opts:    interfaces.ConfinementOptions{DevMode: true},
   306  	snippet: "snippet",
   307  	content: "@complain\ndefault\nsnippet\n",
   308  }, {
   309  	opts:    interfaces.ConfinementOptions{Classic: true},
   310  	snippet: "snippet",
   311  	content: "@unrestricted\ndefault\nsnippet\n",
   312  }, {
   313  	opts:    interfaces.ConfinementOptions{Classic: true, JailMode: true},
   314  	snippet: "snippet",
   315  	content: "default\nsnippet\n",
   316  }}
   317  
   318  func (s *backendSuite) TestCombineSnippets(c *C) {
   319  	restore := release.MockForcedDevmode(false)
   320  	defer restore()
   321  	restore = release.MockSecCompActions([]string{"log"})
   322  	defer restore()
   323  	restore = seccomp.MockRequiresSocketcall(func(string) bool { return false })
   324  	defer restore()
   325  
   326  	// NOTE: replace the real template with a shorter variant
   327  	restore = seccomp.MockTemplate([]byte("default\n"))
   328  	defer restore()
   329  	for _, scenario := range combineSnippetsScenarios {
   330  		s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error {
   331  			if scenario.snippet != "" {
   332  				spec.AddSnippet(scenario.snippet)
   333  			}
   334  			return nil
   335  		}
   336  
   337  		snapInfo := s.InstallSnap(c, scenario.opts, "", ifacetest.SambaYamlV1, 0)
   338  		profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   339  		c.Check(profile+".src", testutil.FileEquals, s.profileHeader+scenario.content)
   340  		stat, err := os.Stat(profile + ".src")
   341  		c.Assert(err, IsNil)
   342  		c.Check(stat.Mode(), Equals, os.FileMode(0644))
   343  		s.RemoveSnap(c, snapInfo)
   344  	}
   345  }
   346  
   347  const snapYaml = `
   348  name: foo
   349  version: 1
   350  developer: acme
   351  apps:
   352      foo:
   353          slots: [iface, iface2]
   354  `
   355  
   356  // Ensure that combined snippets are sorted
   357  func (s *backendSuite) TestCombineSnippetsOrdering(c *C) {
   358  	restore := release.MockForcedDevmode(false)
   359  	defer restore()
   360  	restore = seccomp.MockRequiresSocketcall(func(string) bool { return false })
   361  	defer restore()
   362  
   363  	// NOTE: replace the real template with a shorter variant
   364  	restore = seccomp.MockTemplate([]byte("default\n"))
   365  	defer restore()
   366  
   367  	iface2 := &ifacetest.TestInterface{InterfaceName: "iface2"}
   368  	s.Repo.AddInterface(iface2)
   369  
   370  	s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error {
   371  		spec.AddSnippet("zzz")
   372  		return nil
   373  	}
   374  	iface2.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error {
   375  		spec.AddSnippet("aaa")
   376  		return nil
   377  	}
   378  
   379  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapYaml, 0)
   380  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.foo")
   381  	c.Check(profile+".src", testutil.FileEquals, s.profileHeader+"default\naaa\nzzz\n")
   382  	stat, err := os.Stat(profile + ".src")
   383  	c.Assert(err, IsNil)
   384  	c.Check(stat.Mode(), Equals, os.FileMode(0644))
   385  }
   386  
   387  func (s *backendSuite) TestBindIsAddedForForcedDevModeSystems(c *C) {
   388  	restore := release.MockForcedDevmode(true)
   389  	defer restore()
   390  
   391  	snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil)
   392  	// NOTE: we don't call seccomp.MockTemplate()
   393  	err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   394  	c.Assert(err, IsNil)
   395  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   396  	c.Assert(profile+".src", testutil.FileContains, "\nbind\n")
   397  }
   398  
   399  func (s *backendSuite) TestSocketcallIsAddedWhenRequired(c *C) {
   400  	restore := seccomp.MockRequiresSocketcall(func(string) bool { return true })
   401  	defer restore()
   402  
   403  	snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil)
   404  	// NOTE: we don't call seccomp.MockTemplate()
   405  	err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   406  	c.Assert(err, IsNil)
   407  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   408  	c.Assert(profile+".src", testutil.FileContains, "\nsocketcall\n")
   409  }
   410  
   411  func (s *backendSuite) TestSocketcallIsNotAddedWhenNotRequired(c *C) {
   412  	restore := seccomp.MockRequiresSocketcall(func(string) bool { return false })
   413  	defer restore()
   414  
   415  	snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil)
   416  	// NOTE: we don't call seccomp.MockTemplate()
   417  	err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   418  	c.Assert(err, IsNil)
   419  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   420  	c.Assert(profile+".src", Not(testutil.FileContains), "\nsocketcall\n")
   421  }
   422  
   423  const ClassicYamlV1 = `
   424  name: test-classic
   425  version: 1
   426  developer: acme
   427  confinement: classic
   428  apps:
   429    sh:
   430    `
   431  
   432  func (s *backendSuite) TestSystemKeyRetLogSupported(c *C) {
   433  	restore := release.MockSecCompActions([]string{"allow", "errno", "kill", "log", "trace", "trap"})
   434  	defer restore()
   435  
   436  	snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: true}, "", ifacetest.SambaYamlV1, 0)
   437  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   438  	c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n")
   439  	s.RemoveSnap(c, snapInfo)
   440  
   441  	snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: false}, "", ifacetest.SambaYamlV1, 0)
   442  	profile = filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   443  	c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n")
   444  	s.RemoveSnap(c, snapInfo)
   445  
   446  	snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{Classic: true}, "", ClassicYamlV1, 0)
   447  	profile = filepath.Join(dirs.SnapSeccompDir, "snap.test-classic.sh")
   448  	c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n")
   449  	s.RemoveSnap(c, snapInfo)
   450  }
   451  
   452  func (s *backendSuite) TestSystemKeyRetLogUnsupported(c *C) {
   453  	restore := release.MockSecCompActions([]string{"allow", "errno", "kill", "trace", "trap"})
   454  	defer restore()
   455  
   456  	snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: true}, "", ifacetest.SambaYamlV1, 0)
   457  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   458  	c.Assert(profile+".src", testutil.FileContains, "# complain mode logging unavailable\n")
   459  	s.RemoveSnap(c, snapInfo)
   460  
   461  	snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: false}, "", ifacetest.SambaYamlV1, 0)
   462  	profile = filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   463  	c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n")
   464  	s.RemoveSnap(c, snapInfo)
   465  
   466  	snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{Classic: true}, "", ClassicYamlV1, 0)
   467  	profile = filepath.Join(dirs.SnapSeccompDir, "snap.test-classic.sh")
   468  	c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n")
   469  	s.RemoveSnap(c, snapInfo)
   470  }
   471  
   472  func (s *backendSuite) TestSandboxFeatures(c *C) {
   473  	restore := seccomp.MockKernelFeatures(func() []string { return []string{"foo", "bar"} })
   474  	defer restore()
   475  
   476  	c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "bpf-argument-filtering"})
   477  
   478  	// change version reported by snap-seccomp
   479  	snapSeccomp := testutil.MockCommand(c, filepath.Join(dirs.DistroLibExecDir, "snap-seccomp"), `
   480  if [ "$1" = "version-info" ]; then
   481      echo "abcdef 1.2.3 1234abcd bpf-actlog"
   482  fi`)
   483  	defer snapSeccomp.Restore()
   484  
   485  	// reload cached version info
   486  	err := s.Backend.Initialize()
   487  	c.Assert(err, IsNil)
   488  	c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "bpf-argument-filtering", "bpf-actlog"})
   489  }
   490  
   491  func (s *backendSuite) TestRequiresSocketcallByNotNeededArch(c *C) {
   492  	testArchs := []string{"amd64", "armhf", "arm64", "powerpc", "ppc64el", "unknownDefault"}
   493  	for _, arch := range testArchs {
   494  		restore := seccomp.MockDpkgKernelArchitecture(func() string { return arch })
   495  		defer restore()
   496  		c.Assert(seccomp.RequiresSocketcall(""), Equals, false)
   497  	}
   498  }
   499  
   500  func (s *backendSuite) TestRequiresSocketcallForceByArch(c *C) {
   501  	testArchs := []string{"sparc", "sparc64"}
   502  	for _, arch := range testArchs {
   503  		restore := seccomp.MockDpkgKernelArchitecture(func() string { return arch })
   504  		defer restore()
   505  		c.Assert(seccomp.RequiresSocketcall(""), Equals, true)
   506  	}
   507  }
   508  
   509  func (s *backendSuite) TestRequiresSocketcallForcedViaUbuntuRelease(c *C) {
   510  	// specify "core18" with 4.4 kernel so as not to influence the release
   511  	// check.
   512  	base := "core18"
   513  	restore := osutil.MockKernelVersion("4.4")
   514  	defer restore()
   515  
   516  	tests := []struct {
   517  		distro          string
   518  		arch            string
   519  		release         string
   520  		needsSocketcall bool
   521  	}{
   522  		// with core18 as base and 4.4 kernel, we only require
   523  		// socketcall on i386/s390
   524  		{"ubuntu", "i386", "14.04", true},
   525  		{"ubuntu", "s390x", "14.04", true},
   526  		{"ubuntu", "other", "14.04", false},
   527  
   528  		// releases after 14.04 aren't forced
   529  		{"ubuntu", "i386", "other", false},
   530  		{"ubuntu", "s390x", "other", false},
   531  		{"ubuntu", "other", "other", false},
   532  
   533  		// other distros aren't forced
   534  		{"other", "i386", "14.04", false},
   535  		{"other", "s390x", "14.04", false},
   536  		{"other", "other", "14.04", false},
   537  		{"other", "i386", "other", false},
   538  		{"other", "s390x", "other", false},
   539  		{"other", "other", "other", false},
   540  	}
   541  
   542  	for _, t := range tests {
   543  		restore = seccomp.MockReleaseInfoId(t.distro)
   544  		defer restore()
   545  		restore = seccomp.MockDpkgKernelArchitecture(func() string { return t.arch })
   546  		defer restore()
   547  		restore = seccomp.MockReleaseInfoVersionId(t.release)
   548  		defer restore()
   549  
   550  		c.Assert(seccomp.RequiresSocketcall(base), Equals, t.needsSocketcall)
   551  	}
   552  }
   553  
   554  func (s *backendSuite) TestRequiresSocketcallForcedViaKernelVersion(c *C) {
   555  	// specify "core18" with non-ubuntu so as not to influence the kernel
   556  	// check.
   557  	base := "core18"
   558  
   559  	restore := seccomp.MockReleaseInfoId("other")
   560  	defer restore()
   561  
   562  	tests := []struct {
   563  		arch            string
   564  		version         string
   565  		needsSocketcall bool
   566  	}{
   567  		// i386 needs socketcall on <= 4.2 kernels
   568  		{"i386", "4.2", true},
   569  		{"i386", "4.3", false},
   570  		{"i386", "4.4", false},
   571  
   572  		// s390x needs socketcall on <= 4.2 kernels
   573  		{"s390x", "4.2", true},
   574  		{"s390x", "4.3", false},
   575  		{"s390x", "4.4", false},
   576  
   577  		// other architectures don't require it
   578  		{"other", "4.2", false},
   579  		{"other", "4.3", false},
   580  		{"other", "4.4", false},
   581  	}
   582  
   583  	for _, t := range tests {
   584  		restore := seccomp.MockDpkgKernelArchitecture(func() string { return t.arch })
   585  		defer restore()
   586  		restore = osutil.MockKernelVersion(t.version)
   587  		defer restore()
   588  
   589  		// specify "core18" here so as not to influence the
   590  		// kernel check.
   591  		c.Assert(seccomp.RequiresSocketcall(base), Equals, t.needsSocketcall)
   592  	}
   593  }
   594  
   595  func (s *backendSuite) TestRequiresSocketcallForcedViaBaseSnap(c *C) {
   596  	// Mock up as non-Ubuntu, i386 with new enough kernel so the base snap
   597  	// check is reached
   598  	restore := seccomp.MockReleaseInfoId("other")
   599  	defer restore()
   600  	restore = seccomp.MockDpkgKernelArchitecture(func() string { return "i386" })
   601  	defer restore()
   602  	restore = osutil.MockKernelVersion("4.3")
   603  	defer restore()
   604  
   605  	testBases := []string{"", "core", "core16"}
   606  	for _, baseSnap := range testBases {
   607  		c.Assert(seccomp.RequiresSocketcall(baseSnap), Equals, true)
   608  	}
   609  }
   610  
   611  func (s *backendSuite) TestRequiresSocketcallNotForcedViaBaseSnap(c *C) {
   612  	// Mock up as non-Ubuntu, i386 with new enough kernel so the base snap
   613  	// check is reached
   614  	restore := seccomp.MockReleaseInfoId("other")
   615  	defer restore()
   616  	restore = seccomp.MockDpkgKernelArchitecture(func() string { return "i386" })
   617  	defer restore()
   618  	restore = osutil.MockKernelVersion("4.3")
   619  	defer restore()
   620  
   621  	testBases := []string{"bare", "core18", "fedora-core"}
   622  	for _, baseSnap := range testBases {
   623  		c.Assert(seccomp.RequiresSocketcall(baseSnap), Equals, false)
   624  	}
   625  }
   626  
   627  func (s *backendSuite) TestRebuildsWithVersionInfoWhenNeeded(c *C) {
   628  	restore := release.MockForcedDevmode(false)
   629  	defer restore()
   630  	restore = release.MockSecCompActions([]string{"log"})
   631  	defer restore()
   632  	restore = seccomp.MockRequiresSocketcall(func(string) bool { return false })
   633  	defer restore()
   634  
   635  	// NOTE: replace the real template with a shorter variant
   636  	restore = seccomp.MockTemplate([]byte("\ndefault\n"))
   637  	defer restore()
   638  
   639  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd")
   640  
   641  	snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil)
   642  	err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   643  	c.Assert(err, IsNil)
   644  	c.Check(profile+".src", testutil.FileEquals, s.profileHeader+"\ndefault\n")
   645  
   646  	c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{
   647  		{"snap-seccomp", "compile", profile + ".src", profile + ".bin"},
   648  	})
   649  
   650  	// unchanged snap-seccomp version will not trigger a rebuild
   651  	err = s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   652  	c.Assert(err, IsNil)
   653  
   654  	// compilation from this first Setup()
   655  	c.Check(s.snapSeccomp.Calls(), HasLen, 1)
   656  
   657  	// change version reported by snap-seccomp
   658  	snapSeccomp := testutil.MockCommand(c, filepath.Join(dirs.DistroLibExecDir, "snap-seccomp"), `
   659  if [ "$1" = "version-info" ]; then
   660      echo "abcdef 2.3.3 2345abcd -"
   661  fi`)
   662  	defer snapSeccomp.Restore()
   663  	updatedProfileHeader := `# snap-seccomp version information:
   664  # abcdef 2.3.3 2345abcd -
   665  `
   666  	// reload cached version info
   667  	err = s.Backend.Initialize()
   668  	c.Assert(err, IsNil)
   669  
   670  	c.Check(s.snapSeccomp.Calls(), HasLen, 2)
   671  	c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{
   672  		// compilation from first Setup()
   673  		{"snap-seccomp", "compile", profile + ".src", profile + ".bin"},
   674  		// initialization with new version
   675  		{"snap-seccomp", "version-info"},
   676  	})
   677  
   678  	// the profile should be rebuilt now
   679  	err = s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   680  	c.Assert(err, IsNil)
   681  	c.Check(profile+".src", testutil.FileEquals, updatedProfileHeader+"\ndefault\n")
   682  
   683  	c.Check(s.snapSeccomp.Calls(), HasLen, 3)
   684  	c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{
   685  		// compilation from first Setup()
   686  		{"snap-seccomp", "compile", profile + ".src", profile + ".bin"},
   687  		// initialization with new version
   688  		{"snap-seccomp", "version-info"},
   689  		// compilation of profiles with new compiler version
   690  		{"snap-seccomp", "compile", profile + ".src", profile + ".bin"},
   691  	})
   692  }
   693  
   694  func (s *backendSuite) TestInitializationDuringBootstrap(c *C) {
   695  	// undo what was done in test set-up
   696  	s.snapSeccomp.Restore()
   697  	os.Remove(s.snapSeccomp.Exe())
   698  
   699  	// during bootstrap, before seeding, snapd/core snap is mounted at some
   700  	// random location under /tmp
   701  	tmpDir := c.MkDir()
   702  	restore := cmd.MockOsReadlink(func(string) (string, error) {
   703  		return filepath.Join(tmpDir, "usr/lib/snapd/snapd"), nil
   704  	})
   705  	defer restore()
   706  
   707  	// ensure we have a mocked snap-seccomp on core
   708  	snapSeccompInMountedPath := filepath.Join(tmpDir, "usr/lib/snapd/snap-seccomp")
   709  	err := os.MkdirAll(filepath.Dir(snapSeccompInMountedPath), 0755)
   710  	c.Assert(err, IsNil)
   711  	snapSeccompInMounted := testutil.MockCommand(c, snapSeccompInMountedPath, `if [ "$1" = "version-info" ]; then
   712  echo "2345cdef 2.3.4 2345cdef -"
   713  fi`)
   714  	defer snapSeccompInMounted.Restore()
   715  
   716  	// rerun initialization
   717  	err = s.Backend.Initialize()
   718  	c.Assert(err, IsNil)
   719  
   720  	// ensure the snap-seccomp from the regular path was *not* used
   721  	c.Check(s.snapSeccomp.Calls(), HasLen, 0)
   722  	// the one from mounted snap was used
   723  	c.Check(snapSeccompInMounted.Calls(), DeepEquals, [][]string{
   724  		{"snap-seccomp", "version-info"},
   725  	})
   726  
   727  	sb, ok := s.Backend.(*seccomp.Backend)
   728  	c.Assert(ok, Equals, true)
   729  	c.Check(sb.VersionInfo(), Equals, seccomp_compiler.VersionInfo("2345cdef 2.3.4 2345cdef -"))
   730  }
   731  
   732  func (s *backendSuite) TestCompilerInitUnhappy(c *C) {
   733  	restore := seccomp.MockSeccompCompilerLookup(func(name string) (string, error) {
   734  		c.Check(name, Equals, "snap-seccomp")
   735  		return "", errors.New("failed")
   736  	})
   737  	defer restore()
   738  	err := s.Backend.Initialize()
   739  	c.Assert(err, ErrorMatches, "cannot initialize seccomp profile compiler: failed")
   740  }
   741  
   742  func (s *backendSuite) TestSystemUsernamesPolicy(c *C) {
   743  	snapYaml := `
   744  name: app
   745  version: 0.1
   746  system-usernames:
   747    testid: shared
   748    testid2: shared
   749  apps:
   750    cmd:
   751  `
   752  	snapInfo := snaptest.MockInfo(c, snapYaml, nil)
   753  	// NOTE: we don't call seccomp.MockTemplate()
   754  	err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   755  	c.Assert(err, IsNil)
   756  	// NOTE: we don't call seccomp.MockTemplate()
   757  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.app.cmd")
   758  	data, err := ioutil.ReadFile(profile + ".src")
   759  	c.Assert(err, IsNil)
   760  	for _, line := range []string{
   761  		// NOTE: a few randomly picked lines from the real
   762  		// profile.  Comments and empty lines are avoided as
   763  		// those can be discarded in the future.
   764  		"\n# - create_module, init_module, finit_module, delete_module (kernel modules)\n",
   765  		"\nopen\n",
   766  		"\ngetuid\n",
   767  		"\nsetgroups 0 0\n",
   768  		// and a few randomly picked lines from root syscalls
   769  		// with extra \n checks to ensure we have the right
   770  		// "paragraphs" in the generated output
   771  		"\n\n# allow setresgid to root\n",
   772  		"\n# allow setresuid to root\n",
   773  		"\nsetresuid u:root u:root u:root\n",
   774  		// and a few randomly picked lines from global id syscalls
   775  		"\n\n# allow setresgid to testid\n",
   776  		"\n\n# allow setresuid to testid\n",
   777  		"\nsetresuid -1 u:testid -1\n",
   778  		// also for the second user
   779  		"\n\n# allow setresgid to testid2\n",
   780  		"\n# allow setresuid to testid2\n",
   781  		"\nsetresuid -1 u:testid2 -1\n",
   782  	} {
   783  		c.Assert(string(data), testutil.Contains, line)
   784  	}
   785  
   786  	// make sure the bare syscalls aren't present
   787  	c.Assert(string(data), Not(testutil.Contains), "setresuid\n")
   788  }
   789  
   790  func (s *backendSuite) TestNoSystemUsernamesPolicy(c *C) {
   791  	snapYaml := `
   792  name: app
   793  version: 0.1
   794  apps:
   795    cmd:
   796  `
   797  	snapInfo := snaptest.MockInfo(c, snapYaml, nil)
   798  	// NOTE: we don't call seccomp.MockTemplate()
   799  	err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas)
   800  	c.Assert(err, IsNil)
   801  	// NOTE: we don't call seccomp.MockTemplate()
   802  	profile := filepath.Join(dirs.SnapSeccompDir, "snap.app.cmd")
   803  	data, err := ioutil.ReadFile(profile + ".src")
   804  	c.Assert(err, IsNil)
   805  	for _, line := range []string{
   806  		// and a few randomly picked lines from root syscalls
   807  		"# allow setresgid to root\n",
   808  		"# allow setresuid to root\n",
   809  		"setresuid u:root u:root u:root\n",
   810  		// and a few randomly picked lines from global id syscalls
   811  		"# allow setresgid to testid\n",
   812  		"# allow setresuid to testid\n",
   813  		"setresuid -1 u:testid -1\n",
   814  	} {
   815  		c.Assert(string(data), Not(testutil.Contains), line)
   816  	}
   817  
   818  	// make sure the bare syscalls are present
   819  	c.Assert(string(data), testutil.Contains, "setresuid\n")
   820  }