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
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 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/>.
*
*/
// symlinks is a backend that ensures that configuration files required by
// interfaces are present in the system. Currently it works only on classic and
// modifies the classic rootfs.
package symlinks
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/timings"
)
// Backend is responsible for maintaining symlinks cache.
type Backend struct{}
var _ = interfaces.SecurityBackend(&Backend{})
// Initialize does nothing for this backend.
func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error {
return nil
}
// Name returns the name of the backend.
func (b *Backend) Name() interfaces.SecuritySystem {
return interfaces.SecuritySymlinks
}
// Setup will make the symlinks backend generate the specified symlinks.
func (b *Backend) Setup(appSet *interfaces.SnapAppSet, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
symlinkDirs := map[string]bool{}
for _, iface := range repo.AllInterfaces() {
if symlnIface, ok := iface.(interfaces.SymlinksUser); ok {
for _, d := range symlnIface.TrackedDirectories() {
symlinkDirs[d] = true
}
}
}
snapName := appSet.InstanceName()
// Get the spec that applies to this snap
spec, err := repo.SnapSpecification(b.Name(), appSet, opts)
if err != nil {
return fmt.Errorf("cannot obtain symlinks specification for snap %q: %s",
snapName, err)
}
return b.ensureSymlinks(spec.(*Specification), symlinkDirs)
}
// Remove removes modules symlinks files specific to a given snap.
// This method should be called after removing a snap.
func (b *Backend) Remove(snapName string) error {
// If called for the system (snapd) snap, that is possible only in a
// classic scenario when all other snaps in the system must have been
// removed already to allow the removal of the snapd snap. In that
// case, the config files will have already been removed by a Setup
// call, so we do not need to do anything here.
// TODO but this needs to be revisited for when we start supporting
// symlinks plugs in snaps.
return nil
}
// NewSpecification returns a new specification associated with this backend.
func (b *Backend) NewSpecification(*interfaces.SnapAppSet,
interfaces.ConfinementOptions) interfaces.Specification {
return &Specification{}
}
// SandboxFeatures returns the list of features supported by snapd for symlinks.
func (b *Backend) SandboxFeatures() []string {
return []string{"mediated-symlinks"}
}
func (b *Backend) removeUnwantedInDir(dir string, entries []os.DirEntry, activeLinks SymlinkToTarget) error {
// Loop to remove unwanted symlinks
for _, node := range entries {
if node.Type() != fs.ModeSymlink {
continue
}
path := filepath.Join(dir, node.Name())
controlled, err := linkIsSnapdControlled(path)
if err != nil {
return err
}
if !controlled {
continue
}
// link is active
if _, ok := activeLinks[node.Name()]; ok {
continue
}
// link not active, remove
if err := os.Remove(path); err != nil {
logger.Noticef("symlinks backend cannot remove %q", path)
}
}
return nil
}
func (b *Backend) ensureSymlinks(spec *Specification, symlinkDirs map[string]bool) error {
// Setup symlinks only if the snap has plugs that require it. For the
// moment this is only the system snap.
if len(spec.plugs) == 0 {
return nil
}
for lnDir := range spec.dirsToLinkToTarget {
if _, ok := symlinkDirs[lnDir]; !ok {
return fmt.Errorf("internal error: %s not in any registered symlinks directory", lnDir)
}
}
// TODO supported directories apply currently only to a classic rootfs,
// not to the rootfs of a snap.
for dir := range symlinkDirs {
entries, err := os.ReadDir(dir)
dirExists := true
if err != nil {
var pathErr *os.PathError
if errors.As(err, &pathErr) {
dirExists = false
} else {
return err
}
}
lnsToTarget := spec.dirsToLinkToTarget[dir]
// Remove now unwanted symlinks
if err := b.removeUnwantedInDir(dir, entries, lnsToTarget); err != nil {
return err
}
// Ensure state of the links we want in this directory
if len(lnsToTarget) > 0 {
if !dirExists {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
}
for ln, target := range lnsToTarget {
lnPath := filepath.Join(dir, ln)
logger.Debugf("ensuring symlink %q -> %q", lnPath, target)
osutil.EnsureFileState(lnPath, &osutil.SymlinkFileState{Target: target})
}
}
}
return nil
}
func linkIsSnapdControlled(symlinkPath string) (bool, error) {
dest, err := os.Readlink(symlinkPath)
if err != nil {
return false, err
}
// If dest is under $SNAP or points to snaps data snapd owns the
// symlink, otherwise it is not under snapd control (these symlinks are
// absolute).
//
// TODO an alternative would be to add trusted extended attributes to
// these symlinks (xattr(7)) - adding with the equivalent of "sudo
// setfattr -h -n trusted.<snap> -v <val> <symlink>".
return strings.HasPrefix(dest, dirs.SnapMountDir+"/") ||
strings.HasPrefix(dest, dirs.SnapDataDir+"/"), nil
}
|