github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/interfaces/mount/backend_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 mount_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"sort"
    28  	"strings"
    29  	"testing"
    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/ifacetest"
    36  	"github.com/snapcore/snapd/interfaces/mount"
    37  	"github.com/snapcore/snapd/osutil"
    38  	"github.com/snapcore/snapd/sandbox/cgroup"
    39  	"github.com/snapcore/snapd/snap"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  func Test(t *testing.T) {
    44  	TestingT(t)
    45  }
    46  
    47  type backendSuite struct {
    48  	ifacetest.BackendSuite
    49  
    50  	iface2 *ifacetest.TestInterface
    51  }
    52  
    53  var _ = Suite(&backendSuite{})
    54  
    55  func (s *backendSuite) SetUpTest(c *C) {
    56  	s.Backend = &mount.Backend{}
    57  	s.BackendSuite.SetUpTest(c)
    58  
    59  	c.Assert(s.Repo.AddBackend(s.Backend), IsNil)
    60  
    61  	c.Assert(os.MkdirAll(dirs.SnapMountPolicyDir, 0700), IsNil)
    62  	c.Assert(os.MkdirAll(dirs.SnapRunNsDir, 0700), IsNil)
    63  
    64  	// add second iface so that we actually test combining snippets
    65  	s.iface2 = &ifacetest.TestInterface{InterfaceName: "iface2"}
    66  	c.Assert(s.Repo.AddInterface(s.iface2), IsNil)
    67  }
    68  
    69  func (s *backendSuite) TearDownTest(c *C) {
    70  	s.BackendSuite.TearDownTest(c)
    71  }
    72  
    73  func (s *backendSuite) TestName(c *C) {
    74  	c.Check(s.Backend.Name(), Equals, interfaces.SecurityMount)
    75  }
    76  
    77  func (s *backendSuite) TestRemove(c *C) {
    78  	appCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.hello-world.fstab")
    79  	err := ioutil.WriteFile(appCanaryToGo, []byte("ni! ni! ni!"), 0644)
    80  	c.Assert(err, IsNil)
    81  
    82  	hookCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.hook.configure.fstab")
    83  	err = ioutil.WriteFile(hookCanaryToGo, []byte("ni! ni! ni!"), 0644)
    84  	c.Assert(err, IsNil)
    85  
    86  	snapCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.fstab")
    87  	err = ioutil.WriteFile(snapCanaryToGo, []byte("ni! ni! ni!"), 0644)
    88  	c.Assert(err, IsNil)
    89  
    90  	appCanaryToStay := filepath.Join(dirs.SnapMountPolicyDir, "snap.i-stay.really.fstab")
    91  	err = ioutil.WriteFile(appCanaryToStay, []byte("stay!"), 0644)
    92  	c.Assert(err, IsNil)
    93  
    94  	snapCanaryToStay := filepath.Join(dirs.SnapMountPolicyDir, "snap.i-stay.fstab")
    95  	err = ioutil.WriteFile(snapCanaryToStay, []byte("stay!"), 0644)
    96  	c.Assert(err, IsNil)
    97  
    98  	// Write the .mnt file, the logic for discarding mount namespaces uses it
    99  	// as a canary file to look for to even attempt to run the mount discard
   100  	// tool.
   101  	mntFile := filepath.Join(dirs.SnapRunNsDir, "hello-world.mnt")
   102  	err = ioutil.WriteFile(mntFile, []byte(""), 0644)
   103  	c.Assert(err, IsNil)
   104  
   105  	// Mock snap-discard-ns and allow tweak distro libexec dir so that it is used.
   106  	cmd := testutil.MockCommand(c, "snap-discard-ns", "")
   107  	defer cmd.Restore()
   108  	dirs.DistroLibExecDir = cmd.BinDir()
   109  
   110  	err = s.Backend.Remove("hello-world")
   111  	c.Assert(err, IsNil)
   112  
   113  	c.Assert(osutil.FileExists(snapCanaryToGo), Equals, false)
   114  	c.Assert(osutil.FileExists(appCanaryToGo), Equals, false)
   115  	c.Assert(osutil.FileExists(hookCanaryToGo), Equals, false)
   116  	c.Assert(appCanaryToStay, testutil.FileEquals, "stay!")
   117  	c.Assert(snapCanaryToStay, testutil.FileEquals, "stay!")
   118  	c.Assert(cmd.Calls(), DeepEquals, [][]string{{"snap-discard-ns", "hello-world"}})
   119  }
   120  
   121  var mockSnapYaml = `name: snap-name
   122  version: 1
   123  apps:
   124      app1:
   125      app2:
   126  hooks:
   127      configure:
   128          plugs: [iface-plug]
   129  plugs:
   130      iface-plug:
   131          interface: iface
   132  slots:
   133      iface-slot:
   134          interface: iface2
   135  `
   136  
   137  func (s *backendSuite) TestSetupSetsupSimple(c *C) {
   138  	fsEntry1 := osutil.MountEntry{Name: "/src-1", Dir: "/dst-1", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0}
   139  	fsEntry2 := osutil.MountEntry{Name: "/src-2", Dir: "/dst-2", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0}
   140  	fsEntry3 := osutil.MountEntry{Name: "/src-3", Dir: "/dst-3", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0}
   141  
   142  	// Give the plug a permanent effect
   143  	s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error {
   144  		if err := spec.AddMountEntry(fsEntry1); err != nil {
   145  			return err
   146  		}
   147  		return spec.AddUserMountEntry(fsEntry3)
   148  	}
   149  	// Give the slot a permanent effect
   150  	s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *snap.SlotInfo) error {
   151  		return spec.AddMountEntry(fsEntry2)
   152  	}
   153  
   154  	// confinement options are irrelevant to this security backend
   155  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", mockSnapYaml, 0)
   156  
   157  	// ensure both security effects from iface/iface2 are combined
   158  	// (because mount profiles are global in the whole snap)
   159  	expected := strings.Split(fmt.Sprintf("%s\n%s\n", fsEntry1, fsEntry2), "\n")
   160  	sort.Strings(expected)
   161  	// and that we have the modern fstab file (global for snap)
   162  	fn := filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name.fstab")
   163  	content, err := ioutil.ReadFile(fn)
   164  	c.Assert(err, IsNil, Commentf("Expected mount profile for the whole snap"))
   165  	got := strings.Split(string(content), "\n")
   166  	sort.Strings(got)
   167  	c.Check(got, DeepEquals, expected)
   168  
   169  	// Check that the user-fstab file was written with the user mount
   170  	fn = filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name.user-fstab")
   171  	content, err = ioutil.ReadFile(fn)
   172  	c.Assert(err, IsNil, Commentf("Expected user mount profile for the whole snap"))
   173  	c.Check(string(content), Equals, fsEntry3.String()+"\n")
   174  }
   175  
   176  func (s *backendSuite) TestSetupSetsupWithoutDir(c *C) {
   177  	s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error {
   178  		return spec.AddMountEntry(osutil.MountEntry{})
   179  	}
   180  
   181  	// Ensure that backend.Setup() creates the required dir on demand
   182  	os.Remove(dirs.SnapMountPolicyDir)
   183  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", mockSnapYaml, 0)
   184  }
   185  
   186  func (s *backendSuite) TestParallelInstanceSetup(c *C) {
   187  	old := dirs.SnapDataDir
   188  	defer func() {
   189  		dirs.SnapDataDir = old
   190  	}()
   191  	dirs.SnapDataDir = "/var/snap"
   192  	snapEntry := osutil.MountEntry{Name: "/snap/snap-name_instance", Dir: "/snap/snap-name", Type: "none", Options: []string{"rbind", osutil.XSnapdOriginOvername()}}
   193  	dataEntry := osutil.MountEntry{Name: "/var/snap/snap-name_instance", Dir: "/var/snap/snap-name", Type: "none", Options: []string{"rbind", osutil.XSnapdOriginOvername()}}
   194  	fsEntry1 := osutil.MountEntry{Name: "/src-1", Dir: "/dst-1", Type: "none", Options: []string{"bind", "ro"}}
   195  	fsEntry2 := osutil.MountEntry{Name: "/src-2", Dir: "/dst-2", Type: "none", Options: []string{"bind", "ro"}}
   196  	userFsEntry := osutil.MountEntry{Name: "/src-3", Dir: "/dst-3", Type: "none", Options: []string{"bind", "ro"}}
   197  
   198  	// Give the plug a permanent effect
   199  	s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error {
   200  		if err := spec.AddMountEntry(fsEntry1); err != nil {
   201  			return err
   202  		}
   203  		return spec.AddUserMountEntry(userFsEntry)
   204  	}
   205  	// Give the slot a permanent effect
   206  	s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *snap.SlotInfo) error {
   207  		return spec.AddMountEntry(fsEntry2)
   208  	}
   209  
   210  	// confinement options are irrelevant to this security backend
   211  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "snap-name_instance", mockSnapYaml, 0)
   212  
   213  	// Check that snap fstab file contains parallel instance setup and data from interfaces
   214  	expected := strings.Join([]string{snapEntry.String(), dataEntry.String(), fsEntry2.String(), fsEntry1.String()}, "\n") + "\n"
   215  	fn := filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name_instance.fstab")
   216  	c.Check(fn, testutil.FileEquals, expected)
   217  
   218  	// Check that the user-fstab file was written with user mount only
   219  	fn = filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name_instance.user-fstab")
   220  	c.Check(fn, testutil.FileEquals, userFsEntry.String()+"\n")
   221  }
   222  
   223  func (s *backendSuite) TestSandboxFeatures(c *C) {
   224  	restore := cgroup.MockVersion(cgroup.V1, nil)
   225  	defer restore()
   226  	c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{
   227  		"layouts",
   228  		"mount-namespace",
   229  		"per-snap-persistency",
   230  		"per-snap-profiles",
   231  		"per-snap-updates",
   232  		"per-snap-user-profiles",
   233  		"stale-base-invalidation",
   234  		"freezer-cgroup-v1",
   235  	})
   236  
   237  	restore = cgroup.MockVersion(cgroup.V2, nil)
   238  	defer restore()
   239  	c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{
   240  		"layouts",
   241  		"mount-namespace",
   242  		"per-snap-persistency",
   243  		"per-snap-profiles",
   244  		"per-snap-updates",
   245  		"per-snap-user-profiles",
   246  		"stale-base-invalidation",
   247  	})
   248  }