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