github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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  	"github.com/snapcore/snapd/dirs"
    52  	"github.com/snapcore/snapd/interfaces"
    53  	"github.com/snapcore/snapd/logger"
    54  	"github.com/snapcore/snapd/osutil"
    55  	"github.com/snapcore/snapd/release"
    56  	apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor"
    57  	"github.com/snapcore/snapd/snap"
    58  	"github.com/snapcore/snapd/timings"
    59  )
    60  
    61  var (
    62  	procSelfExe           = "/proc/self/exe"
    63  	isHomeUsingNFS        = osutil.IsHomeUsingNFS
    64  	isRootWritableOverlay = osutil.IsRootWritableOverlay
    65  	kernelFeatures        = apparmor_sandbox.KernelFeatures
    66  	parserFeatures        = apparmor_sandbox.ParserFeatures
    67  
    68  	// make sure that apparmor profile fulfills the late discarding backend
    69  	// interface
    70  	_ interfaces.SecurityBackendDiscardingLate = (*Backend)(nil)
    71  )
    72  
    73  // Backend is responsible for maintaining apparmor profiles for snaps and parts of snapd.
    74  type Backend struct {
    75  	preseed bool
    76  }
    77  
    78  // Name returns the name of the backend.
    79  func (b *Backend) Name() interfaces.SecuritySystem {
    80  	return interfaces.SecurityAppArmor
    81  }
    82  
    83  // Initialize prepares customized apparmor policy for snap-confine.
    84  func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error {
    85  	if opts != nil && opts.Preseed {
    86  		b.preseed = true
    87  	}
    88  	// NOTE: It would be nice if we could also generate the profile for
    89  	// snap-confine executing from the core snap, right here, and not have to
    90  	// do this in the Setup function below. I sadly don't think this is
    91  	// possible because snapd must be able to install a new core and only at
    92  	// that moment generate it.
    93  
    94  	// Inspect the system and sets up local apparmor policy for snap-confine.
    95  	// Local policy is included by the system-wide policy. If the local policy
    96  	// has changed then the apparmor profile for snap-confine is reloaded.
    97  
    98  	// Create the local policy directory if it is not there.
    99  	if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil {
   100  		return fmt.Errorf("cannot create snap-confine policy directory: %s", err)
   101  	}
   102  
   103  	// Check the /proc/self/exe symlink, this is needed below but we want to
   104  	// fail early if this fails for whatever reason.
   105  	exe, err := os.Readlink(procSelfExe)
   106  	if err != nil {
   107  		return fmt.Errorf("cannot read %s: %s", procSelfExe, err)
   108  	}
   109  
   110  	// Location of the generated policy.
   111  	glob := "*"
   112  	policy := make(map[string]osutil.FileState)
   113  
   114  	// Check if NFS is mounted at or under $HOME. Because NFS is not
   115  	// transparent to apparmor we must alter our profile to counter that and
   116  	// allow snap-confine to work.
   117  	if nfs, err := isHomeUsingNFS(); err != nil {
   118  		logger.Noticef("cannot determine if NFS is in use: %v", err)
   119  	} else if nfs {
   120  		policy["nfs-support"] = &osutil.MemoryFileState{
   121  			Content: []byte(nfsSnippet),
   122  			Mode:    0644,
   123  		}
   124  		logger.Noticef("snapd enabled NFS support, additional implicit network permissions granted")
   125  	}
   126  
   127  	// Check if '/' is on overlayfs. If so, add the necessary rules for
   128  	// upperdir and allow snap-confine to work.
   129  	if overlayRoot, err := isRootWritableOverlay(); err != nil {
   130  		logger.Noticef("cannot determine if root filesystem on overlay: %v", err)
   131  	} else if overlayRoot != "" {
   132  		snippet := strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1)
   133  		policy["overlay-root"] = &osutil.MemoryFileState{
   134  			Content: []byte(snippet),
   135  			Mode:    0644,
   136  		}
   137  		logger.Noticef("snapd enabled root filesystem on overlay support, additional upperdir permissions granted")
   138  	}
   139  
   140  	// Ensure that generated policy is what we computed above.
   141  	created, removed, err := osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, policy)
   142  	if err != nil {
   143  		return fmt.Errorf("cannot synchronize snap-confine policy: %s", err)
   144  	}
   145  	if len(created) == 0 && len(removed) == 0 {
   146  		// If the generated policy didn't change, we're all done.
   147  		return nil
   148  	}
   149  
   150  	// If snapd is executing from the core snap the it means it has
   151  	// re-executed. In that case we are no longer using the copy of
   152  	// snap-confine from the host distribution but our own copy. We don't have
   153  	// to re-compile and load the updated profile as that is performed by
   154  	// setupSnapConfineReexec below.
   155  	if strings.HasPrefix(exe, dirs.SnapMountDir) {
   156  		return nil
   157  	}
   158  
   159  	// Reload the apparmor profile of snap-confine. This points to the main
   160  	// file in /etc/apparmor.d/ as that file contains include statements that
   161  	// load any of the files placed in /var/lib/snapd/apparmor/snap-confine/.
   162  	// For historical reasons we may have a filename that ends with .real or
   163  	// not.  If we do then we prefer the file ending with the name .real as
   164  	// that is the more recent name we use.
   165  	var profilePath string
   166  	for _, profileFname := range []string{"usr.lib.snapd.snap-confine.real", "usr.lib.snapd.snap-confine"} {
   167  		profilePath = filepath.Join(apparmor_sandbox.ConfDir, profileFname)
   168  		if _, err := os.Stat(profilePath); err != nil {
   169  			if os.IsNotExist(err) {
   170  				continue
   171  			}
   172  			return err
   173  		}
   174  		break
   175  	}
   176  	if profilePath == "" {
   177  		return fmt.Errorf("cannot find system apparmor profile for snap-confine")
   178  	}
   179  
   180  	aaFlags := skipReadCache
   181  	if b.preseed {
   182  		aaFlags |= skipKernelLoad
   183  	}
   184  
   185  	// We are not using apparmor.LoadProfiles() because it uses other cache.
   186  	if err := loadProfiles([]string{profilePath}, apparmor_sandbox.SystemCacheDir, aaFlags); err != nil {
   187  		// When we cannot reload the profile then let's remove the generated
   188  		// policy. Maybe we have caused the problem so it's better to let other
   189  		// things work.
   190  		osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, nil)
   191  		return fmt.Errorf("cannot reload snap-confine apparmor profile: %v", err)
   192  	}
   193  	return nil
   194  }
   195  
   196  // snapConfineFromSnapProfile returns the apparmor profile for
   197  // snap-confine in the given core/snapd snap.
   198  func snapConfineFromSnapProfile(info *snap.Info) (dir, glob string, content map[string]osutil.FileState, err error) {
   199  	// Find the vanilla apparmor profile for snap-confine as present in the given core snap.
   200  
   201  	// We must test the ".real" suffix first, this is a workaround for
   202  	// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=858004
   203  	vanillaProfilePath := filepath.Join(info.MountDir(), "/etc/apparmor.d/usr.lib.snapd.snap-confine.real")
   204  	vanillaProfileText, err := ioutil.ReadFile(vanillaProfilePath)
   205  	if os.IsNotExist(err) {
   206  		vanillaProfilePath = filepath.Join(info.MountDir(), "/etc/apparmor.d/usr.lib.snapd.snap-confine")
   207  		vanillaProfileText, err = ioutil.ReadFile(vanillaProfilePath)
   208  	}
   209  	if err != nil {
   210  		return "", "", nil, fmt.Errorf("cannot open apparmor profile for vanilla snap-confine: %s", err)
   211  	}
   212  
   213  	// Replace the path to vanilla snap-confine with the path to the mounted snap-confine from core.
   214  	snapConfineInCore := filepath.Join(info.MountDir(), "usr/lib/snapd/snap-confine")
   215  	patchedProfileText := bytes.Replace(
   216  		vanillaProfileText, []byte("/usr/lib/snapd/snap-confine"), []byte(snapConfineInCore), -1)
   217  
   218  	// We need to add a uniqe prefix that can never collide with a
   219  	// snap on the system. Using "snap-confine.*" is similar to
   220  	// "snap-update-ns.*" that is already used there
   221  	//
   222  	// So
   223  	//   /snap/core/111/usr/lib/snapd/snap-confine
   224  	// becomes
   225  	//   snap-confine.core.111
   226  	patchedProfileName := snapConfineProfileName(info.InstanceName(), info.Revision)
   227  	// remove other generated profiles, which is only relevant for the
   228  	// 'core' snap on classic system where we reexec, on core systems the
   229  	// profile is already a part of the rootfs snap
   230  	patchedProfileGlob := fmt.Sprintf("snap-confine.%s.*", info.InstanceName())
   231  
   232  	if info.Type() == snap.TypeSnapd {
   233  		// with the snapd snap, things are a little different, the
   234  		// profile is discarded only late for the revisions that are
   235  		// being removed, also on core devices the rootfs snap and the
   236  		// snapd snap are updated separately, so the profile needs to be
   237  		// around for as long as the given revision of the snapd snap is
   238  		// active, so we use the exact match such that we only replace
   239  		// our own profile, which can happen if system was rebooted
   240  		// before task calling the backend was finished
   241  		patchedProfileGlob = patchedProfileName
   242  	}
   243  
   244  	// Return information for EnsureDirState that describes the re-exec profile for snap-confine.
   245  	content = map[string]osutil.FileState{
   246  		patchedProfileName: &osutil.MemoryFileState{
   247  			Content: []byte(patchedProfileText),
   248  			Mode:    0644,
   249  		},
   250  	}
   251  
   252  	return dirs.SnapAppArmorDir, patchedProfileGlob, content, nil
   253  }
   254  
   255  func snapConfineProfileName(snapName string, rev snap.Revision) string {
   256  	return fmt.Sprintf("snap-confine.%s.%s", snapName, rev)
   257  }
   258  
   259  // setupSnapConfineReexec will setup apparmor profiles inside the host's
   260  // /var/lib/snapd/apparmor/profiles directory. This is needed for
   261  // running snap-confine from the core or snapd snap.
   262  //
   263  // Additionally it will cleanup stale apparmor profiles it created.
   264  func (b *Backend) setupSnapConfineReexec(info *snap.Info) error {
   265  	if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil {
   266  		return fmt.Errorf("cannot create snap-confine policy directory: %s", err)
   267  	}
   268  	dir, glob, content, err := snapConfineFromSnapProfile(info)
   269  	cache := apparmor_sandbox.CacheDir
   270  	if err != nil {
   271  		return fmt.Errorf("cannot compute snap-confine profile: %s", err)
   272  	}
   273  	if err := os.MkdirAll(dir, 0755); err != nil {
   274  		return fmt.Errorf("cannot create snap-confine directory %q: %s", dir, err)
   275  	}
   276  
   277  	changed, removed, errEnsure := osutil.EnsureDirState(dir, glob, content)
   278  	if len(changed) == 0 {
   279  		// XXX: because NFS workaround is handled separately the same correct
   280  		// snap-confine profile may need to be re-loaded. This is because the
   281  		// profile contains include directives and those load a second file
   282  		// that has changed outside of the scope of EnsureDirState.
   283  		//
   284  		// To counter that, always reload the profile by pretending it had
   285  		// changed.
   286  		for fname := range content {
   287  			changed = append(changed, fname)
   288  		}
   289  	}
   290  	pathnames := make([]string, len(changed))
   291  	for i, profile := range changed {
   292  		pathnames[i] = filepath.Join(dir, profile)
   293  	}
   294  
   295  	var aaFlags aaParserFlags
   296  	if b.preseed {
   297  		aaFlags = skipKernelLoad
   298  	}
   299  	errReload := loadProfiles(pathnames, cache, aaFlags)
   300  	errUnload := unloadProfiles(removed, cache)
   301  	if errEnsure != nil {
   302  		return fmt.Errorf("cannot synchronize snap-confine apparmor profile: %s", errEnsure)
   303  	}
   304  	if errReload != nil {
   305  		return fmt.Errorf("cannot reload snap-confine apparmor profile: %s", errReload)
   306  	}
   307  	if errUnload != nil {
   308  		return fmt.Errorf("cannot unload snap-confine apparmor profile: %s", errReload)
   309  	}
   310  	return nil
   311  }
   312  
   313  // nsProfile returns name of the apparmor profile for snap-update-ns for a given snap.
   314  func nsProfile(snapName string) string {
   315  	return fmt.Sprintf("snap-update-ns.%s", snapName)
   316  }
   317  
   318  // profileGlobs returns a list of globs that describe the apparmor profiles of
   319  // a given snap.
   320  //
   321  // Currently the list is just a pair. The first glob describes profiles for all
   322  // apps and hooks while the second profile describes the snap-update-ns profile
   323  // for the whole snap.
   324  func profileGlobs(snapName string) []string {
   325  	return []string{interfaces.SecurityTagGlob(snapName), nsProfile(snapName)}
   326  }
   327  
   328  // Determine if a profile filename is removable during core refresh/rollback.
   329  // This is needed because core devices are also special, the apparmor cache
   330  // gets confused too easy, especially at rollbacks, so we delete the cache. See
   331  // Setup(), below. Some systems employ a unified cache directory where all
   332  // apparmor cache files are stored under one location so ensure we don't remove
   333  // the snap profiles since snapd manages them elsewhere and instead only remove
   334  // snap-confine and system profiles (eg, as shipped by distro package manager
   335  // or created by the administrator). snap-confine profiles are like the
   336  // following:
   337  // - usr.lib.snapd.snap-confine.real
   338  // - usr.lib.snapd.snap-confine (historic)
   339  // - snap.core.NNNN.usr.lib.snapd.snap-confine (historic)
   340  // - var.lib.snapd.snap.core.NNNN.usr.lib.snapd.snap-confine (historic)
   341  // - snap-confine.core.NNNN
   342  // - snap-confine.snapd.NNNN
   343  func profileIsRemovableOnCoreSetup(fn string) bool {
   344  	bn := path.Base(fn)
   345  	if strings.HasPrefix(bn, ".") {
   346  		return false
   347  	} 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") {
   348  		return false
   349  	}
   350  	return true
   351  }
   352  
   353  type profilePathsResults struct {
   354  	changed   []string
   355  	unchanged []string
   356  	removed   []string
   357  }
   358  
   359  func (b *Backend) prepareProfiles(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) (prof *profilePathsResults, err error) {
   360  	snapName := snapInfo.InstanceName()
   361  	spec, err := repo.SnapSpecification(b.Name(), snapName)
   362  	if err != nil {
   363  		return nil, fmt.Errorf("cannot obtain apparmor specification for snap %q: %s", snapName, err)
   364  	}
   365  
   366  	// Add snippets for parallel snap installation mapping
   367  	spec.(*Specification).AddOvername(snapInfo)
   368  
   369  	// Add snippets derived from the layout definition.
   370  	spec.(*Specification).AddLayout(snapInfo)
   371  
   372  	// core on classic is special
   373  	if snapName == "core" && release.OnClassic && apparmor_sandbox.ProbedLevel() != apparmor_sandbox.Unsupported {
   374  		if err := b.setupSnapConfineReexec(snapInfo); err != nil {
   375  			return nil, fmt.Errorf("cannot create host snap-confine apparmor configuration: %s", err)
   376  		}
   377  	}
   378  
   379  	// Deal with the "snapd" snap - we do the setup slightly differently
   380  	// here because this will run both on classic and on Ubuntu Core 18
   381  	// systems but /etc/apparmor.d is not writable on core18 systems
   382  	if snapInfo.Type() == snap.TypeSnapd && apparmor_sandbox.ProbedLevel() != apparmor_sandbox.Unsupported {
   383  		if err := b.setupSnapConfineReexec(snapInfo); err != nil {
   384  			return nil, fmt.Errorf("cannot create host snap-confine apparmor configuration: %s", err)
   385  		}
   386  	}
   387  
   388  	// core on core devices is also special, the apparmor cache gets
   389  	// confused too easy, especially at rollbacks, so we delete the cache.
   390  	// See LP:#1460152 and
   391  	// https://forum.snapcraft.io/t/core-snap-revert-issues-on-core-devices/
   392  	//
   393  	if (snapInfo.Type() == snap.TypeOS || snapInfo.Type() == snap.TypeSnapd) && !release.OnClassic {
   394  		if li, err := filepath.Glob(filepath.Join(apparmor_sandbox.SystemCacheDir, "*")); err == nil {
   395  			for _, p := range li {
   396  				if st, err := os.Stat(p); err == nil && st.Mode().IsRegular() && profileIsRemovableOnCoreSetup(p) {
   397  					if err := os.Remove(p); err != nil {
   398  						logger.Noticef("cannot remove %q: %s", p, err)
   399  					}
   400  				}
   401  			}
   402  		}
   403  	}
   404  
   405  	// Get the files that this snap should have
   406  	content, err := b.deriveContent(spec.(*Specification), snapInfo, opts)
   407  	if err != nil {
   408  		return nil, fmt.Errorf("cannot obtain expected security files for snap %q: %s", snapName, err)
   409  	}
   410  	dir := dirs.SnapAppArmorDir
   411  	globs := profileGlobs(snapInfo.InstanceName())
   412  	if err := os.MkdirAll(dir, 0755); err != nil {
   413  		return nil, fmt.Errorf("cannot create directory for apparmor profiles %q: %s", dir, err)
   414  	}
   415  	changed, removedPaths, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, content)
   416  	// XXX: in the old code this error was reported late, after doing load/unload.
   417  	if errEnsure != nil {
   418  		return nil, fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure)
   419  	}
   420  
   421  	// Find the set of unchanged profiles.
   422  	unchanged := make([]string, 0, len(content)-len(changed))
   423  	for name := range content {
   424  		// changed is pre-sorted by EnsureDirStateGlobs
   425  		x := sort.SearchStrings(changed, name)
   426  		if x < len(changed) && changed[x] == name {
   427  			continue
   428  		}
   429  		unchanged = append(unchanged, name)
   430  	}
   431  	sort.Strings(unchanged)
   432  
   433  	changedPaths := make([]string, len(changed))
   434  	for i, profile := range changed {
   435  		changedPaths[i] = filepath.Join(dir, profile)
   436  	}
   437  
   438  	unchangedPaths := make([]string, len(unchanged))
   439  	for i, profile := range unchanged {
   440  		unchangedPaths[i] = filepath.Join(dir, profile)
   441  	}
   442  
   443  	return &profilePathsResults{changed: changedPaths, removed: removedPaths, unchanged: unchangedPaths}, nil
   444  }
   445  
   446  // Setup creates and loads apparmor profiles specific to a given snap.
   447  // The snap can be in developer mode to make security violations non-fatal to
   448  // the offending application process.
   449  //
   450  // This method should be called after changing plug, slots, connections between
   451  // them or application present in the snap.
   452  func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
   453  	prof, err := b.prepareProfiles(snapInfo, opts, repo)
   454  	if err != nil {
   455  		return err
   456  	}
   457  
   458  	// Load all changed profiles with a flag that asks apparmor to skip reading
   459  	// the cache (since we know those changed for sure).  This allows us to
   460  	// work despite time being wrong (e.g. in the past). For more details see
   461  	// https://forum.snapcraft.io/t/apparmor-profile-caching/1268/18
   462  	var errReloadChanged error
   463  	aaFlags := skipReadCache
   464  	if b.preseed {
   465  		aaFlags |= skipKernelLoad
   466  	}
   467  	timings.Run(tm, "load-profiles[changed]", fmt.Sprintf("load changed security profiles of snap %q", snapInfo.InstanceName()), func(nesttm timings.Measurer) {
   468  		errReloadChanged = loadProfiles(prof.changed, apparmor_sandbox.CacheDir, aaFlags)
   469  	})
   470  
   471  	// Load all unchanged profiles anyway. This ensures those are correct in
   472  	// the kernel even if the files on disk were not changed. We rely on
   473  	// apparmor cache to make this performant.
   474  	var errReloadOther error
   475  	aaFlags = 0
   476  	if b.preseed {
   477  		aaFlags |= skipKernelLoad
   478  	}
   479  	timings.Run(tm, "load-profiles[unchanged]", fmt.Sprintf("load unchanged security profiles of snap %q", snapInfo.InstanceName()), func(nesttm timings.Measurer) {
   480  		errReloadOther = loadProfiles(prof.unchanged, apparmor_sandbox.CacheDir, aaFlags)
   481  	})
   482  	errUnload := unloadProfiles(prof.removed, apparmor_sandbox.CacheDir)
   483  	if errReloadChanged != nil {
   484  		return errReloadChanged
   485  	}
   486  	if errReloadOther != nil {
   487  		return errReloadOther
   488  	}
   489  	return errUnload
   490  }
   491  
   492  // SetupMany creates and loads apparmor profiles for multiple snaps.
   493  // The snaps can be in developer mode to make security violations non-fatal to
   494  // the offending application process.
   495  // SetupMany tries to recreate all profiles without interrupting on errors, but
   496  // collects and returns them all.
   497  //
   498  // This method is useful mainly for regenerating profiles.
   499  func (b *Backend) SetupMany(snaps []*snap.Info, confinement func(snapName string) interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) []error {
   500  	var allChangedPaths, allUnchangedPaths, allRemovedPaths []string
   501  	var fallback bool
   502  	for _, snapInfo := range snaps {
   503  		opts := confinement(snapInfo.InstanceName())
   504  		prof, err := b.prepareProfiles(snapInfo, opts, repo)
   505  		if err != nil {
   506  			fallback = true
   507  			break
   508  		}
   509  		allChangedPaths = append(allChangedPaths, prof.changed...)
   510  		allUnchangedPaths = append(allUnchangedPaths, prof.unchanged...)
   511  		allRemovedPaths = append(allRemovedPaths, prof.removed...)
   512  	}
   513  
   514  	if !fallback {
   515  		aaFlags := skipReadCache | conserveCPU
   516  		if b.preseed {
   517  			aaFlags |= skipKernelLoad
   518  		}
   519  		var errReloadChanged error
   520  		timings.Run(tm, "load-profiles[changed-many]", fmt.Sprintf("load changed security profiles of %d snaps", len(snaps)), func(nesttm timings.Measurer) {
   521  			errReloadChanged = loadProfiles(allChangedPaths, apparmor_sandbox.CacheDir, aaFlags)
   522  		})
   523  
   524  		aaFlags = conserveCPU
   525  		if b.preseed {
   526  			aaFlags |= skipKernelLoad
   527  		}
   528  		var errReloadOther error
   529  		timings.Run(tm, "load-profiles[unchanged-many]", fmt.Sprintf("load unchanged security profiles %d snaps", len(snaps)), func(nesttm timings.Measurer) {
   530  			errReloadOther = loadProfiles(allUnchangedPaths, apparmor_sandbox.CacheDir, aaFlags)
   531  		})
   532  
   533  		errUnload := unloadProfiles(allRemovedPaths, apparmor_sandbox.CacheDir)
   534  		if errReloadChanged != nil {
   535  			logger.Noticef("failed to batch-reload changed profiles: %s", errReloadChanged)
   536  			fallback = true
   537  		}
   538  		if errReloadOther != nil {
   539  			logger.Noticef("failed to batch-reload unchanged profiles: %s", errReloadOther)
   540  			fallback = true
   541  		}
   542  		if errUnload != nil {
   543  			logger.Noticef("failed to batch-unload profiles: %s", errUnload)
   544  			fallback = true
   545  		}
   546  	}
   547  
   548  	var errors []error
   549  	// if an error was encountered when processing all profiles at once, re-try them one by one
   550  	if fallback {
   551  		for _, snapInfo := range snaps {
   552  			opts := confinement(snapInfo.InstanceName())
   553  			if err := b.Setup(snapInfo, opts, repo, tm); err != nil {
   554  				errors = append(errors, fmt.Errorf("cannot setup profiles for snap %q: %s", snapInfo.InstanceName(), err))
   555  			}
   556  		}
   557  	}
   558  	return errors
   559  }
   560  
   561  // Remove removes and unloads apparmor profiles of a given snap.
   562  func (b *Backend) Remove(snapName string) error {
   563  	dir := dirs.SnapAppArmorDir
   564  	globs := profileGlobs(snapName)
   565  	cache := apparmor_sandbox.CacheDir
   566  	_, removed, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, nil)
   567  	// always try to unload affected profiles
   568  	errUnload := unloadProfiles(removed, cache)
   569  	if errEnsure != nil {
   570  		return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure)
   571  	}
   572  	return errUnload
   573  }
   574  
   575  func (b *Backend) RemoveLate(snapName string, rev snap.Revision, typ snap.Type) error {
   576  	logger.Debugf("remove late for snap %v (%s) type %v", snapName, rev, typ)
   577  	if typ != snap.TypeSnapd {
   578  		// late remove is relevant only for snap confine profiles
   579  		return nil
   580  	}
   581  
   582  	globs := []string{snapConfineProfileName(snapName, rev)}
   583  	_, removed, errEnsure := osutil.EnsureDirStateGlobs(dirs.SnapAppArmorDir, globs, nil)
   584  	// XXX: unloadProfiles() does not unload profiles from the kernel, but
   585  	// only removes profiles from the cache
   586  	// always try to unload the affected profile
   587  	errUnload := unloadProfiles(removed, apparmor_sandbox.CacheDir)
   588  	if errEnsure != nil {
   589  		return fmt.Errorf("cannot remove security profiles for snap %q (%s): %s", snapName, rev, errEnsure)
   590  	}
   591  	return errUnload
   592  }
   593  
   594  var (
   595  	templatePattern    = regexp.MustCompile("(###[A-Z_]+###)")
   596  	coreRuntimePattern = regexp.MustCompile("^core([0-9][0-9])?$")
   597  )
   598  
   599  const (
   600  	attachPattern  = "(attach_disconnected,mediate_deleted)"
   601  	attachComplain = "(attach_disconnected,mediate_deleted,complain)"
   602  )
   603  
   604  func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info, opts interfaces.ConfinementOptions) (content map[string]osutil.FileState, err error) {
   605  	content = make(map[string]osutil.FileState, len(snapInfo.Apps)+len(snapInfo.Hooks)+1)
   606  
   607  	// Add profile for each app.
   608  	for _, appInfo := range snapInfo.Apps {
   609  		securityTag := appInfo.SecurityTag()
   610  		addContent(securityTag, snapInfo, appInfo.Name, opts, spec.SnippetForTag(securityTag), content, spec)
   611  	}
   612  	// Add profile for each hook.
   613  	for _, hookInfo := range snapInfo.Hooks {
   614  		securityTag := hookInfo.SecurityTag()
   615  		addContent(securityTag, snapInfo, "hook."+hookInfo.Name, opts, spec.SnippetForTag(securityTag), content, spec)
   616  	}
   617  	// Add profile for snap-update-ns if we have any apps or hooks.
   618  	// If we have neither then we don't have any need to create an executing environment.
   619  	// This applies to, for example, kernel snaps or gadget snaps (unless they have hooks).
   620  	if len(content) > 0 {
   621  		snippets := strings.Join(spec.UpdateNS(), "\n")
   622  		addUpdateNSProfile(snapInfo, opts, snippets, content)
   623  	}
   624  
   625  	return content, nil
   626  }
   627  
   628  // addUpdateNSProfile adds an apparmor profile for snap-update-ns, tailored to a specific snap.
   629  //
   630  // This profile exists so that snap-update-ns doens't need to carry very wide, open permissions
   631  // that are suitable for poking holes (and writing) in nearly arbitrary places. Instead the profile
   632  // contains just the permissions needed to poke a hole and write to the layout-specific paths.
   633  func addUpdateNSProfile(snapInfo *snap.Info, opts interfaces.ConfinementOptions, snippets string, content map[string]osutil.FileState) {
   634  	// Compute the template by injecting special updateNS snippets.
   635  	policy := templatePattern.ReplaceAllStringFunc(updateNSTemplate, func(placeholder string) string {
   636  		switch placeholder {
   637  		case "###SNAP_INSTANCE_NAME###":
   638  			return snapInfo.InstanceName()
   639  		case "###SNIPPETS###":
   640  			if overlayRoot, _ := isRootWritableOverlay(); overlayRoot != "" {
   641  				snippets += strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1)
   642  			}
   643  			return snippets
   644  		}
   645  		return ""
   646  	})
   647  
   648  	// Ensure that the snap-update-ns profile is on disk.
   649  	profileName := nsProfile(snapInfo.InstanceName())
   650  	content[profileName] = &osutil.MemoryFileState{
   651  		Content: []byte(policy),
   652  		Mode:    0644,
   653  	}
   654  }
   655  
   656  func addContent(securityTag string, snapInfo *snap.Info, cmdName string, opts interfaces.ConfinementOptions, snippetForTag string, content map[string]osutil.FileState, spec *Specification) {
   657  	// If base is specified and it doesn't match the core snaps (not
   658  	// specifying a base should use the default core policy since in this
   659  	// case, the 'core' snap is used for the runtime), use the base
   660  	// apparmor template, otherwise use the default template.
   661  	var policy string
   662  	if snapInfo.Base != "" && !coreRuntimePattern.MatchString(snapInfo.Base) {
   663  		policy = defaultOtherBaseTemplate
   664  	} else {
   665  		policy = defaultCoreRuntimeTemplate
   666  	}
   667  
   668  	ignoreSnippets := false
   669  	// Classic confinement (unless overridden by JailMode) has a dedicated
   670  	// permissive template that applies a strict, but very open, policy.
   671  	if opts.Classic && !opts.JailMode {
   672  		policy = classicTemplate
   673  		ignoreSnippets = true
   674  	}
   675  	// If a snap is in devmode (or is using classic confinement) then make the
   676  	// profile non-enforcing where violations are logged but not denied.
   677  	// This is also done for classic so that no confinement applies. Just in
   678  	// case the profile we start with is not permissive enough.
   679  	if (opts.DevMode || opts.Classic) && !opts.JailMode {
   680  		policy = strings.Replace(policy, attachPattern, attachComplain, -1)
   681  	}
   682  	policy = templatePattern.ReplaceAllStringFunc(policy, func(placeholder string) string {
   683  		switch placeholder {
   684  		case "###VAR###":
   685  			return templateVariables(snapInfo, securityTag, cmdName)
   686  		case "###PROFILEATTACH###":
   687  			return fmt.Sprintf("profile \"%s\"", securityTag)
   688  		case "###CHANGEPROFILE_RULE###":
   689  			features, _ := parserFeatures()
   690  			for _, f := range features {
   691  				if f == "unsafe" {
   692  					return "change_profile unsafe /**,"
   693  				}
   694  			}
   695  			return "change_profile,"
   696  		case "###SNIPPETS###":
   697  			var tagSnippets string
   698  			if opts.Classic && opts.JailMode {
   699  				// Add a special internal snippet for snaps using classic confinement
   700  				// and jailmode together. This snippet provides access to the core snap
   701  				// so that the dynamic linker and shared libraries can be used.
   702  				tagSnippets = classicJailmodeSnippet + "\n" + snippetForTag
   703  			} else if ignoreSnippets {
   704  				// When classic confinement template is in effect we are
   705  				// ignoring all apparmor snippets as they may conflict with the
   706  				// super-broad template we are starting with.
   707  			} else {
   708  				// Check if NFS is mounted at or under $HOME. Because NFS is not
   709  				// transparent to apparmor we must alter the profile to counter that and
   710  				// allow access to SNAP_USER_* files.
   711  				tagSnippets = snippetForTag
   712  				if nfs, _ := isHomeUsingNFS(); nfs {
   713  					tagSnippets += nfsSnippet
   714  				}
   715  
   716  				if overlayRoot, _ := isRootWritableOverlay(); overlayRoot != "" {
   717  					snippet := strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1)
   718  					tagSnippets += snippet
   719  				}
   720  			}
   721  
   722  			if !ignoreSnippets {
   723  				// For policy with snippets that request
   724  				// suppression of 'ptrace (trace)' denials, add
   725  				// the suppression rule unless another
   726  				// interface said it uses them.
   727  				if spec.SuppressPtraceTrace() && !spec.UsesPtraceTrace() {
   728  					tagSnippets += ptraceTraceDenySnippet
   729  				}
   730  
   731  				// Use 'ix' rules in the home interface unless an
   732  				// interface asked to suppress them
   733  				repl := "ix"
   734  				if spec.SuppressHomeIx() {
   735  					repl = ""
   736  				}
   737  				tagSnippets = strings.Replace(tagSnippets, "###HOME_IX###", repl, -1)
   738  
   739  				// Conditionally add privilege dropping policy
   740  				if len(snapInfo.SystemUsernames) > 0 {
   741  					tagSnippets += privDropAndChownRules
   742  				}
   743  			}
   744  
   745  			return tagSnippets
   746  		}
   747  		return ""
   748  	})
   749  
   750  	content[securityTag] = &osutil.MemoryFileState{
   751  		Content: []byte(policy),
   752  		Mode:    0644,
   753  	}
   754  }
   755  
   756  // NewSpecification returns a new, empty apparmor specification.
   757  func (b *Backend) NewSpecification() interfaces.Specification {
   758  	return &Specification{}
   759  }
   760  
   761  // SandboxFeatures returns the list of apparmor features supported by the kernel.
   762  func (b *Backend) SandboxFeatures() []string {
   763  	if apparmor_sandbox.ProbedLevel() == apparmor_sandbox.Unsupported {
   764  		return nil
   765  	}
   766  
   767  	kFeatures, _ := kernelFeatures()
   768  	pFeatures, _ := parserFeatures()
   769  	tags := make([]string, 0, len(kFeatures)+len(pFeatures))
   770  	for _, feature := range kFeatures {
   771  		// Prepend "kernel:" to apparmor kernel features to namespace them and
   772  		// allow us to introduce our own tags later.
   773  		tags = append(tags, "kernel:"+feature)
   774  	}
   775  
   776  	for _, feature := range pFeatures {
   777  		// Prepend "parser:" to apparmor kernel features to namespace
   778  		// them and allow us to introduce our own tags later.
   779  		tags = append(tags, "parser:"+feature)
   780  	}
   781  
   782  	level := "full"
   783  	policy := "default"
   784  	if apparmor_sandbox.ProbedLevel() == apparmor_sandbox.Partial {
   785  		level = "partial"
   786  	}
   787  	tags = append(tags, fmt.Sprintf("support-level:%s", level))
   788  	tags = append(tags, fmt.Sprintf("policy:%s", policy))
   789  
   790  	return tags
   791  }
   792  
   793  // MockIsHomeUsingNFS mocks the real implementation of osutil.IsHomeUsingNFS.
   794  // This is exported so that other packages that indirectly interact with AppArmor backend
   795  // can mock isHomeUsingNFS.
   796  func MockIsHomeUsingNFS(new func() (bool, error)) (restore func()) {
   797  	old := isHomeUsingNFS
   798  	isHomeUsingNFS = new
   799  	return func() {
   800  		isHomeUsingNFS = old
   801  	}
   802  }