github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/assets.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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 boot
    21  
    22  import (
    23  	"crypto"
    24  	"encoding/hex"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"os"
    29  	"path/filepath"
    30  
    31  	_ "golang.org/x/crypto/sha3"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/bootloader"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/gadget"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/secboot"
    40  	"github.com/snapcore/snapd/strutil"
    41  )
    42  
    43  type trustedAssetsCache struct {
    44  	cacheDir string
    45  	hash     crypto.Hash
    46  }
    47  
    48  func newTrustedAssetsCache(cacheDir string) *trustedAssetsCache {
    49  	return &trustedAssetsCache{cacheDir: cacheDir, hash: crypto.SHA3_384}
    50  }
    51  
    52  func (c *trustedAssetsCache) tempAssetRelPath(blName, assetName string) string {
    53  	return filepath.Join(blName, assetName+".temp")
    54  }
    55  
    56  func (c *trustedAssetsCache) pathInCache(part string) string {
    57  	return filepath.Join(c.cacheDir, part)
    58  }
    59  
    60  func trustedAssetCacheRelPath(blName, assetName, assetHash string) string {
    61  	return filepath.Join(blName, fmt.Sprintf("%s-%s", assetName, assetHash))
    62  }
    63  
    64  // fileHash calculates the hash of an arbitrary file using the same hash method
    65  // as the cache.
    66  func (c *trustedAssetsCache) fileHash(name string) (string, error) {
    67  	digest, _, err := osutil.FileDigest(name, c.hash)
    68  	if err != nil {
    69  		return "", err
    70  	}
    71  	return hex.EncodeToString(digest), nil
    72  }
    73  
    74  // Add entry for a new named asset owned by a particular bootloader, with the
    75  // binary content of the located at a given path. The cache ensures that only
    76  // one entry for given tuple of (bootloader name, asset name, content-hash)
    77  // exists in the cache.
    78  func (c *trustedAssetsCache) Add(assetPath, blName, assetName string) (*trackedAsset, error) {
    79  	if err := os.MkdirAll(c.pathInCache(blName), 0755); err != nil {
    80  		return nil, fmt.Errorf("cannot create cache directory: %v", err)
    81  	}
    82  
    83  	// input
    84  	inf, err := os.Open(assetPath)
    85  	if err != nil {
    86  		return nil, fmt.Errorf("cannot open asset file: %v", err)
    87  	}
    88  	defer inf.Close()
    89  	// temporary output
    90  	tempPath := c.pathInCache(c.tempAssetRelPath(blName, assetName))
    91  	outf, err := osutil.NewAtomicFile(tempPath, 0644, 0, osutil.NoChown, osutil.NoChown)
    92  	if err != nil {
    93  		return nil, fmt.Errorf("cannot create temporary cache file: %v", err)
    94  	}
    95  	defer outf.Cancel()
    96  
    97  	// copy and hash at the same time
    98  	h := c.hash.New()
    99  	tr := io.TeeReader(inf, h)
   100  	if _, err := io.Copy(outf, tr); err != nil {
   101  		return nil, fmt.Errorf("cannot copy trusted asset to cache: %v", err)
   102  	}
   103  	hashStr := hex.EncodeToString(h.Sum(nil))
   104  	cacheKey := trustedAssetCacheRelPath(blName, assetName, hashStr)
   105  
   106  	ta := &trackedAsset{
   107  		blName: blName,
   108  		name:   assetName,
   109  		hash:   hashStr,
   110  	}
   111  
   112  	targetName := c.pathInCache(cacheKey)
   113  	if osutil.FileExists(targetName) {
   114  		// asset is already cached
   115  		return ta, nil
   116  	}
   117  	// commit under a new name
   118  	if err := outf.CommitAs(targetName); err != nil {
   119  		return nil, fmt.Errorf("cannot commit file to assets cache: %v", err)
   120  	}
   121  	return ta, nil
   122  }
   123  
   124  func (c *trustedAssetsCache) Remove(blName, assetName, hashStr string) error {
   125  	cacheKey := trustedAssetCacheRelPath(blName, assetName, hashStr)
   126  	if err := os.Remove(c.pathInCache(cacheKey)); err != nil && !os.IsNotExist(err) {
   127  		return err
   128  	}
   129  	return nil
   130  }
   131  
   132  // CopyBootAssetsCacheToRoot copies the boot assets cache to a corresponding
   133  // location under a new root directory.
   134  func CopyBootAssetsCacheToRoot(dstRoot string) error {
   135  	if !osutil.IsDirectory(dirs.SnapBootAssetsDir) {
   136  		// nothing to copy
   137  		return nil
   138  	}
   139  
   140  	newCacheRoot := dirs.SnapBootAssetsDirUnder(dstRoot)
   141  	if err := os.MkdirAll(newCacheRoot, 0755); err != nil {
   142  		return fmt.Errorf("cannot create cache directory under new root: %v", err)
   143  	}
   144  	err := filepath.Walk(dirs.SnapBootAssetsDir, func(path string, info os.FileInfo, err error) error {
   145  		if err != nil {
   146  			return err
   147  		}
   148  		relPath, err := filepath.Rel(dirs.SnapBootAssetsDir, path)
   149  		if err != nil {
   150  			return err
   151  		}
   152  		if info.IsDir() {
   153  			if err := os.MkdirAll(filepath.Join(newCacheRoot, relPath), info.Mode()); err != nil {
   154  				return fmt.Errorf("cannot recreate cache directory %q: %v", relPath, err)
   155  			}
   156  			return nil
   157  		}
   158  		if !info.Mode().IsRegular() {
   159  			return fmt.Errorf("unsupported non-file entry %q mode %v", relPath, info.Mode())
   160  		}
   161  		if err := osutil.CopyFile(path, filepath.Join(newCacheRoot, relPath), osutil.CopyFlagPreserveAll); err != nil {
   162  			return fmt.Errorf("cannot copy boot asset cache file %q: %v", relPath, err)
   163  		}
   164  		return nil
   165  	})
   166  	return err
   167  }
   168  
   169  // ErrObserverNotApplicable indicates that observer is not applicable for use
   170  // with the model.
   171  var ErrObserverNotApplicable = errors.New("observer not applicable")
   172  
   173  // TrustedAssetsInstallObserverForModel returns a new trusted assets observer
   174  // for use during installation of the run mode system to track trusted and
   175  // control managed assets, provided the device model indicates this might be
   176  // needed. Otherwise, nil and ErrObserverNotApplicable is returned.
   177  func TrustedAssetsInstallObserverForModel(model *asserts.Model, gadgetDir string, useEncryption bool) (*TrustedAssetsInstallObserver, error) {
   178  	if model.Grade() == asserts.ModelGradeUnset {
   179  		// no need to observe updates when assets are not managed
   180  		return nil, ErrObserverNotApplicable
   181  	}
   182  	if gadgetDir == "" {
   183  		return nil, fmt.Errorf("internal error: gadget dir not provided")
   184  	}
   185  	// TODO:UC20: clarify use of empty rootdir when getting the lists of
   186  	// managed and trusted assets
   187  	runBl, runTrusted, runManaged, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, "",
   188  		&bootloader.Options{
   189  			Role:        bootloader.RoleRunMode,
   190  			NoSlashBoot: true,
   191  		})
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	// and the recovery bootloader, seed is mounted during install
   196  	seedBl, seedTrusted, _, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, InitramfsUbuntuSeedDir,
   197  		&bootloader.Options{
   198  			Role: bootloader.RoleRecovery,
   199  		})
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	if !useEncryption {
   204  		// we do not care about trusted assets when not encrypting data
   205  		// partition
   206  		runTrusted = nil
   207  		seedTrusted = nil
   208  	}
   209  	hasManaged := len(runManaged) > 0
   210  	hasTrusted := len(runTrusted) > 0 || len(seedTrusted) > 0
   211  	if !hasManaged && !hasTrusted && !useEncryption {
   212  		// no managed assets, and no trusted assets or we are not
   213  		// tracking them due to no encryption to data partition
   214  		return nil, ErrObserverNotApplicable
   215  	}
   216  
   217  	return &TrustedAssetsInstallObserver{
   218  		model:     model,
   219  		cache:     newTrustedAssetsCache(dirs.SnapBootAssetsDir),
   220  		gadgetDir: gadgetDir,
   221  
   222  		blName:        runBl.Name(),
   223  		managedAssets: runManaged,
   224  		trustedAssets: runTrusted,
   225  
   226  		recoveryBlName:        seedBl.Name(),
   227  		trustedRecoveryAssets: seedTrusted,
   228  	}, nil
   229  }
   230  
   231  type trackedAsset struct {
   232  	blName, name, hash string
   233  }
   234  
   235  func isAssetAlreadyTracked(bam bootAssetsMap, newAsset *trackedAsset) bool {
   236  	return isAssetHashTrackedInMap(bam, newAsset.name, newAsset.hash)
   237  }
   238  
   239  func isAssetHashTrackedInMap(bam bootAssetsMap, assetName, assetHash string) bool {
   240  	if bam == nil {
   241  		return false
   242  	}
   243  	hashes, ok := bam[assetName]
   244  	if !ok {
   245  		return false
   246  	}
   247  	return strutil.ListContains(hashes, assetHash)
   248  }
   249  
   250  // TrustedAssetsInstallObserver tracks the installation of trusted or managed
   251  // boot assets.
   252  type TrustedAssetsInstallObserver struct {
   253  	model     *asserts.Model
   254  	gadgetDir string
   255  	cache     *trustedAssetsCache
   256  
   257  	blName        string
   258  	managedAssets []string
   259  	trustedAssets []string
   260  	trackedAssets bootAssetsMap
   261  
   262  	recoveryBlName        string
   263  	trustedRecoveryAssets []string
   264  	trackedRecoveryAssets bootAssetsMap
   265  
   266  	dataEncryptionKey secboot.EncryptionKey
   267  	saveEncryptionKey secboot.EncryptionKey
   268  }
   269  
   270  // Observe observes the operation related to the content of a given gadget
   271  // structure. In particular, the TrustedAssetsInstallObserver tracks writing of
   272  // trusted or managed boot assets, such as the bootloader binary which is
   273  // measured as part of the secure boot or the bootloader configuration.
   274  //
   275  // Implements gadget.ContentObserver.
   276  func (o *TrustedAssetsInstallObserver) Observe(op gadget.ContentOperation, affectedStruct *gadget.LaidOutStructure, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) {
   277  	if affectedStruct.Role != gadget.SystemBoot {
   278  		// only care about system-boot
   279  		return gadget.ChangeApply, nil
   280  	}
   281  
   282  	if len(o.managedAssets) != 0 && strutil.ListContains(o.managedAssets, relativeTarget) {
   283  		// this asset is managed by bootloader installation
   284  		return gadget.ChangeIgnore, nil
   285  	}
   286  	if len(o.trustedAssets) == 0 || !strutil.ListContains(o.trustedAssets, relativeTarget) {
   287  		// not one of the trusted assets
   288  		return gadget.ChangeApply, nil
   289  	}
   290  	ta, err := o.cache.Add(data.After, o.blName, filepath.Base(relativeTarget))
   291  	if err != nil {
   292  		return gadget.ChangeAbort, err
   293  	}
   294  	// during installation, modeenv is written out later, at this point we
   295  	// only care that the same file may appear multiple times in gadget
   296  	// structure content, so make sure we are not tracking it yet
   297  	if !isAssetAlreadyTracked(o.trackedAssets, ta) {
   298  		if o.trackedAssets == nil {
   299  			o.trackedAssets = bootAssetsMap{}
   300  		}
   301  		if len(o.trackedAssets[ta.name]) > 0 {
   302  			return gadget.ChangeAbort, fmt.Errorf("cannot reuse asset name %q", ta.name)
   303  		}
   304  		o.trackedAssets[ta.name] = append(o.trackedAssets[ta.name], ta.hash)
   305  	}
   306  	return gadget.ChangeApply, nil
   307  }
   308  
   309  // ObserveExistingTrustedRecoveryAssets observes existing trusted assets of a
   310  // recovery bootloader located inside a given root directory.
   311  func (o *TrustedAssetsInstallObserver) ObserveExistingTrustedRecoveryAssets(recoveryRootDir string) error {
   312  	if len(o.trustedRecoveryAssets) == 0 {
   313  		// not a trusted assets bootloader or has no trusted assets
   314  		return nil
   315  	}
   316  	for _, trustedAsset := range o.trustedRecoveryAssets {
   317  		ta, err := o.cache.Add(filepath.Join(recoveryRootDir, trustedAsset), o.recoveryBlName, filepath.Base(trustedAsset))
   318  		if err != nil {
   319  			return err
   320  		}
   321  		if !isAssetAlreadyTracked(o.trackedRecoveryAssets, ta) {
   322  			if o.trackedRecoveryAssets == nil {
   323  				o.trackedRecoveryAssets = bootAssetsMap{}
   324  			}
   325  			if len(o.trackedRecoveryAssets[ta.name]) > 0 {
   326  				return fmt.Errorf("cannot reuse recovery asset name %q", ta.name)
   327  			}
   328  			o.trackedRecoveryAssets[ta.name] = append(o.trackedRecoveryAssets[ta.name], ta.hash)
   329  		}
   330  	}
   331  	return nil
   332  }
   333  
   334  func (o *TrustedAssetsInstallObserver) currentTrustedBootAssetsMap() bootAssetsMap {
   335  	return o.trackedAssets
   336  }
   337  
   338  func (o *TrustedAssetsInstallObserver) currentTrustedRecoveryBootAssetsMap() bootAssetsMap {
   339  	return o.trackedRecoveryAssets
   340  }
   341  
   342  func (o *TrustedAssetsInstallObserver) ChosenEncryptionKeys(key, saveKey secboot.EncryptionKey) {
   343  	o.dataEncryptionKey = key
   344  	o.saveEncryptionKey = saveKey
   345  }
   346  
   347  // TrustedAssetsUpdateObserverForModel returns a new trusted assets observer for
   348  // tracking changes to the trusted boot assets and preserving managed assets,
   349  // provided the device model indicates this might be needed. Otherwise, nil and
   350  // ErrObserverNotApplicable is returned.
   351  func TrustedAssetsUpdateObserverForModel(model *asserts.Model, gadgetDir string) (*TrustedAssetsUpdateObserver, error) {
   352  	if model.Grade() == asserts.ModelGradeUnset {
   353  		// no need to observe updates when assets are not managed
   354  		return nil, ErrObserverNotApplicable
   355  	}
   356  	// trusted assets need tracking only when the system is using encryption
   357  	// for its data partitions
   358  	trackTrustedAssets := false
   359  	_, err := sealedKeysMethod(dirs.GlobalRootDir)
   360  	switch {
   361  	case err == nil:
   362  		trackTrustedAssets = true
   363  	case err == errNoSealedKeys:
   364  		// nothing to do
   365  	case err != nil:
   366  		// all other errors
   367  		return nil, err
   368  	}
   369  
   370  	// see what we need to observe for the run bootloader
   371  	runBl, runTrusted, runManaged, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, InitramfsUbuntuBootDir,
   372  		&bootloader.Options{
   373  			Role:        bootloader.RoleRunMode,
   374  			NoSlashBoot: true,
   375  		})
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	// and the recovery bootloader
   381  	seedBl, seedTrusted, seedManaged, err := gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, InitramfsUbuntuSeedDir,
   382  		&bootloader.Options{
   383  			Role: bootloader.RoleRecovery,
   384  		})
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	hasManaged := len(runManaged) > 0 || len(seedManaged) > 0
   390  	hasTrusted := len(runTrusted) > 0 || len(seedTrusted) > 0
   391  	if !hasManaged {
   392  		// no managed assets
   393  		if !hasTrusted || !trackTrustedAssets {
   394  			// no trusted assets or we are not tracking them either
   395  			return nil, ErrObserverNotApplicable
   396  		}
   397  	}
   398  
   399  	obs := &TrustedAssetsUpdateObserver{
   400  		cache: newTrustedAssetsCache(dirs.SnapBootAssetsDir),
   401  		model: model,
   402  
   403  		bootBootloader:    runBl,
   404  		bootManagedAssets: runManaged,
   405  
   406  		seedBootloader:    seedBl,
   407  		seedManagedAssets: seedManaged,
   408  	}
   409  	if trackTrustedAssets {
   410  		obs.seedTrustedAssets = seedTrusted
   411  		obs.bootTrustedAssets = runTrusted
   412  	}
   413  	return obs, nil
   414  }
   415  
   416  // TrustedAssetsUpdateObserver tracks the updates of trusted boot assets and
   417  // attempts to reseal when needed or preserves managed boot assets.
   418  type TrustedAssetsUpdateObserver struct {
   419  	cache *trustedAssetsCache
   420  	model *asserts.Model
   421  
   422  	bootBootloader    bootloader.Bootloader
   423  	bootTrustedAssets []string
   424  	bootManagedAssets []string
   425  	changedAssets     []*trackedAsset
   426  
   427  	seedBootloader    bootloader.Bootloader
   428  	seedTrustedAssets []string
   429  	seedManagedAssets []string
   430  	seedChangedAssets []*trackedAsset
   431  
   432  	modeenv *Modeenv
   433  }
   434  
   435  func trustedAndManagedAssetsOfBootloader(bl bootloader.Bootloader) (trustedAssets, managedAssets []string, err error) {
   436  	tbl, ok := bl.(bootloader.TrustedAssetsBootloader)
   437  	if ok {
   438  		trustedAssets, err = tbl.TrustedAssets()
   439  		if err != nil {
   440  			return nil, nil, fmt.Errorf("cannot list %q bootloader trusted assets: %v", bl.Name(), err)
   441  		}
   442  		managedAssets = tbl.ManagedAssets()
   443  	}
   444  	return trustedAssets, managedAssets, nil
   445  }
   446  
   447  func findMaybeTrustedBootloaderAndAssets(rootDir string, opts *bootloader.Options) (foundBl bootloader.Bootloader, trustedAssets []string, err error) {
   448  	foundBl, err = bootloader.Find(rootDir, opts)
   449  	if err != nil {
   450  		return nil, nil, fmt.Errorf("cannot find bootloader: %v", err)
   451  	}
   452  	trustedAssets, _, err = trustedAndManagedAssetsOfBootloader(foundBl)
   453  	return foundBl, trustedAssets, err
   454  }
   455  
   456  func gadgetMaybeTrustedBootloaderAndAssets(gadgetDir, rootDir string, opts *bootloader.Options) (foundBl bootloader.Bootloader, trustedAssets, managedAssets []string, err error) {
   457  	foundBl, err = bootloader.ForGadget(gadgetDir, rootDir, opts)
   458  	if err != nil {
   459  		return nil, nil, nil, fmt.Errorf("cannot find bootloader: %v", err)
   460  	}
   461  	trustedAssets, managedAssets, err = trustedAndManagedAssetsOfBootloader(foundBl)
   462  	return foundBl, trustedAssets, managedAssets, err
   463  }
   464  
   465  // Observe observes the operation related to the update or rollback of the
   466  // content of a given gadget structure. In particular, the
   467  // TrustedAssetsUpdateObserver tracks updates of trusted boot assets such as
   468  // bootloader binaries, or preserves managed assets such as boot configuration.
   469  //
   470  // Implements gadget.ContentUpdateObserver.
   471  func (o *TrustedAssetsUpdateObserver) Observe(op gadget.ContentOperation, affectedStruct *gadget.LaidOutStructure, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) {
   472  	var whichBootloader bootloader.Bootloader
   473  	var whichTrustedAssets []string
   474  	var whichManagedAssets []string
   475  	var err error
   476  	var isRecovery bool
   477  
   478  	switch affectedStruct.Role {
   479  	case gadget.SystemBoot:
   480  		whichBootloader = o.bootBootloader
   481  		whichTrustedAssets = o.bootTrustedAssets
   482  		whichManagedAssets = o.bootManagedAssets
   483  	case gadget.SystemSeed:
   484  		whichBootloader = o.seedBootloader
   485  		whichTrustedAssets = o.seedTrustedAssets
   486  		whichManagedAssets = o.seedManagedAssets
   487  		isRecovery = true
   488  	default:
   489  		// only system-seed and system-boot are of interest
   490  		return gadget.ChangeApply, nil
   491  	}
   492  	// maybe an asset that we manage?
   493  	if len(whichManagedAssets) != 0 && strutil.ListContains(whichManagedAssets, relativeTarget) {
   494  		// this asset is managed directly by the bootloader, preserve it
   495  		if op != gadget.ContentUpdate {
   496  			return gadget.ChangeAbort, fmt.Errorf("internal error: managed bootloader asset change for non update operation %v", op)
   497  		}
   498  		return gadget.ChangeIgnore, nil
   499  	}
   500  
   501  	if len(whichTrustedAssets) == 0 {
   502  		// the system is not using encryption for data partitions, so
   503  		// we're done at this point
   504  		return gadget.ChangeApply, nil
   505  	}
   506  
   507  	// maybe an asset that is trusted in the boot process?
   508  	if !strutil.ListContains(whichTrustedAssets, relativeTarget) {
   509  		// not one of the trusted assets
   510  		return gadget.ChangeApply, nil
   511  	}
   512  	if o.modeenv == nil {
   513  		// we've hit a trusted asset, so a modeenv is needed now too
   514  		o.modeenv, err = ReadModeenv("")
   515  		if err != nil {
   516  			return gadget.ChangeAbort, fmt.Errorf("cannot load modeenv: %v", err)
   517  		}
   518  	}
   519  	switch op {
   520  	case gadget.ContentUpdate:
   521  		return o.observeUpdate(whichBootloader, isRecovery, root, relativeTarget, data)
   522  	case gadget.ContentRollback:
   523  		return o.observeRollback(whichBootloader, isRecovery, root, relativeTarget, data)
   524  	default:
   525  		// we only care about update and rollback actions
   526  		return gadget.ChangeApply, nil
   527  	}
   528  }
   529  
   530  func (o *TrustedAssetsUpdateObserver) observeUpdate(bl bootloader.Bootloader, recovery bool, root, relativeTarget string, change *gadget.ContentChange) (gadget.ContentChangeAction, error) {
   531  	modeenvBefore, err := o.modeenv.Copy()
   532  	if err != nil {
   533  		return gadget.ChangeAbort, fmt.Errorf("cannot copy modeenv: %v", err)
   534  	}
   535  
   536  	// we may be running after a mid-update reboot, where a successful boot
   537  	// would have trimmed the tracked assets hash lists to contain only the
   538  	// asset we booted with
   539  
   540  	var taBefore *trackedAsset
   541  	if change.Before != "" {
   542  		// make sure that the original copy is present in the cache if
   543  		// it existed
   544  		taBefore, err = o.cache.Add(change.Before, bl.Name(), filepath.Base(relativeTarget))
   545  		if err != nil {
   546  			return gadget.ChangeAbort, err
   547  		}
   548  	}
   549  
   550  	ta, err := o.cache.Add(change.After, bl.Name(), filepath.Base(relativeTarget))
   551  	if err != nil {
   552  		return gadget.ChangeAbort, err
   553  	}
   554  
   555  	trustedAssets := &o.modeenv.CurrentTrustedBootAssets
   556  	changedAssets := &o.changedAssets
   557  	if recovery {
   558  		trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets
   559  		changedAssets = &o.seedChangedAssets
   560  	}
   561  	// keep track of the change for cancellation purpose
   562  	*changedAssets = append(*changedAssets, ta)
   563  
   564  	if *trustedAssets == nil {
   565  		*trustedAssets = bootAssetsMap{}
   566  	}
   567  
   568  	if taBefore != nil && !isAssetAlreadyTracked(*trustedAssets, taBefore) {
   569  		// make sure that the boot asset that was was in the filesystem
   570  		// before the update, is properly tracked until either a
   571  		// successful boot or the update is canceled
   572  		// the original asset hash is listed first
   573  		(*trustedAssets)[taBefore.name] = append([]string{taBefore.hash}, (*trustedAssets)[taBefore.name]...)
   574  	}
   575  
   576  	if !isAssetAlreadyTracked(*trustedAssets, ta) {
   577  		if len((*trustedAssets)[ta.name]) > 1 {
   578  			// we expect at most 2 different blobs for a given asset
   579  			// name, the current one and one that will be installed
   580  			// during an update; more entries indicates that the
   581  			// same asset name is used multiple times with different
   582  			// content
   583  			return gadget.ChangeAbort, fmt.Errorf("cannot reuse asset name %q", ta.name)
   584  		}
   585  		(*trustedAssets)[ta.name] = append((*trustedAssets)[ta.name], ta.hash)
   586  	}
   587  
   588  	if o.modeenv.deepEqual(modeenvBefore) {
   589  		return gadget.ChangeApply, nil
   590  	}
   591  	if err := o.modeenv.Write(); err != nil {
   592  		return gadget.ChangeAbort, fmt.Errorf("cannot write modeeenv: %v", err)
   593  	}
   594  	return gadget.ChangeApply, nil
   595  }
   596  
   597  func (o *TrustedAssetsUpdateObserver) observeRollback(bl bootloader.Bootloader, recovery bool, root, relativeTarget string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) {
   598  	trustedAssets := &o.modeenv.CurrentTrustedBootAssets
   599  	otherTrustedAssets := o.modeenv.CurrentTrustedRecoveryBootAssets
   600  	if recovery {
   601  		trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets
   602  		otherTrustedAssets = o.modeenv.CurrentTrustedBootAssets
   603  	}
   604  
   605  	assetName := filepath.Base(relativeTarget)
   606  	hashList, ok := (*trustedAssets)[assetName]
   607  	if !ok || len(hashList) == 0 {
   608  		// asset not tracked in modeenv
   609  		return gadget.ChangeApply, nil
   610  	}
   611  
   612  	// new assets are appended to the list
   613  	expectedOldHash := hashList[0]
   614  	// sanity check, make sure that the current file is what we expect
   615  	newlyAdded := false
   616  	ondiskHash, err := o.cache.fileHash(filepath.Join(root, relativeTarget))
   617  	if err != nil {
   618  		// file may not exist if it was added by the update, that's ok
   619  		if !os.IsNotExist(err) {
   620  			return gadget.ChangeAbort, fmt.Errorf("cannot calculate the digest of current asset: %v", err)
   621  		}
   622  		newlyAdded = true
   623  		if len(hashList) > 1 {
   624  			// we have more than 1 hash of the asset, so we expected
   625  			// a previous revision to be restored, but got nothing
   626  			// instead
   627  			return gadget.ChangeAbort, fmt.Errorf("tracked asset %q is unexpectedly missing from disk",
   628  				assetName)
   629  		}
   630  	} else {
   631  		if ondiskHash != expectedOldHash {
   632  			// this is unexpected, a different file exists on disk?
   633  			return gadget.ChangeAbort, fmt.Errorf("unexpected content of existing asset %q", relativeTarget)
   634  		}
   635  	}
   636  
   637  	newHash := ""
   638  	if len(hashList) == 1 {
   639  		if newlyAdded {
   640  			newHash = hashList[0]
   641  		}
   642  	} else {
   643  		newHash = hashList[1]
   644  	}
   645  	if newHash != "" && !isAssetHashTrackedInMap(otherTrustedAssets, assetName, newHash) {
   646  		// asset revision is not used used elsewhere, we can remove it from the cache
   647  		if err := o.cache.Remove(bl.Name(), assetName, newHash); err != nil {
   648  			// XXX: should this be a log instead?
   649  			return gadget.ChangeAbort, fmt.Errorf("cannot remove unused boot asset %v:%v: %v", assetName, newHash, err)
   650  		}
   651  	}
   652  
   653  	// update modeenv content
   654  	if !newlyAdded {
   655  		(*trustedAssets)[assetName] = hashList[:1]
   656  	} else {
   657  		delete(*trustedAssets, assetName)
   658  	}
   659  
   660  	if err := o.modeenv.Write(); err != nil {
   661  		return gadget.ChangeAbort, fmt.Errorf("cannot write modeeenv: %v", err)
   662  	}
   663  
   664  	return gadget.ChangeApply, nil
   665  }
   666  
   667  // BeforeWrite is called when the update process has been staged for execution.
   668  func (o *TrustedAssetsUpdateObserver) BeforeWrite() error {
   669  	if o.modeenv == nil {
   670  		// modeenv wasn't even loaded yet, meaning none of the trusted
   671  		// boot assets was updated
   672  		return nil
   673  	}
   674  	const expectReseal = true
   675  	if err := resealKeyToModeenv(dirs.GlobalRootDir, o.modeenv, expectReseal); err != nil {
   676  		return err
   677  	}
   678  	return nil
   679  }
   680  
   681  func (o *TrustedAssetsUpdateObserver) canceledUpdate(recovery bool) {
   682  	trustedAssets := &o.modeenv.CurrentTrustedBootAssets
   683  	otherTrustedAssets := o.modeenv.CurrentTrustedRecoveryBootAssets
   684  	changedAssets := o.changedAssets
   685  	if recovery {
   686  		trustedAssets = &o.modeenv.CurrentTrustedRecoveryBootAssets
   687  		otherTrustedAssets = o.modeenv.CurrentTrustedBootAssets
   688  		changedAssets = o.seedChangedAssets
   689  	}
   690  
   691  	if len(*trustedAssets) == 0 {
   692  		return
   693  	}
   694  
   695  	for _, changed := range changedAssets {
   696  		hashList, ok := (*trustedAssets)[changed.name]
   697  		if !ok || len(hashList) == 0 {
   698  			// not tracked already, nothing to do
   699  			continue
   700  		}
   701  		if len(hashList) == 1 {
   702  			currentAssetHash := hashList[0]
   703  			if currentAssetHash != changed.hash {
   704  				// assets list has already been trimmed, nothing
   705  				// to do
   706  				continue
   707  			} else {
   708  				// asset was newly added
   709  				delete(*trustedAssets, changed.name)
   710  			}
   711  		} else {
   712  			// asset updates were appended to the list
   713  			(*trustedAssets)[changed.name] = hashList[:1]
   714  		}
   715  		if !isAssetHashTrackedInMap(otherTrustedAssets, changed.name, changed.hash) {
   716  			// asset revision is not used used elsewhere, we can remove it from the cache
   717  			if err := o.cache.Remove(changed.blName, changed.name, changed.hash); err != nil {
   718  				logger.Noticef("cannot remove unused boot asset %v:%v: %v", changed.name, changed.hash, err)
   719  			}
   720  		}
   721  	}
   722  }
   723  
   724  // Canceled is called when the update has been canceled, or if changes
   725  // were written and the update has been reverted.
   726  func (o *TrustedAssetsUpdateObserver) Canceled() error {
   727  	if o.modeenv == nil {
   728  		// modeenv wasn't even loaded yet, meaning none of the boot
   729  		// assets was updated
   730  		return nil
   731  	}
   732  	for _, isRecovery := range []bool{false, true} {
   733  		o.canceledUpdate(isRecovery)
   734  	}
   735  
   736  	if err := o.modeenv.Write(); err != nil {
   737  		return fmt.Errorf("cannot write modeeenv: %v", err)
   738  	}
   739  
   740  	const expectReseal = true
   741  	if err := resealKeyToModeenv(dirs.GlobalRootDir, o.modeenv, expectReseal); err != nil {
   742  		return fmt.Errorf("while canceling gadget update: %v", err)
   743  	}
   744  	return nil
   745  }
   746  
   747  func observeSuccessfulBootAssetsForBootloader(m *Modeenv, root string, opts *bootloader.Options) (drop []*trackedAsset, err error) {
   748  	trustedAssetsMap := &m.CurrentTrustedBootAssets
   749  	otherTrustedAssetsMap := m.CurrentTrustedRecoveryBootAssets
   750  	whichBootloader := "run mode"
   751  	if opts != nil && opts.Role == bootloader.RoleRecovery {
   752  		trustedAssetsMap = &m.CurrentTrustedRecoveryBootAssets
   753  		otherTrustedAssetsMap = m.CurrentTrustedBootAssets
   754  		whichBootloader = "recovery"
   755  	}
   756  
   757  	if len(*trustedAssetsMap) == 0 {
   758  		// bootloader may have trusted assets, but we are not tracking
   759  		// any for the boot process
   760  		return nil, nil
   761  	}
   762  
   763  	// let's find the bootloader first
   764  	bl, trustedAssets, err := findMaybeTrustedBootloaderAndAssets(root, opts)
   765  	if err != nil {
   766  		return nil, err
   767  	}
   768  	if len(trustedAssets) == 0 {
   769  		// not a trusted assets bootloader, nothing to do
   770  		return nil, nil
   771  	}
   772  
   773  	cache := newTrustedAssetsCache(dirs.SnapBootAssetsDir)
   774  	for _, trustedAsset := range trustedAssets {
   775  		assetName := filepath.Base(trustedAsset)
   776  
   777  		// find the hash of the file on disk
   778  		assetHash, err := cache.fileHash(filepath.Join(root, trustedAsset))
   779  		if err != nil && !os.IsNotExist(err) {
   780  			return nil, fmt.Errorf("cannot calculate the digest of existing trusted asset: %v", err)
   781  		}
   782  		if assetHash == "" {
   783  			// no trusted asset on disk, but we booted nonetheless,
   784  			// at least log something
   785  			logger.Noticef("system booted without %v bootloader trusted asset %q", whichBootloader, trustedAsset)
   786  			// given that asset names cannot be reused, clear the
   787  			// boot assets map for the current bootloader
   788  			delete(*trustedAssetsMap, assetName)
   789  			continue
   790  		}
   791  
   792  		// this is what we booted with
   793  		bootedWith := []string{assetHash}
   794  		// one of these was expected during boot
   795  		hashList := (*trustedAssetsMap)[assetName]
   796  
   797  		assetFound := false
   798  		// find out if anything needs to be dropped
   799  		for _, hash := range hashList {
   800  			if hash == assetHash {
   801  				assetFound = true
   802  				continue
   803  			}
   804  			if !isAssetHashTrackedInMap(otherTrustedAssetsMap, assetName, hash) {
   805  				// asset can be dropped
   806  				drop = append(drop, &trackedAsset{
   807  					blName: bl.Name(),
   808  					name:   assetName,
   809  					hash:   hash,
   810  				})
   811  			}
   812  		}
   813  
   814  		if !assetFound {
   815  			// unexpected, we have booted with an asset whose hash
   816  			// is not listed among the ones we expect
   817  
   818  			// TODO:UC20: try to restore the asset from cache
   819  			return nil, fmt.Errorf("system booted with unexpected %v bootloader asset %q hash %v", whichBootloader, trustedAsset, assetHash)
   820  		}
   821  
   822  		// update the list of what we booted with
   823  		(*trustedAssetsMap)[assetName] = bootedWith
   824  
   825  	}
   826  	return drop, nil
   827  }
   828  
   829  // observeSuccessfulBootAssets observes the state of the trusted boot assets
   830  // after a successful boot. Returns a modified modeenv reflecting a new state,
   831  // and a list of assets that can be dropped from the cache.
   832  func observeSuccessfulBootAssets(m *Modeenv) (newM *Modeenv, drop []*trackedAsset, err error) {
   833  	// TODO:UC20 only care about run mode for now
   834  	if m.Mode != "run" {
   835  		return m, nil, nil
   836  	}
   837  
   838  	newM, err = m.Copy()
   839  	if err != nil {
   840  		return nil, nil, err
   841  	}
   842  
   843  	for _, bl := range []struct {
   844  		root string
   845  		opts *bootloader.Options
   846  	}{
   847  		{
   848  			// ubuntu-boot bootloader
   849  			root: InitramfsUbuntuBootDir,
   850  			opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true},
   851  		}, {
   852  			// ubuntu-seed bootloader
   853  			root: InitramfsUbuntuSeedDir,
   854  			opts: &bootloader.Options{Role: bootloader.RoleRecovery, NoSlashBoot: true},
   855  		},
   856  	} {
   857  		dropForBootloader, err := observeSuccessfulBootAssetsForBootloader(newM, bl.root, bl.opts)
   858  		if err != nil {
   859  			return nil, nil, err
   860  		}
   861  		drop = append(drop, dropForBootloader...)
   862  	}
   863  	return newM, drop, nil
   864  }