github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap-bootstrap/cmd_initramfs_mounts.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2021 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 main
    21  
    22  import (
    23  	"crypto/subtle"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  	"syscall"
    31  
    32  	"github.com/jessevdk/go-flags"
    33  
    34  	"github.com/snapcore/snapd/asserts"
    35  	"github.com/snapcore/snapd/boot"
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/osutil/disks"
    40  
    41  	// to set sysconfig.ApplyFilesystemOnlyDefaultsImpl
    42  	_ "github.com/snapcore/snapd/overlord/configstate/configcore"
    43  	"github.com/snapcore/snapd/overlord/state"
    44  	"github.com/snapcore/snapd/secboot"
    45  	"github.com/snapcore/snapd/snap"
    46  	"github.com/snapcore/snapd/snap/squashfs"
    47  	"github.com/snapcore/snapd/sysconfig"
    48  )
    49  
    50  func init() {
    51  	const (
    52  		short = "Generate mounts for the initramfs"
    53  		long  = "Generate and perform all mounts for the initramfs before transitioning to userspace"
    54  	)
    55  
    56  	addCommandBuilder(func(parser *flags.Parser) {
    57  		if _, err := parser.AddCommand("initramfs-mounts", short, long, &cmdInitramfsMounts{}); err != nil {
    58  			panic(err)
    59  		}
    60  	})
    61  
    62  	snap.SanitizePlugsSlots = func(*snap.Info) {}
    63  }
    64  
    65  type cmdInitramfsMounts struct{}
    66  
    67  func (c *cmdInitramfsMounts) Execute(args []string) error {
    68  	return generateInitramfsMounts()
    69  }
    70  
    71  var (
    72  	osutilIsMounted = osutil.IsMounted
    73  
    74  	snapTypeToMountDir = map[snap.Type]string{
    75  		snap.TypeBase:   "base",
    76  		snap.TypeKernel: "kernel",
    77  		snap.TypeSnapd:  "snapd",
    78  	}
    79  
    80  	secbootMeasureSnapSystemEpochWhenPossible    func() error
    81  	secbootMeasureSnapModelWhenPossible          func(findModel func() (*asserts.Model, error)) error
    82  	secbootUnlockVolumeUsingSealedKeyIfEncrypted func(disk disks.Disk, name string, encryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error)
    83  	secbootUnlockEncryptedVolumeUsingKey         func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error)
    84  
    85  	secbootLockSealedKeys func() error
    86  
    87  	bootFindPartitionUUIDForBootedKernelDisk = boot.FindPartitionUUIDForBootedKernelDisk
    88  )
    89  
    90  func stampedAction(stamp string, action func() error) error {
    91  	stampFile := filepath.Join(dirs.SnapBootstrapRunDir, stamp)
    92  	if osutil.FileExists(stampFile) {
    93  		return nil
    94  	}
    95  	if err := os.MkdirAll(filepath.Dir(stampFile), 0755); err != nil {
    96  		return err
    97  	}
    98  	if err := action(); err != nil {
    99  		return err
   100  	}
   101  	return ioutil.WriteFile(stampFile, nil, 0644)
   102  }
   103  
   104  func generateInitramfsMounts() (err error) {
   105  	// ensure that the last thing we do is to lock access to sealed keys,
   106  	// regardless of mode or early failures.
   107  	defer func() {
   108  		if e := secbootLockSealedKeys(); e != nil {
   109  			e = fmt.Errorf("error locking access to sealed keys: %v", e)
   110  			if err == nil {
   111  				err = e
   112  			} else {
   113  				// preserve err but log
   114  				logger.Noticef("%v", e)
   115  			}
   116  		}
   117  	}()
   118  
   119  	// Ensure there is a very early initial measurement
   120  	err = stampedAction("secboot-epoch-measured", func() error {
   121  		return secbootMeasureSnapSystemEpochWhenPossible()
   122  	})
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	mode, recoverySystem, err := boot.ModeAndRecoverySystemFromKernelCommandLine()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	mst := &initramfsMountsState{
   133  		mode:           mode,
   134  		recoverySystem: recoverySystem,
   135  	}
   136  
   137  	switch mode {
   138  	case "recover":
   139  		err = generateMountsModeRecover(mst)
   140  	case "install":
   141  		err = generateMountsModeInstall(mst)
   142  	case "run":
   143  		err = generateMountsModeRun(mst)
   144  	default:
   145  		// this should never be reached, ModeAndRecoverySystemFromKernelCommandLine
   146  		// will have returned a non-nill error above if there was another mode
   147  		// specified on the kernel command line for some reason
   148  		return fmt.Errorf("internal error: mode in generateInitramfsMounts not handled")
   149  	}
   150  
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	// finally, the initramfs is responsible for reading the boot flags and
   156  	// copying them to /run, so that userspace has an unambiguous place to read
   157  	// the boot flags for the current boot from
   158  	flags, err := boot.InitramfsActiveBootFlags(mode)
   159  	if err != nil {
   160  		// We don't die on failing to read boot flags, we just log the error and
   161  		// don't set any flags, this is because the boot flags in the case of
   162  		// install comes from untrusted input, the bootenv. In the case of run
   163  		// mode, boot flags are read from the modeenv, which should be valid and
   164  		// trusted, but if the modeenv becomes corrupted, we would block
   165  		// accessing the system (except through an initramfs shell), to recover
   166  		// the modeenv (though maybe we could enable some sort of fixing from
   167  		// recover mode instead?)
   168  		logger.Noticef("error accessing boot flags: %v", err)
   169  	} else {
   170  		// write the boot flags
   171  		if err := boot.InitramfsExposeBootFlagsForSystem(flags); err != nil {
   172  			// cannot write to /run, error here since arguably we have major
   173  			// problems if we can't write to /run
   174  			return err
   175  		}
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // generateMountsMode* is called multiple times from initramfs until it
   182  // no longer generates more mount points and just returns an empty output.
   183  func generateMountsModeInstall(mst *initramfsMountsState) error {
   184  	// steps 1 and 2 are shared with recover mode
   185  	model, snaps, err := generateMountsCommonInstallRecover(mst)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	// 3. final step: write modeenv to tmpfs data dir and disable cloud-init in
   191  	//   install mode
   192  	modeEnv, err := mst.EphemeralModeenvForModel(model, snaps)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	if err := modeEnv.WriteTo(boot.InitramfsWritableDir); err != nil {
   197  		return err
   198  	}
   199  
   200  	// done, no output, no error indicates to initramfs we are done with
   201  	// mounting stuff
   202  	return nil
   203  }
   204  
   205  // copyNetworkConfig copies the network configuration to the target
   206  // directory. This is used to copy the network configuration
   207  // data from a real uc20 ubuntu-data partition into a ephemeral one.
   208  func copyNetworkConfig(src, dst string) error {
   209  	for _, globEx := range []string{
   210  		// for network configuration setup by console-conf, etc.
   211  		// TODO:UC20: we want some way to "try" or "verify" the network
   212  		//            configuration or to only use known-to-be-good network
   213  		//            configuration i.e. from ubuntu-save before installing it
   214  		//            onto recover mode, because the network configuration could
   215  		//            have been what was broken so we don't want to break
   216  		//            network configuration for recover mode as well, but for
   217  		//            now this is fine
   218  		"system-data/etc/netplan/*",
   219  		// etc/machine-id is part of what systemd-networkd uses to generate a
   220  		// DHCP clientid (the other part being the interface name), so to have
   221  		// the same IP addresses across run mode and recover mode, we need to
   222  		// also copy the machine-id across
   223  		"system-data/etc/machine-id",
   224  	} {
   225  		if err := copyFromGlobHelper(src, dst, globEx); err != nil {
   226  			return err
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  // copyUbuntuDataMisc copies miscellaneous other files from the run mode system
   233  // to the recover system such as:
   234  //  - timesync clock to keep the same time setting in recover as in run mode
   235  func copyUbuntuDataMisc(src, dst string) error {
   236  	for _, globEx := range []string{
   237  		// systemd's timesync clock file so that the time in recover mode moves
   238  		// forward to what it was in run mode
   239  		// NOTE: we don't sync back the time movement from recover mode to run
   240  		// mode currently, unclear how/when we could do this, but recover mode
   241  		// isn't meant to be long lasting and as such it's probably not a big
   242  		// problem to "lose" the time spent in recover mode
   243  		"system-data/var/lib/systemd/timesync/clock",
   244  	} {
   245  		if err := copyFromGlobHelper(src, dst, globEx); err != nil {
   246  			return err
   247  		}
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  // copyUbuntuDataAuth copies the authentication files like
   254  //  - extrausers passwd,shadow etc
   255  //  - sshd host configuration
   256  //  - user .ssh dir
   257  // to the target directory. This is used to copy the authentication
   258  // data from a real uc20 ubuntu-data partition into a ephemeral one.
   259  func copyUbuntuDataAuth(src, dst string) error {
   260  	for _, globEx := range []string{
   261  		"system-data/var/lib/extrausers/*",
   262  		"system-data/etc/ssh/*",
   263  		"user-data/*/.ssh/*",
   264  		// this ensures we get proper authentication to snapd from "snap"
   265  		// commands in recover mode
   266  		"user-data/*/.snap/auth.json",
   267  		// this ensures we also get non-ssh enabled accounts copied
   268  		"user-data/*/.profile",
   269  		// so that users have proper perms, i.e. console-conf added users are
   270  		// sudoers
   271  		"system-data/etc/sudoers.d/*",
   272  	} {
   273  		if err := copyFromGlobHelper(src, dst, globEx); err != nil {
   274  			return err
   275  		}
   276  	}
   277  
   278  	// ensure the user state is transferred as well
   279  	srcState := filepath.Join(src, "system-data/var/lib/snapd/state.json")
   280  	dstState := filepath.Join(dst, "system-data/var/lib/snapd/state.json")
   281  	err := state.CopyState(srcState, dstState, []string{"auth.users", "auth.macaroon-key", "auth.last-id"})
   282  	if err != nil && err != state.ErrNoState {
   283  		return fmt.Errorf("cannot copy user state: %v", err)
   284  	}
   285  
   286  	return nil
   287  }
   288  
   289  // copySafeDefaultData will copy to the destination a "safe" set of data for
   290  // a blank recover mode, i.e. one where we cannot copy authentication, etc. from
   291  // the actual host ubuntu-data. Currently this is just a file to disable
   292  // console-conf from running.
   293  func copySafeDefaultData(dst string) error {
   294  	consoleConfCompleteFile := filepath.Join(dst, "system-data/var/lib/console-conf/complete")
   295  	if err := os.MkdirAll(filepath.Dir(consoleConfCompleteFile), 0755); err != nil {
   296  		return err
   297  	}
   298  	return ioutil.WriteFile(consoleConfCompleteFile, nil, 0644)
   299  }
   300  
   301  func copyFromGlobHelper(src, dst, globEx string) error {
   302  	matches, err := filepath.Glob(filepath.Join(src, globEx))
   303  	if err != nil {
   304  		return err
   305  	}
   306  	for _, p := range matches {
   307  		comps := strings.Split(strings.TrimPrefix(p, src), "/")
   308  		for i := range comps {
   309  			part := filepath.Join(comps[0 : i+1]...)
   310  			fi, err := os.Stat(filepath.Join(src, part))
   311  			if err != nil {
   312  				return err
   313  			}
   314  			if fi.IsDir() {
   315  				if err := os.Mkdir(filepath.Join(dst, part), fi.Mode()); err != nil && !os.IsExist(err) {
   316  					return err
   317  				}
   318  				st, ok := fi.Sys().(*syscall.Stat_t)
   319  				if !ok {
   320  					return fmt.Errorf("cannot get stat data: %v", err)
   321  				}
   322  				if err := os.Chown(filepath.Join(dst, part), int(st.Uid), int(st.Gid)); err != nil {
   323  					return err
   324  				}
   325  			} else {
   326  				if err := osutil.CopyFile(p, filepath.Join(dst, part), osutil.CopyFlagPreserveAll); err != nil {
   327  					return err
   328  				}
   329  			}
   330  		}
   331  	}
   332  
   333  	return nil
   334  }
   335  
   336  // states for partition state
   337  const (
   338  	// states for LocateState
   339  	partitionFound      = "found"
   340  	partitionNotFound   = "not-found"
   341  	partitionErrFinding = "error-finding"
   342  	// states for MountState
   343  	partitionMounted          = "mounted"
   344  	partitionErrMounting      = "error-mounting"
   345  	partitionAbsentOptional   = "absent-but-optional"
   346  	partitionMountedUntrusted = "mounted-untrusted"
   347  	// states for UnlockState
   348  	partitionUnlocked     = "unlocked"
   349  	partitionErrUnlocking = "error-unlocking"
   350  	// keys used to unlock for UnlockKey
   351  	keyRun      = "run"
   352  	keyFallback = "fallback"
   353  	keyRecovery = "recovery"
   354  )
   355  
   356  // partitionState is the state of a partition after recover mode has completed
   357  // for degraded mode.
   358  type partitionState struct {
   359  	// MountState is whether the partition was mounted successfully or not.
   360  	MountState string `json:"mount-state,omitempty"`
   361  	// MountLocation is where the partition was mounted.
   362  	MountLocation string `json:"mount-location,omitempty"`
   363  	// Device is what device the partition corresponds to. It can be the
   364  	// physical block device if the partition is unencrypted or if it was not
   365  	// successfully unlocked, or it can be a decrypted mapper device if the
   366  	// partition was encrypted and successfully decrypted, or it can be the
   367  	// empty string (or missing) if the partition was not found at all.
   368  	Device string `json:"device,omitempty"`
   369  	// FindState indicates whether the partition was found on the disk or not.
   370  	FindState string `json:"find-state,omitempty"`
   371  	// UnlockState was whether the partition was unlocked successfully or not.
   372  	UnlockState string `json:"unlock-state,omitempty"`
   373  	// UnlockKey was what key the partition was unlocked with, either "run",
   374  	// "fallback" or "recovery".
   375  	UnlockKey string `json:"unlock-key,omitempty"`
   376  
   377  	// unexported internal fields for tracking the device, these are used during
   378  	// state machine execution, and then combined into Device during finalize()
   379  	// for simple representation to the consumer of degraded.json
   380  
   381  	// fsDevice is what decrypted mapper device corresponds to the
   382  	// partition, it can have the following states
   383  	// - successfully decrypted => the decrypted mapper device
   384  	// - unencrypted => the block device of the partition
   385  	// - identified as decrypted, but failed to decrypt => empty string
   386  	fsDevice string
   387  	// partDevice is always the physical block device of the partition, in the
   388  	// encrypted case this is the physical encrypted partition.
   389  	partDevice string
   390  }
   391  
   392  type recoverDegradedState struct {
   393  	// UbuntuData is the state of the ubuntu-data (or ubuntu-data-enc)
   394  	// partition.
   395  	UbuntuData partitionState `json:"ubuntu-data,omitempty"`
   396  	// UbuntuBoot is the state of the ubuntu-boot partition.
   397  	UbuntuBoot partitionState `json:"ubuntu-boot,omitempty"`
   398  	// UbuntuSave is the state of the ubuntu-save (or ubuntu-save-enc)
   399  	// partition.
   400  	UbuntuSave partitionState `json:"ubuntu-save,omitempty"`
   401  	// ErrorLog is the log of error messages encountered during recover mode
   402  	// setting up degraded mode.
   403  	ErrorLog []string `json:"error-log"`
   404  }
   405  
   406  func (r *recoverDegradedState) partition(part string) *partitionState {
   407  	switch part {
   408  	case "ubuntu-data":
   409  		return &r.UbuntuData
   410  	case "ubuntu-boot":
   411  		return &r.UbuntuBoot
   412  	case "ubuntu-save":
   413  		return &r.UbuntuSave
   414  	}
   415  	panic(fmt.Sprintf("unknown partition %s", part))
   416  }
   417  
   418  func (r *recoverDegradedState) LogErrorf(format string, v ...interface{}) {
   419  	msg := fmt.Sprintf(format, v...)
   420  	r.ErrorLog = append(r.ErrorLog, msg)
   421  	logger.Noticef(msg)
   422  }
   423  
   424  // stateFunc is a function which executes a state action, returns the next
   425  // function (for the next) state or nil if it is the final state.
   426  type stateFunc func() (stateFunc, error)
   427  
   428  // recoverModeStateMachine is a state machine implementing the logic for
   429  // degraded recover mode.
   430  // A full state diagram for the state machine can be found in
   431  // /cmd/snap-bootstrap/degraded-recover-mode.svg in this repo.
   432  type recoverModeStateMachine struct {
   433  	// the current state is the one that is about to be executed
   434  	current stateFunc
   435  
   436  	// device model
   437  	model *asserts.Model
   438  
   439  	// the disk we have all our partitions on
   440  	disk disks.Disk
   441  
   442  	// when true, the fallback unlock paths will not be tried
   443  	noFallback bool
   444  
   445  	// TODO:UC20: for clarity turn this into into tristate:
   446  	// unknown|encrypted|unencrypted
   447  	isEncryptedDev bool
   448  
   449  	// state for tracking what happens as we progress through degraded mode of
   450  	// recovery
   451  	degradedState *recoverDegradedState
   452  }
   453  
   454  func (m *recoverModeStateMachine) whichModel() (*asserts.Model, error) {
   455  	return m.model, nil
   456  }
   457  
   458  // degraded returns whether a degraded recover mode state has fallen back from
   459  // the typical operation to some sort of degraded mode.
   460  func (m *recoverModeStateMachine) degraded() bool {
   461  	r := m.degradedState
   462  
   463  	if m.isEncryptedDev {
   464  		// for encrypted devices, we need to have ubuntu-save mounted
   465  		if r.UbuntuSave.MountState != partitionMounted {
   466  			return true
   467  		}
   468  
   469  		// we also should have all the unlock keys as run keys
   470  		if r.UbuntuData.UnlockKey != keyRun {
   471  			return true
   472  		}
   473  
   474  		if r.UbuntuSave.UnlockKey != keyRun {
   475  			return true
   476  		}
   477  	} else {
   478  		// for unencrypted devices, ubuntu-save must either be mounted or
   479  		// absent-but-optional
   480  		if r.UbuntuSave.MountState != partitionMounted {
   481  			if r.UbuntuSave.MountState != partitionAbsentOptional {
   482  				return true
   483  			}
   484  		}
   485  	}
   486  
   487  	// ubuntu-boot and ubuntu-data should both be mounted
   488  	if r.UbuntuBoot.MountState != partitionMounted {
   489  		return true
   490  	}
   491  	if r.UbuntuData.MountState != partitionMounted {
   492  		return true
   493  	}
   494  
   495  	// TODO: should we also check MountLocation too?
   496  
   497  	// we should have nothing in the error log
   498  	if len(r.ErrorLog) != 0 {
   499  		return true
   500  	}
   501  
   502  	return false
   503  }
   504  
   505  func (m *recoverModeStateMachine) diskOpts() *disks.Options {
   506  	if m.isEncryptedDev {
   507  		return &disks.Options{
   508  			IsDecryptedDevice: true,
   509  		}
   510  	}
   511  	return nil
   512  }
   513  
   514  func (m *recoverModeStateMachine) verifyMountPoint(dir, name string) error {
   515  	matches, err := m.disk.MountPointIsFromDisk(dir, m.diskOpts())
   516  	if err != nil {
   517  		return err
   518  	}
   519  	if !matches {
   520  		return fmt.Errorf("cannot validate mount: %s mountpoint target %s is expected to be from disk %s but is not", name, dir, m.disk.Dev())
   521  	}
   522  	return nil
   523  }
   524  
   525  func (m *recoverModeStateMachine) setFindState(partName, partUUID string, err error, optionalPartition bool) error {
   526  	part := m.degradedState.partition(partName)
   527  	if err != nil {
   528  		if _, ok := err.(disks.PartitionNotFoundError); ok {
   529  			// explicit error that the device was not found
   530  			part.FindState = partitionNotFound
   531  			if !optionalPartition {
   532  				// partition is not optional, thus the error is relevant
   533  				m.degradedState.LogErrorf("cannot find %v partition on disk %s", partName, m.disk.Dev())
   534  			}
   535  			return nil
   536  		}
   537  		// the error is not "not-found", so we have a real error
   538  		part.FindState = partitionErrFinding
   539  		m.degradedState.LogErrorf("error finding %v partition on disk %s: %v", partName, m.disk.Dev(), err)
   540  		return nil
   541  	}
   542  
   543  	// device was found
   544  	part.FindState = partitionFound
   545  	dev := fmt.Sprintf("/dev/disk/by-partuuid/%s", partUUID)
   546  	part.partDevice = dev
   547  	part.fsDevice = dev
   548  	return nil
   549  }
   550  
   551  func (m *recoverModeStateMachine) setMountState(part, where string, err error) error {
   552  	if err != nil {
   553  		m.degradedState.LogErrorf("cannot mount %v: %v", part, err)
   554  		m.degradedState.partition(part).MountState = partitionErrMounting
   555  		return nil
   556  	}
   557  
   558  	m.degradedState.partition(part).MountState = partitionMounted
   559  	m.degradedState.partition(part).MountLocation = where
   560  
   561  	if err := m.verifyMountPoint(where, part); err != nil {
   562  		m.degradedState.LogErrorf("cannot verify %s mount point at %v: %v", part, where, err)
   563  		return err
   564  	}
   565  	return nil
   566  }
   567  
   568  func (m *recoverModeStateMachine) setUnlockStateWithRunKey(partName string, unlockRes secboot.UnlockResult, err error) error {
   569  	part := m.degradedState.partition(partName)
   570  	// save the device if we found it from secboot
   571  	if unlockRes.PartDevice != "" {
   572  		part.FindState = partitionFound
   573  		part.partDevice = unlockRes.PartDevice
   574  		part.fsDevice = unlockRes.FsDevice
   575  	} else {
   576  		part.FindState = partitionNotFound
   577  	}
   578  	if unlockRes.IsEncrypted {
   579  		m.isEncryptedDev = true
   580  	}
   581  
   582  	if err != nil {
   583  		// create different error message for encrypted vs unencrypted
   584  		if unlockRes.IsEncrypted {
   585  			// if we know the device is decrypted we must also always know at
   586  			// least the partDevice (which is the encrypted block device)
   587  			m.degradedState.LogErrorf("cannot unlock encrypted %s (device %s) with sealed run key: %v", partName, part.partDevice, err)
   588  			part.UnlockState = partitionErrUnlocking
   589  		} else {
   590  			// TODO: we don't know if this is a plain not found or  a different error
   591  			m.degradedState.LogErrorf("cannot locate %s partition for mounting host data: %v", partName, err)
   592  		}
   593  
   594  		return nil
   595  	}
   596  
   597  	if unlockRes.IsEncrypted {
   598  		// unlocked successfully
   599  		part.UnlockState = partitionUnlocked
   600  		part.UnlockKey = keyRun
   601  	}
   602  
   603  	return nil
   604  }
   605  
   606  func (m *recoverModeStateMachine) setUnlockStateWithFallbackKey(partName string, unlockRes secboot.UnlockResult, err error) error {
   607  	// first check the result and error for consistency; since we are using udev
   608  	// there could be inconsistent results at different points in time
   609  
   610  	// TODO: consider refactoring UnlockVolumeUsingSealedKeyIfEncrypted to not
   611  	//       also find the partition on the disk, that should eliminate this
   612  	//       consistency checking as we can code it such that we don't get these
   613  	//       possible inconsistencies
   614  
   615  	// do basic consistency checking on unlockRes to make sure the
   616  	// result makes sense.
   617  	if unlockRes.FsDevice != "" && err != nil {
   618  		// This case should be impossible to enter, we can't
   619  		// have a filesystem device but an error set
   620  		return fmt.Errorf("internal error: inconsistent return values from UnlockVolumeUsingSealedKeyIfEncrypted for partition %s: %v", partName, err)
   621  	}
   622  
   623  	part := m.degradedState.partition(partName)
   624  	// Also make sure that if we previously saw a partition device that we see
   625  	// the same device again.
   626  	if unlockRes.PartDevice != "" && part.partDevice != "" && unlockRes.PartDevice != part.partDevice {
   627  		return fmt.Errorf("inconsistent partitions found for %s: previously found %s but now found %s", partName, part.partDevice, unlockRes.PartDevice)
   628  	}
   629  
   630  	// ensure consistency between encrypted state of the device/disk and what we
   631  	// may have seen previously
   632  	if m.isEncryptedDev && !unlockRes.IsEncrypted {
   633  		// then we previously were able to positively identify an
   634  		// ubuntu-data-enc but can't anymore, so we have inconsistent results
   635  		// from inspecting the disk which is suspicious and we should fail
   636  		return fmt.Errorf("inconsistent disk encryption status: previous access resulted in encrypted, but now is unencrypted from partition %s", partName)
   637  	}
   638  
   639  	// now actually process the result into the state
   640  	if unlockRes.PartDevice != "" {
   641  		part.FindState = partitionFound
   642  		// Note that in some case this may be redundantly assigning the same
   643  		// value to partDevice again.
   644  		part.partDevice = unlockRes.PartDevice
   645  		part.fsDevice = unlockRes.FsDevice
   646  	}
   647  
   648  	// There are a few cases where this could be the first time that we found a
   649  	// decrypted device in the UnlockResult, but m.isEncryptedDev is still
   650  	// false.
   651  	// - The first case is if we couldn't find ubuntu-boot at all, in which case
   652  	// we can't use the run object keys from there and instead need to directly
   653  	// fallback to trying the fallback object keys from ubuntu-seed
   654  	// - The second case is if we couldn't identify an ubuntu-data-enc or an
   655  	// ubuntu-data partition at all, we still could have an ubuntu-save-enc
   656  	// partition in which case we maybe could still have an encrypted disk that
   657  	// needs unlocking with the fallback object keys from ubuntu-seed
   658  	//
   659  	// As such, if m.isEncryptedDev is false, but unlockRes.IsEncrypted is
   660  	// true, then it is safe to assign m.isEncryptedDev to true.
   661  	if !m.isEncryptedDev && unlockRes.IsEncrypted {
   662  		m.isEncryptedDev = true
   663  	}
   664  
   665  	if err != nil {
   666  		// create different error message for encrypted vs unencrypted
   667  		if m.isEncryptedDev {
   668  			m.degradedState.LogErrorf("cannot unlock encrypted %s partition with sealed fallback key: %v", partName, err)
   669  			part.UnlockState = partitionErrUnlocking
   670  		} else {
   671  			// if we don't have an encrypted device and err != nil, then the
   672  			// device must be not-found, see above checks
   673  
   674  			// log an error the partition is mandatory
   675  			m.degradedState.LogErrorf("cannot locate %s partition: %v", partName, err)
   676  		}
   677  
   678  		return nil
   679  	}
   680  
   681  	if m.isEncryptedDev {
   682  		// unlocked successfully
   683  		part.UnlockState = partitionUnlocked
   684  
   685  		// figure out which key/method we used to unlock the partition
   686  		switch unlockRes.UnlockMethod {
   687  		case secboot.UnlockedWithSealedKey:
   688  			part.UnlockKey = keyFallback
   689  		case secboot.UnlockedWithRecoveryKey:
   690  			part.UnlockKey = keyRecovery
   691  
   692  			// TODO: should we fail with internal error for default case here?
   693  		}
   694  	}
   695  
   696  	return nil
   697  }
   698  
   699  func newRecoverModeStateMachine(model *asserts.Model, disk disks.Disk, allowFallback bool) *recoverModeStateMachine {
   700  	m := &recoverModeStateMachine{
   701  		model: model,
   702  		disk:  disk,
   703  		degradedState: &recoverDegradedState{
   704  			ErrorLog: []string{},
   705  		},
   706  		noFallback: !allowFallback,
   707  	}
   708  	// first step is to mount ubuntu-boot to check for run mode keys to unlock
   709  	// ubuntu-data
   710  	m.current = m.mountBoot
   711  	return m
   712  }
   713  
   714  func (m *recoverModeStateMachine) execute() (finished bool, err error) {
   715  	next, err := m.current()
   716  	m.current = next
   717  	finished = next == nil
   718  	if finished && err == nil {
   719  		if err := m.finalize(); err != nil {
   720  			return true, err
   721  		}
   722  	}
   723  	return finished, err
   724  }
   725  
   726  func (m *recoverModeStateMachine) finalize() error {
   727  	// check soundness
   728  	// the grade check makes sure that if data was mounted unencrypted
   729  	// but the model is secured it will end up marked as untrusted
   730  	isEncrypted := m.isEncryptedDev || m.model.StorageSafety() == asserts.StorageSafetyEncrypted
   731  	part := m.degradedState.partition("ubuntu-data")
   732  	if part.MountState == partitionMounted && isEncrypted {
   733  		// check that save and data match
   734  		// We want to avoid a chosen ubuntu-data
   735  		// (e.g. activated with a recovery key) to get access
   736  		// via its logins to the secrets in ubuntu-save (in
   737  		// particular the policy update auth key)
   738  		// TODO:UC20: we should try to be a bit more specific here in checking that
   739  		//       data and save match, and not mark data as untrusted if we
   740  		//       know that the real save is locked/protected (or doesn't exist
   741  		//       in the case of bad corruption) because currently this code will
   742  		//       mark data as untrusted, even if it was unlocked with the run
   743  		//       object key and we failed to unlock ubuntu-save at all, which is
   744  		//       undesirable. This effectively means that you need to have both
   745  		//       ubuntu-data and ubuntu-save unlockable and have matching marker
   746  		//       files in order to use the files from ubuntu-data to log-in,
   747  		//       etc.
   748  		trustData, _ := checkDataAndSavePairing(boot.InitramfsHostWritableDir)
   749  		if !trustData {
   750  			part.MountState = partitionMountedUntrusted
   751  			m.degradedState.LogErrorf("cannot trust ubuntu-data, ubuntu-save and ubuntu-data are not marked as from the same install")
   752  		}
   753  	}
   754  
   755  	// finally, combine the states of partDevice and fsDevice into the
   756  	// exported Device field for marshalling
   757  	// ubuntu-boot is easy - it will always be unencrypted so we just set
   758  	// Device to partDevice
   759  	m.degradedState.partition("ubuntu-boot").Device = m.degradedState.partition("ubuntu-boot").partDevice
   760  
   761  	// for ubuntu-data and save, we need to actually look at the states
   762  	for _, partName := range []string{"ubuntu-data", "ubuntu-save"} {
   763  		part := m.degradedState.partition(partName)
   764  		if part.fsDevice == "" {
   765  			// then the device is encrypted, but we failed to decrypt it, so
   766  			// set Device to the encrypted block device
   767  			part.Device = part.partDevice
   768  		} else {
   769  			// all other cases, fsDevice is set to what we want to
   770  			// export, either it is set to the decrypted mapper device in the
   771  			// case it was successfully decrypted, or it is set to the encrypted
   772  			// block device if we failed to decrypt it, or it was set to the
   773  			// unencrypted block device if it was unencrypted
   774  			part.Device = part.fsDevice
   775  		}
   776  	}
   777  
   778  	return nil
   779  }
   780  
   781  func (m *recoverModeStateMachine) trustData() bool {
   782  	return m.degradedState.partition("ubuntu-data").MountState == partitionMounted
   783  }
   784  
   785  // mountBoot is the first state to execute in the state machine, it can
   786  // transition to the following states:
   787  // - if ubuntu-boot is mounted successfully, execute unlockDataRunKey
   788  // - if ubuntu-boot can't be mounted, execute unlockDataFallbackKey
   789  // - if we mounted the wrong ubuntu-boot (or otherwise can't verify which one we
   790  //   mounted), return fatal error
   791  func (m *recoverModeStateMachine) mountBoot() (stateFunc, error) {
   792  	part := m.degradedState.partition("ubuntu-boot")
   793  	// use the disk we mounted ubuntu-seed from as a reference to find
   794  	// ubuntu-seed and mount it
   795  	partUUID, findErr := m.disk.FindMatchingPartitionUUIDWithFsLabel("ubuntu-boot")
   796  	const partitionMandatory = false
   797  	if err := m.setFindState("ubuntu-boot", partUUID, findErr, partitionMandatory); err != nil {
   798  		return nil, err
   799  	}
   800  	if part.FindState != partitionFound {
   801  		// if we didn't find ubuntu-boot, we can't try to unlock data with the
   802  		// run key, and should instead just jump straight to attempting to
   803  		// unlock with the fallback key
   804  		return m.unlockDataFallbackKey, nil
   805  	}
   806  
   807  	// should we fsck ubuntu-boot? probably yes because on some platforms
   808  	// (u-boot for example) ubuntu-boot is vfat and it could have been unmounted
   809  	// dirtily, and we need to fsck it to ensure it is mounted safely before
   810  	// reading keys from it
   811  	fsckSystemdOpts := &systemdMountOptions{
   812  		NeedsFsck: true,
   813  	}
   814  	mountErr := doSystemdMount(part.fsDevice, boot.InitramfsUbuntuBootDir, fsckSystemdOpts)
   815  	if err := m.setMountState("ubuntu-boot", boot.InitramfsUbuntuBootDir, mountErr); err != nil {
   816  		return nil, err
   817  	}
   818  	if part.MountState == partitionErrMounting {
   819  		// if we didn't mount data, then try to unlock data with the
   820  		// fallback key
   821  		return m.unlockDataFallbackKey, nil
   822  	}
   823  
   824  	// next step try to unlock data with run object
   825  	return m.unlockDataRunKey, nil
   826  }
   827  
   828  // stateUnlockDataRunKey will try to unlock ubuntu-data with the normal run-mode
   829  // key, and if it fails, progresses to the next state, which is either:
   830  // - failed to unlock data, but we know it's an encrypted device -> try to unlock with fallback key
   831  // - failed to find data at all -> try to unlock save
   832  // - unlocked data with run key -> mount data
   833  func (m *recoverModeStateMachine) unlockDataRunKey() (stateFunc, error) {
   834  	runModeKey := filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key")
   835  	unlockOpts := &secboot.UnlockVolumeUsingSealedKeyOptions{
   836  		// don't allow using the recovery key to unlock, we only try using the
   837  		// recovery key after we first try the fallback object
   838  		AllowRecoveryKey: false,
   839  		WhichModel:       m.whichModel,
   840  	}
   841  	unlockRes, unlockErr := secbootUnlockVolumeUsingSealedKeyIfEncrypted(m.disk, "ubuntu-data", runModeKey, unlockOpts)
   842  	if err := m.setUnlockStateWithRunKey("ubuntu-data", unlockRes, unlockErr); err != nil {
   843  		return nil, err
   844  	}
   845  	if unlockErr != nil {
   846  		// we couldn't unlock ubuntu-data with the primary key, or we didn't
   847  		// find it in the unencrypted case
   848  		if unlockRes.IsEncrypted {
   849  			// we know the device is encrypted, so the next state is to try
   850  			// unlocking with the fallback key
   851  			return m.unlockDataFallbackKey, nil
   852  		}
   853  
   854  		// if we didn't even find the device to the point where it would have
   855  		// been identified as decrypted or unencrypted device, we could have
   856  		// just entirely lost ubuntu-data-enc, and we could still have an
   857  		// encrypted device, so instead try to unlock ubuntu-save with the
   858  		// fallback key, the logic there can also handle an unencrypted ubuntu-save
   859  		return m.unlockMaybeEncryptedAloneSaveFallbackKey, nil
   860  	}
   861  
   862  	// otherwise successfully unlocked it (or just found it if it was unencrypted)
   863  	// so just mount it
   864  	return m.mountData, nil
   865  }
   866  
   867  func (m *recoverModeStateMachine) unlockDataFallbackKey() (stateFunc, error) {
   868  	if m.noFallback {
   869  		return nil, fmt.Errorf("cannot unlock ubuntu-data (fallback disabled)")
   870  	}
   871  
   872  	// try to unlock data with the fallback key on ubuntu-seed, which must have
   873  	// been mounted at this point
   874  	unlockOpts := &secboot.UnlockVolumeUsingSealedKeyOptions{
   875  		// we want to allow using the recovery key if the fallback key fails as
   876  		// using the fallback object is the last chance before we give up trying
   877  		// to unlock data
   878  		AllowRecoveryKey: true,
   879  		WhichModel:       m.whichModel,
   880  	}
   881  	// TODO: this prompts for a recovery key
   882  	// TODO: we should somehow customize the prompt to mention what key we need
   883  	// the user to enter, and what we are unlocking (as currently the prompt
   884  	// says "recovery key" and the partition UUID for what is being unlocked)
   885  	dataFallbackKey := filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key")
   886  	unlockRes, unlockErr := secbootUnlockVolumeUsingSealedKeyIfEncrypted(m.disk, "ubuntu-data", dataFallbackKey, unlockOpts)
   887  	if err := m.setUnlockStateWithFallbackKey("ubuntu-data", unlockRes, unlockErr); err != nil {
   888  		return nil, err
   889  	}
   890  	if unlockErr != nil {
   891  		// skip trying to mount data, since we did not unlock data we cannot
   892  		// open save with with the run key, so try the fallback one
   893  		return m.unlockEncryptedSaveFallbackKey, nil
   894  	}
   895  
   896  	// unlocked it, now go mount it
   897  	return m.mountData, nil
   898  }
   899  
   900  func (m *recoverModeStateMachine) mountData() (stateFunc, error) {
   901  	data := m.degradedState.partition("ubuntu-data")
   902  	// don't do fsck on the data partition, it could be corrupted
   903  	// however, data should always be mounted nosuid to prevent snaps from
   904  	// extracting suid executables there and trying to circumvent the sandbox
   905  	nosuidMountOpts := &systemdMountOptions{
   906  		NoSuid: true,
   907  	}
   908  	mountErr := doSystemdMount(data.fsDevice, boot.InitramfsHostUbuntuDataDir, nosuidMountOpts)
   909  	if err := m.setMountState("ubuntu-data", boot.InitramfsHostUbuntuDataDir, mountErr); err != nil {
   910  		return nil, err
   911  	}
   912  	if m.isEncryptedDev {
   913  		if mountErr == nil {
   914  			// if we succeeded in mounting data and we are encrypted, the next step
   915  			// is to unlock save with the run key from ubuntu-data
   916  			return m.unlockEncryptedSaveRunKey, nil
   917  		} else {
   918  			// we are encrypted and we failed to mount data successfully, meaning we
   919  			// don't have the bare key from ubuntu-data to use, and need to fall back
   920  			// to the sealed key from ubuntu-seed
   921  			return m.unlockEncryptedSaveFallbackKey, nil
   922  		}
   923  	}
   924  
   925  	// the data is not encrypted, in which case the ubuntu-save, if it
   926  	// exists, will be plain too
   927  	return m.openUnencryptedSave, nil
   928  }
   929  
   930  func (m *recoverModeStateMachine) unlockEncryptedSaveRunKey() (stateFunc, error) {
   931  	// to get to this state, we needed to have mounted ubuntu-data on host, so
   932  	// if encrypted, we can try to read the run key from host ubuntu-data
   933  	saveKey := filepath.Join(dirs.SnapFDEDirUnder(boot.InitramfsHostWritableDir), "ubuntu-save.key")
   934  	key, err := ioutil.ReadFile(saveKey)
   935  	if err != nil {
   936  		// log the error and skip to trying the fallback key
   937  		m.degradedState.LogErrorf("cannot access run ubuntu-save key: %v", err)
   938  		return m.unlockEncryptedSaveFallbackKey, nil
   939  	}
   940  
   941  	unlockRes, unlockErr := secbootUnlockEncryptedVolumeUsingKey(m.disk, "ubuntu-save", key)
   942  	if err := m.setUnlockStateWithRunKey("ubuntu-save", unlockRes, unlockErr); err != nil {
   943  		return nil, err
   944  	}
   945  	if unlockErr != nil {
   946  		// failed to unlock with run key, try fallback key
   947  		return m.unlockEncryptedSaveFallbackKey, nil
   948  	}
   949  
   950  	// unlocked it properly, go mount it
   951  	return m.mountSave, nil
   952  }
   953  
   954  func (m *recoverModeStateMachine) unlockMaybeEncryptedAloneSaveFallbackKey() (stateFunc, error) {
   955  	// we can only get here by not finding ubuntu-data at all, meaning the
   956  	// system can still be encrypted and have an encrypted ubuntu-save,
   957  	// which we will determine now
   958  
   959  	// first check whether there is an encrypted save
   960  	_, findErr := m.disk.FindMatchingPartitionUUIDWithFsLabel(secboot.EncryptedPartitionName("ubuntu-save"))
   961  	if findErr == nil {
   962  		// well there is one, go try and unlock it
   963  		return m.unlockEncryptedSaveFallbackKey, nil
   964  	}
   965  	// encrypted ubuntu-save does not exist, there may still be an
   966  	// unencrypted one
   967  	return m.openUnencryptedSave, nil
   968  }
   969  
   970  func (m *recoverModeStateMachine) openUnencryptedSave() (stateFunc, error) {
   971  	// do we have ubuntu-save at all?
   972  	partSave := m.degradedState.partition("ubuntu-save")
   973  	const partitionOptional = true
   974  	partUUID, findErr := m.disk.FindMatchingPartitionUUIDWithFsLabel("ubuntu-save")
   975  	if err := m.setFindState("ubuntu-save", partUUID, findErr, partitionOptional); err != nil {
   976  		return nil, err
   977  	}
   978  	if partSave.FindState == partitionFound {
   979  		// we have ubuntu-save, go mount it
   980  		return m.mountSave, nil
   981  	}
   982  
   983  	// unencrypted ubuntu-save was not found, try to log something in case
   984  	// the early boot output can be collected for debugging purposes
   985  	if uuid, err := m.disk.FindMatchingPartitionUUIDWithFsLabel(secboot.EncryptedPartitionName("ubuntu-save")); err == nil {
   986  		// highly unlikely that encrypted save exists
   987  		logger.Noticef("ignoring unexpected encrypted ubuntu-save with UUID %q", uuid)
   988  	} else {
   989  		logger.Noticef("ubuntu-save was not found")
   990  	}
   991  
   992  	// save is optional in an unencrypted system
   993  	partSave.MountState = partitionAbsentOptional
   994  
   995  	// we're done, nothing more to try
   996  	return nil, nil
   997  }
   998  
   999  func (m *recoverModeStateMachine) unlockEncryptedSaveFallbackKey() (stateFunc, error) {
  1000  	// try to unlock save with the fallback key on ubuntu-seed, which must have
  1001  	// been mounted at this point
  1002  
  1003  	if m.noFallback {
  1004  		return nil, fmt.Errorf("cannot unlock ubuntu-save (fallback disabled)")
  1005  	}
  1006  
  1007  	unlockOpts := &secboot.UnlockVolumeUsingSealedKeyOptions{
  1008  		// we want to allow using the recovery key if the fallback key fails as
  1009  		// using the fallback object is the last chance before we give up trying
  1010  		// to unlock save
  1011  		AllowRecoveryKey: true,
  1012  		WhichModel:       m.whichModel,
  1013  	}
  1014  	saveFallbackKey := filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key")
  1015  	// TODO: this prompts again for a recover key, but really this is the
  1016  	// reinstall key we will prompt for
  1017  	// TODO: we should somehow customize the prompt to mention what key we need
  1018  	// the user to enter, and what we are unlocking (as currently the prompt
  1019  	// says "recovery key" and the partition UUID for what is being unlocked)
  1020  	unlockRes, unlockErr := secbootUnlockVolumeUsingSealedKeyIfEncrypted(m.disk, "ubuntu-save", saveFallbackKey, unlockOpts)
  1021  	if err := m.setUnlockStateWithFallbackKey("ubuntu-save", unlockRes, unlockErr); err != nil {
  1022  		return nil, err
  1023  	}
  1024  	if unlockErr != nil {
  1025  		// all done, nothing left to try and mount, mounting ubuntu-save is the
  1026  		// last step but we couldn't find or unlock it
  1027  		return nil, nil
  1028  	}
  1029  	// otherwise we unlocked it, so go mount it
  1030  	return m.mountSave, nil
  1031  }
  1032  
  1033  func (m *recoverModeStateMachine) mountSave() (stateFunc, error) {
  1034  	save := m.degradedState.partition("ubuntu-save")
  1035  	// TODO: should we fsck ubuntu-save ?
  1036  	mountErr := doSystemdMount(save.fsDevice, boot.InitramfsUbuntuSaveDir, nil)
  1037  	if err := m.setMountState("ubuntu-save", boot.InitramfsUbuntuSaveDir, mountErr); err != nil {
  1038  		return nil, err
  1039  	}
  1040  	// all done, nothing left to try and mount
  1041  	return nil, nil
  1042  }
  1043  
  1044  func generateMountsModeRecover(mst *initramfsMountsState) error {
  1045  	// steps 1 and 2 are shared with install mode
  1046  	model, snaps, err := generateMountsCommonInstallRecover(mst)
  1047  	if err != nil {
  1048  		return err
  1049  	}
  1050  
  1051  	// get the disk that we mounted the ubuntu-seed partition from as a
  1052  	// reference point for future mounts
  1053  	disk, err := disks.DiskFromMountPoint(boot.InitramfsUbuntuSeedDir, nil)
  1054  	if err != nil {
  1055  		return err
  1056  	}
  1057  
  1058  	// for most cases we allow the use of fallback to unlock/mount things
  1059  	allowFallback := true
  1060  
  1061  	tryingCurrentSystem, err := boot.InitramfsIsTryingRecoverySystem(mst.recoverySystem)
  1062  	if err != nil {
  1063  		if boot.IsInconsistentRecoverySystemState(err) {
  1064  			// there is some try recovery system state in bootenv
  1065  			// but it is inconsistent, make sure we clear it and
  1066  			// return back to run mode
  1067  
  1068  			// finalize reboots or panics
  1069  			logger.Noticef("try recovery system state is inconsistent: %v", err)
  1070  			finalizeTryRecoverySystemAndReboot(boot.TryRecoverySystemOutcomeInconsistent)
  1071  		}
  1072  		return err
  1073  	}
  1074  	if tryingCurrentSystem {
  1075  		// but in this case, use only the run keys
  1076  		allowFallback = false
  1077  
  1078  		// make sure that if rebooted, the next boot goes into run mode
  1079  		if err := boot.EnsureNextBootToRunMode(""); err != nil {
  1080  			return err
  1081  		}
  1082  	}
  1083  
  1084  	// 3. run the state machine logic for mounting partitions, this involves
  1085  	//    trying to unlock then mount ubuntu-data, and then unlocking and
  1086  	//    mounting ubuntu-save
  1087  	//    see the state* functions for details of what each step does and
  1088  	//    possible transition points
  1089  
  1090  	machine, err := func() (machine *recoverModeStateMachine, err error) {
  1091  		// first state to execute is to unlock ubuntu-data with the run key
  1092  		machine = newRecoverModeStateMachine(model, disk, allowFallback)
  1093  		for {
  1094  			finished, err := machine.execute()
  1095  			// TODO: consider whether certain errors are fatal or not
  1096  			if err != nil {
  1097  				return nil, err
  1098  			}
  1099  			if finished {
  1100  				break
  1101  			}
  1102  		}
  1103  
  1104  		return machine, nil
  1105  	}()
  1106  	if tryingCurrentSystem {
  1107  		// end of the line for a recovery system we are only trying out,
  1108  		// this branch always ends with a reboot (or a panic)
  1109  		var outcome boot.TryRecoverySystemOutcome
  1110  		if err == nil && !machine.degraded() {
  1111  			outcome = boot.TryRecoverySystemOutcomeSuccess
  1112  		} else {
  1113  			outcome = boot.TryRecoverySystemOutcomeFailure
  1114  			if err == nil {
  1115  				err = fmt.Errorf("in degraded state")
  1116  			}
  1117  			logger.Noticef("try recovery system %q failed: %v", mst.recoverySystem, err)
  1118  		}
  1119  		// finalize reboots or panics
  1120  		finalizeTryRecoverySystemAndReboot(outcome)
  1121  	}
  1122  
  1123  	if err != nil {
  1124  		return err
  1125  	}
  1126  
  1127  	// 3.1 write out degraded.json if we ended up falling back somewhere
  1128  	if machine.degraded() {
  1129  		b, err := json.Marshal(machine.degradedState)
  1130  		if err != nil {
  1131  			return err
  1132  		}
  1133  
  1134  		if err := os.MkdirAll(dirs.SnapBootstrapRunDir, 0755); err != nil {
  1135  			return err
  1136  		}
  1137  
  1138  		// leave the information about degraded state at an ephemeral location
  1139  		if err := ioutil.WriteFile(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), b, 0644); err != nil {
  1140  			return err
  1141  		}
  1142  	}
  1143  
  1144  	// 4. final step: copy the auth data and network config from
  1145  	//    the real ubuntu-data dir to the ephemeral ubuntu-data
  1146  	//    dir, write the modeenv to the tmpfs data, and disable
  1147  	//    cloud-init in recover mode
  1148  
  1149  	// if we have the host location, then we were able to successfully mount
  1150  	// ubuntu-data, and as such we can proceed with copying files from there
  1151  	// onto the tmpfs
  1152  	// Proceed only if we trust ubuntu-data to be paired with ubuntu-save
  1153  	if machine.trustData() {
  1154  		// TODO: erroring here should fallback to copySafeDefaultData and
  1155  		// proceed on with degraded mode anyways
  1156  		if err := copyUbuntuDataAuth(boot.InitramfsHostUbuntuDataDir, boot.InitramfsDataDir); err != nil {
  1157  			return err
  1158  		}
  1159  		if err := copyNetworkConfig(boot.InitramfsHostUbuntuDataDir, boot.InitramfsDataDir); err != nil {
  1160  			return err
  1161  		}
  1162  		if err := copyUbuntuDataMisc(boot.InitramfsHostUbuntuDataDir, boot.InitramfsDataDir); err != nil {
  1163  			return err
  1164  		}
  1165  	} else {
  1166  		// we don't have ubuntu-data host mountpoint, so we should setup safe
  1167  		// defaults for i.e. console-conf in the running image to block
  1168  		// attackers from accessing the system - just because we can't access
  1169  		// ubuntu-data doesn't mean that attackers wouldn't be able to if they
  1170  		// could login
  1171  
  1172  		if err := copySafeDefaultData(boot.InitramfsDataDir); err != nil {
  1173  			return err
  1174  		}
  1175  	}
  1176  
  1177  	modeEnv, err := mst.EphemeralModeenvForModel(model, snaps)
  1178  	if err != nil {
  1179  		return err
  1180  	}
  1181  	if err := modeEnv.WriteTo(boot.InitramfsWritableDir); err != nil {
  1182  		return err
  1183  	}
  1184  
  1185  	// finally we need to modify the bootenv to mark the system as successful,
  1186  	// this ensures that when you reboot from recover mode without doing
  1187  	// anything else, you are auto-transitioned back to run mode
  1188  	// TODO:UC20: as discussed unclear we need to pass the recovery system here
  1189  	if err := boot.EnsureNextBootToRunMode(mst.recoverySystem); err != nil {
  1190  		return err
  1191  	}
  1192  
  1193  	// done, no output, no error indicates to initramfs we are done with
  1194  	// mounting stuff
  1195  	return nil
  1196  }
  1197  
  1198  // checkDataAndSavePairing make sure that ubuntu-data and ubuntu-save
  1199  // come from the same install by comparing secret markers in them
  1200  func checkDataAndSavePairing(rootdir string) (bool, error) {
  1201  	// read the secret marker file from ubuntu-data
  1202  	markerFile1 := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "marker")
  1203  	marker1, err := ioutil.ReadFile(markerFile1)
  1204  	if err != nil {
  1205  		return false, err
  1206  	}
  1207  	// read the secret marker file from ubuntu-save
  1208  	markerFile2 := filepath.Join(dirs.SnapFDEDirUnderSave(boot.InitramfsUbuntuSaveDir), "marker")
  1209  	marker2, err := ioutil.ReadFile(markerFile2)
  1210  	if err != nil {
  1211  		return false, err
  1212  	}
  1213  	return subtle.ConstantTimeCompare(marker1, marker2) == 1, nil
  1214  }
  1215  
  1216  // mountNonDataPartitionMatchingKernelDisk will select the partition to mount at
  1217  // dir, using the boot package function FindPartitionUUIDForBootedKernelDisk to
  1218  // determine what partition the booted kernel came from. If which disk the
  1219  // kernel came from cannot be determined, then it will fallback to mounting via
  1220  // the specified disk label.
  1221  func mountNonDataPartitionMatchingKernelDisk(dir, fallbacklabel string) error {
  1222  	partuuid, err := bootFindPartitionUUIDForBootedKernelDisk()
  1223  	// TODO: the by-partuuid is only available on gpt disks, on mbr we need
  1224  	//       to use by-uuid or by-id
  1225  	partSrc := filepath.Join("/dev/disk/by-partuuid", partuuid)
  1226  	if err != nil {
  1227  		// no luck, try mounting by label instead
  1228  		partSrc = filepath.Join("/dev/disk/by-label", fallbacklabel)
  1229  	}
  1230  
  1231  	opts := &systemdMountOptions{
  1232  		// always fsck the partition when we are mounting it, as this is the
  1233  		// first partition we will be mounting, we can't know if anything is
  1234  		// corrupted yet
  1235  		NeedsFsck: true,
  1236  		// don't need nosuid option here, since this function is only used
  1237  		// for ubuntu-boot and ubuntu-seed, never ubuntu-data
  1238  	}
  1239  	return doSystemdMount(partSrc, dir, opts)
  1240  }
  1241  
  1242  func generateMountsCommonInstallRecover(mst *initramfsMountsState) (model *asserts.Model, sysSnaps map[snap.Type]snap.PlaceInfo, err error) {
  1243  	// 1. always ensure seed partition is mounted first before the others,
  1244  	//      since the seed partition is needed to mount the snap files there
  1245  	if err := mountNonDataPartitionMatchingKernelDisk(boot.InitramfsUbuntuSeedDir, "ubuntu-seed"); err != nil {
  1246  		return nil, nil, err
  1247  	}
  1248  
  1249  	// load model and verified essential snaps metadata
  1250  	typs := []snap.Type{snap.TypeBase, snap.TypeKernel, snap.TypeSnapd, snap.TypeGadget}
  1251  	model, essSnaps, err := mst.ReadEssential("", typs)
  1252  	if err != nil {
  1253  		return nil, nil, fmt.Errorf("cannot load metadata and verify essential bootstrap snaps %v: %v", typs, err)
  1254  	}
  1255  
  1256  	// 2.1. measure model
  1257  	err = stampedAction(fmt.Sprintf("%s-model-measured", mst.recoverySystem), func() error {
  1258  		return secbootMeasureSnapModelWhenPossible(func() (*asserts.Model, error) {
  1259  			return model, nil
  1260  		})
  1261  	})
  1262  	if err != nil {
  1263  		return nil, nil, err
  1264  	}
  1265  	// at this point on a system with TPM-based encryption
  1266  	// data can be open only if the measured model matches the actual
  1267  	// expected recovery model we sealed against.
  1268  	// TODO:UC20: on ARM systems and no TPM with encryption
  1269  	// we need other ways to make sure that the disk is opened
  1270  	// and we continue booting only for expected recovery models
  1271  
  1272  	// 2.2. (auto) select recovery system and mount seed snaps
  1273  	// TODO:UC20: do we need more cross checks here?
  1274  
  1275  	systemSnaps := make(map[snap.Type]snap.PlaceInfo)
  1276  
  1277  	for _, essentialSnap := range essSnaps {
  1278  		if essentialSnap.EssentialType == snap.TypeGadget {
  1279  			// don't need to mount the gadget anywhere, but we use the snap
  1280  			// later hence it is loaded
  1281  			continue
  1282  		}
  1283  		systemSnaps[essentialSnap.EssentialType] = essentialSnap.PlaceInfo()
  1284  
  1285  		dir := snapTypeToMountDir[essentialSnap.EssentialType]
  1286  		// TODO:UC20: we need to cross-check the kernel path with snapd_recovery_kernel used by grub
  1287  		if err := doSystemdMount(essentialSnap.Path, filepath.Join(boot.InitramfsRunMntDir, dir), nil); err != nil {
  1288  			return nil, nil, err
  1289  		}
  1290  	}
  1291  
  1292  	// TODO:UC20: after we have the kernel and base snaps mounted, we should do
  1293  	//            the bind mounts from the kernel modules on top of the base
  1294  	//            mount and delete the corresponding systemd units from the
  1295  	//            initramfs layout
  1296  
  1297  	// TODO:UC20: after the kernel and base snaps are mounted, we should setup
  1298  	//            writable here as well to take over from "the-modeenv" script
  1299  	//            in the initrd too
  1300  
  1301  	// TODO:UC20: after the kernel and base snaps are mounted and writable is
  1302  	//            mounted, we should also implement writable-paths here too as
  1303  	//            writing it in Go instead of shellscript is desirable
  1304  
  1305  	// 2.3. mount "ubuntu-data" on a tmpfs, and also mount with nosuid to prevent
  1306  	// snaps from being able to bypass the sandbox by creating suid root files
  1307  	// there and try to escape the sandbox
  1308  	mntOpts := &systemdMountOptions{
  1309  		Tmpfs:  true,
  1310  		NoSuid: true,
  1311  	}
  1312  	err = doSystemdMount("tmpfs", boot.InitramfsDataDir, mntOpts)
  1313  	if err != nil {
  1314  		return nil, nil, err
  1315  	}
  1316  
  1317  	// finally get the gadget snap from the essential snaps and use it to
  1318  	// configure the ephemeral system
  1319  	// should only be one seed snap
  1320  	gadgetPath := ""
  1321  	for _, essentialSnap := range essSnaps {
  1322  		if essentialSnap.EssentialType == snap.TypeGadget {
  1323  			gadgetPath = essentialSnap.Path
  1324  		}
  1325  	}
  1326  	gadgetSnap := squashfs.New(gadgetPath)
  1327  
  1328  	// we need to configure the ephemeral system with defaults and such using
  1329  	// from the seed gadget
  1330  	configOpts := &sysconfig.Options{
  1331  		// never allow cloud-init to run inside the ephemeral system, in the
  1332  		// install case we don't want it to ever run, and in the recover case
  1333  		// cloud-init will already have run in run mode, so things like network
  1334  		// config and users should already be setup and we will copy those
  1335  		// further down in the setup for recover mode
  1336  		AllowCloudInit: false,
  1337  		TargetRootDir:  boot.InitramfsWritableDir,
  1338  		GadgetSnap:     gadgetSnap,
  1339  	}
  1340  	if err := sysconfig.ConfigureTargetSystem(model, configOpts); err != nil {
  1341  		return nil, nil, err
  1342  	}
  1343  
  1344  	return model, systemSnaps, nil
  1345  }
  1346  
  1347  func maybeMountSave(disk disks.Disk, rootdir string, encrypted bool, mountOpts *systemdMountOptions) (haveSave bool, err error) {
  1348  	var saveDevice string
  1349  	if encrypted {
  1350  		saveKey := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "ubuntu-save.key")
  1351  		// if ubuntu-save exists and is encrypted, the key has been created during install
  1352  		if !osutil.FileExists(saveKey) {
  1353  			// ubuntu-data is encrypted, but we appear to be missing
  1354  			// a key to open ubuntu-save
  1355  			return false, fmt.Errorf("cannot find ubuntu-save encryption key at %v", saveKey)
  1356  		}
  1357  		// we have save.key, volume exists and is encrypted
  1358  		key, err := ioutil.ReadFile(saveKey)
  1359  		if err != nil {
  1360  			return true, err
  1361  		}
  1362  		unlockRes, err := secbootUnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", key)
  1363  		if err != nil {
  1364  			return true, fmt.Errorf("cannot unlock ubuntu-save volume: %v", err)
  1365  		}
  1366  		saveDevice = unlockRes.FsDevice
  1367  	} else {
  1368  		partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel("ubuntu-save")
  1369  		if err != nil {
  1370  			if _, ok := err.(disks.PartitionNotFoundError); ok {
  1371  				// this is ok, ubuntu-save may not exist for
  1372  				// non-encrypted device
  1373  				return false, nil
  1374  			}
  1375  			return false, err
  1376  		}
  1377  		saveDevice = filepath.Join("/dev/disk/by-partuuid", partUUID)
  1378  	}
  1379  	if err := doSystemdMount(saveDevice, boot.InitramfsUbuntuSaveDir, mountOpts); err != nil {
  1380  		return true, err
  1381  	}
  1382  	return true, nil
  1383  }
  1384  
  1385  func generateMountsModeRun(mst *initramfsMountsState) error {
  1386  	// 1. mount ubuntu-boot
  1387  	if err := mountNonDataPartitionMatchingKernelDisk(boot.InitramfsUbuntuBootDir, "ubuntu-boot"); err != nil {
  1388  		return err
  1389  	}
  1390  
  1391  	// get the disk that we mounted the ubuntu-boot partition from as a
  1392  	// reference point for future mounts
  1393  	disk, err := disks.DiskFromMountPoint(boot.InitramfsUbuntuBootDir, nil)
  1394  	if err != nil {
  1395  		return err
  1396  	}
  1397  
  1398  	// 2. mount ubuntu-seed
  1399  	// use the disk we mounted ubuntu-boot from as a reference to find
  1400  	// ubuntu-seed and mount it
  1401  	partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel("ubuntu-seed")
  1402  	if err != nil {
  1403  		return err
  1404  	}
  1405  
  1406  	// fsck is safe to run on ubuntu-seed as per the manpage, it should not
  1407  	// meaningfully contribute to corruption if we fsck it every time we boot,
  1408  	// and it is important to fsck it because it is vfat and mounted writable
  1409  	// TODO:UC20: mount it as read-only here and remount as writable when we
  1410  	//            need it to be writable for i.e. transitioning to recover mode
  1411  	fsckSystemdOpts := &systemdMountOptions{
  1412  		NeedsFsck: true,
  1413  	}
  1414  	if err := doSystemdMount(fmt.Sprintf("/dev/disk/by-partuuid/%s", partUUID), boot.InitramfsUbuntuSeedDir, fsckSystemdOpts); err != nil {
  1415  		return err
  1416  	}
  1417  
  1418  	// 3.1. measure model
  1419  	err = stampedAction("run-model-measured", func() error {
  1420  		return secbootMeasureSnapModelWhenPossible(mst.UnverifiedBootModel)
  1421  	})
  1422  	if err != nil {
  1423  		return err
  1424  	}
  1425  	// at this point on a system with TPM-based encryption
  1426  	// data can be open only if the measured model matches the actual
  1427  	// run model.
  1428  	// TODO:UC20: on ARM systems and no TPM with encryption
  1429  	// we need other ways to make sure that the disk is opened
  1430  	// and we continue booting only for expected models
  1431  
  1432  	// 3.2. mount Data
  1433  	runModeKey := filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key")
  1434  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
  1435  		AllowRecoveryKey: true,
  1436  		WhichModel:       mst.UnverifiedBootModel,
  1437  	}
  1438  	unlockRes, err := secbootUnlockVolumeUsingSealedKeyIfEncrypted(disk, "ubuntu-data", runModeKey, opts)
  1439  	if err != nil {
  1440  		return err
  1441  	}
  1442  
  1443  	// TODO: do we actually need fsck if we are mounting a mapper device?
  1444  	// probably not?
  1445  	// fsck and mount with nosuid to prevent snaps from being able to bypass
  1446  	// the sandbox by creating suid root files there and trying to escape the
  1447  	// sandbox
  1448  	dataMountOpts := &systemdMountOptions{
  1449  		NeedsFsck: true,
  1450  		NoSuid:    true,
  1451  	}
  1452  	if err := doSystemdMount(unlockRes.FsDevice, boot.InitramfsDataDir, dataMountOpts); err != nil {
  1453  		return err
  1454  	}
  1455  	isEncryptedDev := unlockRes.IsEncrypted
  1456  
  1457  	// 3.3. mount ubuntu-save (if present)
  1458  	haveSave, err := maybeMountSave(disk, boot.InitramfsWritableDir, isEncryptedDev, fsckSystemdOpts)
  1459  	if err != nil {
  1460  		return err
  1461  	}
  1462  
  1463  	// 4.1 verify that ubuntu-data comes from where we expect it to
  1464  	diskOpts := &disks.Options{}
  1465  	if unlockRes.IsEncrypted {
  1466  		// then we need to specify that the data mountpoint is expected to be a
  1467  		// decrypted device, applies to both ubuntu-data and ubuntu-save
  1468  		diskOpts.IsDecryptedDevice = true
  1469  	}
  1470  
  1471  	matches, err := disk.MountPointIsFromDisk(boot.InitramfsDataDir, diskOpts)
  1472  	if err != nil {
  1473  		return err
  1474  	}
  1475  	if !matches {
  1476  		// failed to verify that ubuntu-data mountpoint comes from the same disk
  1477  		// as ubuntu-boot
  1478  		return fmt.Errorf("cannot validate boot: ubuntu-data mountpoint is expected to be from disk %s but is not", disk.Dev())
  1479  	}
  1480  	if haveSave {
  1481  		// 4.1a we have ubuntu-save, verify it as well
  1482  		matches, err = disk.MountPointIsFromDisk(boot.InitramfsUbuntuSaveDir, diskOpts)
  1483  		if err != nil {
  1484  			return err
  1485  		}
  1486  		if !matches {
  1487  			return fmt.Errorf("cannot validate boot: ubuntu-save mountpoint is expected to be from disk %s but is not", disk.Dev())
  1488  		}
  1489  
  1490  		if isEncryptedDev {
  1491  			// in run mode the path to open an encrypted save is for
  1492  			// data to be encrypted and the save key in it
  1493  			// to be successfully used. This already should stop
  1494  			// allowing to chose ubuntu-data to try to access
  1495  			// save. as safety boot also stops if the keys cannot
  1496  			// be locked.
  1497  			// for symmetry with recover code and extra paranoia
  1498  			// though also check that the markers match.
  1499  			paired, err := checkDataAndSavePairing(boot.InitramfsWritableDir)
  1500  			if err != nil {
  1501  				return err
  1502  			}
  1503  			if !paired {
  1504  				return fmt.Errorf("cannot validate boot: ubuntu-save and ubuntu-data are not marked as from the same install")
  1505  			}
  1506  		}
  1507  	}
  1508  
  1509  	// 4.2. read modeenv
  1510  	modeEnv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
  1511  	if err != nil {
  1512  		return err
  1513  	}
  1514  
  1515  	typs := []snap.Type{snap.TypeBase, snap.TypeKernel}
  1516  
  1517  	// 4.2 choose base and kernel snaps (this includes updating modeenv if
  1518  	//     needed to try the base snap)
  1519  	mounts, err := boot.InitramfsRunModeSelectSnapsToMount(typs, modeEnv)
  1520  	if err != nil {
  1521  		return err
  1522  	}
  1523  
  1524  	// TODO:UC20: with grade > dangerous, verify the kernel snap hash against
  1525  	//            what we booted using the tpm log, this may need to be passed
  1526  	//            to the function above to make decisions there, or perhaps this
  1527  	//            code actually belongs in the bootloader implementation itself
  1528  
  1529  	// 4.3 mount base and kernel snaps
  1530  	// make sure this is a deterministic order
  1531  	for _, typ := range []snap.Type{snap.TypeBase, snap.TypeKernel} {
  1532  		if sn, ok := mounts[typ]; ok {
  1533  			dir := snapTypeToMountDir[typ]
  1534  			snapPath := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), sn.Filename())
  1535  			if err := doSystemdMount(snapPath, filepath.Join(boot.InitramfsRunMntDir, dir), nil); err != nil {
  1536  				return err
  1537  			}
  1538  		}
  1539  	}
  1540  
  1541  	// 4.4 mount snapd snap only on first boot
  1542  	if modeEnv.RecoverySystem != "" {
  1543  		// load the recovery system and generate mount for snapd
  1544  		_, essSnaps, err := mst.ReadEssential(modeEnv.RecoverySystem, []snap.Type{snap.TypeSnapd})
  1545  		if err != nil {
  1546  			return fmt.Errorf("cannot load metadata and verify snapd snap: %v", err)
  1547  		}
  1548  
  1549  		return doSystemdMount(essSnaps[0].Path, filepath.Join(boot.InitramfsRunMntDir, "snapd"), nil)
  1550  	}
  1551  
  1552  	return nil
  1553  }
  1554  
  1555  var tryRecoverySystemHealthCheck = func() error {
  1556  	// check that writable is accessible by checking whether the
  1557  	// state file exists
  1558  	if !osutil.FileExists(dirs.SnapStateFileUnder(boot.InitramfsHostWritableDir)) {
  1559  		return fmt.Errorf("host state file is not accessible")
  1560  	}
  1561  	return nil
  1562  }
  1563  
  1564  func finalizeTryRecoverySystemAndReboot(outcome boot.TryRecoverySystemOutcome) (err error) {
  1565  	// from this point on, we must finish with a system reboot
  1566  	defer func() {
  1567  		if rebootErr := boot.InitramfsReboot(); rebootErr != nil {
  1568  			if err != nil {
  1569  				err = fmt.Errorf("%v (cannot reboot to run system: %v)", err, rebootErr)
  1570  			} else {
  1571  				err = fmt.Errorf("cannot reboot to run system: %v", rebootErr)
  1572  			}
  1573  		}
  1574  		// not reached, unless in tests
  1575  		panic(fmt.Errorf("finalize try recovery system did not reboot, last error: %v", err))
  1576  	}()
  1577  
  1578  	if outcome == boot.TryRecoverySystemOutcomeSuccess {
  1579  		if err := tryRecoverySystemHealthCheck(); err != nil {
  1580  			// health checks failed, the recovery system is considered
  1581  			// unsuccessful
  1582  			outcome = boot.TryRecoverySystemOutcomeFailure
  1583  			logger.Noticef("try recovery system health check failed: %v", err)
  1584  		}
  1585  	}
  1586  
  1587  	// that's it, we've tried booting a new recovery system to this point,
  1588  	// whether things are looking good or bad we will reboot back to run
  1589  	// mode and update the boot variables accordingly
  1590  	if err := boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome); err != nil {
  1591  		logger.Noticef("cannot update the try recovery system state: %v", err)
  1592  		return fmt.Errorf("cannot mark recovery system successful: %v", err)
  1593  	}
  1594  	return nil
  1595  }