gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/shared_memory_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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 builtin_test
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"gitee.com/mysnapcore/mysnapd/dirs"
    31  	"gitee.com/mysnapcore/mysnapd/interfaces"
    32  	"gitee.com/mysnapcore/mysnapd/interfaces/apparmor"
    33  	"gitee.com/mysnapcore/mysnapd/interfaces/builtin"
    34  	"gitee.com/mysnapcore/mysnapd/interfaces/mount"
    35  	"gitee.com/mysnapcore/mysnapd/osutil"
    36  	"gitee.com/mysnapcore/mysnapd/snap"
    37  	"gitee.com/mysnapcore/mysnapd/testutil"
    38  )
    39  
    40  type SharedMemoryInterfaceSuite struct {
    41  	testutil.BaseTest
    42  
    43  	iface            interfaces.Interface
    44  	slotInfo         *snap.SlotInfo
    45  	slot             *interfaces.ConnectedSlot
    46  	plugInfo         *snap.PlugInfo
    47  	plug             *interfaces.ConnectedPlug
    48  	wildcardPlugInfo *snap.PlugInfo
    49  	wildcardPlug     *interfaces.ConnectedPlug
    50  	wildcardSlotInfo *snap.SlotInfo
    51  	wildcardSlot     *interfaces.ConnectedSlot
    52  	privatePlugInfo  *snap.PlugInfo
    53  	privatePlug      *interfaces.ConnectedPlug
    54  	privateSlotInfo  *snap.SlotInfo
    55  	privateSlot      *interfaces.ConnectedSlot
    56  }
    57  
    58  var _ = Suite(&SharedMemoryInterfaceSuite{
    59  	iface: builtin.MustInterface("shared-memory"),
    60  })
    61  
    62  const sharedMemoryConsumerYaml = `name: consumer
    63  version: 0
    64  plugs:
    65   shmem:
    66    interface: shared-memory
    67    shared-memory: foo
    68    private: false
    69   shmem-wildcard:
    70    interface: shared-memory
    71    shared-memory: foo-wildcard
    72    private: false
    73   shmem-private:
    74    interface: shared-memory
    75    private: true
    76  apps:
    77   app:
    78    plugs: [shmem]
    79  `
    80  
    81  const sharedMemoryProviderYaml = `name: provider
    82  version: 0
    83  slots:
    84   shmem:
    85    interface: shared-memory
    86    shared-memory: foo
    87    write: [ bar ]
    88    read: [ bar-ro ]
    89    private: false
    90   shmem-wildcard:
    91    interface: shared-memory
    92    shared-memory: foo-wildcard
    93    write: [ bar* ]
    94    read: [ bar-ro* ]
    95    private: false
    96  apps:
    97   app:
    98    slots: [shmem]
    99  `
   100  
   101  const sharedMemoryCoreYaml = `name: core
   102  version: 0
   103  type: os
   104  slots:
   105   shared-memory:
   106    interface: shared-memory
   107  apps:
   108   app:
   109  `
   110  
   111  func (s *SharedMemoryInterfaceSuite) SetUpTest(c *C) {
   112  	s.BaseTest.SetUpTest(c)
   113  
   114  	s.plug, s.plugInfo = MockConnectedPlug(c, sharedMemoryConsumerYaml, nil, "shmem")
   115  	s.slot, s.slotInfo = MockConnectedSlot(c, sharedMemoryProviderYaml, nil, "shmem")
   116  
   117  	s.wildcardPlug, s.wildcardPlugInfo = MockConnectedPlug(c, sharedMemoryConsumerYaml, nil, "shmem-wildcard")
   118  	s.wildcardSlot, s.wildcardSlotInfo = MockConnectedSlot(c, sharedMemoryProviderYaml, nil, "shmem-wildcard")
   119  
   120  	s.privatePlug, s.privatePlugInfo = MockConnectedPlug(c, sharedMemoryConsumerYaml, nil, "shmem-private")
   121  	s.privateSlot, s.privateSlotInfo = MockConnectedSlot(c, sharedMemoryCoreYaml, nil, "shared-memory")
   122  }
   123  
   124  func (s *SharedMemoryInterfaceSuite) TestName(c *C) {
   125  	c.Assert(s.iface.Name(), Equals, "shared-memory")
   126  }
   127  
   128  func (s *SharedMemoryInterfaceSuite) TestSanitizePlug(c *C) {
   129  	c.Check(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
   130  	c.Check(interfaces.BeforeConnectPlug(s.iface, s.plug), IsNil)
   131  
   132  	c.Check(interfaces.BeforePreparePlug(s.iface, s.wildcardPlugInfo), IsNil)
   133  	c.Check(interfaces.BeforeConnectPlug(s.iface, s.wildcardPlug), IsNil)
   134  }
   135  
   136  func (s *SharedMemoryInterfaceSuite) TestSanitizePlugUnhappy(c *C) {
   137  	var sharedMemoryYaml = `name: consumer
   138  version: 0
   139  plugs:
   140   shmem:
   141    interface: shared-memory
   142    %s
   143  apps:
   144   app:
   145    plugs: [shmem]
   146  `
   147  	data := []struct {
   148  		plugYaml      string
   149  		expectedError string
   150  	}{
   151  		{
   152  			"shared-memory: [one two]",
   153  			`shared-memory "shared-memory" attribute must be a string, not \[one two\]`,
   154  		},
   155  		{
   156  			"private: hello",
   157  			`shared-memory "private" attribute must be a bool, not hello`,
   158  		},
   159  		{
   160  			"private: true\n  shared-memory: foo",
   161  			`shared-memory "shared-memory" attribute must not be set together with "private: true"`,
   162  		},
   163  	}
   164  
   165  	for _, testData := range data {
   166  		snapYaml := fmt.Sprintf(sharedMemoryYaml, testData.plugYaml)
   167  		_, plug := MockConnectedPlug(c, snapYaml, nil, "shmem")
   168  		err := interfaces.BeforePreparePlug(s.iface, plug)
   169  		c.Check(err, ErrorMatches, testData.expectedError, Commentf("yaml: %s", testData.plugYaml))
   170  	}
   171  }
   172  
   173  func (s *SharedMemoryInterfaceSuite) TestPlugPrivateAttribute(c *C) {
   174  	const snapYaml = `name: consumer
   175  version: 0
   176  plugs:
   177   shmem:
   178    interface: shared-memory
   179    private: true
   180  apps:
   181   app:
   182    plugs: [shmem]
   183  `
   184  	_, plug := MockConnectedPlug(c, snapYaml, nil, "shmem")
   185  	err := interfaces.BeforePreparePlug(s.iface, plug)
   186  	c.Assert(err, IsNil)
   187  	c.Check(plug.Attrs["private"], Equals, true)
   188  	c.Check(plug.Attrs["shared-memory"], Equals, nil)
   189  }
   190  
   191  func (s *SharedMemoryInterfaceSuite) TestPlugPrivateConflictsWithNonPrivate(c *C) {
   192  	const snapYaml1 = `name: consumer
   193  version: 0
   194  plugs:
   195    shmem:
   196      interface: shared-memory
   197    shmem-private:
   198      interface: shared-memory
   199      private: true
   200  `
   201  	_, plug := MockConnectedPlug(c, snapYaml1, nil, "shmem-private")
   202  	err := interfaces.BeforePreparePlug(s.iface, plug)
   203  	c.Check(err, ErrorMatches, `shared-memory plug with "private: true" set cannot be used with other shared-memory plugs`)
   204  
   205  	const snapYaml2 = `name: consumer
   206  version: 0
   207  plugs:
   208    shmem-private:
   209      interface: shared-memory
   210      private: true
   211  slots:
   212    shmem:
   213      interface: shared-memory
   214  `
   215  	_, plug = MockConnectedPlug(c, snapYaml2, nil, "shmem-private")
   216  	err = interfaces.BeforePreparePlug(s.iface, plug)
   217  	c.Check(err, ErrorMatches, `shared-memory plug with \"private: true\" set cannot be used with shared-memory slots`)
   218  }
   219  
   220  func (s *SharedMemoryInterfaceSuite) TestPlugShmAttribute(c *C) {
   221  	var plugYamlTemplate = `name: consumer
   222  version: 0
   223  plugs:
   224   shmem:
   225    interface: shared-memory
   226    %s
   227  apps:
   228   app:
   229    plugs: [shmem]
   230  `
   231  
   232  	data := []struct {
   233  		plugYaml     string
   234  		expectedName string
   235  	}{
   236  		{
   237  			"",      // missing "shared-memory" attribute
   238  			"shmem", // use the name of the plug
   239  		},
   240  		{
   241  			"shared-memory: shmemFoo",
   242  			"shmemFoo",
   243  		},
   244  	}
   245  
   246  	for _, testData := range data {
   247  		snapYaml := fmt.Sprintf(plugYamlTemplate, testData.plugYaml)
   248  		_, plug := MockConnectedPlug(c, snapYaml, nil, "shmem")
   249  		err := interfaces.BeforePreparePlug(s.iface, plug)
   250  		c.Assert(err, IsNil)
   251  		c.Check(plug.Attrs["private"], Equals, false,
   252  			Commentf(`yaml: %q`, testData.plugYaml))
   253  		c.Check(plug.Attrs["shared-memory"], Equals, testData.expectedName,
   254  			Commentf(`yaml: %q`, testData.plugYaml))
   255  	}
   256  }
   257  
   258  func (s *SharedMemoryInterfaceSuite) TestSanitizeSlot(c *C) {
   259  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
   260  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.wildcardSlotInfo), IsNil)
   261  }
   262  
   263  func (s *SharedMemoryInterfaceSuite) TestSanitizeSlotUnhappy(c *C) {
   264  	var sharedMemoryYaml = `name: provider
   265  version: 0
   266  slots:
   267   shmem:
   268    interface: shared-memory
   269    %s
   270  apps:
   271   app:
   272    slots: [shmem]
   273  `
   274  	data := []struct {
   275  		slotYaml      string
   276  		expectedError string
   277  	}{
   278  		{
   279  			"shared-memory: 12",
   280  			`shared-memory "shared-memory" attribute must be a string, not 12`,
   281  		},
   282  		{
   283  			"", // missing "write" attribute
   284  			`shared memory interface requires at least a valid "read" or "write" attribute`,
   285  		},
   286  		{
   287  			"write: a string",
   288  			`shared-memory "write" attribute must be a list of strings, not "a string"`,
   289  		},
   290  		{
   291  			"read: [Mixed, 12, False, list]",
   292  			`shared-memory "read" attribute must be a list of strings, not "\[Mixed 12 false list\]"`,
   293  		},
   294  		{
   295  			`read: ["ok", "trailing-space "]`,
   296  			`shared-memory interface path has leading or trailing spaces: "trailing-space "`,
   297  		},
   298  		{
   299  			`write: [" leading-space"]`,
   300  			`shared-memory interface path has leading or trailing spaces: " leading-space"`,
   301  		},
   302  		{
   303  			`write: [""]`,
   304  			`shared-memory interface path is empty`,
   305  		},
   306  		{
   307  			`write: [mem**]`,
   308  			`shared-memory interface path is invalid: "mem\*\*" contains \*\* which is unsupported.*`,
   309  		},
   310  		{
   311  			`read: [..]`,
   312  			`shared-memory interface path is not clean: ".."`,
   313  		},
   314  		{
   315  			`write: [/dev/shm/bar]`,
   316  			`shared-memory interface path should not contain '/': "/dev/shm/bar"`,
   317  		},
   318  		{
   319  			`write: [mem/../etc]`,
   320  			`shared-memory interface path should not contain '/': "mem/../etc"`,
   321  		},
   322  		{
   323  			"write: [valid]\n  read: [../invalid]",
   324  			`shared-memory interface path should not contain '/': "../invalid"`,
   325  		},
   326  		{
   327  			"read: [valid]\n  write: [../invalid]",
   328  			`shared-memory interface path should not contain '/': "../invalid"`,
   329  		},
   330  	}
   331  
   332  	for _, testData := range data {
   333  		snapYaml := fmt.Sprintf(sharedMemoryYaml, testData.slotYaml)
   334  		_, slot := MockConnectedSlot(c, snapYaml, nil, "shmem")
   335  		err := interfaces.BeforePrepareSlot(s.iface, slot)
   336  		c.Check(err, ErrorMatches, testData.expectedError, Commentf("yaml: %s", testData.slotYaml))
   337  	}
   338  }
   339  
   340  func (s *SharedMemoryInterfaceSuite) TestSlotShmAttribute(c *C) {
   341  	var slotYamlTemplate = `name: consumer
   342  version: 0
   343  slots:
   344   shmem:
   345    interface: shared-memory
   346    write: [foo]
   347    %s
   348  apps:
   349   app:
   350    slots: [shmem]
   351  `
   352  
   353  	data := []struct {
   354  		slotYaml     string
   355  		expectedName string
   356  	}{
   357  		{
   358  			"",      // missing "shared-memory" attribute
   359  			"shmem", // use the name of the slot
   360  		},
   361  		{
   362  			"shared-memory: shmemBar",
   363  			"shmemBar",
   364  		},
   365  	}
   366  
   367  	for _, testData := range data {
   368  		snapYaml := fmt.Sprintf(slotYamlTemplate, testData.slotYaml)
   369  		_, slot := MockConnectedSlot(c, snapYaml, nil, "shmem")
   370  		err := interfaces.BeforePrepareSlot(s.iface, slot)
   371  		c.Assert(err, IsNil)
   372  		c.Check(slot.Attrs["shared-memory"], Equals, testData.expectedName,
   373  			Commentf(`yaml: %q`, testData.slotYaml))
   374  	}
   375  }
   376  
   377  func (s *SharedMemoryInterfaceSuite) TestStaticInfo(c *C) {
   378  	si := interfaces.StaticInfoOf(s.iface)
   379  	c.Check(si.ImplicitOnCore, Equals, true)
   380  	c.Check(si.ImplicitOnClassic, Equals, true)
   381  	c.Check(si.Summary, Equals, `allows two snaps to use predefined shared memory objects`)
   382  	c.Check(si.BaseDeclarationSlots, testutil.Contains, "shared-memory")
   383  }
   384  
   385  func (s *SharedMemoryInterfaceSuite) TestAppArmorSpec(c *C) {
   386  	spec := &apparmor.Specification{}
   387  
   388  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
   389  	plugSnippet := spec.SnippetForTag("snap.consumer.app")
   390  
   391  	c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil)
   392  	slotSnippet := spec.SnippetForTag("snap.provider.app")
   393  
   394  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.provider.app"})
   395  
   396  	c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar" mrwlk,`)
   397  	c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" r,`)
   398  
   399  	// Slot has read-write permissions to all paths
   400  	c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar" mrwlk,`)
   401  	c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" mrwlk,`)
   402  
   403  	wildcardSpec := &apparmor.Specification{}
   404  	c.Assert(wildcardSpec.AddConnectedPlug(s.iface, s.wildcardPlug, s.wildcardSlot), IsNil)
   405  	wildcardPlugSnippet := wildcardSpec.SnippetForTag("snap.consumer.app")
   406  
   407  	c.Assert(wildcardSpec.AddConnectedSlot(s.iface, s.wildcardPlug, s.wildcardSlot), IsNil)
   408  	wildcardSlotSnippet := wildcardSpec.SnippetForTag("snap.provider.app")
   409  
   410  	c.Assert(wildcardSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.provider.app"})
   411  
   412  	c.Check(wildcardPlugSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" mrwlk,`)
   413  	c.Check(wildcardPlugSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro*" r,`)
   414  
   415  	// Slot has read-write permissions to all paths
   416  	c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" mrwlk,`)
   417  	c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro*" mrwlk,`)
   418  
   419  	spec = &apparmor.Specification{}
   420  	c.Assert(spec.AddConnectedPlug(s.iface, s.privatePlug, s.privateSlot), IsNil)
   421  	privatePlugSnippet := spec.SnippetForTag("snap.consumer.app")
   422  	privateUpdateNS := spec.UpdateNS()
   423  
   424  	c.Assert(spec.AddConnectedSlot(s.iface, s.privatePlug, s.privateSlot), IsNil)
   425  	privateSlotSnippet := spec.SnippetForTag("snap.core.app")
   426  
   427  	c.Check(privatePlugSnippet, testutil.Contains, `"/dev/shm/*" mrwlkix`)
   428  	c.Check(privateSlotSnippet, Equals, "")
   429  	c.Check(strings.Join(privateUpdateNS, ""), Equals, `  # Private /dev/shm
   430    /dev/ r,
   431    /dev/shm/{,**} rw,
   432    mount options=(bind, rw) /dev/shm/snap.consumer/ -> /dev/shm/,
   433    umount /dev/shm/,`)
   434  }
   435  
   436  func (s *SharedMemoryInterfaceSuite) TestMountSpec(c *C) {
   437  	tmpdir := c.MkDir()
   438  	dirs.SetRootDir(tmpdir)
   439  	defer dirs.SetRootDir("/")
   440  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/dev/shm"), 0777), IsNil)
   441  
   442  	// No mount entries for non-private shared-memory plugs
   443  	spec := &mount.Specification{}
   444  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
   445  	c.Check(spec.MountEntries(), HasLen, 0)
   446  
   447  	spec = &mount.Specification{}
   448  	c.Assert(spec.AddConnectedPlug(s.iface, s.privatePlug, s.privateSlot), IsNil)
   449  	mounts := []osutil.MountEntry{
   450  		{
   451  			Name:    filepath.Join(tmpdir, "/dev/shm/snap.consumer"),
   452  			Dir:     "/dev/shm",
   453  			Options: []string{"bind", "rw"},
   454  		},
   455  	}
   456  	c.Check(spec.MountEntries(), DeepEquals, mounts)
   457  
   458  	// Cannot set up mount entries if /dev/shm is a symlink
   459  	c.Assert(os.Remove(filepath.Join(tmpdir, "/dev/shm")), IsNil)
   460  	c.Assert(os.Symlink("/run/shm", filepath.Join(tmpdir, "/dev/shm")), IsNil)
   461  	spec = &mount.Specification{}
   462  	err := spec.AddConnectedPlug(s.iface, s.privatePlug, s.privateSlot)
   463  	c.Check(err, ErrorMatches, `shared-memory plug with "private: true" cannot be connected if ".*/dev/shm" is a symlink`)
   464  }
   465  
   466  func (s *SharedMemoryInterfaceSuite) TestAutoConnect(c *C) {
   467  	c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true)
   468  }
   469  
   470  func (s *SharedMemoryInterfaceSuite) TestInterfaces(c *C) {
   471  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   472  }