github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/interfaces/builtin/bool_file_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 builtin_test
    21  
    22  import (
    23  	"fmt"
    24  	"testing"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/interfaces"
    29  	"github.com/snapcore/snapd/interfaces/apparmor"
    30  	"github.com/snapcore/snapd/interfaces/builtin"
    31  	"github.com/snapcore/snapd/interfaces/dbus"
    32  	"github.com/snapcore/snapd/interfaces/seccomp"
    33  	"github.com/snapcore/snapd/interfaces/udev"
    34  	"github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/snap/snaptest"
    36  	"github.com/snapcore/snapd/testutil"
    37  )
    38  
    39  func Test(t *testing.T) {
    40  	TestingT(t)
    41  }
    42  
    43  type BoolFileInterfaceSuite struct {
    44  	testutil.BaseTest
    45  	iface                 interfaces.Interface
    46  	gpioSlotInfo          *snap.SlotInfo
    47  	gpioSlot              *interfaces.ConnectedSlot
    48  	gpioCleanedSlotInfo   *snap.SlotInfo
    49  	gpioCleanedSlot       *interfaces.ConnectedSlot
    50  	ledSlotInfo           *snap.SlotInfo
    51  	ledSlot               *interfaces.ConnectedSlot
    52  	badPathSlotInfo       *snap.SlotInfo
    53  	badPathSlot           *interfaces.ConnectedSlot
    54  	parentDirPathSlotInfo *snap.SlotInfo
    55  	parentDirPathSlot     *interfaces.ConnectedSlot
    56  	missingPathSlotInfo   *snap.SlotInfo
    57  	missingPathSlot       *interfaces.ConnectedSlot
    58  	badInterfaceSlot      *interfaces.ConnectedSlot
    59  	plugInfo              *snap.PlugInfo
    60  	plug                  *interfaces.ConnectedPlug
    61  	badInterfaceSlotInfo  *snap.SlotInfo
    62  	badInterfacePlugInfo  *snap.PlugInfo
    63  	badInterfacePlug      *interfaces.ConnectedPlug
    64  }
    65  
    66  var _ = Suite(&BoolFileInterfaceSuite{
    67  	iface: builtin.MustInterface("bool-file"),
    68  })
    69  
    70  func (s *BoolFileInterfaceSuite) SetUpTest(c *C) {
    71  	plugSnapinfo := snaptest.MockInfo(c, `
    72  name: other
    73  version: 0
    74  plugs:
    75   plug: bool-file
    76  apps:
    77   app:
    78    command: foo
    79  `, nil)
    80  	info := snaptest.MockInfo(c, `
    81  name: ubuntu-core
    82  version: 0
    83  slots:
    84      gpio:
    85          interface: bool-file
    86          path: /sys/class/gpio/gpio13/value
    87      gpio-unclean:
    88          interface: bool-file
    89          path: /sys/class/gpio/gpio14/value///
    90      led:
    91          interface: bool-file
    92          path: "/sys/class/leds/input27::capslock/brightness"
    93      missing-path: bool-file
    94      bad-path:
    95          interface: bool-file
    96          path: path
    97      parent-dir-path:
    98          interface: bool-file
    99          path: "/sys/class/gpio/../value"
   100      bad-interface-slot: other-interface
   101  plugs:
   102      plug: bool-file
   103      bad-interface-plug: other-interface
   104  `, &snap.SideInfo{})
   105  	s.gpioSlotInfo = info.Slots["gpio"]
   106  	s.gpioSlot = interfaces.NewConnectedSlot(s.gpioSlotInfo, nil, nil)
   107  	s.gpioCleanedSlotInfo = info.Slots["gpio-unclean"]
   108  	s.gpioCleanedSlot = interfaces.NewConnectedSlot(s.gpioCleanedSlotInfo, nil, nil)
   109  	s.ledSlotInfo = info.Slots["led"]
   110  	s.ledSlot = interfaces.NewConnectedSlot(s.ledSlotInfo, nil, nil)
   111  	s.missingPathSlotInfo = info.Slots["missing-path"]
   112  	s.missingPathSlot = interfaces.NewConnectedSlot(s.missingPathSlotInfo, nil, nil)
   113  	s.badPathSlotInfo = info.Slots["bad-path"]
   114  	s.badPathSlot = interfaces.NewConnectedSlot(s.badPathSlotInfo, nil, nil)
   115  	s.parentDirPathSlotInfo = info.Slots["parent-dir-path"]
   116  	s.parentDirPathSlot = interfaces.NewConnectedSlot(s.parentDirPathSlotInfo, nil, nil)
   117  	s.badInterfaceSlotInfo = info.Slots["bad-interface-slot"]
   118  	s.badInterfaceSlot = interfaces.NewConnectedSlot(s.badInterfaceSlotInfo, nil, nil)
   119  	s.plugInfo = plugSnapinfo.Plugs["plug"]
   120  	s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil)
   121  	s.badInterfacePlugInfo = info.Plugs["bad-interface-plug"]
   122  	s.badInterfacePlug = interfaces.NewConnectedPlug(s.badInterfacePlugInfo, nil, nil)
   123  }
   124  
   125  // TODO: add test for permanent slot when we have hook support.
   126  
   127  func (s *BoolFileInterfaceSuite) TestName(c *C) {
   128  	c.Assert(s.iface.Name(), Equals, "bool-file")
   129  }
   130  
   131  func (s *BoolFileInterfaceSuite) TestSanitizeSlot(c *C) {
   132  	// Both LED and GPIO slots are accepted
   133  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.ledSlotInfo), IsNil)
   134  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gpioSlotInfo), IsNil)
   135  	// Slots without the "path" attribute are rejected.
   136  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.missingPathSlotInfo), ErrorMatches,
   137  		"bool-file must contain the path attribute")
   138  	// Slots without the "path" attribute are rejected.
   139  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.parentDirPathSlotInfo), ErrorMatches,
   140  		"bool-file can only point at LED brightness or GPIO value")
   141  	// Slots with incorrect value of the "path" attribute are rejected.
   142  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.badPathSlotInfo), ErrorMatches,
   143  		"bool-file can only point at LED brightness or GPIO value")
   144  	// Verify historically filepath.Clean()d paths are still valid
   145  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gpioCleanedSlotInfo), IsNil)
   146  }
   147  
   148  func (s *BoolFileInterfaceSuite) TestSanitizePlug(c *C) {
   149  	c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
   150  }
   151  
   152  func (s *BoolFileInterfaceSuite) TestPlugSnippetHandlesSymlinkErrors(c *C) {
   153  	// Symbolic link traversal is handled correctly
   154  	builtin.MockEvalSymlinks(&s.BaseTest, func(path string) (string, error) {
   155  		return "", fmt.Errorf("broken symbolic link")
   156  	})
   157  
   158  	apparmorSpec := &apparmor.Specification{}
   159  	err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.gpioSlot)
   160  	c.Assert(err, ErrorMatches, "cannot compute plug security snippet: broken symbolic link")
   161  	c.Assert(apparmorSpec.SecurityTags(), HasLen, 0)
   162  }
   163  
   164  func (s *BoolFileInterfaceSuite) TestPlugSnippetDereferencesSymlinks(c *C) {
   165  	// Use a fake (successful) dereferencing function for the remainder of the test.
   166  	builtin.MockEvalSymlinks(&s.BaseTest, func(path string) (string, error) {
   167  		return "(dereferenced)" + path, nil
   168  	})
   169  	// Extra apparmor permission to access GPIO value
   170  	// The path uses dereferenced symbolic links.
   171  	apparmorSpec := &apparmor.Specification{}
   172  	err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.gpioSlot)
   173  	c.Assert(err, IsNil)
   174  	c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"})
   175  	c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Equals, "(dereferenced)/sys/class/gpio/gpio13/value rwk,")
   176  	// Extra apparmor permission to access LED brightness.
   177  	// The path uses dereferenced symbolic links.
   178  	apparmorSpec = &apparmor.Specification{}
   179  	err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.ledSlot)
   180  	c.Assert(err, IsNil)
   181  	c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"})
   182  	c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Equals, "(dereferenced)/sys/class/leds/input27::capslock/brightness rwk,")
   183  }
   184  
   185  func (s *BoolFileInterfaceSuite) TestConnectedPlugSnippetPanicksOnUnsanitizedSlots(c *C) {
   186  	// Unsanitized slots should never be used and cause a panic.
   187  	c.Assert(func() {
   188  		apparmorSpec := &apparmor.Specification{}
   189  		apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.missingPathSlot)
   190  	}, PanicMatches, "slot is not sanitized")
   191  }
   192  
   193  func (s *BoolFileInterfaceSuite) TestConnectedPlugSnippetUnusedSecuritySystems(c *C) {
   194  	for _, slot := range []*interfaces.ConnectedSlot{s.ledSlot, s.gpioSlot} {
   195  		// No extra seccomp permissions for plug
   196  		seccompSpec := &seccomp.Specification{}
   197  		err := seccompSpec.AddConnectedPlug(s.iface, s.plug, slot)
   198  		c.Assert(err, IsNil)
   199  		c.Assert(seccompSpec.Snippets(), HasLen, 0)
   200  		// No extra dbus permissions for plug
   201  		dbusSpec := &dbus.Specification{}
   202  		err = dbusSpec.AddConnectedPlug(s.iface, s.plug, slot)
   203  		c.Assert(err, IsNil)
   204  		c.Assert(dbusSpec.Snippets(), HasLen, 0)
   205  		// No extra udev permissions for plug
   206  		udevSpec := &udev.Specification{}
   207  		c.Assert(udevSpec.AddConnectedPlug(s.iface, s.plug, slot), IsNil)
   208  		c.Assert(udevSpec.Snippets(), HasLen, 0)
   209  	}
   210  }
   211  
   212  func (s *BoolFileInterfaceSuite) TestPermanentPlugSnippetUnusedSecuritySystems(c *C) {
   213  	// No extra seccomp permissions for plug
   214  	seccompSpec := &seccomp.Specification{}
   215  	err := seccompSpec.AddPermanentPlug(s.iface, s.plugInfo)
   216  	c.Assert(err, IsNil)
   217  	c.Assert(seccompSpec.Snippets(), HasLen, 0)
   218  	// No extra dbus permissions for plug
   219  	dbusSpec := &dbus.Specification{}
   220  	err = dbusSpec.AddPermanentPlug(s.iface, s.plugInfo)
   221  	c.Assert(err, IsNil)
   222  	c.Assert(dbusSpec.Snippets(), HasLen, 0)
   223  	// No extra udev permissions for plug
   224  	udevSpec := &udev.Specification{}
   225  	c.Assert(udevSpec.AddPermanentPlug(s.iface, s.plugInfo), IsNil)
   226  	c.Assert(udevSpec.Snippets(), HasLen, 0)
   227  }
   228  
   229  func (s *BoolFileInterfaceSuite) TestInterfaces(c *C) {
   230  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   231  }