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