File: bool_file_test.go

package info (click to toggle)
snapd 2.71-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 79,536 kB
  • sloc: ansic: 16,114; sh: 16,105; python: 9,941; makefile: 1,890; exp: 190; awk: 40; xml: 22
file content (273 lines) | stat: -rw-r--r-- 10,770 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2016 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package builtin_test

import (
	"fmt"
	"testing"

	. "gopkg.in/check.v1"

	"github.com/snapcore/snapd/interfaces"
	"github.com/snapcore/snapd/interfaces/apparmor"
	"github.com/snapcore/snapd/interfaces/builtin"
	"github.com/snapcore/snapd/interfaces/dbus"
	"github.com/snapcore/snapd/interfaces/seccomp"
	"github.com/snapcore/snapd/interfaces/udev"
	"github.com/snapcore/snapd/snap"
	"github.com/snapcore/snapd/snap/snaptest"
	"github.com/snapcore/snapd/testutil"
)

func Test(t *testing.T) {
	TestingT(t)
}

type BoolFileInterfaceSuite struct {
	testutil.BaseTest
	iface                 interfaces.Interface
	gpioSlotInfo          *snap.SlotInfo
	gpioSlot              *interfaces.ConnectedSlot
	gpioCleanedSlotInfo   *snap.SlotInfo
	gpioCleanedSlot       *interfaces.ConnectedSlot
	ledSlotInfo           *snap.SlotInfo
	ledSlot               *interfaces.ConnectedSlot
	badPathSlotInfo       *snap.SlotInfo
	badPathSlot           *interfaces.ConnectedSlot
	parentDirPathSlotInfo *snap.SlotInfo
	parentDirPathSlot     *interfaces.ConnectedSlot
	missingPathSlotInfo   *snap.SlotInfo
	missingPathSlot       *interfaces.ConnectedSlot
	badInterfaceSlot      *interfaces.ConnectedSlot
	plugInfo              *snap.PlugInfo
	plug                  *interfaces.ConnectedPlug
	badInterfaceSlotInfo  *snap.SlotInfo
	badInterfacePlugInfo  *snap.PlugInfo
	badInterfacePlug      *interfaces.ConnectedPlug
}

var _ = Suite(&BoolFileInterfaceSuite{
	iface: builtin.MustInterface("bool-file"),
})

func (s *BoolFileInterfaceSuite) SetUpTest(c *C) {
	info := snaptest.MockInfo(c, `
name: ubuntu-core
version: 0
slots:
    gpio:
        interface: bool-file
        path: /sys/class/gpio/gpio13/value
    gpio-unclean:
        interface: bool-file
        path: /sys/class/gpio/gpio14/value///
    led:
        interface: bool-file
        path: "/sys/class/leds/input27::capslock/brightness"
    missing-path: bool-file
    bad-path:
        interface: bool-file
        path: path
    parent-dir-path:
        interface: bool-file
        path: "/sys/class/gpio/../value"
    bad-interface-slot: other-interface
plugs:
    plug: bool-file
    bad-interface-plug: other-interface
`, &snap.SideInfo{})
	appSet, err := interfaces.NewSnapAppSet(info, nil)
	c.Assert(err, IsNil)

	s.gpioSlotInfo = info.Slots["gpio"]
	s.gpioSlot = interfaces.NewConnectedSlot(s.gpioSlotInfo, appSet, nil, nil)
	s.gpioCleanedSlotInfo = info.Slots["gpio-unclean"]
	s.gpioCleanedSlot = interfaces.NewConnectedSlot(s.gpioCleanedSlotInfo, appSet, nil, nil)
	s.ledSlotInfo = info.Slots["led"]
	s.ledSlot = interfaces.NewConnectedSlot(s.ledSlotInfo, appSet, nil, nil)
	s.missingPathSlotInfo = info.Slots["missing-path"]
	s.missingPathSlot = interfaces.NewConnectedSlot(s.missingPathSlotInfo, appSet, nil, nil)
	s.badPathSlotInfo = info.Slots["bad-path"]
	s.badPathSlot = interfaces.NewConnectedSlot(s.badPathSlotInfo, appSet, nil, nil)
	s.parentDirPathSlotInfo = info.Slots["parent-dir-path"]
	s.parentDirPathSlot = interfaces.NewConnectedSlot(s.parentDirPathSlotInfo, appSet, nil, nil)
	s.badInterfaceSlotInfo = info.Slots["bad-interface-slot"]
	s.badInterfaceSlot = interfaces.NewConnectedSlot(s.badInterfaceSlotInfo, appSet, nil, nil)
	s.badInterfacePlugInfo = info.Plugs["bad-interface-plug"]
	s.badInterfacePlug = interfaces.NewConnectedPlug(s.badInterfacePlugInfo, appSet, nil, nil)

	plugSnapinfo := snaptest.MockInfo(c, `
name: other
version: 0
plugs:
 plug: bool-file
apps:
 app:
  command: foo
`, nil)
	appSet, err = interfaces.NewSnapAppSet(plugSnapinfo, nil)
	c.Assert(err, IsNil)

	s.plugInfo = plugSnapinfo.Plugs["plug"]
	s.plug = interfaces.NewConnectedPlug(s.plugInfo, appSet, nil, nil)
}

