gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/apparmor/backend.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2020 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 apparmor implements integration between snappy and
    21  // ubuntu-core-launcher around apparmor.
    22  //
    23  // Snappy creates apparmor profiles for each application (for each snap)
    24  // present in the system.  Upon each execution of ubuntu-core-launcher
    25  // application process is launched under the profile. Prior to that the profile
    26  // must be parsed, compiled and loaded into the kernel using the support tool
    27  // "apparmor_parser".
    28  //
    29  // Each apparmor profile contains a simple <header><content><footer> structure.
    30  // The header specifies the profile name that the launcher will use to launch a
    31  // process under this profile.  Snappy uses "abstract identifiers" as profile
    32  // names.
    33  //
    34  // The actual profiles are stored in /var/lib/snappy/apparmor/profiles.
    35  //
    36  // NOTE: A systemd job (apparmor.service) loads all snappy-specific apparmor
    37  // profiles into the kernel during the boot process.
    38  package apparmor
    39  
    40  import (
    41  	"bytes"
    42  	"fmt"
    43  	"io/ioutil"
    44  	"os"
    45  	"path"
    46  	"path/filepath"
    47  	"regexp"
    48  	"sort"
    49  	"strings"
    50  
    51  	"gitee.com/mysnapcore/mysnapd/dirs"
    52  	"gitee.com/mysnapcore/mysnapd/interfaces"
    53  	"gitee.com/mysnapcore/mysnapd/logger"
    54  	"gitee.com/mysnapcore/mysnapd/osutil"
    55  	"gitee.com/mysnapcore/mysnapd/release"
    56  	apparmor_sandbox "gitee.com/mysnapcore/mysnapd/sandbox/apparmor"
    57  	"gitee.com/mysnapcore/mysnapd/snap"
    58  	"gitee.com/mysnapcore/mysnapd/strutil"
    59  	"gitee.com/mysnapcore/mysnapd/timings"
    60  )
    61  
    62  var (
    63  	procSelfExe           = "/proc/self/exe"
    64  	isHomeUsingNFS        = osutil.IsHomeUsingNFS
    65  	isRootWritableOverlay = osutil.IsRootWritableOverlay
    66  	kernelFeatures        = apparmor_sandbox.KernelFeatures
    67  	parserFeatures        = apparmor_sandbox.ParserFeatures
    68  	loadProfiles          = apparmor_sandbox.LoadProfiles
    69  	unloadProfiles        = apparmor_sandbox.UnloadProfiles
    70  
    71  	// make sure that apparmor profile fulfills the late discarding backend
    72  	// interface
    73  	_ interfaces.SecurityBackendDiscardingLate = (*Backend)(nil)
    74  )
    75  
    76  // Backend is responsible for maintaining apparmor profiles for snaps and parts of snapd.
    77  type Backend struct {
    78  	preseed bool
    79  
    80  	coreSnap  *snap.Info
    81  	snapdSnap *snap.Info
    82  }
    83  
    84  // Name returns the name of the backend.
    85  func (b *Backend) Name() interfaces.SecuritySystem {
    86  	return interfaces.SecurityAppArmor
    87  }
    88  
    89  // Initialize prepares customized apparmor policy for snap-confine.
    90  func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error {
    91  	if opts != nil && opts.Preseed {
    92  		b.preseed = true
    93  	}
    94  
    95  	if opts != nil {
    96  		b.coreSnap = opts.CoreSnapInfo
    97  		b.snapdSnap = opts.SnapdSnapInfo
    98  	}
    99  	// NOTE: It would be nice if we could also generate the profile for
   100  	// snap-confine executing from the core snap, right here, and not have to
   101  	// do this in the Setup function below. I sadly don't think this is
   102  	// possible because snapd must be able to install a new core and only at
   103  	// that moment generate it.
   104  
   105  	// Inspect the system and sets up local apparmor policy for snap-confine.
   106  	// Local policy is included by the system-wide policy. If the local policy
   107  	// has changed then the apparmor profile for snap-confine is reloaded.
   108  
   109  	// Create the local policy directory if it is not there.
   110  	if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil {
   111  		return fmt.Errorf("cannot create snap-confine policy directory: %s", err)
   112  	}
   113  
   114  	// Check the /proc/self/exe symlink, this is needed below but we want to
   115  	// fail early if this fails for whatever reason.
   116  	exe, err := os.Readlink(procSelfExe)
   117  	if err != nil {
   118  		return fmt.Errorf("cannot read %s: %s", procSelfExe, err)
   119  	}
   120  
   121  	// Location of the generated policy.
   122  	glob := "*"
   123  	policy := make(map[string]osutil.FileState)
   124  
   125  	// Check if NFS is mounted at or under $HOME. Because NFS is not
   126  	// transparent to apparmor we must alter our profile to counter that and
   127  	// allow snap-confine to work.
   128  	if nfs, err := isHomeUsingNFS(); err != nil {
   129  		logger.Noticef("cannot determine if NFS is in use: %v", err)
   130  	} else if nfs {
   131  		policy["nfs-support"] = &osutil.MemoryFileState{
   132  			Content: []byte(nfsSnippet),
   133  			Mode:    0644,
   134  		}
   135  		logger.Noticef("snapd enabled NFS support, additional implicit network permissions granted")
   136  	}
   137  
   138  	// Check if '/' is on overlayfs. If so, add the necessary rules for
   139  	// upperdir and allow snap-confine to work.
   140  	if overlayRoot, err := isRootWritableOverlay(); err != nil {
   141  		logger.Noticef("cannot determine if root filesystem on overlay: %v", err)
   142  	} else if overlayRoot != "" {
   143  		snippet := strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1)
   144  		policy["overlay-root"] = &osutil.MemoryFileState{
   145  			Content: []byte(snippet),
   146  			Mode:    0644,
   147  		}
   148  		logger.Noticef("snapd enabled root filesystem on overlay support, additional upperdir permissions granted")
   149  	}
   150  
   151  	// Check whether apparmor_parser supports bpf capability. Some older
   152  	// versions do not, hence the capability cannot be part of the default
   153  	// profile of snap-confine as loading it would fail.
   154  	if features, err := apparmor_sandbox.ParserFeatures(); err != nil {
   155  		logger.Noticef("cannot determine apparmor_parser features: %v", err)
   156  	} else if strutil.ListContains(features, "cap-bpf") {
   157  		policy["cap-bpf"] = &osutil.MemoryFileState{
   158  			Content: []byte(capabilityBPFSnippet),
   159  			Mode:    0644,
   160  		}
   161  	}
   162  
   163  	// Ensure that generated policy is what we computed above.
   164  	created, removed, err := osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, policy)
   165  	if err != nil {
   166  		return fmt.Errorf("cannot synchronize snap-confine policy: %s", err)
   167  	}
   168  	if len(created) == 0 && len(removed) == 0 {
   169  		// If the generated policy didn't change, we're all done.
   170  		return nil
   171  	}
   172  
   173  	// If snapd is executing from the core snap the it means it has
   174  	// re-executed. In that case we are no longer using the copy of
   175  	// snap-confine from the host distribution but our own copy. We don't have
   176  	// to re-compile and load the updated profile as that is performed by
   177  	// setupSnapConfineReexec below.
   178  	if strings.HasPrefix(exe, dirs.SnapMountDir) {
   179  		return nil
   180  	}
   181  
   182  	// Reload the apparmor profile of snap-confine. This points to the main
   183  	// file in /etc/apparmor.d/ as that file contains include statements that
   184  	// load any of the files placed in /var/lib/snapd/apparmor/snap-confine/.
   185  	profilePath := apparmor_sandbox.SnapConfineDistroProfilePath()
   186  	if profilePath == "" {
   187  		// XXX: is profile mandatory on some distros?
   188  
   189  		// There is no AppArmor profile for snap-confine, quite likely
   190  		// AppArmor support is enabled in the kernel and relevant
   191  		// userspace tools exist, but snap-confine was built without it,
   192  		// nothing we need to update then.
   193  		logger.Noticef("snap-confine apparmor profile is absent, nothing to update")
   194  		return nil
   195  	}
   196  
   197  	aaFlags := apparmor_sandbox.SkipReadCache
   198  	if b.preseed {
   199  		aaFlags |= apparmor_sandbox.SkipKernelLoad
   200  	}
   201  
   202  	if err := loadProfiles([]string{profilePath}, apparmor_sandbox.SystemCacheDir, aaFlags); err != nil {
   203  		// When we cannot reload the profile then let's remove the generated
   204  		// policy. Maybe we have caused the problem so it's better to let other
   205  		// things work.
   206  		osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, nil)
   207  		return fmt.Errorf("cannot reload snap-confine apparmor profile: %v", err)
   208  	}
   209  	return nil
   210  }
   211  
   212  // snapConfineFromSnapProfile returns the apparmor profile for
   213  // snap-confine in the given core/snapd snap.
   214  func snapConfineFromSnapProfile(info *snap.Info) (dir, glob string, content map[string]osutil.FileState, err error) {
   215  	// TODO: fix this for distros using /usr/libexec/snapd when those start
   216  	// to use reexec
   217  
   218  	// Find the vanilla apparmor profile for snap-confine as present in the given core snap.
   219  
   220  	// We must test the ".real" suffix first, this is a workaround for
   221  	// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=858004
   222  	vanillaProfilePath := filepath.Join(info.MountDir(), "/etc/apparmor.d/usr.lib.snapd.snap-confine.real")
   223  	vanillaProfileText, err := ioutil.ReadFile(vanillaProfilePath)
   224  	if os.IsNotExist(err) {
   225  		vanillaProfilePath = filepath.Join(info.MountDir(), "/etc/apparmor.d/usr.lib.snapd.snap-confine")
   226  		vanillaProfileText, err = ioutil.ReadFile(vanillaProfilePath)
   227  	}
   228  	if err != nil {
   229  		return "", "", nil, fmt.Errorf("cannot open apparmor profile for vanilla snap-confine: %s", err)
   230  	}
   231  
   232  	// Replace the path to vanilla snap-confine with the path to the mounted snap-confine from core.
   233  	snapConfineInCore := filepath.Join(info.MountDir(), "usr/lib/snapd/snap-confine")
   234  	patchedProfileText := bytes.Replace(
   235  		vanillaProfileText, []byte("/usr/lib/snapd/snap-confine"), []byte(snapConfineInCore), -1)
   236  
   237  	// Also replace the test providing access to verbatim
   238  	// /usr/lib/snapd/snap-confine, which is necessary because to execute snaps
   239  	// from strict snaps, we need to be able read and map
   240  	// /usr/lib/snapd/snap-confine from inside the strict snap mount namespace,
   241  	// even though /usr/lib/snapd/snap-confine from inside the strict snap mount
   242  	// namespace is actually a bind mount to the "snapConfineInCore"
   243  	patchedProfileText = bytes.Replace(
   244  		patchedProfileText, []byte("#@VERBATIM_LIBEXECDIR_SNAP_CONFINE@"), []byte("/usr/lib/snapd/snap-confine"), -1)
   245  
   246  	// We need to add a unique prefix that can never collide with a
   247  	// snap on the system. Using "snap-confine.*" is similar to
   248  	// "snap-update-ns.*" that is already used there
   249  	//
   250  	// So
   251  	//   /snap/core/111/usr/lib/snapd/snap-confine
   252  	// becomes
   253  	//   snap-confine.core.111
   254  	patchedProfileName := snapConfineProfileName(info.InstanceName(), info.Revision)
   255  	// remove other generated profiles, which is only relevant for the
   256  	// 'core' snap on classic system where we reexec, on core systems the
   257  	// profile is already a part of the rootfs snap
   258  	patchedProfileGlob := fmt.Sprintf("snap-confine.%s.*", info.InstanceName())
   259  
   260  	if info.Type() == snap.TypeSnapd {
   261  		// with the snapd snap, things are a little different, the
   262  		// profile is discarded only late for the revisions that are
   263  		// being removed, also on core devices the rootfs snap and the
   264  		// snapd snap are updated separately, so the profile needs to be
   265  		// around for as long as the given revision of the snapd snap is
   266  		// active, so we use the exact match such that we only replace
   267  		// our own profile, which can happen if system was rebooted
   268  		// before task calling the backend was finished
   269  		patchedProfileGlob = patchedProfileName
   270  	}
   271  
   272  	// Return information for EnsureDirState that describes the re-exec profile for snap-confine.
   273  	content = map[string]osutil.FileState{
   274  		patchedProfileName: &osutil.MemoryFileState{
   275  			Content: []byte(patchedProfileText),
   276  			Mode:    0644,
   277  		},
   278  	}
   279  
   280  	return dirs.SnapAppArmorDir, patchedProfileGlob, content, nil
   281  }
   282  
   283  func snapConfineProfileName(snapName string, rev snap.Revision) string {
   284  	return fmt.Sprintf("snap-confine.%s.%s", snapName, rev)
   285  }
   286  
   287  // setupSnapConfineReexec will setup apparmor profiles inside the host's
   288  // /var/lib/snapd/apparmor/profiles directory. This is needed for
   289  // running snap-confine from the core or snapd snap.
   290  //
   291  // Additionally it will cleanup stale apparmor profiles it created.
   292  func (b *Backend) setupSnapConfineReexec(info *snap.Info) error {
   293  	if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil {
   294  		return fmt.Errorf("cannot create snap-confine policy directory: %s", err)
   295  	}
   296  	dir, glob, content, err := snapConfineFromSnapProfile(info)
   297  	cache := apparmor_sandbox.CacheDir
   298  	if err != nil {
   299  		return fmt.Errorf("cannot compute snap-confine profile: %s", err)
   300  	}
   301  	if err := os.MkdirAll(dir, 0755); err != nil {
   302  		return fmt.Errorf("cannot create snap-confine directory %q: %s", dir, err)
   303  	}
   304  
   305  	changed, removed, errEnsure := osutil.EnsureDirState(dir, glob, content)
   306  	if len(changed) == 0 {
   307  		// XXX: because NFS workaround is handled separately the same correct
   308  		// snap-confine profile may need to be re-loaded. This is because the
   309  		// profile contains include directives and those load a second file
   310  		// that has changed outside of the scope of EnsureDirState.
   311  		//
   312  		// To counter that, always reload the profile by pretending it had
   313  		// changed.
   314  		for fname := range content {
   315  			changed = append(changed, fname)
   316  		}
   317  	}
   318  	pathnames := make([]string, len(changed))
   319  	for i, profile := range changed {
   320  		pathnames[i] = filepath.Join(dir, profile)
   321  	}
   322  
   323  	var aaFlags apparmor_sandbox.AaParserFlags
   324  	if b.preseed {
   325  		aaFlags = apparmor_sandbox.SkipKernelLoad
   326  	}
   327  	errReload := loadProfiles(pathnames, cache, aaFlags)
   328  	errUnload := unloadProfiles(removed, cache)
   329  	if errEnsure != nil {
   330  		return fmt.Errorf("cannot synchronize snap-confine apparmor profile: %s", errEnsure)
   331  	}
   332  	if errReload != nil {
   333  		return fmt.Errorf("cannot reload snap-confine apparmor profile: %s", errReload)
   334  	}
   335  	if errUnload != nil {
   336  		return fmt.Errorf("cannot unload snap-confine apparmor profile: %s", errReload)
   337  	}
   338  	return nil
   339  }
   340  
   341  // nsProfile returns name of the apparmor profile for snap-update-ns for a given snap.
   342  func nsProfile(snapName string) string {
   343  	return fmt.Sprintf("snap-update-ns.%s", snapName)
   344  }
   345  
   346  // profileGlobs returns a list of globs that describe the apparmor profiles of
   347  // a given snap.
   348  //
   349  // Currently the list is just a pair. The first glob describes profiles for all
   350  // apps and hooks while the second profile describes the snap-update-ns profile
   351  // for the whole snap.
   352  func profileGlobs(snapName string) []string {
   353  	return []string{interfaces.SecurityTagGlob(snapName), nsProfile(snapName)}
   354  }
   355  
   356  // Determine if a profile filename is removable during core refresh/rollback.
   357  // This is needed because core devices are also special, the apparmor cache
   358  // gets confused too easy, especially at rollbacks, so we delete the cache. See
   359  // Setup(), below. Some systems employ a unified cache directory where all
   360  // apparmor cache files are stored under one location so ensure we don't remove
   361  // the snap profiles since snapd manages them elsewhere and instead only remove
   362  // snap-confine and system profiles (eg, as shipped by distro package manager
   363  // or created by the administrator). snap-confine profiles are like the
   364  // following:
   365  // - usr.lib.snapd.snap-confine.real
   366  // - usr.lib.snapd.snap-confine (historic)
   367  // - snap.core.NNNN.usr.lib.snapd.snap-confine (historic)
   368  // - var.lib.snapd.snap.core.NNNN.usr.lib.snapd.snap-confine (historic)
   369  // - snap-confine.core.NNNN
   370  // - snap-confine.snapd.NNNN
   371  func profileIsRemovableOnCoreSetup(fn string) bool {
   372  	bn := path.Base(fn)
   373  	if strings.HasPrefix(bn, ".") {
   374  		return false
   375  	} else if strings.HasPrefix(bn, "snap") && !strings.HasPrefix(bn, "snap-confine.core.") && !strings.HasPrefix(bn, "snap-confine.snapd.") && !strings.Contains(bn, "usr.lib.snapd.snap-confine") {
   376  		return false
   377  	}
   378  	return true
   379  }
   380  
   381  type profilePathsResults struct {
   382  	changed   []string
   383  	unchanged []string
   384  	removed   []string
   385  }
   386  
   387  func (b *Backend) prepareProfiles(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) (prof *profilePathsResults, err error) {
   388  	snapName := snapInfo.InstanceName()
   389  	spec, err := repo.SnapSpecification(b.Name(), snapName)
   390  	if err != nil {
   391  		return nil, fmt.Errorf("cannot obtain apparmor specification for snap %q: %s", snapName, err)
   392  	}
   393  
   394  	// Add snippets for parallel snap installation mapping
   395  	spec.(*Specification).AddOvername(snapInfo)
   396  
   397  	// Add snippets derived from the layout definition.
   398  	spec.(*Specification).AddLayout(snapInfo)
   399  
   400  	// Add additional mount layouts rules for the snap.
   401  	spec.(*Specification).AddExtraLayouts(snapInfo, opts.ExtraLayouts)
   402  
   403  	// core on classic is special
   404  	if snapName == "core" && release.OnClassic && apparmor_sandbox.ProbedLevel() != apparmor_sandbox.Unsupported {
   405  		if err := b.setupSnapConfineReexec(snapInfo); err != nil {
   406  			return nil, fmt.Errorf("cannot create host snap-confine apparmor configuration: %s", err)
   407  		}
   408  	}
   409  
   410  	// Deal with the "snapd" snap - we do the setup slightly differently
   411  	// here because this will run both on classic and on Ubuntu Core 18
   412  	// systems but /etc/apparmor.d is not writable on core18 systems
   413  	if snapInfo.Type() == snap.TypeSnapd && apparmor_sandbox.ProbedLevel() != apparmor_sandbox.Unsupported {
   414  		if err := b.setupSnapConfineReexec(snapInfo); err != nil {
   415  			return nil, fmt.Errorf("cannot create host snap-confine apparmor configuration: %s", err)
   416  		}
   417  	}
   418  
   419  	// core on core devices is also special, the apparmor cache gets
   420  	// confused too easy, especially at rollbacks, so we delete the cache.
   421  	// See LP:#1460152 and
   422  	// https://forum.snapcraft.io/t/core-snap-revert-issues-on-core-devices/
   423  	//
   424  	if (snapInfo.Type() == snap.TypeOS || snapInfo.Type() == snap.TypeSnapd) && !release.OnClassic {
   425  		if li, err := filepath.Glob(filepath.Join(apparmor_sandbox.SystemCacheDir, "*")); err == nil {
   426  			for _, p := range li {
   427  				if st, err := os.Stat(p); err == nil && st.Mode().IsRegular() && profileIsRemovableOnCoreSetup(p) {
   428  					if err := os.Remove(p); err != nil {
   429  						logger.Noticef("cannot remove %q: %s", p, err)
   430  					}
   431  				}
   432  			}
   433  		}
   434  	}
   435  
   436  	// Get the files that this snap should have
   437  	content := b.deriveContent(spec.(*Specification), snapInfo, opts)
   438  
   439  	dir := dirs.SnapAppArmorDir
   440  	globs := profileGlobs(snapInfo.InstanceName())
   441  	if err := os.MkdirAll(dir, 0755); err != nil {
   442  		return nil, fmt.Errorf("cannot create directory for apparmor profiles %q: %s", dir, err)
   443  	}
   444  	changed, removedPaths, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, content)
   445  	// XXX: in the old code this error was reported late, after doing load/unload.
   446  	if errEnsure != nil {
   447  		return nil, fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure)
   448  	}
   449  
   450  	// Find the set of unchanged profiles.
   451  	unchanged := make([]string, 0, len(content)-len(changed))
   452  	for name := range content {
   453  		// changed is pre-sorted by EnsureDirStateGlobs
   454  		x := sort.SearchStrings(changed, name)
   455  		if x < len(changed) && changed[x] == name {
   456  			continue
   457  		}
   458  		unchanged = append(unchanged, name)
   459  	}
   460  	sort.Strings(unchanged)
   461  
   462  	changedPaths := make([]string, len(changed))
   463  	for i, profile := range changed {
   464  		changedPaths[i] = filepath.Join(dir, profile)
   465  	}
   466  
   467  	unchangedPaths := make([]string, len(unchanged))
   468  	for i, profile := range unchanged {
   469  		unchangedPaths[i] = filepath.Join(dir, profile)
   470  	}
   471  
   472  	return &profilePathsResults{changed: changedPaths, removed: removedPaths, unchanged: unchangedPaths}, nil
   473  }
   474  
   475  // Setup creates and loads apparmor profiles specific to a given snap.
   476  // The snap can be in developer mode to make security violations non-fatal to
   477  // the offending application process.
   478  //
   479  // This method should be called after changing plug, slots, connections between
   480  // them or application present in the snap.
   481  func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
   482  	prof, err := b.prepareProfiles(snapInfo, opts, repo)
   483  	if err != nil {
   484  		return err
   485  	}
   486  
   487  	// Load all changed profiles with a flag that asks apparmor to skip reading
   488  	// the cache (since we know those changed for sure).  This allows us to
   489  	// work despite time being wrong (e.g. in the past). For more details see
   490  	// https://forum.snapcraft.io/t/apparmor-profile-caching/1268/18
   491  	var errReloadChanged error
   492  	aaFlags := apparmor_sandbox.SkipReadCache
   493  	if b.preseed {
   494  		aaFlags |= apparmor_sandbox.SkipKernelLoad
   495  	}
   496  	timings.Run(tm, "load-profiles[changed]", fmt.Sprintf("load changed security profiles of snap %q", snapInfo.InstanceName()), func(nesttm timings.Measurer) {
   497  		errReloadChanged = loadProfiles(prof.changed, apparmor_sandbox.CacheDir, aaFlags)
   498  	})
   499  
   500  	// Load all unchanged profiles anyway. This ensures those are correct in
   501  	// the kernel even if the files on disk were not changed. We rely on
   502  	// apparmor cache to make this performant.
   503  	var errReloadOther error
   504  	aaFlags = 0
   505  	if b.preseed {
   506  		aaFlags |= apparmor_sandbox.SkipKernelLoad
   507  	}
   508  	timings.Run(tm, "load-profiles[unchanged]", fmt.Sprintf("load unchanged security profiles of snap %q", snapInfo.InstanceName()), func(nesttm timings.Measurer) {
   509  		errReloadOther = loadProfiles(prof.unchanged, apparmor_sandbox.CacheDir, aaFlags)
   510  	})
   511  	errUnload := unloadProfiles(prof.removed, apparmor_sandbox.CacheDir)
   512  	if errReloadChanged != nil {
   513  		return errReloadChanged
   514  	}
   515  	if errReloadOther != nil {
   516  		return errReloadOther
   517  	}
   518  	return errUnload
   519  }
   520  
   521  // SetupMany creates and loads apparmor profiles for multiple snaps.
   522  // The snaps can be in developer mode to make security violations non-fatal to
   523  // the offending application process.
   524  // SetupMany tries to recreate all profiles without interrupting on errors, but
   525  // collects and returns them all.
   526  //
   527  // This method is useful mainly for regenerating profiles.
   528  func (b *Backend) SetupMany(snaps []*snap.Info, confinement func(snapName string) interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) []error {
   529  	var allChangedPaths, allUnchangedPaths, allRemovedPaths []string
   530  	var fallback bool
   531  	for _, snapInfo := range snaps {
   532  		opts := confinement(snapInfo.InstanceName())
   533  		prof, err := b.prepareProfiles(snapInfo, opts, repo)
   534  		if err != nil {
   535  			fallback = true
   536  			break
   537  		}
   538  		allChangedPaths = append(allChangedPaths, prof.changed...)
   539  		allUnchangedPaths = append(allUnchangedPaths, prof.unchanged...)
   540  		allRemovedPaths = append(allRemovedPaths, prof.removed...)
   541  	}
   542  
   543  	if !fallback {
   544  		aaFlags := apparmor_sandbox.SkipReadCache | apparmor_sandbox.ConserveCPU
   545  		if b.preseed {
   546  			aaFlags |= apparmor_sandbox.SkipKernelLoad
   547  		}
   548  		var errReloadChanged error
   549  		timings.Run(tm, "load-profiles[changed-many]", fmt.Sprintf("load changed security profiles of %d snaps", len(snaps)), func(nesttm timings.Measurer) {
   550  			errReloadChanged = loadProfiles(allChangedPaths, apparmor_sandbox.CacheDir, aaFlags)
   551  		})
   552  
   553  		aaFlags = apparmor_sandbox.ConserveCPU
   554  		if b.preseed {
   555  			aaFlags |= apparmor_sandbox.SkipKernelLoad
   556  		}
   557  		var errReloadOther error
   558  		timings.Run(tm, "load-profiles[unchanged-many]", fmt.Sprintf("load unchanged security profiles %d snaps", len(snaps)), func(nesttm timings.Measurer) {
   559  			errReloadOther = loadProfiles(allUnchangedPaths, apparmor_sandbox.CacheDir, aaFlags)
   560  		})
   561  
   562  		errUnload := unloadProfiles(allRemovedPaths, apparmor_sandbox.CacheDir)
   563  		if errReloadChanged != nil {
   564  			logger.Noticef("failed to batch-reload changed profiles: %s", errReloadChanged)
   565  			fallback = true
   566  		}
   567  		if errReloadOther != nil {
   568  			logger.Noticef("failed to batch-reload unchanged profiles: %s", errReloadOther)
   569  			fallback = true
   570  		}
   571  		if errUnload != nil {
   572  			logger.Noticef("failed to batch-unload profiles: %s", errUnload)
   573  			fallback = true
   574  		}
   575  	}
   576  
   577  	var errors []error
   578  	// if an error was encountered when processing all profiles at once, re-try them one by one
   579  	if fallback {
   580  		for _, snapInfo := range snaps {
   581  			opts := confinement(snapInfo.InstanceName())
   582  			if err := b.Setup(snapInfo, opts, repo, tm); err != nil {
   583  				errors = append(errors, fmt.Errorf("cannot setup profiles for snap %q: %s", snapInfo.InstanceName(), err))
   584  			}
   585  		}
   586  	}
   587  	return errors
   588  }
   589  
   590  // Remove removes and unloads apparmor profiles of a given snap.
   591  func (b *Backend) Remove(snapName string) error {
   592  	dir := dirs.SnapAppArmorDir
   593  	globs := profileGlobs(snapName)
   594  	cache := apparmor_sandbox.CacheDir
   595  	_, removed, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, nil)
   596  	// always try to unload affected profiles
   597  	errUnload := unloadProfiles(removed, cache)
   598  	if errEnsure != nil {
   599  		return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure)
   600  	}
   601  	return errUnload
   602  }
   603  
   604  func (b *Backend) RemoveLate(snapName string, rev snap.Revision, typ snap.Type) error {
   605  	logger.Debugf("remove late for snap %v (%s) type %v", snapName, rev, typ)
   606  	if typ != snap.TypeSnapd {
   607  		// late remove is relevant only for snap confine profiles
   608  		return nil
   609  	}
   610  
   611  	globs := []string{snapConfineProfileName(snapName, rev)}
   612  	_, removed, errEnsure := osutil.EnsureDirStateGlobs(dirs.SnapAppArmorDir, globs, nil)
   613  	// XXX: unloadProfiles() does not unload profiles from the kernel, but
   614  	// only removes profiles from the cache
   615  	// always try to unload the affected profile
   616  	errUnload := unloadProfiles(removed, apparmor_sandbox.CacheDir)
   617  	if errEnsure != nil {
   618  		return fmt.Errorf("cannot remove security profiles for snap %q (%s): %s", snapName, rev, errEnsure)
   619  	}
   620  	return errUnload
   621  }
   622  
   623  var (
   624  	templatePattern    = regexp.MustCompile("(###[A-Z_]+###)")
   625  	coreRuntimePattern = regexp.MustCompile("^core([0-9][0-9])?$")
   626  )
   627  
   628  const (
   629  	attachPattern  = "(attach_disconnected,mediate_deleted)"
   630  	attachComplain = "(attach_disconnected,mediate_deleted,complain)"
   631  )
   632  
   633  func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info, opts interfaces.ConfinementOptions) (content map[string]osutil.FileState) {
   634  	content = make(map[string]osutil.FileState, len(snapInfo.Apps)+len(snapInfo.Hooks)+1)
   635  
   636  	// Add profile for each app.
   637  	for _, appInfo := range snapInfo.Apps {
   638  		securityTag := appInfo.SecurityTag()
   639  		b.addContent(securityTag, snapInfo, appInfo.Name, opts, spec.SnippetForTag(securityTag), content, spec)
   640  	}
   641  	// Add profile for each hook.
   642  	for _, hookInfo := range snapInfo.Hooks {
   643  		securityTag := hookInfo.SecurityTag()
   644  		b.addContent(securityTag, snapInfo, "hook."+hookInfo.Name, opts, spec.SnippetForTag(securityTag), content, spec)
   645  	}
   646  	// Add profile for snap-update-ns if we have any apps or hooks.
   647  	// If we have neither then we don't have any need to create an executing environment.
   648  	// This applies to, for example, kernel snaps or gadget snaps (unless they have hooks).
   649  	if len(content) > 0 {
   650  		snippets := strings.Join(spec.UpdateNS(), "\n")
   651  		addUpdateNSProfile(snapInfo, snippets, content)
   652  	}
   653  
   654  	return content
   655  }
   656  
   657  // addUpdateNSProfile adds an apparmor profile for snap-update-ns, tailored to a specific snap.
   658  //
   659  // This profile exists so that snap-update-ns doens't need to carry very wide, open permissions
   660  // that are suitable for poking holes (and writing) in nearly arbitrary places. Instead the profile
   661  // contains just the permissions needed to poke a hole and write to the layout-specific paths.
   662  func addUpdateNSProfile(snapInfo *snap.Info, snippets string, content map[string]osutil.FileState) {
   663  	// Compute the template by injecting special updateNS snippets.
   664  	policy := templatePattern.ReplaceAllStringFunc(updateNSTemplate, func(placeholder string) string {
   665  		switch placeholder {
   666  		case "###SNAP_INSTANCE_NAME###":
   667  			return snapInfo.InstanceName()
   668  		case "###SNIPPETS###":
   669  			if overlayRoot, _ := isRootWritableOverlay(); overlayRoot != "" {
   670  				snippets += strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1)
   671  			}
   672  			return snippets
   673  		}
   674  		return ""
   675  	})
   676  
   677  	// Ensure that the snap-update-ns profile is on disk.
   678  	profileName := nsProfile(snapInfo.InstanceName())
   679  	content[profileName] = &osutil.MemoryFileState{
   680  		Content: []byte(policy),
   681  		Mode:    0644,
   682  	}
   683  }
   684  
   685  func (b *Backend) addContent(securityTag string, snapInfo *snap.Info, cmdName string, opts interfaces.ConfinementOptions, snippetForTag string, content map[string]osutil.FileState, spec *Specification) {
   686  	// If base is specified and it doesn't match the core snaps (not
   687  	// specifying a base should use the default core policy since in this
   688  	// case, the 'core' snap is used for the runtime), use the base
   689  	// apparmor template, otherwise use the default template.
   690  	var policy string
   691  	if snapInfo.Base != "" && !coreRuntimePattern.MatchString(snapInfo.Base) {
   692  		policy = defaultOtherBaseTemplate
   693  	} else {
   694  		policy = defaultCoreRuntimeTemplate
   695  	}
   696  
   697  	ignoreSnippets := false
   698  	// Classic confinement (unless overridden by JailMode) has a dedicated
   699  	// permissive template that applies a strict, but very open, policy.
   700  	if opts.Classic && !opts.JailMode {
   701  		policy = classicTemplate
   702  		ignoreSnippets = true
   703  	}
   704  	// If a snap is in devmode (or is using classic confinement) then make the
   705  	// profile non-enforcing where violations are logged but not denied.
   706  	// This is also done for classic so that no confinement applies. Just in
   707  	// case the profile we start with is not permissive enough.
   708  	if (opts.DevMode || opts.Classic) && !opts.JailMode {
   709  		policy = strings.Replace(policy, attachPattern, attachComplain, -1)
   710  	}
   711  	policy = templatePattern.ReplaceAllStringFunc(policy, func(placeholder string) string {
   712  		switch placeholder {
   713  		case "###DEVMODE_SNAP_CONFINE###":
   714  			if !opts.DevMode {
   715  				// nothing to add if we are not in devmode
   716  				return ""
   717  			}
   718  
   719  			// otherwise we need to generate special policy to allow executing
   720  			// snap-confine from inside a devmode snap
   721  
   722  			// TODO: we should deprecate this and drop it in a future release
   723  
   724  			// assumes coreSnapInfo is not nil
   725  			coreProfileTarget := func() string {
   726  				return fmt.Sprintf("/snap/core/%s/usr/lib/snapd/snap-confine", b.coreSnap.SnapRevision().String())
   727  			}
   728  
   729  			// assumes snapdSnapInfo is not nil
   730  			snapdProfileTarget := func() string {
   731  				return fmt.Sprintf("/snap/snapd/%s/usr/lib/snapd/snap-confine", b.snapdSnap.SnapRevision().String())
   732  			}
   733  
   734  			// There are 3 main apparmor exec transition rules we need to
   735  			// generate:
   736  			// * exec( /usr/lib/snapd/snap-confine ... )
   737  			// * exec( /snap/snapd/<rev>/usr/lib/snapd/snap-confine ... )
   738  			// * exec( /snap/core/<rev>/usr/lib/snapd/snap-confine ... )
   739  
   740  			// The latter two can always transition to their respective
   741  			// revisioned profiles unambiguously if each snap is installed.
   742  
   743  			// The former rule for /usr/lib/snapd/snap-confine however is
   744  			// more tricky. First, we can say that if only the snapd snap is
   745  			// installed, to just transition to that profile and be done. If
   746  			// just the core snap is installed, then we can deduce this
   747  			// system is either UC16 or a classic one, in both cases though
   748  			// we have /usr/lib/snapd/snap-confine defined as the profile to
   749  			// transition to.
   750  			// If both snaps are installed however, then we need to branch
   751  			// and pick a profile that exists, we can't just arbitrarily
   752  			// pick one profile because not all profiles will exist on all
   753  			// systems actually, for example the snap-confine profile from
   754  			// the core snap will not be generated/installed on UC18+. We
   755  			// can simplify the logic however by realizing that no matter
   756  			// the relative version numbers of snapd and core, when
   757  			// executing a snap with base other than core (i.e. base core18
   758  			// or core20), the snapd snap's version of snap-confine will
   759  			// always be used for various reasons. This is also true for
   760  			// base: core snaps, but only on non-classic systems. So we
   761  			// essentially say that /usr/lib/snapd/snap-confine always
   762  			// transitions to the snapd snap profile if the base is not
   763  			// core or if the system is not classic. If the base is core and
   764  			// the system is classic, then the core snap profile will be
   765  			// used.
   766  
   767  			usrLibSnapdConfineTransitionTarget := ""
   768  			switch {
   769  			case b.coreSnap != nil && b.snapdSnap == nil:
   770  				// only core snap - use /usr/lib/snapd/snap-confine always
   771  				usrLibSnapdConfineTransitionTarget = "/usr/lib/snapd/snap-confine"
   772  			case b.snapdSnap != nil && b.coreSnap == nil:
   773  				// only snapd snap - use snapd snap version
   774  				usrLibSnapdConfineTransitionTarget = snapdProfileTarget()
   775  			case b.snapdSnap != nil && b.coreSnap != nil:
   776  				// both are installed - need to check which one to use
   777  				// note that a base of "core" is represented by base == "" for
   778  				// historical reasons
   779  				if release.OnClassic && snapInfo.Base == "" {
   780  					// use the core snap as the target only if we are on
   781  					// classic and the base is core
   782  					usrLibSnapdConfineTransitionTarget = coreProfileTarget()
   783  				} else {
   784  					// otherwise always use snapd
   785  					usrLibSnapdConfineTransitionTarget = snapdProfileTarget()
   786  				}
   787  
   788  			default:
   789  				// neither of the snaps are installed
   790  
   791  				// TODO: this panic is unfortunate, but we don't have time
   792  				// to do any better for this security release
   793  				// It is actually important that we panic here, the only
   794  				// known circumstance where this happens is when we are
   795  				// seeding during first boot of UC16 with a very new core
   796  				// snap (i.e. with the security fix of 2.54.3) and also have
   797  				// a devmode confined snap in the seed to prepare. In this
   798  				// situation, when we panic(), we force snapd to exit, and
   799  				// systemd will restart us and we actually recover the
   800  				// initial seed change and continue on. This code will be
   801  				// removed/adapted before it is merged to the main branch,
   802  				// it is only meant to exist on the security release branch.
   803  				msg := fmt.Sprintf("neither snapd nor core snap available while preparing apparmor profile for devmode snap %s, panicing to restart snapd to continue seeding", snapInfo.InstanceName())
   804  				panic(msg)
   805  			}
   806  
   807  			// We use Pxr for all these rules since the snap-confine profile
   808  			// is not a child profile of the devmode complain profile we are
   809  			// generating right now.
   810  			usrLibSnapdConfineTransitionRule := fmt.Sprintf("/usr/lib/snapd/snap-confine Pxr -> %s,\n", usrLibSnapdConfineTransitionTarget)
   811  
   812  			coreSnapConfineSnippet := ""
   813  			if b.coreSnap != nil {
   814  				coreSnapConfineSnippet = fmt.Sprintf("/snap/core/*/usr/lib/snapd/snap-confine Pxr -> %s,\n", coreProfileTarget())
   815  			}
   816  
   817  			snapdSnapConfineSnippet := ""
   818  			if b.snapdSnap != nil {
   819  				snapdSnapConfineSnippet = fmt.Sprintf("/snap/snapd/*/usr/lib/snapd/snap-confine Pxr -> %s,\n", snapdProfileTarget())
   820  			}
   821  
   822  			nonBaseCoreTransitionSnippet := coreSnapConfineSnippet + "\n" + snapdSnapConfineSnippet
   823  
   824  			// include both rules for the core snap and the snapd snap since
   825  			// we can't know which one will be used at runtime (for example
   826  			// SNAP_REEXEC could be set which affects which one is used)
   827  			return fmt.Sprintf(`
   828    # allow executing the snap command from either the rootfs (for base: core) or
   829    # from the system snaps (all other bases) - this is very specifically only to
   830    # enable proper apparmor profile transition to snap-confine below, if we don't
   831    # include these exec rules, then when executing the snap command, apparmor 
   832    # will create a new, unique sub-profile which then cannot be transitioned from
   833    # to the actual snap-confine profile
   834    /usr/bin/snap ixr,
   835    /snap/{snapd,core}/*/usr/bin/snap ixr,
   836  
   837    # allow transitioning to snap-confine to support executing strict snaps from
   838    # inside devmode confined snaps
   839  
   840    # this first rule is to handle the case of exec()ing 
   841    # /usr/lib/snapd/snap-confine directly, the profile we transition to depends
   842    # on whether we are classic or not, what snaps (snapd or core) are installed
   843    # and also whether this snap is a base: core snap or a differently based snap.
   844    # see the comment in interfaces/backend/apparmor.go where this snippet is
   845    # generated for the full context
   846    %[1]s
   847  
   848    # the second (and possibly third if both core and snapd are installed) rule is
   849    # to handle direct exec() of snap-confine from the respective snaps directly, 
   850    # this happens mostly on non-core based snaps, wherein the base snap has a 
   851    # symlink from /usr/bin/snap -> /snap/snapd/current/usr/bin/snap, which makes
   852    # the snap command execute snap-confine directly from the associated system 
   853    # snap in /snap/{snapd,core}/<rev>/usr/lib/snapd/snap-confine
   854    %[2]s
   855  `, usrLibSnapdConfineTransitionRule, nonBaseCoreTransitionSnippet)
   856  
   857  		case "###VAR###":
   858  			return templateVariables(snapInfo, securityTag, cmdName)
   859  		case "###PROFILEATTACH###":
   860  			return fmt.Sprintf("profile \"%s\"", securityTag)
   861  		case "###CHANGEPROFILE_RULE###":
   862  			features, _ := parserFeatures()
   863  			for _, f := range features {
   864  				if f == "unsafe" {
   865  					return "change_profile unsafe /**,"
   866  				}
   867  			}
   868  			return "change_profile,"
   869  		case "###SNIPPETS###":
   870  			var tagSnippets string
   871  			if opts.Classic && opts.JailMode {
   872  				// Add a special internal snippet for snaps using classic confinement
   873  				// and jailmode together. This snippet provides access to the core snap
   874  				// so that the dynamic linker and shared libraries can be used.
   875  				tagSnippets = classicJailmodeSnippet + "\n" + snippetForTag
   876  			} else if ignoreSnippets {
   877  				// When classic confinement template is in effect we are
   878  				// ignoring all apparmor snippets as they may conflict with the
   879  				// super-broad template we are starting with.
   880  			} else {
   881  				// Check if NFS is mounted at or under $HOME. Because NFS is not
   882  				// transparent to apparmor we must alter the profile to counter that and
   883  				// allow access to SNAP_USER_* files.
   884  				tagSnippets = snippetForTag
   885  				if nfs, _ := isHomeUsingNFS(); nfs {
   886  					tagSnippets += nfsSnippet
   887  				}
   888  
   889  				if overlayRoot, _ := isRootWritableOverlay(); overlayRoot != "" {
   890  					snippet := strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1)
   891  					tagSnippets += snippet
   892  				}
   893  
   894  				// Add core specific snippets when not on classic
   895  				if !release.OnClassic {
   896  					tagSnippets += coreSnippet
   897  				}
   898  			}
   899  
   900  			if !ignoreSnippets {
   901  				// For policy with snippets that request
   902  				// suppression of 'ptrace (trace)' denials, add
   903  				// the suppression rule unless another
   904  				// interface said it uses them.
   905  				if spec.SuppressPtraceTrace() && !spec.UsesPtraceTrace() {
   906  					tagSnippets += ptraceTraceDenySnippet
   907  				}
   908  
   909  				// Deny the sys_module capability unless it has been explicitly
   910  				// requested
   911  				if spec.SuppressSysModuleCapability() && !spec.UsesSysModuleCapability() {
   912  					tagSnippets += sysModuleCapabilityDenySnippet
   913  				}
   914  
   915  				// Use 'ix' rules in the home interface unless an
   916  				// interface asked to suppress them
   917  				repl := "ix"
   918  				if spec.SuppressHomeIx() {
   919  					repl = ""
   920  				}
   921  				tagSnippets = strings.Replace(tagSnippets, "###HOME_IX###", repl, -1)
   922  
   923  				// Conditionally add privilege dropping policy
   924  				if len(snapInfo.SystemUsernames) > 0 {
   925  					tagSnippets += privDropAndChownRules
   926  				}
   927  			}
   928  
   929  			return tagSnippets
   930  		}
   931  		return ""
   932  	})
   933  
   934  	content[securityTag] = &osutil.MemoryFileState{
   935  		Content: []byte(policy),
   936  		Mode:    0644,
   937  	}
   938  }
   939  
   940  // NewSpecification returns a new, empty apparmor specification.
   941  func (b *Backend) NewSpecification() interfaces.Specification {
   942  	return &Specification{}
   943  }
   944  
   945  // SandboxFeatures returns the list of apparmor features supported by the kernel.
   946  func (b *Backend) SandboxFeatures() []string {
   947  	if apparmor_sandbox.ProbedLevel() == apparmor_sandbox.Unsupported {
   948  		return nil
   949  	}
   950  
   951  	kFeatures, _ := kernelFeatures()
   952  	pFeatures, _ := parserFeatures()
   953  	tags := make([]string, 0, len(kFeatures)+len(pFeatures))
   954  	for _, feature := range kFeatures {
   955  		// Prepend "kernel:" to apparmor kernel features to namespace them and
   956  		// allow us to introduce our own tags later.
   957  		tags = append(tags, "kernel:"+feature)
   958  	}
   959  
   960  	for _, feature := range pFeatures {
   961  		// Prepend "parser:" to apparmor kernel features to namespace
   962  		// them and allow us to introduce our own tags later.
   963  		tags = append(tags, "parser:"+feature)
   964  	}
   965  
   966  	level := "full"
   967  	policy := "default"
   968  	if apparmor_sandbox.ProbedLevel() == apparmor_sandbox.Partial {
   969  		level = "partial"
   970  	}
   971  	tags = append(tags, fmt.Sprintf("support-level:%s", level))
   972  	tags = append(tags, fmt.Sprintf("policy:%s", policy))
   973  
   974  	return tags
   975  }
   976  
   977  // MockIsHomeUsingNFS mocks the real implementation of osutil.IsHomeUsingNFS.
   978  // This is exported so that other packages that indirectly interact with AppArmor backend
   979  // can mock isHomeUsingNFS.
   980  func MockIsHomeUsingNFS(new func() (bool, error)) (restore func()) {
   981  	old := isHomeUsingNFS
   982  	isHomeUsingNFS = new
   983  	return func() {
   984  		isHomeUsingNFS = old
   985  	}
   986  }