File: backend.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 (168 lines) | stat: -rw-r--r-- 6,685 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
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2016-2024 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 mount implements mounts that get mapped into the snap
//
// Snappy creates fstab like  configuration files that describe what
// directories from the system or from other snaps should get mapped
// into the snap.
//
// Each fstab like file looks like a regular fstab entry:
//
//	/src/dir /dst/dir none bind 0 0
//	/src/dir /dst/dir none bind,rw 0 0
//
// but only bind mounts are supported
package mount

import (
	"bytes"
	"fmt"
	"os"

	"github.com/snapcore/snapd/dirs"
	"github.com/snapcore/snapd/interfaces"
	"github.com/snapcore/snapd/logger"
	"github.com/snapcore/snapd/osutil"
	"github.com/snapcore/snapd/sandbox/cgroup"
	"github.com/snapcore/snapd/snap"
	"github.com/snapcore/snapd/timings"
)

// Backend is responsible for maintaining mount files for snap-confine
type Backend struct{}

// Initialize does nothing.
func (b *Backend) Initialize(*interfaces.SecurityBackendOptions) error {
	return nil
}

// Name returns the name of the backend.
func (b *Backend) Name() interfaces.SecuritySystem {
	return interfaces.SecurityMount
}

// Setup creates mount mount profile files specific to a given snap.
func (b *Backend) Setup(appSet *interfaces.SnapAppSet, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
	// Record all changes to the mount system for this snap.
	snapName := appSet.InstanceName()
	spec, err := repo.SnapSpecification(b.Name(), appSet, opts)
	if err != nil {
		return fmt.Errorf("cannot obtain mount security snippets for snap %q: %s", snapName, err)
	}

	snapInfo := appSet.Info()

	spec.(*Specification).AddOvername(snapInfo)
	spec.(*Specification).AddLayout(snapInfo)
	spec.(*Specification).AddExtraLayouts(opts.ExtraLayouts)
	content := deriveContent(spec.(*Specification), snapInfo)
	// synchronize the content with the filesystem
	glob := fmt.Sprintf("snap.%s.*fstab", snapName)
	dir := dirs.SnapMountPolicyDir
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("cannot create directory for mount configuration files %q: %s", dir, err)
	}
	if _, _, err := osutil.EnsureDirState(dir, glob, content); err != nil {
		return fmt.Errorf("cannot synchronize mount configuration files for snap %q: %s", snapName, err)
	}
	if err := UpdateSnapNamespace(snapName); err != nil {
		// try to discard the mount namespace but only if there aren't enduring daemons in the snap
		for _, app := range snapInfo.Apps {
			if app.Daemon != "" && app.RefreshMode == "endure" {
				return fmt.Errorf("cannot update mount namespace of snap %q, and cannot discard it because it contains an enduring daemon: %s", snapName, err)
			}
		}
		logger.Noticef("discarding mount namespace of snap %q due update failure: %v", snapName, err)
		// In some snaps, if the layout change from a version to the next by replacing a bind by a symlink,
		// the update can fail. Discarding the namespace allows to solve this.
		if err = DiscardSnapNamespace(snapName); err != nil {
			return fmt.Errorf("cannot discard mount namespace of snap %q when trying to update it: %s", snapName, err)
		}
	}
	return nil
}

// Remove removes mount configuration files of a given snap.
//
// This method should be called after removing a snap.
func (b *Backend) Remove(snapName string) error {
	glob := fmt.Sprintf("snap.%s.*fstab", snapName)
	_, _, err := osutil.EnsureDirState(dirs.SnapMountPolicyDir, glob, nil)
	if err != nil {
		return fmt.Errorf("cannot synchronize mount configuration files for snap %q: %s", snapName, err)
	}
	return DiscardSnapNamespace(snapName)
}

// addMountProfile adds a mount profile with the given name, based on the given entries.
//
// If there are no entries no profile is generated.
func addMountProfile(content map[string]osutil.FileState, fname string, entries []osutil.MountEntry) {
	if len(entries) == 0 {
		return
	}
	var buffer bytes.Buffer
	for _, entry := range entries {
		fmt.Fprintf(&buffer, "%s\n", entry)
	}
	content[fname] = &osutil.MemoryFileState{Content: buffer.Bytes(), Mode: 0644}
}

// deriveContent computes .fstab tables based on requests made to the specification.
func deriveContent(spec *Specification, snapInfo *snap.Info) map[string]osutil.FileState {
	content := make(map[string]osutil.FileState, 2)
	snapName := snapInfo.InstanceName()
	// Add the per-snap fstab file.
	// This file is read by snap-update-ns in the global pass.
	addMountProfile(content, fmt.Sprintf("snap.%s.fstab", snapName), spec.MountEntries())
	// Add the per-snap user-fstab file.
	// This file will be read by snap-update-ns in the per-user pass.
	addMountProfile(content, fmt.Sprintf("snap.%s.user-fstab", snapName), spec.UserMountEntries())
	return content
}

// NewSpecification returns a new mount specification.
func (b *Backend) NewSpecification(*interfaces.SnapAppSet, interfaces.ConfinementOptions) interfaces.Specification {
	return &Specification{}
}

// SandboxFeatures returns the list of features supported by snapd for composing mount namespaces.
func (b *Backend) SandboxFeatures() []string {
	commonFeatures := []string{
		"layouts",                 /* Mount profiles take layout data into account */
		"mount-namespace",         /* Snapd creates a mount namespace for each snap */
		"per-snap-persistency",    /* Per-snap profiles are persisted across invocations */
		"per-snap-profiles",       /* Per-snap profiles allow changing mount namespace of a given snap */
		"per-snap-updates",        /* Changes to per-snap mount profiles are applied instantly */
		"per-snap-user-profiles",  /* Per-snap profiles allow changing mount namespace of a given snap for a given user */
		"stale-base-invalidation", /* Mount namespaces that go stale because base snap changes are automatically invalidated */
	}
	cgroupv1Features := []string{
		"freezer-cgroup-v1", /* Snapd creates a freezer cgroup (v1) for each snap */
	}

	if cgroup.IsUnified() {
		// TODO: update we get feature parity on cgroup v2
		return commonFeatures
	}

	features := append(commonFeatures, cgroupv1Features...)
	return features
}