// TODO: add test for permanent slot when we have hook support.

func (s *BoolFileInterfaceSuite) TestName(c *C) {
	c.Assert(s.iface.Name(), Equals, "bool-file")
}

func (s *BoolFileInterfaceSuite) TestSanitizeSlot(c *C) {
	// Both LED and GPIO slots are accepted
	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.ledSlotInfo), IsNil)
	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gpioSlotInfo), IsNil)
	// Slots without the "path" attribute are rejected.
	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.missingPathSlotInfo), ErrorMatches,
		"bool-file must contain the path attribute")
	// Slots without the "path" attribute are rejected.
	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.parentDirPathSlotInfo), ErrorMatches,
		"bool-file can only point at LED brightness or GPIO value")
	// Slots with incorrect value of the "path" attribute are rejected.
	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.badPathSlotInfo), ErrorMatches,
		"bool-file can only point at LED brightness or GPIO value")
	// Verify historically filepath.Clean()d paths are still valid
	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gpioCleanedSlotInfo), IsNil)
}

func (s *BoolFileInterfaceSuite) TestSanitizePlug(c *C) {
	c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
}

func (s *BoolFileInterfaceSuite) TestPlugSnippetHandlesSymlinkErrors(c *C) {
	// Symbolic link traversal is handled correctly
	builtin.MockEvalSymlinks(&s.BaseTest, func(path string) (string, error) {
		return "", fmt.Errorf("broken symbolic link")
	})

	apparmorSpec := apparmor.NewSpecification(s.plug.AppSet())
	err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.gpioSlot)
	c.Assert(err, ErrorMatches, "cannot compute plug security snippet: broken symbolic link")
	c.Assert(apparmorSpec.SecurityTags(), HasLen, 0)
}

func (s *BoolFileInterfaceSuite) TestAddConnectedPlugAdditionalSnippetsForLeds(c *C) {
	// Use a fake eval that returns just the path
	builtin.MockEvalSymlinks(&s.BaseTest, func(path string) (string, error) {
		return path, nil
	})
	// Using a led that doesn't match, does not add
	apparmorSpec := apparmor.NewSpecification(s.plug.AppSet())
	err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.ledSlot)
	c.Assert(err, IsNil)
	c.Assert(apparmorSpec.Snippets(), DeepEquals, map[string][]string{
		"snap.other.app": {
			"/sys/class/leds/input27::capslock/brightness rwk,",
		},
	})

	// Make the fake eval return a path that successfully leads to added snippets
	builtin.MockEvalSymlinks(&s.BaseTest, func(path string) (string, error) {
		return "/sys/devices/platform/leds/leds/status-grn-led/brightness", nil
	})

	// Make sure that using a path that matches the boolFileLedPattern adds the
	// additional snippets
	apparmorSpec2 := apparmor.NewSpecification(s.plug.AppSet())
	err = apparmorSpec2.AddConnectedPlug(s.iface, s.plug, s.ledSlot)
	c.Assert(err, IsNil)
	c.Assert(apparmorSpec2.Snippets(), DeepEquals, map[string][]string{
		"snap.other.app": {
			"/sys/devices/platform/leds/leds/status-grn-led/brightness rwk,",
			"/sys/devices/platform/leds/leds/status-grn-led/delay_off rw,",
			"/sys/devices/platform/leds/leds/status-grn-led/delay_on rw,",
			"/sys/devices/platform/leds/leds/status-grn-led/trigger rw,",
		},
	})
}

