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