github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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  	// TODO:UC20: for clarity turn this into into tristate:
   411  	// unknown|encrypted|unencrypted
   412  	isEncryptedDev bool
   413  
   414  	// state for tracking what happens as we progress through degraded mode of
   415  	// recovery
   416  	degradedState *recoverDegradedState
   417  }
   418  
   419  // degraded returns whether a degraded recover mode state has fallen back from
   420  // the typical operation to some sort of degraded mode.
   421  func (m *recoverModeStateMachine) degraded() bool {
   422  	r := m.degradedState
   423  
   424  	if m.isEncryptedDev {
   425  		// for encrypted devices, we need to have ubuntu-save mounted
   426  		if r.UbuntuSave.MountState != partitionMounted {
   427  			return true
   428  		}
   429  
   430  		// we also should have all the unlock keys as run keys
   431  		if r.UbuntuData.UnlockKey != keyRun {
   432  			return true
   433  		}
   434  
   435  		if r.UbuntuSave.UnlockKey != keyRun {
   436  			return true
   437  		}
   438  	} else {
   439  		// for unencrypted devices, ubuntu-save must either be mounted or
   440  		// absent-but-optional
   441  		if r.UbuntuSave.MountState != partitionMounted {
   442  			if r.UbuntuSave.MountState != partitionAbsentOptional {
   443  				return true
   444  			}
   445  		}
   446  	}
   447  
   448  	// ubuntu-boot and ubuntu-data should both be mounted
   449  	if r.UbuntuBoot.MountState != partitionMounted {
   450  		return true
   451  	}
   452  	if r.UbuntuData.MountState != partitionMounted {
   453  		return true
   454  	}
   455  
   456  	// TODO: should we also check MountLocation too?
   457  
   458  	// we should have nothing in the error log
   459  	if len(r.ErrorLog) != 0 {
   460  		return true
   461  	}
   462  
   463  	return false
   464  }
   465  
   466  func (m *recoverModeStateMachine) diskOpts() *disks.Options {
   467  	if m.isEncryptedDev {
   468  		return &disks.Options{
   469  			IsDecryptedDevice: true,
   470  		}
   471  	}
   472  	return nil
   473  }
   474  
   475  func (m *recoverModeStateMachine) verifyMountPoint(dir, name string) error {
   476  	matches, err := m.disk.MountPointIsFromDisk(dir, m.diskOpts())
   477  	if err != nil {
   478  		return err
   479  	}
   480  	if !matches {
   481  		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())
   482  	}
   483  	return nil
   484  }
   485  
   486  func (m *recoverModeStateMachine) setFindState(partName, partUUID string, err error) error {
   487  	part := m.degradedState.partition(partName)
   488  	if err != nil {
   489  		if _, ok := err.(disks.PartitionNotFoundError); ok {
   490  			// explicit error that the device was not found
   491  			part.FindState = partitionNotFound
   492  			m.degradedState.LogErrorf("cannot find %v partition on disk %s", partName, m.disk.Dev())
   493  			return nil
   494  		}
   495  		// the error is not "not-found", so we have a real error
   496  		part.FindState = partitionErrFinding
   497  		m.degradedState.LogErrorf("error finding %v partition on disk %s: %v", partName, m.disk.Dev(), err)
   498  		return nil
   499  	}
   500  
   501  	// device was found
   502  	part.FindState = partitionFound
   503  	dev := fmt.Sprintf("/dev/disk/by-partuuid/%s", partUUID)
   504  	part.partDevice = dev
   505  	part.fsDevice = dev
   506  	return nil
   507  }
   508  
   509  func (m *recoverModeStateMachine) setMountState(part, where string, err error) error {
   510  	if err != nil {
   511  		m.degradedState.LogErrorf("cannot mount %v: %v", part, err)
   512  		m.degradedState.partition(part).MountState = partitionErrMounting
   513  		return nil
   514  	}
   515  
   516  	m.degradedState.partition(part).MountState = partitionMounted
   517  	m.degradedState.partition(part).MountLocation = where
   518  
   519  	if err := m.verifyMountPoint(where, part); err != nil {
   520  		m.degradedState.LogErrorf("cannot verify %s mount point at %v: %v", part, where, err)
   521  		return err
   522  	}
   523  	return nil
   524  }
   525  
   526  func (m *recoverModeStateMachine) setUnlockStateWithRunKey(partName string, unlockRes secboot.UnlockResult, err error) error {
   527  	part := m.degradedState.partition(partName)
   528  	// save the device if we found it from secboot
   529  	if unlockRes.PartDevice != "" {
   530  		part.FindState = partitionFound
   531  		part.partDevice = unlockRes.PartDevice
   532  		part.fsDevice = unlockRes.FsDevice
   533  	} else {
   534  		part.FindState = partitionNotFound
   535  	}
   536  	if unlockRes.IsEncrypted {
   537  		m.isEncryptedDev = true
   538  	}
   539  
   540  	if err != nil {
   541  		// create different error message for encrypted vs unencrypted
   542  		if unlockRes.IsEncrypted {
   543  			// if we know the device is decrypted we must also always know at
   544  			// least the partDevice (which is the encrypted block device)
   545  			m.degradedState.LogErrorf("cannot unlock encrypted %s (device %s) with sealed run key: %v", partName, part.partDevice, err)
   546  			part.UnlockState = partitionErrUnlocking
   547  		} else {
   548  			// TODO: we don't know if this is a plain not found or  a different error
   549  			m.degradedState.LogErrorf("cannot locate %s partition for mounting host data: %v", partName, err)
   550  		}
   551  
   552  		return nil
   553  	}
   554  
   555  	if unlockRes.IsEncrypted {
   556  		// unlocked successfully
   557  		part.UnlockState = partitionUnlocked
   558  		part.UnlockKey = keyRun
   559  	}
   560  
   561  	return nil
   562  }
   563  
   564  func (m *recoverModeStateMachine) setUnlockStateWithFallbackKey(partName string, unlockRes secboot.UnlockResult, err error, partitionOptional bool) error {
   565  	// first check the result and error for consistency; since we are using udev
   566  	// there could be inconsistent results at different points in time
   567  
   568  	// TODO: consider refactoring UnlockVolumeUsingSealedKeyIfEncrypted to not
   569  	//       also find the partition on the disk, that should eliminate this
   570  	//       consistency checking as we can code it such that we don't get these
   571  	//       possible inconsistencies
   572  
   573  	// do basic consistency checking on unlockRes to make sure the
   574  	// result makes sense.
   575  	if unlockRes.FsDevice != "" && err != nil {
   576  		// This case should be impossible to enter, we can't
   577  		// have a filesystem device but an error set
   578  		return fmt.Errorf("internal error: inconsistent return values from UnlockVolumeUsingSealedKeyIfEncrypted for partition %s: %v", partName, err)
   579  	}
   580  
   581  	part := m.degradedState.partition(partName)
   582  	// Also make sure that if we previously saw a partition device that we see
   583  	// the same device again.
   584  	if unlockRes.PartDevice != "" && part.partDevice != "" && unlockRes.PartDevice != part.partDevice {
   585  		return fmt.Errorf("inconsistent partitions found for %s: previously found %s but now found %s", partName, part.partDevice, unlockRes.PartDevice)
   586  	}
   587  
   588  	// ensure consistency between encrypted state of the device/disk and what we
   589  	// may have seen previously
   590  	if m.isEncryptedDev && !unlockRes.IsEncrypted {
   591  		// then we previously were able to positively identify an
   592  		// ubuntu-data-enc but can't anymore, so we have inconsistent results
   593  		// from inspecting the disk which is suspicious and we should fail
   594  		return fmt.Errorf("inconsistent disk encryption status: previous access resulted in encrypted, but now is unencrypted from partition %s", partName)
   595  	}
   596  
   597  	// now actually process the result into the state
   598  	if unlockRes.PartDevice != "" {
   599  		part.FindState = partitionFound
   600  		// Note that in some case this may be redundantly assigning the same
   601  		// value to partDevice again.
   602  		part.partDevice = unlockRes.PartDevice
   603  		part.fsDevice = unlockRes.FsDevice
   604  	}
   605  
   606  	// There are a few cases where this could be the first time that we found a
   607  	// decrypted device in the UnlockResult, but m.isEncryptedDev is still
   608  	// false.
   609  	// - The first case is if we couldn't find ubuntu-boot at all, in which case
   610  	// we can't use the run object keys from there and instead need to directly
   611  	// fallback to trying the fallback object keys from ubuntu-seed
   612  	// - The second case is if we couldn't identify an ubuntu-data-enc or an
   613  	// ubuntu-data partition at all, we still could have an ubuntu-save-enc
   614  	// partition in which case we maybe could still have an encrypted disk that
   615  	// needs unlocking with the fallback object keys from ubuntu-seed
   616  	//
   617  	// As such, if m.isEncryptedDev is false, but unlockRes.IsEncrypted is
   618  	// true, then it is safe to assign m.isEncryptedDev to true.
   619  	if !m.isEncryptedDev && unlockRes.IsEncrypted {
   620  		m.isEncryptedDev = true
   621  	}
   622  
   623  	if err != nil {
   624  		// create different error message for encrypted vs unencrypted
   625  		if m.isEncryptedDev {
   626  			m.degradedState.LogErrorf("cannot unlock encrypted %s partition with sealed fallback key: %v", partName, err)
   627  			part.UnlockState = partitionErrUnlocking
   628  		} else {
   629  			// if we don't have an encrypted device and err != nil, then the
   630  			// device must be not-found, see above checks
   631  
   632  			// if the partition is optional (like ubuntu-save is) then don't
   633  			// report an error for ubuntu-save not being found and also set it
   634  			// as absent-but-optional
   635  			if unlockRes.PartDevice == "" && partitionOptional {
   636  				part.MountState = partitionAbsentOptional
   637  			} else {
   638  				// log the error the partition is mandatory
   639  				m.degradedState.LogErrorf("cannot locate %s partition: %v", partName, err)
   640  			}
   641  		}
   642  
   643  		return nil
   644  	}
   645  
   646  	if m.isEncryptedDev {
   647  		// unlocked successfully
   648  		part.UnlockState = partitionUnlocked
   649  
   650  		// figure out which key/method we used to unlock the partition
   651  		switch unlockRes.UnlockMethod {
   652  		case secboot.UnlockedWithSealedKey:
   653  			part.UnlockKey = keyFallback
   654  		case secboot.UnlockedWithRecoveryKey:
   655  			part.UnlockKey = keyRecovery
   656  
   657  			// TODO: should we fail with internal error for default case here?
   658  		}
   659  	}
   660  
   661  	return nil
   662  }
   663  
   664  func newRecoverModeStateMachine(model *asserts.Model, disk disks.Disk) *recoverModeStateMachine {
   665  	m := &recoverModeStateMachine{
   666  		model: model,
   667  		disk:  disk,
   668  		degradedState: &recoverDegradedState{
   669  			ErrorLog: []string{},
   670  		},
   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  	if err := m.setFindState("ubuntu-boot", partUUID, findErr); err != nil {
   761  		return nil, err
   762  	}
   763  	if part.FindState != partitionFound {
   764  		// if we didn't find ubuntu-boot, we can't try to unlock data with the
   765  		// run key, and should instead just jump straight to attempting to
   766  		// unlock with the fallback key
   767  		return m.unlockDataFallbackKey, nil
   768  	}
   769  
   770  	// should we fsck ubuntu-boot? probably yes because on some platforms
   771  	// (u-boot for example) ubuntu-boot is vfat and it could have been unmounted
   772  	// dirtily, and we need to fsck it to ensure it is mounted safely before
   773  	// reading keys from it
   774  	fsckSystemdOpts := &systemdMountOptions{
   775  		NeedsFsck: true,
   776  	}
   777  	mountErr := doSystemdMount(part.fsDevice, boot.InitramfsUbuntuBootDir, fsckSystemdOpts)
   778  	if err := m.setMountState("ubuntu-boot", boot.InitramfsUbuntuBootDir, mountErr); err != nil {
   779  		return nil, err
   780  	}
   781  	if part.MountState == partitionErrMounting {
   782  		// if we didn't mount data, then try to unlock data with the
   783  		// fallback key
   784  		return m.unlockDataFallbackKey, nil
   785  	}
   786  
   787  	// next step try to unlock data with run object
   788  	return m.unlockDataRunKey, nil
   789  }
   790  
   791  // stateUnlockDataRunKey will try to unlock ubuntu-data with the normal run-mode
   792  // key, and if it fails, progresses to the next state, which is either:
   793  // - failed to unlock data, but we know it's an encrypted device -> try to unlock with fallback key
   794  // - failed to find data at all -> try to unlock save
   795  // - unlocked data with run key -> mount data
   796  func (m *recoverModeStateMachine) unlockDataRunKey() (stateFunc, error) {
   797  	runModeKey := filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key")
   798  	unlockOpts := &secboot.UnlockVolumeUsingSealedKeyOptions{
   799  		// don't allow using the recovery key to unlock, we only try using the
   800  		// recovery key after we first try the fallback object
   801  		AllowRecoveryKey: false,
   802  	}
   803  	unlockRes, unlockErr := secbootUnlockVolumeUsingSealedKeyIfEncrypted(m.disk, "ubuntu-data", runModeKey, unlockOpts)
   804  	if err := m.setUnlockStateWithRunKey("ubuntu-data", unlockRes, unlockErr); err != nil {
   805  		return nil, err
   806  	}
   807  	if unlockErr != nil {
   808  		// we couldn't unlock ubuntu-data with the primary key, or we didn't
   809  		// find it in the unencrypted case
   810  		if unlockRes.IsEncrypted {
   811  			// we know the device is encrypted, so the next state is to try
   812  			// unlocking with the fallback key
   813  			return m.unlockDataFallbackKey, nil
   814  		}
   815  
   816  		// if we didn't even find the device to the point where it would have
   817  		// been identified as decrypted or unencrypted device, we could have
   818  		// just entirely lost ubuntu-data-enc, and we could still have an
   819  		// encrypted device, so instead try to unlock ubuntu-save with the
   820  		// fallback key, the logic there can also handle an unencrypted ubuntu-save
   821  		return m.unlockSaveFallbackKey, nil
   822  	}
   823  
   824  	// otherwise successfully unlocked it (or just found it if it was unencrypted)
   825  	// so just mount it
   826  	return m.mountData, nil
   827  }
   828  
   829  func (m *recoverModeStateMachine) unlockDataFallbackKey() (stateFunc, error) {
   830  	// try to unlock data with the fallback key on ubuntu-seed, which must have
   831  	// been mounted at this point
   832  	unlockOpts := &secboot.UnlockVolumeUsingSealedKeyOptions{
   833  		// we want to allow using the recovery key if the fallback key fails as
   834  		// using the fallback object is the last chance before we give up trying
   835  		// to unlock data
   836  		AllowRecoveryKey: true,
   837  	}
   838  	// TODO: this prompts for a recovery key
   839  	// TODO: we should somehow customize the prompt to mention what key we need
   840  	// the user to enter, and what we are unlocking (as currently the prompt
   841  	// says "recovery key" and the partition UUID for what is being unlocked)
   842  	dataFallbackKey := filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key")
   843  	unlockRes, unlockErr := secbootUnlockVolumeUsingSealedKeyIfEncrypted(m.disk, "ubuntu-data", dataFallbackKey, unlockOpts)
   844  	const partitionMandatory = false
   845  	if err := m.setUnlockStateWithFallbackKey("ubuntu-data", unlockRes, unlockErr, partitionMandatory); err != nil {
   846  		return nil, err
   847  	}
   848  	if unlockErr != nil {
   849  		// skip trying to mount data, since we did not unlock data we cannot
   850  		// open save with with the run key, so try the fallback one
   851  		return m.unlockSaveFallbackKey, nil
   852  	}
   853  
   854  	// unlocked it, now go mount it
   855  	return m.mountData, nil
   856  }
   857  
   858  func (m *recoverModeStateMachine) mountData() (stateFunc, error) {
   859  	data := m.degradedState.partition("ubuntu-data")
   860  	// don't do fsck on the data partition, it could be corrupted
   861  	mountErr := doSystemdMount(data.fsDevice, boot.InitramfsHostUbuntuDataDir, nil)
   862  	if err := m.setMountState("ubuntu-data", boot.InitramfsHostUbuntuDataDir, mountErr); err != nil {
   863  		return nil, err
   864  	}
   865  	if mountErr == nil && m.isEncryptedDev {
   866  		// if we succeeded in mounting data and we are encrypted, the next step
   867  		// is to unlock save with the run key from ubuntu-data
   868  		return m.unlockSaveRunKey, nil
   869  	}
   870  
   871  	// otherwise we always fall back to unlocking save with the fallback key,
   872  	// this could be two cases:
   873  	// 1. we are unencrypted in which case the secboot function used in
   874  	//    unlockSaveRunKey will fail and then proceed to trying the fallback key
   875  	//    function anyways which uses a secboot function that is suitable for
   876  	//    unencrypted data
   877  	// 2. we are encrypted and we failed to mount data successfully, meaning we
   878  	//    don't have the bare key from ubuntu-data to use, and need to fall back
   879  	//    to the sealed key from ubuntu-seed
   880  	return m.unlockSaveFallbackKey, nil
   881  }
   882  
   883  func (m *recoverModeStateMachine) unlockSaveRunKey() (stateFunc, error) {
   884  	// to get to this state, we needed to have mounted ubuntu-data on host, so
   885  	// if encrypted, we can try to read the run key from host ubuntu-data
   886  	saveKey := filepath.Join(dirs.SnapFDEDirUnder(boot.InitramfsHostWritableDir), "ubuntu-save.key")
   887  	key, err := ioutil.ReadFile(saveKey)
   888  	if err != nil {
   889  		// log the error and skip to trying the fallback key
   890  		m.degradedState.LogErrorf("cannot access run ubuntu-save key: %v", err)
   891  		return m.unlockSaveFallbackKey, nil
   892  	}
   893  
   894  	unlockRes, unlockErr := secbootUnlockEncryptedVolumeUsingKey(m.disk, "ubuntu-save", key)
   895  	if err := m.setUnlockStateWithRunKey("ubuntu-save", unlockRes, unlockErr); err != nil {
   896  		return nil, err
   897  	}
   898  	if unlockErr != nil {
   899  		// failed to unlock with run key, try fallback key
   900  		return m.unlockSaveFallbackKey, nil
   901  	}
   902  
   903  	// unlocked it properly, go mount it
   904  	return m.mountSave, nil
   905  }
   906  
   907  func (m *recoverModeStateMachine) unlockSaveFallbackKey() (stateFunc, error) {
   908  	// remember what we assumed about encryption before looking at
   909  	// save
   910  	assumeEncrypted := m.isEncryptedDev
   911  
   912  	// try to unlock save with the fallback key on ubuntu-seed, which must have
   913  	// been mounted at this point
   914  	unlockOpts := &secboot.UnlockVolumeUsingSealedKeyOptions{
   915  		// we want to allow using the recovery key if the fallback key fails as
   916  		// using the fallback object is the last chance before we give up trying
   917  		// to unlock save
   918  		AllowRecoveryKey: true,
   919  	}
   920  	saveFallbackKey := filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key")
   921  	// TODO: this prompts again for a recover key, but really this is the
   922  	// reinstall key we will prompt for
   923  	// TODO: we should somehow customize the prompt to mention what key we need
   924  	// the user to enter, and what we are unlocking (as currently the prompt
   925  	// says "recovery key" and the partition UUID for what is being unlocked)
   926  	unlockRes, unlockErr := secbootUnlockVolumeUsingSealedKeyIfEncrypted(m.disk, "ubuntu-save", saveFallbackKey, unlockOpts)
   927  	const partitionOptionalIfUnencrypted = true
   928  	if err := m.setUnlockStateWithFallbackKey("ubuntu-save", unlockRes, unlockErr, partitionOptionalIfUnencrypted); err != nil {
   929  		return nil, err
   930  	}
   931  	if unlockErr != nil {
   932  		// all done, nothing left to try and mount, mounting ubuntu-save is the
   933  		// last step but we couldn't find or unlock it
   934  		return nil, nil
   935  	}
   936  
   937  	// do a consistency check to make sure that if we found ubuntu-data
   938  	// unencrypted that we don't also mount ubuntu-save as encrypted
   939  	data := m.degradedState.partition("ubuntu-data")
   940  	if unlockRes.IsEncrypted && data.FindState == partitionFound && !assumeEncrypted {
   941  		return nil, fmt.Errorf("inconsistent encryption status for disk %s: ubuntu-data (device %s) was found unencrypted but ubuntu-save (device %s) was found to be encrypted", m.disk.Dev(), data.fsDevice, unlockRes.FsDevice)
   942  	}
   943  
   944  	// otherwise we unlocked it, so go mount it
   945  	return m.mountSave, nil
   946  }
   947  
   948  func (m *recoverModeStateMachine) mountSave() (stateFunc, error) {
   949  	save := m.degradedState.partition("ubuntu-save")
   950  	// TODO: should we fsck ubuntu-save ?
   951  	mountErr := doSystemdMount(save.fsDevice, boot.InitramfsUbuntuSaveDir, nil)
   952  	if err := m.setMountState("ubuntu-save", boot.InitramfsUbuntuSaveDir, mountErr); err != nil {
   953  		return nil, err
   954  	}
   955  	// all done, nothing left to try and mount
   956  	return nil, nil
   957  }
   958  
   959  func generateMountsModeRecover(mst *initramfsMountsState) error {
   960  	// steps 1 and 2 are shared with install mode
   961  	model, snaps, err := generateMountsCommonInstallRecover(mst)
   962  	if err != nil {
   963  		return err
   964  	}
   965  
   966  	// get the disk that we mounted the ubuntu-seed partition from as a
   967  	// reference point for future mounts
   968  	disk, err := disks.DiskFromMountPoint(boot.InitramfsUbuntuSeedDir, nil)
   969  	if err != nil {
   970  		return err
   971  	}
   972  
   973  	// 3. run the state machine logic for mounting partitions, this involves
   974  	//    trying to unlock then mount ubuntu-data, and then unlocking and
   975  	//    mounting ubuntu-save
   976  	//    see the state* functions for details of what each step does and
   977  	//    possible transition points
   978  
   979  	machine, err := func() (machine *recoverModeStateMachine, err error) {
   980  		// first state to execute is to unlock ubuntu-data with the run key
   981  		machine = newRecoverModeStateMachine(model, disk)
   982  		for {
   983  			finished, err := machine.execute()
   984  			// TODO: consider whether certain errors are fatal or not
   985  			if err != nil {
   986  				return nil, err
   987  			}
   988  			if finished {
   989  				break
   990  			}
   991  		}
   992  
   993  		return machine, nil
   994  	}()
   995  	if err != nil {
   996  		return err
   997  	}
   998  
   999  	// 3.1 write out degraded.json if we ended up falling back somewhere
  1000  	if machine.degraded() {
  1001  		b, err := json.Marshal(machine.degradedState)
  1002  		if err != nil {
  1003  			return err
  1004  		}
  1005  
  1006  		if err := os.MkdirAll(dirs.SnapBootstrapRunDir, 0755); err != nil {
  1007  			return err
  1008  		}
  1009  
  1010  		// leave the information about degraded state at an ephemeral location
  1011  		if err := ioutil.WriteFile(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), b, 0644); err != nil {
  1012  			return err
  1013  		}
  1014  	}
  1015  
  1016  	// 4. final step: copy the auth data and network config from
  1017  	//    the real ubuntu-data dir to the ephemeral ubuntu-data
  1018  	//    dir, write the modeenv to the tmpfs data, and disable
  1019  	//    cloud-init in recover mode
  1020  
  1021  	// if we have the host location, then we were able to successfully mount
  1022  	// ubuntu-data, and as such we can proceed with copying files from there
  1023  	// onto the tmpfs
  1024  	// Proceed only if we trust ubuntu-data to be paired with ubuntu-save
  1025  	if machine.trustData() {
  1026  		// TODO: erroring here should fallback to copySafeDefaultData and
  1027  		// proceed on with degraded mode anyways
  1028  		if err := copyUbuntuDataAuth(boot.InitramfsHostUbuntuDataDir, boot.InitramfsDataDir); err != nil {
  1029  			return err
  1030  		}
  1031  		if err := copyNetworkConfig(boot.InitramfsHostUbuntuDataDir, boot.InitramfsDataDir); err != nil {
  1032  			return err
  1033  		}
  1034  		if err := copyUbuntuDataMisc(boot.InitramfsHostUbuntuDataDir, boot.InitramfsDataDir); err != nil {
  1035  			return err
  1036  		}
  1037  	} else {
  1038  		// we don't have ubuntu-data host mountpoint, so we should setup safe
  1039  		// defaults for i.e. console-conf in the running image to block
  1040  		// attackers from accessing the system - just because we can't access
  1041  		// ubuntu-data doesn't mean that attackers wouldn't be able to if they
  1042  		// could login
  1043  
  1044  		if err := copySafeDefaultData(boot.InitramfsDataDir); err != nil {
  1045  			return err
  1046  		}
  1047  	}
  1048  
  1049  	modeEnv, err := mst.EphemeralModeenvForModel(model, snaps)
  1050  	if err != nil {
  1051  		return err
  1052  	}
  1053  	if err := modeEnv.WriteTo(boot.InitramfsWritableDir); err != nil {
  1054  		return err
  1055  	}
  1056  
  1057  	// finally we need to modify the bootenv to mark the system as successful,
  1058  	// this ensures that when you reboot from recover mode without doing
  1059  	// anything else, you are auto-transitioned back to run mode
  1060  	// TODO:UC20: as discussed unclear we need to pass the recovery system here
  1061  	if err := boot.EnsureNextBootToRunMode(mst.recoverySystem); err != nil {
  1062  		return err
  1063  	}
  1064  
  1065  	// done, no output, no error indicates to initramfs we are done with
  1066  	// mounting stuff
  1067  	return nil
  1068  }
  1069  
  1070  // checkDataAndSavePairing make sure that ubuntu-data and ubuntu-save
  1071  // come from the same install by comparing secret markers in them
  1072  func checkDataAndSavePairing(rootdir string) (bool, error) {
  1073  	// read the secret marker file from ubuntu-data
  1074  	markerFile1 := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "marker")
  1075  	marker1, err := ioutil.ReadFile(markerFile1)
  1076  	if err != nil {
  1077  		return false, err
  1078  	}
  1079  	// read the secret marker file from ubuntu-save
  1080  	markerFile2 := filepath.Join(dirs.SnapFDEDirUnderSave(boot.InitramfsUbuntuSaveDir), "marker")
  1081  	marker2, err := ioutil.ReadFile(markerFile2)
  1082  	if err != nil {
  1083  		return false, err
  1084  	}
  1085  	return subtle.ConstantTimeCompare(marker1, marker2) == 1, nil
  1086  }
  1087  
  1088  // mountPartitionMatchingKernelDisk will select the partition to mount at dir,
  1089  // using the boot package function FindPartitionUUIDForBootedKernelDisk to
  1090  // determine what partition the booted kernel came from. If which disk the
  1091  // kernel came from cannot be determined, then it will fallback to mounting via
  1092  // the specified disk label.
  1093  func mountPartitionMatchingKernelDisk(dir, fallbacklabel string) error {
  1094  	partuuid, err := bootFindPartitionUUIDForBootedKernelDisk()
  1095  	// TODO: the by-partuuid is only available on gpt disks, on mbr we need
  1096  	//       to use by-uuid or by-id
  1097  	partSrc := filepath.Join("/dev/disk/by-partuuid", partuuid)
  1098  	if err != nil {
  1099  		// no luck, try mounting by label instead
  1100  		partSrc = filepath.Join("/dev/disk/by-label", fallbacklabel)
  1101  	}
  1102  
  1103  	opts := &systemdMountOptions{
  1104  		// always fsck the partition when we are mounting it, as this is the
  1105  		// first partition we will be mounting, we can't know if anything is
  1106  		// corrupted yet
  1107  		NeedsFsck: true,
  1108  	}
  1109  	return doSystemdMount(partSrc, dir, opts)
  1110  }
  1111  
  1112  func generateMountsCommonInstallRecover(mst *initramfsMountsState) (model *asserts.Model, sysSnaps map[snap.Type]snap.PlaceInfo, err error) {
  1113  	// 1. always ensure seed partition is mounted first before the others,
  1114  	//      since the seed partition is needed to mount the snap files there
  1115  	if err := mountPartitionMatchingKernelDisk(boot.InitramfsUbuntuSeedDir, "ubuntu-seed"); err != nil {
  1116  		return nil, nil, err
  1117  	}
  1118  
  1119  	// load model and verified essential snaps metadata
  1120  	typs := []snap.Type{snap.TypeBase, snap.TypeKernel, snap.TypeSnapd, snap.TypeGadget}
  1121  	model, essSnaps, err := mst.ReadEssential("", typs)
  1122  	if err != nil {
  1123  		return nil, nil, fmt.Errorf("cannot load metadata and verify essential bootstrap snaps %v: %v", typs, err)
  1124  	}
  1125  
  1126  	// 2.1. measure model
  1127  	err = stampedAction(fmt.Sprintf("%s-model-measured", mst.recoverySystem), func() error {
  1128  		return secbootMeasureSnapModelWhenPossible(func() (*asserts.Model, error) {
  1129  			return model, nil
  1130  		})
  1131  	})
  1132  	if err != nil {
  1133  		return nil, nil, err
  1134  	}
  1135  	// at this point on a system with TPM-based encryption
  1136  	// data can be open only if the measured model matches the actual
  1137  	// expected recovery model we sealed against.
  1138  	// TODO:UC20: on ARM systems and no TPM with encryption
  1139  	// we need other ways to make sure that the disk is opened
  1140  	// and we continue booting only for expected recovery models
  1141  
  1142  	// 2.2. (auto) select recovery system and mount seed snaps
  1143  	// TODO:UC20: do we need more cross checks here?
  1144  
  1145  	systemSnaps := make(map[snap.Type]snap.PlaceInfo)
  1146  
  1147  	for _, essentialSnap := range essSnaps {
  1148  		if essentialSnap.EssentialType == snap.TypeGadget {
  1149  			// don't need to mount the gadget anywhere, but we use the snap
  1150  			// later hence it is loaded
  1151  			continue
  1152  		}
  1153  		systemSnaps[essentialSnap.EssentialType] = essentialSnap.PlaceInfo()
  1154  
  1155  		dir := snapTypeToMountDir[essentialSnap.EssentialType]
  1156  		// TODO:UC20: we need to cross-check the kernel path with snapd_recovery_kernel used by grub
  1157  		if err := doSystemdMount(essentialSnap.Path, filepath.Join(boot.InitramfsRunMntDir, dir), nil); err != nil {
  1158  			return nil, nil, err
  1159  		}
  1160  	}
  1161  
  1162  	// TODO:UC20: after we have the kernel and base snaps mounted, we should do
  1163  	//            the bind mounts from the kernel modules on top of the base
  1164  	//            mount and delete the corresponding systemd units from the
  1165  	//            initramfs layout
  1166  
  1167  	// TODO:UC20: after the kernel and base snaps are mounted, we should setup
  1168  	//            writable here as well to take over from "the-modeenv" script
  1169  	//            in the initrd too
  1170  
  1171  	// TODO:UC20: after the kernel and base snaps are mounted and writable is
  1172  	//            mounted, we should also implement writable-paths here too as
  1173  	//            writing it in Go instead of shellscript is desirable
  1174  
  1175  	// 2.3. mount "ubuntu-data" on a tmpfs
  1176  	mntOpts := &systemdMountOptions{
  1177  		Tmpfs: true,
  1178  	}
  1179  	err = doSystemdMount("tmpfs", boot.InitramfsDataDir, mntOpts)
  1180  	if err != nil {
  1181  		return nil, nil, err
  1182  	}
  1183  
  1184  	// finally get the gadget snap from the essential snaps and use it to
  1185  	// configure the ephemeral system
  1186  	// should only be one seed snap
  1187  	gadgetPath := ""
  1188  	for _, essentialSnap := range essSnaps {
  1189  		if essentialSnap.EssentialType == snap.TypeGadget {
  1190  			gadgetPath = essentialSnap.Path
  1191  		}
  1192  	}
  1193  	gadgetSnap := squashfs.New(gadgetPath)
  1194  
  1195  	// we need to configure the ephemeral system with defaults and such using
  1196  	// from the seed gadget
  1197  	configOpts := &sysconfig.Options{
  1198  		// never allow cloud-init to run inside the ephemeral system, in the
  1199  		// install case we don't want it to ever run, and in the recover case
  1200  		// cloud-init will already have run in run mode, so things like network
  1201  		// config and users should already be setup and we will copy those
  1202  		// further down in the setup for recover mode
  1203  		AllowCloudInit: false,
  1204  		TargetRootDir:  boot.InitramfsWritableDir,
  1205  		GadgetSnap:     gadgetSnap,
  1206  	}
  1207  	if err := sysconfig.ConfigureTargetSystem(configOpts); err != nil {
  1208  		return nil, nil, err
  1209  	}
  1210  
  1211  	return model, systemSnaps, err
  1212  }
  1213  
  1214  func maybeMountSave(disk disks.Disk, rootdir string, encrypted bool, mountOpts *systemdMountOptions) (haveSave bool, err error) {
  1215  	var saveDevice string
  1216  	if encrypted {
  1217  		saveKey := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "ubuntu-save.key")
  1218  		// if ubuntu-save exists and is encrypted, the key has been created during install
  1219  		if !osutil.FileExists(saveKey) {
  1220  			// ubuntu-data is encrypted, but we appear to be missing
  1221  			// a key to open ubuntu-save
  1222  			return false, fmt.Errorf("cannot find ubuntu-save encryption key at %v", saveKey)
  1223  		}
  1224  		// we have save.key, volume exists and is encrypted
  1225  		key, err := ioutil.ReadFile(saveKey)
  1226  		if err != nil {
  1227  			return true, err
  1228  		}
  1229  		unlockRes, err := secbootUnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", key)
  1230  		if err != nil {
  1231  			return true, fmt.Errorf("cannot unlock ubuntu-save volume: %v", err)
  1232  		}
  1233  		saveDevice = unlockRes.FsDevice
  1234  	} else {
  1235  		partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel("ubuntu-save")
  1236  		if err != nil {
  1237  			if _, ok := err.(disks.PartitionNotFoundError); ok {
  1238  				// this is ok, ubuntu-save may not exist for
  1239  				// non-encrypted device
  1240  				return false, nil
  1241  			}
  1242  			return false, err
  1243  		}
  1244  		saveDevice = filepath.Join("/dev/disk/by-partuuid", partUUID)
  1245  	}
  1246  	if err := doSystemdMount(saveDevice, boot.InitramfsUbuntuSaveDir, mountOpts); err != nil {
  1247  		return true, err
  1248  	}
  1249  	return true, nil
  1250  }
  1251  
  1252  func generateMountsModeRun(mst *initramfsMountsState) error {
  1253  	// 1. mount ubuntu-boot
  1254  	if err := mountPartitionMatchingKernelDisk(boot.InitramfsUbuntuBootDir, "ubuntu-boot"); err != nil {
  1255  		return err
  1256  	}
  1257  
  1258  	// get the disk that we mounted the ubuntu-boot partition from as a
  1259  	// reference point for future mounts
  1260  	disk, err := disks.DiskFromMountPoint(boot.InitramfsUbuntuBootDir, nil)
  1261  	if err != nil {
  1262  		return err
  1263  	}
  1264  
  1265  	// 2. mount ubuntu-seed
  1266  	// use the disk we mounted ubuntu-boot from as a reference to find
  1267  	// ubuntu-seed and mount it
  1268  	partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel("ubuntu-seed")
  1269  	if err != nil {
  1270  		return err
  1271  	}
  1272  
  1273  	// fsck is safe to run on ubuntu-seed as per the manpage, it should not
  1274  	// meaningfully contribute to corruption if we fsck it every time we boot,
  1275  	// and it is important to fsck it because it is vfat and mounted writable
  1276  	// TODO:UC20: mount it as read-only here and remount as writable when we
  1277  	//            need it to be writable for i.e. transitioning to recover mode
  1278  	fsckSystemdOpts := &systemdMountOptions{
  1279  		NeedsFsck: true,
  1280  	}
  1281  	if err := doSystemdMount(fmt.Sprintf("/dev/disk/by-partuuid/%s", partUUID), boot.InitramfsUbuntuSeedDir, fsckSystemdOpts); err != nil {
  1282  		return err
  1283  	}
  1284  
  1285  	// 3.1. measure model
  1286  	err = stampedAction("run-model-measured", func() error {
  1287  		return secbootMeasureSnapModelWhenPossible(mst.UnverifiedBootModel)
  1288  	})
  1289  	if err != nil {
  1290  		return err
  1291  	}
  1292  	// at this point on a system with TPM-based encryption
  1293  	// data can be open only if the measured model matches the actual
  1294  	// run model.
  1295  	// TODO:UC20: on ARM systems and no TPM with encryption
  1296  	// we need other ways to make sure that the disk is opened
  1297  	// and we continue booting only for expected models
  1298  
  1299  	// 3.2. mount Data
  1300  	runModeKey := filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key")
  1301  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
  1302  		AllowRecoveryKey: true,
  1303  	}
  1304  	unlockRes, err := secbootUnlockVolumeUsingSealedKeyIfEncrypted(disk, "ubuntu-data", runModeKey, opts)
  1305  	if err != nil {
  1306  		return err
  1307  	}
  1308  
  1309  	// TODO: do we actually need fsck if we are mounting a mapper device?
  1310  	// probably not?
  1311  	if err := doSystemdMount(unlockRes.FsDevice, boot.InitramfsDataDir, fsckSystemdOpts); err != nil {
  1312  		return err
  1313  	}
  1314  	isEncryptedDev := unlockRes.IsEncrypted
  1315  
  1316  	// 3.3. mount ubuntu-save (if present)
  1317  	haveSave, err := maybeMountSave(disk, boot.InitramfsWritableDir, isEncryptedDev, fsckSystemdOpts)
  1318  	if err != nil {
  1319  		return err
  1320  	}
  1321  
  1322  	// 4.1 verify that ubuntu-data comes from where we expect it to
  1323  	diskOpts := &disks.Options{}
  1324  	if unlockRes.IsEncrypted {
  1325  		// then we need to specify that the data mountpoint is expected to be a
  1326  		// decrypted device, applies to both ubuntu-data and ubuntu-save
  1327  		diskOpts.IsDecryptedDevice = true
  1328  	}
  1329  
  1330  	matches, err := disk.MountPointIsFromDisk(boot.InitramfsDataDir, diskOpts)
  1331  	if err != nil {
  1332  		return err
  1333  	}
  1334  	if !matches {
  1335  		// failed to verify that ubuntu-data mountpoint comes from the same disk
  1336  		// as ubuntu-boot
  1337  		return fmt.Errorf("cannot validate boot: ubuntu-data mountpoint is expected to be from disk %s but is not", disk.Dev())
  1338  	}
  1339  	if haveSave {
  1340  		// 4.1a we have ubuntu-save, verify it as well
  1341  		matches, err = disk.MountPointIsFromDisk(boot.InitramfsUbuntuSaveDir, diskOpts)
  1342  		if err != nil {
  1343  			return err
  1344  		}
  1345  		if !matches {
  1346  			return fmt.Errorf("cannot validate boot: ubuntu-save mountpoint is expected to be from disk %s but is not", disk.Dev())
  1347  		}
  1348  
  1349  		if isEncryptedDev {
  1350  			// in run mode the path to open an encrypted save is for
  1351  			// data to be encrypted and the save key in it
  1352  			// to be successfully used. This already should stop
  1353  			// allowing to chose ubuntu-data to try to access
  1354  			// save. as safety boot also stops if the keys cannot
  1355  			// be locked.
  1356  			// for symmetry with recover code and extra paranoia
  1357  			// though also check that the markers match.
  1358  			paired, err := checkDataAndSavePairing(boot.InitramfsWritableDir)
  1359  			if err != nil {
  1360  				return err
  1361  			}
  1362  			if !paired {
  1363  				return fmt.Errorf("cannot validate boot: ubuntu-save and ubuntu-data are not marked as from the same install")
  1364  			}
  1365  		}
  1366  	}
  1367  
  1368  	// 4.2. read modeenv
  1369  	modeEnv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
  1370  	if err != nil {
  1371  		return err
  1372  	}
  1373  
  1374  	typs := []snap.Type{snap.TypeBase, snap.TypeKernel}
  1375  
  1376  	// 4.2 choose base and kernel snaps (this includes updating modeenv if
  1377  	//     needed to try the base snap)
  1378  	mounts, err := boot.InitramfsRunModeSelectSnapsToMount(typs, modeEnv)
  1379  	if err != nil {
  1380  		return err
  1381  	}
  1382  
  1383  	// TODO:UC20: with grade > dangerous, verify the kernel snap hash against
  1384  	//            what we booted using the tpm log, this may need to be passed
  1385  	//            to the function above to make decisions there, or perhaps this
  1386  	//            code actually belongs in the bootloader implementation itself
  1387  
  1388  	// 4.3 mount base and kernel snaps
  1389  	// make sure this is a deterministic order
  1390  	for _, typ := range []snap.Type{snap.TypeBase, snap.TypeKernel} {
  1391  		if sn, ok := mounts[typ]; ok {
  1392  			dir := snapTypeToMountDir[typ]
  1393  			snapPath := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), sn.Filename())
  1394  			if err := doSystemdMount(snapPath, filepath.Join(boot.InitramfsRunMntDir, dir), nil); err != nil {
  1395  				return err
  1396  			}
  1397  		}
  1398  	}
  1399  
  1400  	// 4.4 mount snapd snap only on first boot
  1401  	if modeEnv.RecoverySystem != "" {
  1402  		// load the recovery system and generate mount for snapd
  1403  		_, essSnaps, err := mst.ReadEssential(modeEnv.RecoverySystem, []snap.Type{snap.TypeSnapd})
  1404  		if err != nil {
  1405  			return fmt.Errorf("cannot load metadata and verify snapd snap: %v", err)
  1406  		}
  1407  
  1408  		return doSystemdMount(essSnaps[0].Path, filepath.Join(boot.InitramfsRunMntDir, "snapd"), nil)
  1409  	}
  1410  
  1411  	return nil
  1412  }