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