func (s *BoolFileInterfaceSuite) TestPlugSnippetDereferencesSymlinks(c *C) {
	// Use a fake (successful) dereferencing function for the remainder of the test.
	builtin.MockEvalSymlinks(&s.BaseTest, func(path string) (string, error) {
		return "(dereferenced)" + path, nil
	})
	// Extra apparmor permission to access GPIO value
	// The path uses dereferenced symbolic links.
	apparmorSpec := apparmor.NewSpecification(s.plug.AppSet())
	err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.gpioSlot)
	c.Assert(err, IsNil)
	c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"})
	c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Equals, "(dereferenced)/sys/class/gpio/gpio13/value rwk,")
	// Extra apparmor permission to access LED brightness.
	// The path uses dereferenced symbolic links.
	apparmorSpec = apparmor.NewSpecification(s.plug.AppSet())
	err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.ledSlot)
	c.Assert(err, IsNil)
	c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"})
	c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Equals, "(dereferenced)/sys/class/leds/input27::capslock/brightness rwk,")
}

func (s *BoolFileInterfaceSuite) TestConnectedPlugSnippetPanicksOnUnsanitizedSlots(c *C) {
	// Unsanitized slots should never be used and cause a panic.
	c.Assert(func() {
		apparmorSpec := apparmor.NewSpecification(s.plug.AppSet())
		apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.missingPathSlot)
	}, PanicMatches, "slot is not sanitized")
}

func (s *BoolFileInterfaceSuite) TestConnectedPlugSnippetUnusedSecuritySystems(c *C) {
	for _, slot := range []*interfaces.ConnectedSlot{s.ledSlot, s.gpioSlot} {
		// No extra seccomp permissions for plug
		seccompSpec := seccomp.NewSpecification(s.plug.AppSet())
		err := seccompSpec.AddConnectedPlug(s.iface, s.plug, slot)
		c.Assert(err, IsNil)
		c.Assert(seccompSpec.Snippets(), HasLen, 0)
		// No extra dbus permissions for plug
		dbusSpec := dbus.NewSpecification(s.plug.AppSet())
		err = dbusSpec.AddConnectedPlug(s.iface, s.plug, slot)
		c.Assert(err, IsNil)
		c.Assert(dbusSpec.Snippets(), HasLen, 0)
		// No extra udev permissions for plug
		udevSpec := udev.NewSpecification(s.plug.AppSet())
		c.Assert(udevSpec.AddConnectedPlug(s.iface, s.plug, slot), IsNil)
		c.Assert(udevSpec.Snippets(), HasLen, 0)
	}
}

func (s *BoolFileInterfaceSuite) TestPermanentPlugSnippetUnusedSecuritySystems(c *C) {
	// No extra seccomp permissions for plug
	seccompSpec := seccomp.NewSpecification(s.plug.AppSet())
	err := seccompSpec.AddPermanentPlug(s.iface, s.plugInfo)
	c.Assert(err, IsNil)
	c.Assert(seccompSpec.Snippets(), HasLen, 0)
	// No extra dbus permissions for plug
	dbusSpec := dbus.NewSpecification(s.plug.AppSet())
	err = dbusSpec.AddPermanentPlug(s.iface, s.plugInfo)
	c.Assert(err, IsNil)
	c.Assert(dbusSpec.Snippets(), HasLen, 0)
	// No extra udev permissions for plug
	udevSpec := udev.NewSpecification(s.plug.AppSet())
	c.Assert(udevSpec.AddPermanentPlug(s.iface, s.plugInfo), IsNil)
	c.Assert(udevSpec.Snippets(), HasLen, 0)
}

func (s *BoolFileInterfaceSuite) TestInterfaces(c *C) {
	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
}