github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/apparmor/backend.go (about)

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