gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/snapstate/check_snap.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2016 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 snapstate
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"github.com/snapcore/snapd/arch"
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/logger"
    31  	"github.com/snapcore/snapd/osutil"
    32  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    33  	"github.com/snapcore/snapd/overlord/state"
    34  	"github.com/snapcore/snapd/release"
    35  	seccomp_compiler "github.com/snapcore/snapd/sandbox/seccomp"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/snapdtool"
    38  	"github.com/snapcore/snapd/strutil"
    39  )
    40  
    41  // featureSet contains the flag values that can be listed in assumes entries
    42  // that this ubuntu-core actually provides.
    43  var featureSet = map[string]bool{
    44  	// Support for common data directory across revisions of a snap.
    45  	"common-data-dir": true,
    46  	// Support for the "Environment:" feature in snap.yaml
    47  	"snap-env": true,
    48  	// Support for the "command-chain" feature for apps and hooks in snap.yaml
    49  	"command-chain": true,
    50  	// Support for "kernel-assets" in gadget.yaml. I.e. having volume
    51  	// content of the style $kernel:ref`
    52  	"kernel-assets": true,
    53  }
    54  
    55  func checkAssumes(si *snap.Info) error {
    56  	missing := ([]string)(nil)
    57  	for _, flag := range si.Assumes {
    58  		if strings.HasPrefix(flag, "snapd") && checkVersion(flag[5:]) {
    59  			continue
    60  		}
    61  		if !featureSet[flag] {
    62  			missing = append(missing, flag)
    63  		}
    64  	}
    65  	if len(missing) > 0 {
    66  		return fmt.Errorf("snap %q assumes unsupported features: %s (try to refresh snapd)", si.InstanceName(), strings.Join(missing, ", "))
    67  	}
    68  	return nil
    69  }
    70  
    71  // regular expression which matches a version expressed as groups of digits
    72  // separated with dots, with optional non-numbers afterwards
    73  var versionExp = regexp.MustCompile(`^(?:[1-9][0-9]*)(?:\.(?:[0-9]+))*`)
    74  
    75  func checkVersion(version string) bool {
    76  	// double check that the input looks like a snapd version
    77  	reqVersionNumMatch := versionExp.FindStringSubmatch(version)
    78  	if reqVersionNumMatch == nil {
    79  		return false
    80  	}
    81  	// this check ensures that no one can use an assumes like snapd2.48.3~pre2
    82  	// or snapd2.48.5+20.10, as modifiers past the version number are not meant
    83  	// to be relied on for snaps via assumes, however the check against the real
    84  	// snapd version number below allows such non-numeric modifiers since real
    85  	// snapds do have versions like that (for example debian pkg of snapd)
    86  	if reqVersionNumMatch[0] != version {
    87  		return false
    88  	}
    89  
    90  	req := strings.Split(reqVersionNumMatch[0], ".")
    91  
    92  	if snapdtool.Version == "unknown" {
    93  		return true // Development tree.
    94  	}
    95  
    96  	// We could (should?) use strutil.VersionCompare here and simplify
    97  	// this code (see PR#7344). However this would change current
    98  	// behavior, i.e. "2.41~pre1" would *not* match [snapd2.41] anymore
    99  	// (which the code below does).
   100  	curVersionNumMatch := versionExp.FindStringSubmatch(snapdtool.Version)
   101  	if curVersionNumMatch == nil {
   102  		return false
   103  	}
   104  	cur := strings.Split(curVersionNumMatch[0], ".")
   105  
   106  	for i := range req {
   107  		if i == len(cur) {
   108  			// we hit the end of the elements of the current version number and have
   109  			// more required version numbers left, so this doesn't match, if the
   110  			// previous element was higher we would have broken out already, so the
   111  			// only case left here is where we have version requirements that are
   112  			// not met
   113  			return false
   114  		}
   115  		reqN, err1 := strconv.Atoi(req[i])
   116  		curN, err2 := strconv.Atoi(cur[i])
   117  		if err1 != nil || err2 != nil {
   118  			panic("internal error: version regexp is broken")
   119  		}
   120  		if curN != reqN {
   121  			return curN > reqN
   122  		}
   123  	}
   124  
   125  	return true
   126  }
   127  
   128  type SnapNeedsDevModeError struct {
   129  	Snap string
   130  }
   131  
   132  func (e *SnapNeedsDevModeError) Error() string {
   133  	return fmt.Sprintf("snap %q requires devmode or confinement override", e.Snap)
   134  }
   135  
   136  type SnapNeedsClassicError struct {
   137  	Snap string
   138  }
   139  
   140  func (e *SnapNeedsClassicError) Error() string {
   141  	return fmt.Sprintf("snap %q requires classic confinement", e.Snap)
   142  }
   143  
   144  type SnapNeedsClassicSystemError struct {
   145  	Snap string
   146  }
   147  
   148  func (e *SnapNeedsClassicSystemError) Error() string {
   149  	return fmt.Sprintf("snap %q requires classic confinement which is only available on classic systems", e.Snap)
   150  }
   151  
   152  type SnapNotClassicError struct {
   153  	Snap string
   154  }
   155  
   156  func (e *SnapNotClassicError) Error() string {
   157  	return fmt.Sprintf("snap %q is not a classic confined snap", e.Snap)
   158  }
   159  
   160  // determine whether the flags (and system overrides thereof) are
   161  // compatible with the given *snap.Info
   162  func validateFlagsForInfo(info *snap.Info, snapst *SnapState, flags Flags) error {
   163  	if flags.Classic && !info.NeedsClassic() {
   164  		return &SnapNotClassicError{Snap: info.InstanceName()}
   165  	}
   166  
   167  	switch c := info.Confinement; c {
   168  	case snap.StrictConfinement, "":
   169  		// strict is always fine
   170  		return nil
   171  	case snap.DevModeConfinement:
   172  		// --devmode needs to be specified every time (==> ignore snapst)
   173  		if flags.DevModeAllowed() {
   174  			return nil
   175  		}
   176  		return &SnapNeedsDevModeError{
   177  			Snap: info.InstanceName(),
   178  		}
   179  	case snap.ClassicConfinement:
   180  		if !release.OnClassic {
   181  			return &SnapNeedsClassicSystemError{Snap: info.InstanceName()}
   182  		}
   183  
   184  		if flags.Classic {
   185  			return nil
   186  		}
   187  
   188  		if snapst != nil && snapst.Flags.Classic {
   189  			return nil
   190  		}
   191  
   192  		return &SnapNeedsClassicError{
   193  			Snap: info.InstanceName(),
   194  		}
   195  	default:
   196  		return fmt.Errorf("unknown confinement %q", c)
   197  	}
   198  }
   199  
   200  // do a reasonably lightweight check that a snap described by Info,
   201  // with the given SnapState and the user-specified Flags should be
   202  // installable on the current system.
   203  func validateInfoAndFlags(info *snap.Info, snapst *SnapState, flags Flags) error {
   204  	if err := validateFlagsForInfo(info, snapst, flags); err != nil {
   205  		return err
   206  	}
   207  
   208  	// verify we have a valid architecture
   209  	if !arch.IsSupportedArchitecture(info.Architectures) {
   210  		return fmt.Errorf("snap %q supported architectures (%s) are incompatible with this system (%s)", info.InstanceName(), strings.Join(info.Architectures, ", "), arch.DpkgArchitecture())
   211  	}
   212  
   213  	// check assumes
   214  	if err := checkAssumes(info); err != nil {
   215  		return err
   216  	}
   217  
   218  	// check and create system-usernames
   219  	if err := checkAndCreateSystemUsernames(info); err != nil {
   220  		return err
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  var openSnapFile = backend.OpenSnapFile
   227  
   228  func validateContainer(c snap.Container, s *snap.Info, logf func(format string, v ...interface{})) error {
   229  	err := snap.ValidateContainer(c, s, logf)
   230  	if err == nil {
   231  		return nil
   232  	}
   233  	return fmt.Errorf("%v; contact developer", err)
   234  }
   235  
   236  // checkSnap ensures that the snap can be installed.
   237  func checkSnap(st *state.State, snapFilePath, instanceName string, si *snap.SideInfo, curInfo *snap.Info, flags Flags, deviceCtx DeviceContext) error {
   238  	// This assumes that the snap was already verified or --dangerous was used.
   239  
   240  	s, c, err := openSnapFile(snapFilePath, si)
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	if err := validateInfoAndFlags(s, nil, flags); err != nil {
   246  		return err
   247  	}
   248  
   249  	if err := validateContainer(c, s, logger.Noticef); err != nil {
   250  		return err
   251  	}
   252  
   253  	snapName, instanceKey := snap.SplitInstanceName(instanceName)
   254  	// update instance key to what was requested
   255  	s.InstanceKey = instanceKey
   256  
   257  	st.Lock()
   258  	defer st.Unlock()
   259  
   260  	// allow registered checks to run first as they may produce more
   261  	// precise errors
   262  	for _, check := range checkSnapCallbacks {
   263  		err := check(st, s, curInfo, c, flags, deviceCtx)
   264  		if err != nil {
   265  			return err
   266  		}
   267  	}
   268  
   269  	if snapName != s.SnapName() {
   270  		return fmt.Errorf("cannot install snap %q using instance name %q", s.SnapName(), instanceName)
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  // CheckSnapCallback defines callbacks for checking a snap for installation or refresh.
   277  type CheckSnapCallback func(st *state.State, snap, curSnap *snap.Info, snapf snap.Container, flags Flags, deviceCtx DeviceContext) error
   278  
   279  var checkSnapCallbacks []CheckSnapCallback
   280  
   281  // AddCheckSnapCallback installs a callback to check a snap for installation or refresh.
   282  func AddCheckSnapCallback(check CheckSnapCallback) {
   283  	checkSnapCallbacks = append(checkSnapCallbacks, check)
   284  }
   285  
   286  func MockCheckSnapCallbacks(checks []CheckSnapCallback) (restore func()) {
   287  	prev := checkSnapCallbacks
   288  	checkSnapCallbacks = checks
   289  	return func() {
   290  		checkSnapCallbacks = prev
   291  	}
   292  }
   293  
   294  func checkSnapdName(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags Flags, deviceCtx DeviceContext) error {
   295  	if snapInfo.Type() != snap.TypeSnapd {
   296  		// not a relevant check
   297  		return nil
   298  	}
   299  	if snapInfo.InstanceName() != "snapd" {
   300  		return fmt.Errorf(`cannot install snap %q of type "snapd" with a name other than "snapd"`, snapInfo.InstanceName())
   301  	}
   302  
   303  	return nil
   304  }
   305  
   306  func checkCoreName(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags Flags, deviceCtx DeviceContext) error {
   307  	if snapInfo.Type() != snap.TypeOS {
   308  		// not a relevant check
   309  		return nil
   310  	}
   311  	if curInfo != nil {
   312  		// already one of these installed
   313  		return nil
   314  	}
   315  	core, err := coreInfo(st)
   316  	if err == state.ErrNoState {
   317  		return nil
   318  	}
   319  	if err != nil {
   320  		return err
   321  	}
   322  
   323  	// Allow installing "core" even if "ubuntu-core" is already
   324  	// installed. Ideally we should only allow this if we know
   325  	// this install is part of the ubuntu-core->core transition
   326  	// (e.g. via a flag) because if this happens outside of this
   327  	// transition we will end up with not connected interface
   328  	// connections in the "core" snap. But the transition will
   329  	// kick in automatically quickly so an extra flag is overkill.
   330  	if snapInfo.InstanceName() == "core" && core.InstanceName() == "ubuntu-core" {
   331  		return nil
   332  	}
   333  
   334  	// but generally do not allow to have two cores installed
   335  	if core.InstanceName() != snapInfo.InstanceName() {
   336  		return fmt.Errorf("cannot install core snap %q when core snap %q is already present", snapInfo.InstanceName(), core.InstanceName())
   337  	}
   338  
   339  	return nil
   340  }
   341  
   342  func checkGadgetOrKernel(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags Flags, deviceCtx DeviceContext) error {
   343  	typ := snapInfo.Type()
   344  	kind := ""
   345  	var whichName func(*asserts.Model) string
   346  	switch typ {
   347  	case snap.TypeGadget:
   348  		kind = "gadget"
   349  		whichName = (*asserts.Model).Gadget
   350  	case snap.TypeKernel:
   351  		kind = "kernel"
   352  		whichName = (*asserts.Model).Kernel
   353  	default:
   354  		// not a relevant check
   355  		return nil
   356  	}
   357  
   358  	ok, err := HasSnapOfType(st, typ)
   359  	if err != nil {
   360  		return fmt.Errorf("cannot detect original %s snap: %v", kind, err)
   361  	}
   362  	// in firstboot we have no gadget/kernel yet - that is ok
   363  	// first install rules are in devicestate!
   364  	if !ok {
   365  		return nil
   366  	}
   367  
   368  	currentSnap, err := infoForDeviceSnap(st, deviceCtx, kind, whichName)
   369  	if err == state.ErrNoState {
   370  		// check if we are in the remodel case
   371  		if deviceCtx != nil && deviceCtx.ForRemodeling() {
   372  			if whichName(deviceCtx.Model()) == snapInfo.InstanceName() {
   373  				return nil
   374  			}
   375  		}
   376  		return fmt.Errorf("internal error: no state for %s snap %q", kind, snapInfo.InstanceName())
   377  	}
   378  	if err != nil {
   379  		return fmt.Errorf("cannot find original %s snap: %v", kind, err)
   380  	}
   381  
   382  	if currentSnap.SnapID != "" && snapInfo.SnapID == "" {
   383  		return fmt.Errorf("cannot replace signed %s snap with an unasserted one", kind)
   384  	}
   385  
   386  	if currentSnap.SnapID != "" && snapInfo.SnapID != "" {
   387  		if currentSnap.SnapID == snapInfo.SnapID {
   388  			// same snap
   389  			return nil
   390  		}
   391  		return fmt.Errorf("cannot replace %s snap with a different one", kind)
   392  	}
   393  
   394  	if currentSnap.InstanceName() != snapInfo.InstanceName() {
   395  		return fmt.Errorf("cannot replace %s snap with a different one", kind)
   396  	}
   397  
   398  	return nil
   399  }
   400  
   401  func checkBases(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags Flags, deviceCtx DeviceContext) error {
   402  	// check if this is relevant
   403  	if snapInfo.Type() != snap.TypeApp && snapInfo.Type() != snap.TypeGadget {
   404  		return nil
   405  	}
   406  	if snapInfo.Base == "" {
   407  		return nil
   408  	}
   409  	if snapInfo.Base == "none" {
   410  		return nil
   411  	}
   412  
   413  	snapStates, err := All(st)
   414  	if err != nil {
   415  		return err
   416  	}
   417  	for otherSnap, snapst := range snapStates {
   418  		typ, err := snapst.Type()
   419  		if err != nil {
   420  			return err
   421  		}
   422  		if typ == snap.TypeBase && otherSnap == snapInfo.Base {
   423  			return nil
   424  		}
   425  		// core can be used instead for core16
   426  		if snapInfo.Base == "core16" && otherSnap == "core" {
   427  			return nil
   428  		}
   429  	}
   430  
   431  	return fmt.Errorf("cannot find required base %q", snapInfo.Base)
   432  }
   433  
   434  func checkEpochs(_ *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, _ Flags, deviceCtx DeviceContext) error {
   435  	if curInfo == nil {
   436  		return nil
   437  	}
   438  	if snapInfo.Epoch.CanRead(curInfo.Epoch) {
   439  		return nil
   440  	}
   441  	desc := "local snap"
   442  	if snapInfo.SideInfo.Revision.Store() {
   443  		desc = fmt.Sprintf("new revision %s", snapInfo.SideInfo.Revision)
   444  	}
   445  
   446  	return fmt.Errorf("cannot refresh %q to %s with epoch %s, because it can't read the current epoch of %s", snapInfo.InstanceName(), desc, snapInfo.Epoch, curInfo.Epoch)
   447  }
   448  
   449  // check that the snap installed in the system (via snapst) can be
   450  // upgraded to info (i.e. that info's epoch can read sanpst's epoch)
   451  func earlyEpochCheck(info *snap.Info, snapst *SnapState) error {
   452  	if snapst == nil {
   453  		// no snapst, no problem
   454  		return nil
   455  	}
   456  	cur, err := snapst.CurrentInfo()
   457  	if err != nil {
   458  		if err == ErrNoCurrent {
   459  			// refreshing a disabled snap (maybe via InstallPath)
   460  			return nil
   461  		}
   462  		return err
   463  	}
   464  
   465  	return checkEpochs(nil, info, cur, nil, Flags{}, nil)
   466  }
   467  
   468  func earlyChecks(st *state.State, snapst *SnapState, update *snap.Info, flags Flags) (Flags, error) {
   469  	flags, err := ensureInstallPreconditions(st, update, flags, snapst)
   470  	if err != nil {
   471  		return flags, err
   472  	}
   473  
   474  	if err := earlyEpochCheck(update, snapst); err != nil {
   475  		return flags, err
   476  	}
   477  	return flags, nil
   478  }
   479  
   480  // check that the listed system users are valid
   481  var osutilEnsureUserGroup = osutil.EnsureUserGroup
   482  
   483  func validateSystemUsernames(si *snap.Info) error {
   484  	for _, user := range si.SystemUsernames {
   485  		systemUserName, ok := snap.SupportedSystemUsernames[user.Name]
   486  		if !ok {
   487  			return fmt.Errorf(`snap %q requires unsupported system username "%s"`, si.InstanceName(), user.Name)
   488  		}
   489  
   490  		if systemUserName.AllowedSnapIds != nil && si.SnapID != "" {
   491  			// Only certain snaps can use this user; let's check whether ours
   492  			// is one of these
   493  			if !strutil.ListContains(systemUserName.AllowedSnapIds, si.SnapID) {
   494  				return fmt.Errorf(`snap %q is not allowed to use the system user %q`,
   495  					si.InstanceName(), user.Name)
   496  			}
   497  		}
   498  
   499  		switch user.Scope {
   500  		case "shared":
   501  			// this is supported
   502  			continue
   503  		case "private", "external":
   504  			// not supported yet
   505  			return fmt.Errorf(`snap %q requires unsupported user scope "%s" for this version of snapd`, si.InstanceName(), user.Scope)
   506  		default:
   507  			return fmt.Errorf(`snap %q requires unsupported user scope "%s"`, si.InstanceName(), user.Scope)
   508  		}
   509  	}
   510  	return nil
   511  }
   512  
   513  func checkAndCreateSystemUsernames(si *snap.Info) error {
   514  	// No need to check support if no system-usernames
   515  	if len(si.SystemUsernames) == 0 {
   516  		return nil
   517  	}
   518  
   519  	// Run /.../snap-seccomp version-info
   520  	vi, err := seccomp_compiler.CompilerVersionInfo(snapdtool.InternalToolPath)
   521  	if err != nil {
   522  		return fmt.Errorf("cannot obtain seccomp compiler information: %v", err)
   523  	}
   524  
   525  	// If the system doesn't support robust argument filtering then we
   526  	// can't support system-usernames
   527  	if err := vi.SupportsRobustArgumentFiltering(); err != nil {
   528  		if re, ok := err.(*seccomp_compiler.BuildTimeRequirementError); ok {
   529  			return fmt.Errorf("snap %q system usernames require a snapd built against %s", si.InstanceName(), re.RequirementsString())
   530  		}
   531  		return err
   532  	}
   533  
   534  	// first validate
   535  	if err := validateSystemUsernames(si); err != nil {
   536  		return err
   537  	}
   538  
   539  	// then create
   540  	// TODO: move user creation to a more appropriate place like "link-snap"
   541  	extrausers := !release.OnClassic
   542  	for _, user := range si.SystemUsernames {
   543  		id := snap.SupportedSystemUsernames[user.Name].Id
   544  		switch user.Scope {
   545  		case "shared":
   546  			// Create the snapd-range-<base>-root user and group so
   547  			// systemd-nspawn can avoid our range. Our ranges will always
   548  			// be in 65536 chunks, so mask off the lower bits to obtain our
   549  			// base (see above)
   550  			rangeStart := id & 0xFFFF0000
   551  			rangeName := fmt.Sprintf("snapd-range-%d-root", rangeStart)
   552  			if err := osutilEnsureUserGroup(rangeName, rangeStart, extrausers); err != nil {
   553  				return fmt.Errorf(`cannot ensure users for snap %q required system username "%s": %v`, si.InstanceName(), user.Name, err)
   554  			}
   555  
   556  			// Create the requested user and group
   557  			if err := osutilEnsureUserGroup(user.Name, id, extrausers); err != nil {
   558  				return fmt.Errorf(`cannot ensure users for snap %q required system username "%s": %v`, si.InstanceName(), user.Name, err)
   559  			}
   560  		}
   561  	}
   562  	return nil
   563  }
   564  
   565  func init() {
   566  	AddCheckSnapCallback(checkCoreName)
   567  	AddCheckSnapCallback(checkSnapdName)
   568  	AddCheckSnapCallback(checkGadgetOrKernel)
   569  	AddCheckSnapCallback(checkBases)
   570  	AddCheckSnapCallback(checkEpochs)
   571  }