github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/cmd/snap-bootstrap/cmd_initramfs_mounts.go (about)

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