File: raw_volume.go

package info (click to toggle)
snapd 2.49-1%2Bdeb11u2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 36,432 kB
  • sloc: ansic: 12,125; sh: 8,453; python: 2,163; makefile: 1,284; exp: 173; xml: 22
file content (155 lines) | stat: -rw-r--r-- 5,093 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
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2019 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

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/snapcore/snapd/interfaces"
	"github.com/snapcore/snapd/interfaces/apparmor"
	"github.com/snapcore/snapd/interfaces/udev"
	"github.com/snapcore/snapd/snap"
)

const rawVolumeSummary = `allows read/write access to specific disk partition`

// raw-volume grants full access to a particular disk partition. Since the
// volume is device-specific, it is desirable to limit the plugging snap's
// connection (eg to avoid situations of intending to grant access to a 'data'
// disk on one device but granting access to a 'system' disk on another).
// Therefore, require a snap declaration for connecting the interface at all.
const rawVolumeBaseDeclarationSlots = `
  raw-volume:
    allow-installation:
      slot-snap-type:
        - core
        - gadget
    deny-connection: true
    deny-auto-connection: true
`

// Only allow disk device partitions; not loop, ram, CDROM, generic SCSI,
// network, tape, raid, etc devices
const rawVolumeConnectedPlugAppArmorPath = `
# Description: can access disk partition read/write
%s rw,

# needed for write access
capability sys_admin,

# allow read access to sysfs and udev for block devices
@{PROC}/devices r,
/run/udev/data/b[0-9]*:[0-9]* r,
/sys/block/ r,
/sys/devices/**/block/** r,
`

// The type for this interface
type rawVolumeInterface struct{}

// Getter for the name of this interface
func (iface *rawVolumeInterface) Name() string {
	return "raw-volume"
}

func (iface *rawVolumeInterface) StaticInfo() interfaces.StaticInfo {
	return interfaces.StaticInfo{
		Summary:              rawVolumeSummary,
		BaseDeclarationSlots: rawVolumeBaseDeclarationSlots,
	}
}

func (iface *rawVolumeInterface) String() string {
	return iface.Name()
}

// https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
//
// For now, only list common devices and skip the following:
// - Acorn MFM mfma-mfmb
// - ACSI ada-adp
// - Parallel port IDE pda-pdd
// - Parallel port ATAPI pf0-3
// - USB block device uba-ubz
//
// The '0' partition number (eg, hda0) is omitted since it refers to the whole
// disk.

// IDE, MFM, RLL hda-hdt, 1-63 partitions:
const hdPat = `hd[a-t]([1-9]|[1-5][0-9]|6[0-3])`

// SCSI sda-sdiv, 1-15 partitions:
const sdPat = `sd([a-z]|[a-h][a-z]|i[a-v])([1-9]|1[0-5])`

// I2O i2o/hda-hddx, 1-15 partitions:
const i2oPat = `i2o/hd([a-z]|[a-c][a-z]|d[a-x])([1-9]|1[0-5])`

// MMC mmcblk0-999, 1-63 partitions (number of partitions is kernel cmdline
// configurable. Ubuntu uses 32, so use 64 for headroom):
const mmcPat = `mmcblk([0-9]|[1-9][0-9]{1,2})p([1-9]|[1-5][0-9]|6[0-3])`

// NVMe nvme0-99, 1-63 partitions with 1-63 optional namespaces:
const nvmePat = `nvme([0-9]|[1-9][0-9])(n([1-9]|[1-5][0-9]|6[0-3])){0,1}p([1-9]|[1-5][0-9]|6[0-3])`

// virtio vda-vdz, 1-63 partitions:
const vdPat = `vd[a-z]([1-9]|[1-5][0-9]|6[0-3])`

var rawVolumePartitionPattern = regexp.MustCompile(fmt.Sprintf("^/dev/(%s|%s|%s|%s|%s|%s)$", hdPat, sdPat, i2oPat, mmcPat, nvmePat, vdPat))

const invalidDeviceNodeSlotPathErrFmt = "slot %q path attribute must be a valid device node"

// Check validity of the defined slot
func (iface *rawVolumeInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
	_, err := verifySlotPathAttribute(&interfaces.SlotRef{Snap: slot.Snap.InstanceName(), Name: slot.Name}, slot, rawVolumePartitionPattern, invalidDeviceNodeSlotPathErrFmt)
	return err
}

func (iface *rawVolumeInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
	cleanedPath, err := verifySlotPathAttribute(slot.Ref(), slot, rawVolumePartitionPattern, invalidDeviceNodeSlotPathErrFmt)
	if err != nil {
		return nil
	}

	spec.AddSnippet(fmt.Sprintf(rawVolumeConnectedPlugAppArmorPath, cleanedPath))

	return nil
}

func (iface *rawVolumeInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
	cleanedPath, err := verifySlotPathAttribute(slot.Ref(), slot, rawVolumePartitionPattern, invalidDeviceNodeSlotPathErrFmt)
	if err != nil {
		return nil
	}

	spec.TagDevice(fmt.Sprintf(`KERNEL=="%s"`, strings.TrimPrefix(cleanedPath, "/dev/")))

	return nil
}

func (iface *rawVolumeInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
	// Allow what is allowed in the declarations
	return true
}

func init() {
	registerIface(&rawVolumeInterface{})
}