github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/assets_test.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_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"syscall"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/boot"
    33  	"github.com/snapcore/snapd/boot/boottest"
    34  	"github.com/snapcore/snapd/bootloader"
    35  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/gadget"
    38  	"github.com/snapcore/snapd/logger"
    39  	"github.com/snapcore/snapd/secboot"
    40  	"github.com/snapcore/snapd/seed"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/testutil"
    43  	"github.com/snapcore/snapd/timings"
    44  )
    45  
    46  type assetsSuite struct {
    47  	baseBootenvSuite
    48  }
    49  
    50  var _ = Suite(&assetsSuite{})
    51  
    52  func (s *assetsSuite) SetUpTest(c *C) {
    53  	s.baseBootenvSuite.SetUpTest(c)
    54  	c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil)
    55  	c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil)
    56  
    57  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil })
    58  	s.AddCleanup(restore)
    59  }
    60  
    61  func checkContentGlob(c *C, glob string, expected []string) {
    62  	l, err := filepath.Glob(glob)
    63  	c.Assert(err, IsNil)
    64  	c.Check(l, DeepEquals, expected)
    65  }
    66  
    67  func (s *assetsSuite) uc20UpdateObserverEncryptedSystemMockedBootloader(c *C) (*boot.TrustedAssetsUpdateObserver, *asserts.Model) {
    68  	// checked by TrustedAssetsUpdateObserverForModel and
    69  	// resealKeyToModeenv
    70  	s.stampSealedKeys(c, dirs.GlobalRootDir)
    71  	return s.uc20UpdateObserver(c, c.MkDir())
    72  }
    73  
    74  func (s *assetsSuite) uc20UpdateObserver(c *C, gadgetDir string) (*boot.TrustedAssetsUpdateObserver, *asserts.Model) {
    75  	uc20Model := boottest.MakeMockUC20Model()
    76  	obs, err := boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
    77  	c.Assert(obs, NotNil)
    78  	c.Assert(err, IsNil)
    79  	return obs, uc20Model
    80  }
    81  
    82  func (s *assetsSuite) bootloaderWithTrustedAssets(c *C, trustedAssets []string) *bootloadertest.MockTrustedAssetsBootloader {
    83  	tab := bootloadertest.Mock("trusted", "").WithTrustedAssets()
    84  	bootloader.Force(tab)
    85  	tab.TrustedAssetsList = trustedAssets
    86  	s.AddCleanup(func() { bootloader.Force(nil) })
    87  	return tab
    88  }
    89  
    90  func (s *assetsSuite) TestAssetsCacheAddRemove(c *C) {
    91  	cacheDir := c.MkDir()
    92  	d := c.MkDir()
    93  
    94  	cache := boot.NewTrustedAssetsCache(cacheDir)
    95  
    96  	data := []byte("foobar")
    97  	// SHA3-384
    98  	hash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
    99  	err := ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
   100  	c.Assert(err, IsNil)
   101  
   102  	// add a new file
   103  	ta, err := cache.Add(filepath.Join(d, "foobar"), "grub", "grubx64.efi")
   104  	c.Assert(err, IsNil)
   105  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", hash)), testutil.FileEquals, string(data))
   106  	c.Check(ta, NotNil)
   107  
   108  	// try the same file again
   109  	taAgain, err := cache.Add(filepath.Join(d, "foobar"), "grub", "grubx64.efi")
   110  	c.Assert(err, IsNil)
   111  	// file already cached
   112  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", hash)), testutil.FileEquals, string(data))
   113  	// and there's just one entry in the cache
   114  	checkContentGlob(c, filepath.Join(cacheDir, "grub", "*"), []string{
   115  		filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", hash)),
   116  	})
   117  	// let go-check do the deep equals check
   118  	c.Check(taAgain, DeepEquals, ta)
   119  
   120  	// same data but different asset name
   121  	taDifferentAsset, err := cache.Add(filepath.Join(d, "foobar"), "grub", "bootx64.efi")
   122  	c.Assert(err, IsNil)
   123  	// new entry in cache
   124  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", hash)), testutil.FileEquals, string(data))
   125  	// 2 files now
   126  	checkContentGlob(c, filepath.Join(cacheDir, "grub", "*"), []string{
   127  		filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", hash)),
   128  		filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", hash)),
   129  	})
   130  	c.Check(taDifferentAsset, NotNil)
   131  
   132  	// same source, data (new hash), existing asset name
   133  	newData := []byte("new foobar")
   134  	newHash := "5aa87615f6613a37d63c9a29746ef57457286c37148a4ae78493b0face5976c1fea940a19486e6bef65d43aec6b8f5a2"
   135  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), newData, 0644)
   136  	c.Assert(err, IsNil)
   137  
   138  	taExistingAssetName, err := cache.Add(filepath.Join(d, "foobar"), "grub", "bootx64.efi")
   139  	c.Assert(err, IsNil)
   140  	// new entry in cache
   141  	c.Check(taExistingAssetName, NotNil)
   142  	// we have both new and old asset
   143  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", newHash)), testutil.FileEquals, string(newData))
   144  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", hash)), testutil.FileEquals, string(data))
   145  	// 3 files in total
   146  	checkContentGlob(c, filepath.Join(cacheDir, "grub", "*"), []string{
   147  		filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", hash)),
   148  		filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", newHash)),
   149  		filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", hash)),
   150  	})
   151  
   152  	// drop
   153  	err = cache.Remove("grub", "bootx64.efi", newHash)
   154  	c.Assert(err, IsNil)
   155  	// asset bootx64.efi with given hash was dropped
   156  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", newHash)), testutil.FileAbsent)
   157  	// the other file still exists
   158  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", hash)), testutil.FileEquals, string(data))
   159  	// remove it too
   160  	err = cache.Remove("grub", "bootx64.efi", hash)
   161  	c.Assert(err, IsNil)
   162  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("bootx64.efi-%s", hash)), testutil.FileAbsent)
   163  
   164  	// what is left is the grub assets only
   165  	checkContentGlob(c, filepath.Join(cacheDir, "grub", "*"), []string{
   166  		filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", hash)),
   167  	})
   168  }
   169  
   170  func (s *assetsSuite) TestAssetsCacheAddErr(c *C) {
   171  	cacheDir := c.MkDir()
   172  	d := c.MkDir()
   173  	cache := boot.NewTrustedAssetsCache(cacheDir)
   174  
   175  	defer os.Chmod(cacheDir, 0755)
   176  	err := os.Chmod(cacheDir, 0000)
   177  	c.Assert(err, IsNil)
   178  
   179  	if os.Geteuid() != 0 {
   180  		err = ioutil.WriteFile(filepath.Join(d, "foobar"), []byte("foo"), 0644)
   181  		c.Assert(err, IsNil)
   182  		// cannot create bootloader subdirectory
   183  		ta, err := cache.Add(filepath.Join(d, "foobar"), "grub", "grubx64.efi")
   184  		c.Assert(err, ErrorMatches, "cannot create cache directory: mkdir .*/grub: permission denied")
   185  		c.Check(ta, IsNil)
   186  	}
   187  
   188  	// fix it now
   189  	err = os.Chmod(cacheDir, 0755)
   190  	c.Assert(err, IsNil)
   191  
   192  	_, err = cache.Add(filepath.Join(d, "no-file"), "grub", "grubx64.efi")
   193  	c.Assert(err, ErrorMatches, "cannot open asset file: open .*/no-file: no such file or directory")
   194  
   195  	if os.Geteuid() != 0 {
   196  		blDir := filepath.Join(cacheDir, "grub")
   197  		defer os.Chmod(blDir, 0755)
   198  		err = os.Chmod(blDir, 0000)
   199  		c.Assert(err, IsNil)
   200  
   201  		_, err = cache.Add(filepath.Join(d, "foobar"), "grub", "grubx64.efi")
   202  		c.Assert(err, ErrorMatches, `cannot create temporary cache file: open .*/grub/grubx64\.efi\.temp\.[a-zA-Z0-9]+~: permission denied`)
   203  	}
   204  }
   205  
   206  func (s *assetsSuite) TestAssetsCacheRemoveErr(c *C) {
   207  	cacheDir := c.MkDir()
   208  	d := c.MkDir()
   209  	cache := boot.NewTrustedAssetsCache(cacheDir)
   210  
   211  	data := []byte("foobar")
   212  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
   213  	err := ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
   214  	c.Assert(err, IsNil)
   215  	// cannot create bootloader subdirectory
   216  	_, err = cache.Add(filepath.Join(d, "foobar"), "grub", "grubx64.efi")
   217  	c.Assert(err, IsNil)
   218  	// sanity
   219  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", dataHash)), testutil.FileEquals, string(data))
   220  
   221  	err = cache.Remove("grub", "no file", "some-hash")
   222  	c.Assert(err, IsNil)
   223  
   224  	// different asset name but known hash
   225  	err = cache.Remove("grub", "different-name", dataHash)
   226  	c.Assert(err, IsNil)
   227  	c.Check(filepath.Join(cacheDir, "grub", fmt.Sprintf("grubx64.efi-%s", dataHash)), testutil.FileEquals, string(data))
   228  }
   229  
   230  func (s *assetsSuite) TestInstallObserverNew(c *C) {
   231  	d := c.MkDir()
   232  	// bootloader in gadget cannot be identified
   233  	uc20Model := boottest.MakeMockUC20Model()
   234  	for _, encryption := range []bool{true, false} {
   235  		obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, encryption)
   236  		c.Assert(err, ErrorMatches, "cannot find bootloader: cannot determine bootloader")
   237  		c.Assert(obs, IsNil)
   238  	}
   239  
   240  	// pretend grub is used
   241  	c.Assert(ioutil.WriteFile(filepath.Join(d, "grub.conf"), nil, 0755), IsNil)
   242  
   243  	for _, encryption := range []bool{true, false} {
   244  		obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, encryption)
   245  		c.Assert(err, IsNil)
   246  		c.Assert(obs, NotNil)
   247  	}
   248  
   249  	// but nil for non UC20
   250  	nonUC20Model := boottest.MakeMockModel()
   251  	nonUC20obs, err := boot.TrustedAssetsInstallObserverForModel(nonUC20Model, d, false)
   252  	c.Assert(err, Equals, boot.ErrObserverNotApplicable)
   253  	c.Assert(nonUC20obs, IsNil)
   254  
   255  	// listing trusted assets fails
   256  	tab := s.bootloaderWithTrustedAssets(c, []string{
   257  		"asset",
   258  	})
   259  	tab.TrustedAssetsErr = fmt.Errorf("fail")
   260  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, true)
   261  	c.Assert(err, ErrorMatches, `cannot list "trusted" bootloader trusted assets: fail`)
   262  	c.Assert(obs, IsNil)
   263  	// failed when listing run bootloader assets
   264  	c.Check(tab.TrustedAssetsCalls, Equals, 1)
   265  
   266  	// force an error
   267  	bootloader.ForceError(fmt.Errorf("fail bootloader"))
   268  	obs, err = boot.TrustedAssetsInstallObserverForModel(uc20Model, d, true)
   269  	c.Assert(err, ErrorMatches, `cannot find bootloader: fail bootloader`)
   270  	c.Assert(obs, IsNil)
   271  }
   272  
   273  var (
   274  	mockRunBootStruct = &gadget.LaidOutStructure{
   275  		VolumeStructure: &gadget.VolumeStructure{
   276  			Role: gadget.SystemBoot,
   277  		},
   278  	}
   279  	mockSeedStruct = &gadget.LaidOutStructure{
   280  		VolumeStructure: &gadget.VolumeStructure{
   281  			Role: gadget.SystemSeed,
   282  		},
   283  	}
   284  )
   285  
   286  func (s *assetsSuite) TestInstallObserverObserveSystemBootRealGrub(c *C) {
   287  	d := c.MkDir()
   288  
   289  	// mock a bootloader that uses trusted assets
   290  	err := ioutil.WriteFile(filepath.Join(d, "grub.conf"), nil, 0644)
   291  	c.Assert(err, IsNil)
   292  
   293  	// we get an observer for UC20
   294  	uc20Model := boottest.MakeMockUC20Model()
   295  	useEncryption := true
   296  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   297  	c.Assert(err, IsNil)
   298  	c.Assert(obs, NotNil)
   299  
   300  	data := []byte("foobar")
   301  	// SHA3-384
   302  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
   303  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
   304  	c.Assert(err, IsNil)
   305  
   306  	otherData := []byte("other foobar")
   307  	err = ioutil.WriteFile(filepath.Join(d, "other-foobar"), otherData, 0644)
   308  	c.Assert(err, IsNil)
   309  
   310  	writeChange := &gadget.ContentChange{
   311  		// file that contains the data of the installed file
   312  		After: filepath.Join(d, "foobar"),
   313  		// there is no original file in place
   314  		Before: "",
   315  	}
   316  	// only grubx64.efi gets installed to system-boot
   317  	res, err := obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   318  		"EFI/boot/grubx64.efi", writeChange)
   319  	c.Assert(err, IsNil)
   320  	c.Check(res, Equals, gadget.ChangeApply)
   321  	// Observe is called when populating content, but one can freely specify
   322  	// overlapping content entries, so a same file may be observed more than
   323  	// once
   324  	res, err = obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   325  		"EFI/boot/grubx64.efi", writeChange)
   326  	c.Assert(err, IsNil)
   327  	c.Check(res, Equals, gadget.ChangeApply)
   328  	// try with one more file, which is not a trusted asset of a run mode, so it is ignored
   329  	res, err = obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   330  		"EFI/boot/bootx64.efi", writeChange)
   331  	c.Assert(err, IsNil)
   332  	c.Check(res, Equals, gadget.ChangeApply)
   333  	// a managed boot asset is to be held
   334  	res, err = obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   335  		"EFI/ubuntu/grub.cfg", writeChange)
   336  	c.Assert(err, IsNil)
   337  	c.Check(res, Equals, gadget.ChangeIgnore)
   338  
   339  	// a single file in cache
   340  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "grub", "*"), []string{
   341  		filepath.Join(dirs.SnapBootAssetsDir, "grub", fmt.Sprintf("grubx64.efi-%s", dataHash)),
   342  	})
   343  
   344  	// and one more, a non system-boot structure, so the file is ignored
   345  	systemSeedStruct := &gadget.LaidOutStructure{
   346  		VolumeStructure: &gadget.VolumeStructure{
   347  			Role: gadget.SystemSeed,
   348  		},
   349  	}
   350  	otherWriteChange := &gadget.ContentChange{
   351  		After: filepath.Join(d, "other-foobar"),
   352  	}
   353  	res, err = obs.Observe(gadget.ContentWrite, systemSeedStruct, boot.InitramfsUbuntuBootDir,
   354  		"EFI/boot/grubx64.efi", otherWriteChange)
   355  	c.Assert(err, IsNil)
   356  	c.Check(res, Equals, gadget.ChangeApply)
   357  	// still, only one entry in the cache
   358  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "grub", "*"), []string{
   359  		filepath.Join(dirs.SnapBootAssetsDir, "grub", fmt.Sprintf("grubx64.efi-%s", dataHash)),
   360  	})
   361  
   362  	// let's see what the observer has tracked
   363  	tracked := obs.CurrentTrustedBootAssetsMap()
   364  	c.Check(tracked, DeepEquals, boot.BootAssetsMap{
   365  		"grubx64.efi": []string{dataHash},
   366  	})
   367  }
   368  
   369  func (s *assetsSuite) TestInstallObserverObserveSystemBootMocked(c *C) {
   370  	d := c.MkDir()
   371  
   372  	tab := s.bootloaderWithTrustedAssets(c, []string{
   373  		"asset",
   374  		"nested/other-asset",
   375  	})
   376  
   377  	// we get an observer for UC20
   378  	uc20Model := boottest.MakeMockUC20Model()
   379  	useEncryption := true
   380  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   381  	c.Assert(err, IsNil)
   382  	c.Assert(obs, NotNil)
   383  	// the list of trusted assets was asked for run and recovery bootloaders
   384  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   385  
   386  	data := []byte("foobar")
   387  	// SHA3-384
   388  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
   389  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
   390  	c.Assert(err, IsNil)
   391  
   392  	writeChange := &gadget.ContentChange{
   393  		// file that contains the data of the installed file
   394  		After: filepath.Join(d, "foobar"),
   395  		// there is no original file in place
   396  		Before: "",
   397  	}
   398  	res, err := obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   399  		"asset", writeChange)
   400  	c.Assert(err, IsNil)
   401  	c.Check(res, Equals, gadget.ChangeApply)
   402  	// observe same asset again
   403  	res, err = obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   404  		"asset", writeChange)
   405  	c.Assert(err, IsNil)
   406  	c.Check(res, Equals, gadget.ChangeApply)
   407  	// different one
   408  	res, err = obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   409  		"nested/other-asset", writeChange)
   410  	c.Assert(err, IsNil)
   411  	c.Check(res, Equals, gadget.ChangeApply)
   412  	// a non trusted asset
   413  	res, err = obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   414  		"non-trusted", writeChange)
   415  	c.Assert(err, IsNil)
   416  	c.Check(res, Equals, gadget.ChangeApply)
   417  	// a single file in cache
   418  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
   419  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
   420  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("other-asset-%s", dataHash)),
   421  	})
   422  	// let's see what the observer has tracked
   423  	tracked := obs.CurrentTrustedBootAssetsMap()
   424  	c.Check(tracked, DeepEquals, boot.BootAssetsMap{
   425  		"asset":       []string{dataHash},
   426  		"other-asset": []string{dataHash},
   427  	})
   428  }
   429  
   430  func (s *assetsSuite) TestInstallObserverObserveSystemBootMockedNoEncryption(c *C) {
   431  	d := c.MkDir()
   432  	s.bootloaderWithTrustedAssets(c, []string{"asset"})
   433  	uc20Model := boottest.MakeMockUC20Model()
   434  	useEncryption := false
   435  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   436  	c.Assert(err, Equals, boot.ErrObserverNotApplicable)
   437  	c.Assert(obs, IsNil)
   438  }
   439  
   440  func (s *assetsSuite) TestInstallObserverObserveSystemBootMockedUnencryptedWithManaged(c *C) {
   441  	d := c.MkDir()
   442  	tab := s.bootloaderWithTrustedAssets(c, []string{"asset"})
   443  	tab.ManagedAssetsList = []string{"managed"}
   444  	uc20Model := boottest.MakeMockUC20Model()
   445  	useEncryption := false
   446  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   447  	c.Assert(err, IsNil)
   448  	c.Assert(obs, NotNil)
   449  
   450  	c.Assert(ioutil.WriteFile(filepath.Join(d, "foobar"), nil, 0755), IsNil)
   451  	writeChange := &gadget.ContentChange{
   452  		// file that contains the data of the installed file
   453  		After: filepath.Join(d, "foobar"),
   454  		// there is no original file in place
   455  		Before: "",
   456  	}
   457  	res, err := obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir,
   458  		"managed", writeChange)
   459  	c.Assert(err, IsNil)
   460  	c.Check(res, Equals, gadget.ChangeIgnore)
   461  }
   462  
   463  func (s *assetsSuite) TestInstallObserverNonTrustedBootloader(c *C) {
   464  	// bootloader is not a trusted assets one, but we use encryption, one
   465  	// may try setting encryption key on the observer
   466  
   467  	d := c.MkDir()
   468  
   469  	// MockBootloader does not implement trusted assets
   470  	bootloader.Force(bootloadertest.Mock("mock", ""))
   471  	defer bootloader.Force(nil)
   472  
   473  	// we get an observer for UC20
   474  	uc20Model := boottest.MakeMockUC20Model()
   475  	useEncryption := true
   476  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   477  	c.Assert(err, IsNil)
   478  	c.Assert(obs, NotNil)
   479  	obs.ChosenEncryptionKeys(secboot.EncryptionKey{1, 2, 3, 4}, secboot.EncryptionKey{5, 6, 7, 8})
   480  	c.Check(obs.CurrentDataEncryptionKey(), DeepEquals, secboot.EncryptionKey{1, 2, 3, 4})
   481  	c.Check(obs.CurrentSaveEncryptionKey(), DeepEquals, secboot.EncryptionKey{5, 6, 7, 8})
   482  }
   483  
   484  func (s *assetsSuite) TestInstallObserverTrustedButNoAssets(c *C) {
   485  	// bootloader has no trusted assets, but encryption is enabled, and one
   486  	// may try setting a key on the observer
   487  
   488  	d := c.MkDir()
   489  
   490  	tab := bootloadertest.Mock("trusted-assets", "").WithTrustedAssets()
   491  	bootloader.Force(tab)
   492  	defer bootloader.Force(nil)
   493  
   494  	// we get an observer for UC20
   495  	uc20Model := boottest.MakeMockUC20Model()
   496  	useEncryption := true
   497  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   498  	c.Assert(err, IsNil)
   499  	c.Assert(obs, NotNil)
   500  	obs.ChosenEncryptionKeys(secboot.EncryptionKey{1, 2, 3, 4}, secboot.EncryptionKey{5, 6, 7, 8})
   501  	c.Check(obs.CurrentDataEncryptionKey(), DeepEquals, secboot.EncryptionKey{1, 2, 3, 4})
   502  	c.Check(obs.CurrentSaveEncryptionKey(), DeepEquals, secboot.EncryptionKey{5, 6, 7, 8})
   503  }
   504  
   505  func (s *assetsSuite) TestInstallObserverTrustedReuseNameErr(c *C) {
   506  	d := c.MkDir()
   507  
   508  	tab := s.bootloaderWithTrustedAssets(c, []string{
   509  		"asset",
   510  		"nested/asset",
   511  	})
   512  
   513  	// we get an observer for UC20
   514  	uc20Model := boottest.MakeMockUC20Model()
   515  	useEncryption := true
   516  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   517  	c.Assert(err, IsNil)
   518  	c.Assert(obs, NotNil)
   519  	// the list of trusted assets was asked for run and recovery bootloaders
   520  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   521  
   522  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), []byte("foobar"), 0644)
   523  	c.Assert(err, IsNil)
   524  	err = ioutil.WriteFile(filepath.Join(d, "other"), []byte("other"), 0644)
   525  	c.Assert(err, IsNil)
   526  	res, err := obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir, "asset",
   527  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   528  	c.Assert(err, IsNil)
   529  	c.Check(res, Equals, gadget.ChangeApply)
   530  	// same asset name but different content
   531  	res, err = obs.Observe(gadget.ContentWrite, mockRunBootStruct, boot.InitramfsUbuntuBootDir, "nested/asset",
   532  		&gadget.ContentChange{After: filepath.Join(d, "other")})
   533  	c.Assert(err, ErrorMatches, `cannot reuse asset name "asset"`)
   534  	c.Check(res, Equals, gadget.ChangeAbort)
   535  }
   536  
   537  func (s *assetsSuite) TestInstallObserverObserveExistingRecoveryMocked(c *C) {
   538  	d := c.MkDir()
   539  
   540  	tab := s.bootloaderWithTrustedAssets(c, []string{
   541  		"asset",
   542  		"nested/other-asset",
   543  		"shim",
   544  	})
   545  
   546  	// we get an observer for UC20
   547  	uc20Model := boottest.MakeMockUC20Model()
   548  	useEncryption := true
   549  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   550  	c.Assert(err, IsNil)
   551  	c.Assert(obs, NotNil)
   552  	// trusted assets for the run and recovery bootloaders were asked for
   553  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   554  
   555  	data := []byte("foobar")
   556  	// SHA3-384
   557  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
   558  	err = ioutil.WriteFile(filepath.Join(d, "asset"), data, 0644)
   559  	c.Assert(err, IsNil)
   560  	err = os.Mkdir(filepath.Join(d, "nested"), 0755)
   561  	c.Assert(err, IsNil)
   562  	err = ioutil.WriteFile(filepath.Join(d, "nested/other-asset"), data, 0644)
   563  	c.Assert(err, IsNil)
   564  	shim := []byte("shim")
   565  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
   566  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
   567  	c.Assert(err, IsNil)
   568  
   569  	err = obs.ObserveExistingTrustedRecoveryAssets(d)
   570  	c.Assert(err, IsNil)
   571  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
   572  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
   573  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("other-asset-%s", dataHash)),
   574  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
   575  	})
   576  	// the list of trusted assets for recovery was asked for
   577  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   578  	// let's see what the observer has tracked
   579  	tracked := obs.CurrentTrustedRecoveryBootAssetsMap()
   580  	c.Check(tracked, DeepEquals, boot.BootAssetsMap{
   581  		"asset":       []string{dataHash},
   582  		"other-asset": []string{dataHash},
   583  		"shim":        []string{shimHash},
   584  	})
   585  }
   586  
   587  func (s *assetsSuite) TestInstallObserverObserveExistingRecoveryReuseNameErr(c *C) {
   588  	d := c.MkDir()
   589  
   590  	tab := s.bootloaderWithTrustedAssets(c, []string{
   591  		"asset",
   592  		"nested/asset",
   593  	})
   594  	// we get an observer for UC20
   595  	uc20Model := boottest.MakeMockUC20Model()
   596  	useEncryption := true
   597  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   598  	c.Assert(err, IsNil)
   599  	c.Assert(obs, NotNil)
   600  	// got the list of trusted assets for run and recovery bootloaders
   601  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   602  
   603  	err = ioutil.WriteFile(filepath.Join(d, "asset"), []byte("foobar"), 0644)
   604  	c.Assert(err, IsNil)
   605  	err = os.MkdirAll(filepath.Join(d, "nested"), 0755)
   606  	c.Assert(err, IsNil)
   607  	// same asset name but different content
   608  	err = ioutil.WriteFile(filepath.Join(d, "nested/asset"), []byte("other"), 0644)
   609  	c.Assert(err, IsNil)
   610  	err = obs.ObserveExistingTrustedRecoveryAssets(d)
   611  	// same asset name but different content
   612  	c.Assert(err, ErrorMatches, `cannot reuse recovery asset name "asset"`)
   613  	// got the list of trusted assets for recovery bootloader
   614  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   615  }
   616  
   617  func (s *assetsSuite) TestInstallObserverObserveExistingRecoveryButMissingErr(c *C) {
   618  	d := c.MkDir()
   619  
   620  	tab := s.bootloaderWithTrustedAssets(c, []string{
   621  		"asset",
   622  	})
   623  
   624  	uc20Model := boottest.MakeMockUC20Model()
   625  	useEncryption := true
   626  	obs, err := boot.TrustedAssetsInstallObserverForModel(uc20Model, d, useEncryption)
   627  	c.Assert(err, IsNil)
   628  	c.Assert(obs, NotNil)
   629  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   630  
   631  	// trusted asset is missing
   632  	err = obs.ObserveExistingTrustedRecoveryAssets(d)
   633  	c.Assert(err, ErrorMatches, "cannot open asset file: .*/asset: no such file or directory")
   634  }
   635  
   636  func (s *assetsSuite) TestUpdateObserverNew(c *C) {
   637  	tab := s.bootloaderWithTrustedAssets(c, nil)
   638  
   639  	uc20Model := boottest.MakeMockUC20Model()
   640  
   641  	gadgetDir := c.MkDir()
   642  
   643  	// no trusted or managed assets
   644  	obs, err := boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
   645  	c.Assert(err, Equals, boot.ErrObserverNotApplicable)
   646  	c.Check(obs, IsNil)
   647  
   648  	// no managed, some trusted assets, but we are not tracking them
   649  	tab.TrustedAssetsList = []string{"asset"}
   650  	obs, err = boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
   651  	c.Assert(err, Equals, boot.ErrObserverNotApplicable)
   652  	c.Check(obs, IsNil)
   653  
   654  	// let's see some managed assets, but not trusted assets
   655  	tab.ManagedAssetsList = []string{"managed"}
   656  	tab.TrustedAssetsList = nil
   657  	obs, err = boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
   658  	c.Assert(err, IsNil)
   659  	c.Check(obs, NotNil)
   660  
   661  	// no managed, some trusted which we need to track
   662  	s.stampSealedKeys(c, dirs.GlobalRootDir)
   663  	tab.ManagedAssetsList = nil
   664  	tab.TrustedAssetsList = []string{"asset"}
   665  	obs, err = boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
   666  	c.Assert(err, IsNil)
   667  	c.Assert(obs, NotNil)
   668  
   669  	// but nil for non UC20
   670  	nonUC20Model := boottest.MakeMockModel()
   671  	nonUC20obs, err := boot.TrustedAssetsUpdateObserverForModel(nonUC20Model, gadgetDir)
   672  	c.Assert(err, Equals, boot.ErrObserverNotApplicable)
   673  	c.Assert(nonUC20obs, IsNil)
   674  }
   675  
   676  func (s *assetsSuite) TestUpdateObserverUpdateMockedWithReseal(c *C) {
   677  	// observe an update where some of the assets exist and some are new,
   678  	// followed by reseal
   679  
   680  	d := c.MkDir()
   681  	backups := c.MkDir()
   682  	root := c.MkDir()
   683  
   684  	// try to arrange the backups like the updater would do it
   685  	before := []byte("before")
   686  	beforeHash := "2df0976fd45ba2392dc7985cdfb7c2d096c1ea4917929dd7a0e9bffae90a443271e702663fc6a4189c1f4ab3ce7daee3"
   687  	err := ioutil.WriteFile(filepath.Join(backups, "asset.backup"), before, 0644)
   688  	c.Assert(err, IsNil)
   689  
   690  	data := []byte("foobar")
   691  	// SHA3-384
   692  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
   693  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
   694  	c.Assert(err, IsNil)
   695  	shim := []byte("shim")
   696  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
   697  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
   698  	c.Assert(err, IsNil)
   699  
   700  	m := boot.Modeenv{
   701  		Mode: "run",
   702  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   703  			"asset": {beforeHash},
   704  			"shim":  {"shim-hash"},
   705  		},
   706  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   707  			"asset": {beforeHash},
   708  		},
   709  	}
   710  	err = m.WriteTo("")
   711  	c.Assert(err, IsNil)
   712  
   713  	tab := s.bootloaderWithTrustedAssets(c, []string{
   714  		"asset",
   715  		"nested/other-asset",
   716  		"shim",
   717  	})
   718  	tab.ManagedAssetsList = []string{
   719  		"managed-asset",
   720  	}
   721  
   722  	// we get an observer for UC20
   723  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
   724  	// the list of trusted assets is obtained upfront
   725  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   726  
   727  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
   728  		&gadget.ContentChange{
   729  			After: filepath.Join(d, "foobar"),
   730  			// original content would get backed up by the updater
   731  			Before: filepath.Join(backups, "asset.backup"),
   732  		})
   733  	c.Assert(err, IsNil)
   734  	c.Check(res, Equals, gadget.ChangeApply)
   735  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
   736  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
   737  	c.Assert(err, IsNil)
   738  	c.Check(res, Equals, gadget.ChangeApply)
   739  	// observe the recovery struct
   740  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
   741  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
   742  	c.Assert(err, IsNil)
   743  	c.Check(res, Equals, gadget.ChangeApply)
   744  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
   745  		&gadget.ContentChange{
   746  			After: filepath.Join(d, "foobar"),
   747  			// original content
   748  			Before: filepath.Join(backups, "asset.backup"),
   749  		})
   750  	c.Assert(err, IsNil)
   751  	c.Check(res, Equals, gadget.ChangeApply)
   752  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "nested/other-asset",
   753  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   754  	c.Assert(err, IsNil)
   755  	c.Check(res, Equals, gadget.ChangeApply)
   756  	// all files are in cache
   757  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
   758  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
   759  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", beforeHash)),
   760  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("other-asset-%s", dataHash)),
   761  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
   762  	})
   763  	// check modeenv
   764  	newM, err := boot.ReadModeenv("")
   765  	c.Assert(err, IsNil)
   766  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
   767  		"asset": {beforeHash, dataHash},
   768  		"shim":  {"shim-hash", shimHash},
   769  	})
   770  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
   771  		"asset":       {beforeHash, dataHash},
   772  		"shim":        {shimHash},
   773  		"other-asset": {dataHash},
   774  	})
   775  
   776  	// verify that managed assets are to be preserved
   777  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "managed-asset",
   778  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   779  	c.Assert(err, IsNil)
   780  	c.Check(res, Equals, gadget.ChangeIgnore)
   781  
   782  	// everything is set up, trigger a reseal
   783  	resealCalls := 0
   784  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   785  		resealCalls++
   786  		return nil
   787  	})
   788  	defer restore()
   789  
   790  	err = obs.BeforeWrite()
   791  	c.Assert(err, IsNil)
   792  	c.Check(resealCalls, Equals, 1)
   793  }
   794  
   795  func (s *assetsSuite) TestUpdateObserverUpdateExistingAssetMocked(c *C) {
   796  	d := c.MkDir()
   797  	root := c.MkDir()
   798  
   799  	tab := s.bootloaderWithTrustedAssets(c, []string{
   800  		"asset",
   801  		"shim",
   802  	})
   803  	tab.ManagedAssetsList = []string{
   804  		"managed-asset",
   805  		"nested/managed-asset",
   806  	}
   807  
   808  	data := []byte("foobar")
   809  	// SHA3-384
   810  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
   811  	err := ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
   812  	c.Assert(err, IsNil)
   813  	shim := []byte("shim")
   814  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
   815  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
   816  	c.Assert(err, IsNil)
   817  
   818  	// add one file to the cache, as if the system got rebooted before
   819  	// modeenv got updated
   820  	cache := boot.NewTrustedAssetsCache(dirs.SnapBootAssetsDir)
   821  	_, err = cache.Add(filepath.Join(d, "foobar"), "trusted", "asset")
   822  	c.Assert(err, IsNil)
   823  	// file is in the cache
   824  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
   825  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
   826  	})
   827  
   828  	m := boot.Modeenv{
   829  		Mode: "run",
   830  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   831  			"asset": {"asset-hash"},
   832  		},
   833  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   834  			// shim with same hash is listed as trusted, but missing
   835  			// from cache
   836  			"shim": {shimHash},
   837  		},
   838  	}
   839  	err = m.WriteTo("")
   840  	c.Assert(err, IsNil)
   841  
   842  	// we get an observer for UC20
   843  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
   844  
   845  	// observe the updates
   846  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
   847  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   848  	c.Assert(err, IsNil)
   849  	c.Check(res, Equals, gadget.ChangeApply)
   850  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
   851  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   852  	c.Assert(err, IsNil)
   853  	c.Check(res, Equals, gadget.ChangeApply)
   854  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
   855  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
   856  	c.Assert(err, IsNil)
   857  	c.Check(res, Equals, gadget.ChangeApply)
   858  	// trusted assets were asked for
   859  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   860  	// file is in the cache
   861  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
   862  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
   863  		// shim was added to cache
   864  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
   865  	})
   866  	// check modeenv
   867  	newM, err := boot.ReadModeenv("")
   868  	c.Assert(err, IsNil)
   869  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
   870  		"asset": {"asset-hash", dataHash},
   871  	})
   872  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
   873  		"asset": {dataHash},
   874  		"shim":  {shimHash},
   875  	})
   876  
   877  	// verify that managed assets are to be preserved
   878  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "managed-asset",
   879  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   880  	c.Assert(err, IsNil)
   881  	c.Check(res, Equals, gadget.ChangeIgnore)
   882  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "nested/managed-asset",
   883  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   884  	c.Assert(err, IsNil)
   885  	c.Check(res, Equals, gadget.ChangeIgnore)
   886  
   887  	// everything is set up, trigger reseal
   888  	resealCalls := 0
   889  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   890  		resealCalls++
   891  		return nil
   892  	})
   893  	defer restore()
   894  
   895  	// execute before-write action
   896  	err = obs.BeforeWrite()
   897  	c.Assert(err, IsNil)
   898  	c.Check(resealCalls, Equals, 1)
   899  }
   900  
   901  func (s *assetsSuite) TestUpdateObserverUpdateNothingTrackedMocked(c *C) {
   902  	d := c.MkDir()
   903  	root := c.MkDir()
   904  
   905  	tab := s.bootloaderWithTrustedAssets(c, []string{
   906  		"asset",
   907  	})
   908  
   909  	data := []byte("foobar")
   910  	// SHA3-384
   911  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
   912  	err := ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
   913  	c.Assert(err, IsNil)
   914  
   915  	m := boot.Modeenv{
   916  		Mode: "run",
   917  		// nothing is tracked in modeenv yet
   918  	}
   919  	err = m.WriteTo("")
   920  	c.Assert(err, IsNil)
   921  
   922  	// we get an observer for UC20
   923  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
   924  
   925  	// observe the updates
   926  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
   927  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   928  	c.Assert(err, IsNil)
   929  	c.Check(res, Equals, gadget.ChangeApply)
   930  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
   931  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   932  	c.Assert(err, IsNil)
   933  	c.Check(res, Equals, gadget.ChangeApply)
   934  	// trusted assets were asked for
   935  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   936  	// file is in the cache
   937  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
   938  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
   939  	})
   940  	// check modeenv
   941  	newM, err := boot.ReadModeenv("")
   942  	c.Assert(err, IsNil)
   943  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
   944  		"asset": {dataHash},
   945  	})
   946  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
   947  		"asset": {dataHash},
   948  	})
   949  
   950  	// reseal does nothing
   951  	err = obs.BeforeWrite()
   952  	c.Assert(err, IsNil)
   953  	c.Check(tab.RecoveryBootChainCalls, HasLen, 0)
   954  	c.Check(tab.BootChainKernelPath, HasLen, 0)
   955  }
   956  
   957  func (s *assetsSuite) TestUpdateObserverUpdateOtherRoleStructMocked(c *C) {
   958  	d := c.MkDir()
   959  	root := c.MkDir()
   960  
   961  	tab := s.bootloaderWithTrustedAssets(c, []string{
   962  		"asset",
   963  	})
   964  
   965  	// modeenv is not set up, but the observer should not care
   966  
   967  	// we get an observer for UC20
   968  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
   969  	// and once again for the recovery bootloader
   970  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
   971  
   972  	// non system-boot or system-seed structure gets ignored
   973  	mockVolumeStruct := &gadget.LaidOutStructure{
   974  		VolumeStructure: &gadget.VolumeStructure{
   975  			Role: gadget.SystemData,
   976  		},
   977  	}
   978  
   979  	// observe the updates
   980  	res, err := obs.Observe(gadget.ContentUpdate, mockVolumeStruct, root, "asset",
   981  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
   982  	c.Assert(err, IsNil)
   983  	c.Check(res, Equals, gadget.ChangeApply)
   984  }
   985  
   986  func (s *assetsSuite) TestUpdateObserverUpdateTrivialErr(c *C) {
   987  	// test trivial error scenarios of the update observer
   988  
   989  	s.stampSealedKeys(c, dirs.GlobalRootDir)
   990  
   991  	d := c.MkDir()
   992  	root := c.MkDir()
   993  	gadgetDir := c.MkDir()
   994  
   995  	uc20Model := boottest.MakeMockUC20Model()
   996  
   997  	// first no bootloader
   998  	bootloader.ForceError(fmt.Errorf("bootloader fail"))
   999  
  1000  	obs, err := boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
  1001  	c.Assert(obs, IsNil)
  1002  	c.Assert(err, ErrorMatches, "cannot find bootloader: bootloader fail")
  1003  
  1004  	bootloader.ForceError(nil)
  1005  	bl := bootloadertest.Mock("trusted", "").WithTrustedAssets()
  1006  	bootloader.Force(bl)
  1007  	defer bootloader.Force(nil)
  1008  
  1009  	bl.TrustedAssetsErr = fmt.Errorf("fail")
  1010  	obs, err = boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
  1011  	c.Assert(obs, IsNil)
  1012  	c.Assert(err, ErrorMatches, `cannot list "trusted" bootloader trusted assets: fail`)
  1013  	// failed listing trusted assets
  1014  	c.Check(bl.TrustedAssetsCalls, Equals, 1)
  1015  
  1016  	// grab a new bootloader mock
  1017  	bl = bootloadertest.Mock("trusted", "").WithTrustedAssets()
  1018  	bootloader.Force(bl)
  1019  	bl.TrustedAssetsList = []string{"asset"}
  1020  
  1021  	obs, err = boot.TrustedAssetsUpdateObserverForModel(uc20Model, gadgetDir)
  1022  	c.Assert(err, IsNil)
  1023  	c.Assert(obs, NotNil)
  1024  	c.Check(bl.TrustedAssetsCalls, Equals, 2)
  1025  
  1026  	// no modeenv
  1027  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  1028  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1029  	c.Assert(err, ErrorMatches, `cannot load modeenv: .* no such file or directory`)
  1030  	c.Check(res, Equals, gadget.ChangeAbort)
  1031  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  1032  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1033  	c.Assert(err, ErrorMatches, `cannot load modeenv: .* no such file or directory`)
  1034  	c.Check(res, Equals, gadget.ChangeAbort)
  1035  
  1036  	m := boot.Modeenv{
  1037  		Mode: "run",
  1038  	}
  1039  	err = m.WriteTo("")
  1040  	c.Assert(err, IsNil)
  1041  
  1042  	// no source file, hash will fail
  1043  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  1044  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1045  	c.Assert(err, ErrorMatches, `cannot open asset file: .*/foobar: no such file or directory`)
  1046  	c.Check(res, Equals, gadget.ChangeAbort)
  1047  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  1048  		&gadget.ContentChange{Before: filepath.Join(d, "before"), After: filepath.Join(d, "foobar")})
  1049  	c.Assert(err, ErrorMatches, `cannot open asset file: .*/before: no such file or directory`)
  1050  	c.Check(res, Equals, gadget.ChangeAbort)
  1051  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  1052  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1053  	c.Assert(err, ErrorMatches, `cannot open asset file: .*/foobar: no such file or directory`)
  1054  	c.Check(res, Equals, gadget.ChangeAbort)
  1055  }
  1056  
  1057  func (s *assetsSuite) TestUpdateObserverUpdateRepeatedAssetErr(c *C) {
  1058  	d := c.MkDir()
  1059  	root := c.MkDir()
  1060  
  1061  	bl := bootloadertest.Mock("trusted", "").WithTrustedAssets()
  1062  	bootloader.Force(bl)
  1063  	defer bootloader.Force(nil)
  1064  	bl.TrustedAssetsList = []string{"asset"}
  1065  
  1066  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1067  
  1068  	// we are already tracking 2 assets, this is an unexpected state for observing content updates
  1069  	m := boot.Modeenv{
  1070  		Mode: "run",
  1071  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1072  			"asset": {"one", "two"},
  1073  		},
  1074  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1075  			"asset": {"one", "two"},
  1076  		},
  1077  	}
  1078  	err := m.WriteTo("")
  1079  	c.Assert(err, IsNil)
  1080  
  1081  	// and the source file
  1082  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), nil, 0644)
  1083  	c.Assert(err, IsNil)
  1084  
  1085  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  1086  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1087  	c.Assert(err, ErrorMatches, `cannot reuse asset name "asset"`)
  1088  	c.Check(res, Equals, gadget.ChangeAbort)
  1089  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  1090  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1091  	c.Assert(err, ErrorMatches, `cannot reuse asset name "asset"`)
  1092  	c.Check(res, Equals, gadget.ChangeAbort)
  1093  }
  1094  
  1095  func (s *assetsSuite) TestUpdateObserverUpdateAfterSuccessfulBootMocked(c *C) {
  1096  	//observe an update in a scenario when a mid-gadget-update reboot
  1097  	//happened and we have successfully booted with new assets only, but the
  1098  	//update is incomplete and gets started again
  1099  
  1100  	d := c.MkDir()
  1101  	backups := c.MkDir()
  1102  	root := c.MkDir()
  1103  
  1104  	// try to arrange the backups like the updater would do it
  1105  	before := []byte("before")
  1106  	beforeHash := "2df0976fd45ba2392dc7985cdfb7c2d096c1ea4917929dd7a0e9bffae90a443271e702663fc6a4189c1f4ab3ce7daee3"
  1107  	err := ioutil.WriteFile(filepath.Join(backups, "asset.backup"), before, 0644)
  1108  	c.Assert(err, IsNil)
  1109  
  1110  	data := []byte("foobar")
  1111  	// SHA3-384
  1112  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  1113  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
  1114  	c.Assert(err, IsNil)
  1115  
  1116  	// pretend we rebooted mid update and have successfully booted with the
  1117  	// new assets already, the old asset may have been dropped from the cache already
  1118  	cache := boot.NewTrustedAssetsCache(dirs.SnapBootAssetsDir)
  1119  	_, err = cache.Add(filepath.Join(d, "foobar"), "trusted", "asset")
  1120  	c.Assert(err, IsNil)
  1121  	// file is in the cache
  1122  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1123  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
  1124  	})
  1125  	// and similarly, only the new asset in modeenv
  1126  	m := boot.Modeenv{
  1127  		Mode: "run",
  1128  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1129  			"asset": {dataHash},
  1130  		},
  1131  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1132  			"asset": {dataHash},
  1133  		},
  1134  	}
  1135  	err = m.WriteTo("")
  1136  	c.Assert(err, IsNil)
  1137  
  1138  	s.bootloaderWithTrustedAssets(c, []string{
  1139  		"asset",
  1140  	})
  1141  
  1142  	// we get an observer for UC20
  1143  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1144  
  1145  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  1146  		&gadget.ContentChange{
  1147  			After: filepath.Join(d, "foobar"),
  1148  			// original content would get backed up by the updater
  1149  			Before: filepath.Join(backups, "asset.backup"),
  1150  		})
  1151  	c.Assert(err, IsNil)
  1152  	c.Check(res, Equals, gadget.ChangeApply)
  1153  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  1154  		&gadget.ContentChange{
  1155  			After: filepath.Join(d, "foobar"),
  1156  			// original content
  1157  			Before: filepath.Join(backups, "asset.backup"),
  1158  		})
  1159  	c.Assert(err, IsNil)
  1160  	c.Check(res, Equals, gadget.ChangeApply)
  1161  
  1162  	// all files are in cache
  1163  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1164  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
  1165  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", beforeHash)),
  1166  	})
  1167  	// check modeenv
  1168  	newM, err := boot.ReadModeenv("")
  1169  	c.Assert(err, IsNil)
  1170  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  1171  		// original asset is restored, listed first
  1172  		"asset": {beforeHash, dataHash},
  1173  	})
  1174  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  1175  		// same here
  1176  		"asset": {beforeHash, dataHash},
  1177  	})
  1178  }
  1179  
  1180  func (s *assetsSuite) TestUpdateObserverRollbackModeenvManipulationMocked(c *C) {
  1181  	root := c.MkDir()
  1182  	rootSeed := c.MkDir()
  1183  	d := c.MkDir()
  1184  	backups := c.MkDir()
  1185  
  1186  	tab := s.bootloaderWithTrustedAssets(c, []string{
  1187  		"asset",
  1188  		"nested/other-asset",
  1189  		"shim",
  1190  	})
  1191  
  1192  	data := []byte("foobar")
  1193  	// SHA3-384
  1194  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  1195  	// file exists in both run and seed bootloader rootdirs
  1196  	c.Assert(ioutil.WriteFile(filepath.Join(root, "asset"), data, 0644), IsNil)
  1197  	c.Assert(ioutil.WriteFile(filepath.Join(rootSeed, "asset"), data, 0644), IsNil)
  1198  	// and in the gadget
  1199  	c.Assert(ioutil.WriteFile(filepath.Join(d, "asset"), data, 0644), IsNil)
  1200  	// would be listed as Before
  1201  	c.Assert(ioutil.WriteFile(filepath.Join(backups, "asset.backup"), data, 0644), IsNil)
  1202  
  1203  	shim := []byte("shim")
  1204  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  1205  	// only exists in seed bootloader rootdir
  1206  	c.Assert(ioutil.WriteFile(filepath.Join(rootSeed, "shim"), shim, 0644), IsNil)
  1207  	// and in the gadget
  1208  	c.Assert(ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644), IsNil)
  1209  	// would be listed as Before
  1210  	c.Assert(ioutil.WriteFile(filepath.Join(backups, "shim.backup"), data, 0644), IsNil)
  1211  
  1212  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0755), IsNil)
  1213  	// mock some files in cache
  1214  	for _, name := range []string{
  1215  		fmt.Sprintf("asset-%s", dataHash),
  1216  		fmt.Sprintf("shim-%s", shimHash),
  1217  		"shim-newshimhash",
  1218  		"asset-newhash",
  1219  		"other-asset-newotherhash",
  1220  	} {
  1221  		err := ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "trusted", name), nil, 0644)
  1222  		c.Assert(err, IsNil)
  1223  	}
  1224  
  1225  	// we get an observer for UC20
  1226  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1227  	// the list of trusted assets is obtained upfront
  1228  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
  1229  
  1230  	m := boot.Modeenv{
  1231  		Mode: "run",
  1232  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1233  			// new version added during update
  1234  			"asset": {dataHash, "newhash"},
  1235  		},
  1236  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1237  			// no new version added during update
  1238  			"asset": {dataHash},
  1239  			// new version added during update
  1240  			"shim": {shimHash, "newshimhash"},
  1241  			// completely new file
  1242  			"other-asset": {"newotherhash"},
  1243  		},
  1244  	}
  1245  	err := m.WriteTo("")
  1246  	c.Assert(err, IsNil)
  1247  
  1248  	res, err := obs.Observe(gadget.ContentRollback, mockRunBootStruct, root, "asset",
  1249  		&gadget.ContentChange{
  1250  			After:  filepath.Join(d, "asset"),
  1251  			Before: filepath.Join(backups, "asset.backup"),
  1252  		})
  1253  	c.Assert(err, IsNil)
  1254  	c.Check(res, Equals, gadget.ChangeApply)
  1255  	res, err = obs.Observe(gadget.ContentRollback, mockRunBootStruct, root, "shim",
  1256  		&gadget.ContentChange{
  1257  			After: filepath.Join(d, "shim"),
  1258  			// no before content, new file
  1259  		})
  1260  	c.Assert(err, IsNil)
  1261  	c.Check(res, Equals, gadget.ChangeApply)
  1262  	// observe the recovery struct
  1263  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, rootSeed, "shim",
  1264  		&gadget.ContentChange{
  1265  			After:  filepath.Join(d, "shim"),
  1266  			Before: filepath.Join(backups, "shim.backup"),
  1267  		})
  1268  	c.Assert(err, IsNil)
  1269  	c.Check(res, Equals, gadget.ChangeApply)
  1270  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, rootSeed, "asset",
  1271  		&gadget.ContentChange{
  1272  			After:  filepath.Join(d, "asset"),
  1273  			Before: filepath.Join(backups, "asset.backup"),
  1274  		})
  1275  	c.Assert(err, IsNil)
  1276  	c.Check(res, Equals, gadget.ChangeApply)
  1277  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, rootSeed, "nested/other-asset",
  1278  		&gadget.ContentChange{
  1279  			After: filepath.Join(d, "asset"),
  1280  		})
  1281  	c.Assert(err, IsNil)
  1282  	c.Check(res, Equals, gadget.ChangeApply)
  1283  	// all files are in cache
  1284  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1285  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
  1286  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
  1287  	})
  1288  	// check modeenv
  1289  	newM, err := boot.ReadModeenv("")
  1290  	c.Assert(err, IsNil)
  1291  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  1292  		"asset": {dataHash},
  1293  	})
  1294  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  1295  		"asset": {dataHash},
  1296  		"shim":  {shimHash},
  1297  	})
  1298  }
  1299  
  1300  func (s *assetsSuite) TestUpdateObserverRollbackFileSanity(c *C) {
  1301  	root := c.MkDir()
  1302  
  1303  	tab := s.bootloaderWithTrustedAssets(c, []string{"asset"})
  1304  
  1305  	// we get an observer for UC20
  1306  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1307  	// list of trusted assets is obtained upfront
  1308  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
  1309  
  1310  	// sane state of modeenv before rollback
  1311  	m := boot.Modeenv{
  1312  		Mode: "run",
  1313  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1314  			// only one hash is listed, indicating it's a new file
  1315  			"asset": {"newhash"},
  1316  		},
  1317  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1318  			// same thing
  1319  			"asset": {"newhash"},
  1320  		},
  1321  	}
  1322  	err := m.WriteTo("")
  1323  	c.Assert(err, IsNil)
  1324  	// file does not exist on disk
  1325  	res, err := obs.Observe(gadget.ContentRollback, mockRunBootStruct, root, "asset",
  1326  		&gadget.ContentChange{})
  1327  	c.Assert(err, IsNil)
  1328  	c.Check(res, Equals, gadget.ChangeApply)
  1329  	// observe the recovery struct
  1330  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, root, "asset",
  1331  		&gadget.ContentChange{})
  1332  	c.Assert(err, IsNil)
  1333  	c.Check(res, Equals, gadget.ChangeApply)
  1334  	// check modeenv
  1335  	newM, err := boot.ReadModeenv("")
  1336  	c.Assert(err, IsNil)
  1337  	c.Check(newM.CurrentTrustedBootAssets, HasLen, 0)
  1338  	c.Check(newM.CurrentTrustedRecoveryBootAssets, HasLen, 0)
  1339  
  1340  	// new observer
  1341  	obs, _ = s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1342  	m = boot.Modeenv{
  1343  		Mode: "run",
  1344  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1345  			// only one hash is listed, indicating it's a new file
  1346  			"asset": {"newhash", "bogushash"},
  1347  		},
  1348  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1349  			// same thing
  1350  			"asset": {"newhash", "bogushash"},
  1351  		},
  1352  	}
  1353  	err = m.WriteTo("")
  1354  	c.Assert(err, IsNil)
  1355  	// again, file does not exist on disk, but we expected it to be there
  1356  	res, err = obs.Observe(gadget.ContentRollback, mockRunBootStruct, root, "asset",
  1357  		&gadget.ContentChange{})
  1358  	c.Assert(err, ErrorMatches, `tracked asset "asset" is unexpectedly missing from disk`)
  1359  	c.Check(res, Equals, gadget.ChangeAbort)
  1360  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, root, "asset",
  1361  		&gadget.ContentChange{})
  1362  	c.Assert(err, ErrorMatches, `tracked asset "asset" is unexpectedly missing from disk`)
  1363  	c.Check(res, Equals, gadget.ChangeAbort)
  1364  
  1365  	// create the file which will fail checksum check
  1366  	err = ioutil.WriteFile(filepath.Join(root, "asset"), nil, 0644)
  1367  	c.Assert(err, IsNil)
  1368  	// once more, the file exists on disk, but has unexpected checksum
  1369  	res, err = obs.Observe(gadget.ContentRollback, mockRunBootStruct, root, "asset",
  1370  		&gadget.ContentChange{})
  1371  	c.Assert(err, ErrorMatches, `unexpected content of existing asset "asset"`)
  1372  	c.Check(res, Equals, gadget.ChangeAbort)
  1373  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, root, "asset",
  1374  		&gadget.ContentChange{})
  1375  	c.Assert(err, ErrorMatches, `unexpected content of existing asset "asset"`)
  1376  	c.Check(res, Equals, gadget.ChangeAbort)
  1377  }
  1378  
  1379  func (s *assetsSuite) TestUpdateObserverUpdateRollbackGrub(c *C) {
  1380  	// exercise a full update/rollback cycle with grub
  1381  
  1382  	gadgetDir := c.MkDir()
  1383  	bootDir := c.MkDir()
  1384  	seedDir := c.MkDir()
  1385  
  1386  	// prepare a marker for grub bootloader
  1387  	c.Assert(ioutil.WriteFile(filepath.Join(gadgetDir, "grub.conf"), nil, 0644), IsNil)
  1388  
  1389  	// we get an observer for UC20
  1390  	s.stampSealedKeys(c, dirs.GlobalRootDir)
  1391  	obs, _ := s.uc20UpdateObserver(c, gadgetDir)
  1392  
  1393  	cache := boot.NewTrustedAssetsCache(dirs.SnapBootAssetsDir)
  1394  
  1395  	for _, dir := range []struct {
  1396  		root              string
  1397  		fileWithContent   [][]string
  1398  		addContentToCache bool
  1399  	}{
  1400  		{
  1401  			// data of boot bootloader
  1402  			root: bootDir,
  1403  			// SHA3-384: 0d0c6522fcc813770f2bb9ca68ad3b4f0ccc6b4bfbd2e8497030079e6146f92177ad8f6f83d96ab61d7d42f5228a4389
  1404  			fileWithContent: [][]string{
  1405  				{"EFI/boot/grubx64.efi", "grub efi"},
  1406  			},
  1407  			addContentToCache: true,
  1408  		}, {
  1409  			// data of seed bootloader
  1410  			root: seedDir,
  1411  			fileWithContent: [][]string{
  1412  				// SHA3-384: 6c3e6fc78ade5aadc5f9f0603a127346cc174436eb5e0188e108a376c3ba4d8951c460a8f51674e797c06951f74cb10d
  1413  				{"EFI/boot/grubx64.efi", "recovery grub efi"},
  1414  				// SHA3-384: c0437507ac094a7e9c699725cc0a4726cd10799af9eb79bbeaa136c2773163c80432295c2a04d3aa2ddd535ce8f1a12b
  1415  				{"EFI/boot/bootx64.efi", "recovery shim efi"},
  1416  			},
  1417  			addContentToCache: true,
  1418  		}, {
  1419  			// gadget content
  1420  			root: gadgetDir,
  1421  			fileWithContent: [][]string{
  1422  				// SHA3-384: f9554844308e89b565c1cdbcbdb9b09b8210dd2f1a11cb3b361de0a59f780ae3d4bd6941729a60e0f8ce15b2edef605d
  1423  				{"grubx64.efi", "new grub efi"},
  1424  				// SHA3-384: cc0663cc7e6c7ada990261c3ff1d72da001dc02451558716422d3d2443b8789463363c9ff0cd1b853c6ced3e8e7dc39d
  1425  				{"bootx64.efi", "new recovery shim efi"},
  1426  				{"grub.conf", "grub from gadget"},
  1427  			},
  1428  		},
  1429  		// just the markers
  1430  		{
  1431  			root: bootDir,
  1432  			fileWithContent: [][]string{
  1433  				{"EFI/ubuntu/grub.cfg", "grub marker"},
  1434  			},
  1435  		}, {
  1436  			root: seedDir,
  1437  			fileWithContent: [][]string{
  1438  				{"EFI/ubuntu/grub.cfg", "grub marker"},
  1439  			},
  1440  		},
  1441  	} {
  1442  		for _, f := range dir.fileWithContent {
  1443  			p := filepath.Join(dir.root, f[0])
  1444  			err := os.MkdirAll(filepath.Dir(p), 0755)
  1445  			c.Assert(err, IsNil)
  1446  			err = ioutil.WriteFile(p, []byte(f[1]), 0644)
  1447  			c.Assert(err, IsNil)
  1448  			if dir.addContentToCache {
  1449  				_, err = cache.Add(p, "grub", filepath.Base(p))
  1450  				c.Assert(err, IsNil)
  1451  			}
  1452  		}
  1453  	}
  1454  	cacheContentBefore := []string{
  1455  		// recovery shim
  1456  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "bootx64.efi-c0437507ac094a7e9c699725cc0a4726cd10799af9eb79bbeaa136c2773163c80432295c2a04d3aa2ddd535ce8f1a12b"),
  1457  		// boot bootloader
  1458  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "grubx64.efi-0d0c6522fcc813770f2bb9ca68ad3b4f0ccc6b4bfbd2e8497030079e6146f92177ad8f6f83d96ab61d7d42f5228a4389"),
  1459  		// recovery bootloader
  1460  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "grubx64.efi-6c3e6fc78ade5aadc5f9f0603a127346cc174436eb5e0188e108a376c3ba4d8951c460a8f51674e797c06951f74cb10d"),
  1461  	}
  1462  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "grub", "*"), cacheContentBefore)
  1463  	// current files are tracked
  1464  	m := boot.Modeenv{
  1465  		Mode: "run",
  1466  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1467  			"grubx64.efi": {"0d0c6522fcc813770f2bb9ca68ad3b4f0ccc6b4bfbd2e8497030079e6146f92177ad8f6f83d96ab61d7d42f5228a4389"},
  1468  		},
  1469  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1470  			"grubx64.efi": {"6c3e6fc78ade5aadc5f9f0603a127346cc174436eb5e0188e108a376c3ba4d8951c460a8f51674e797c06951f74cb10d"},
  1471  			"bootx64.efi": {"c0437507ac094a7e9c699725cc0a4726cd10799af9eb79bbeaa136c2773163c80432295c2a04d3aa2ddd535ce8f1a12b"},
  1472  		},
  1473  	}
  1474  	err := m.WriteTo("")
  1475  	c.Assert(err, IsNil)
  1476  
  1477  	// updates first
  1478  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, bootDir, "EFI/boot/grubx64.efi",
  1479  		&gadget.ContentChange{After: filepath.Join(gadgetDir, "grubx64.efi")})
  1480  	c.Assert(err, IsNil)
  1481  	c.Check(res, Equals, gadget.ChangeApply)
  1482  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, seedDir, "EFI/boot/grubx64.efi",
  1483  		&gadget.ContentChange{After: filepath.Join(gadgetDir, "grubx64.efi")})
  1484  	c.Assert(err, IsNil)
  1485  	c.Check(res, Equals, gadget.ChangeApply)
  1486  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, seedDir, "EFI/boot/bootx64.efi",
  1487  		&gadget.ContentChange{After: filepath.Join(gadgetDir, "bootx64.efi")})
  1488  	c.Assert(err, IsNil)
  1489  	c.Check(res, Equals, gadget.ChangeApply)
  1490  	// grub.cfg on ubuntu-seed and ubuntu-boot is managed by snapd
  1491  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, seedDir, "EFI/ubuntu/grub.cfg",
  1492  		&gadget.ContentChange{After: filepath.Join(gadgetDir, "grub.conf")})
  1493  	c.Assert(err, IsNil)
  1494  	c.Check(res, Equals, gadget.ChangeIgnore)
  1495  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, seedDir, "EFI/ubuntu/grub.cfg",
  1496  		&gadget.ContentChange{After: filepath.Join(gadgetDir, "grub.conf")})
  1497  	c.Assert(err, IsNil)
  1498  	c.Check(res, Equals, gadget.ChangeIgnore)
  1499  
  1500  	// verify cache contents
  1501  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "grub", "*"), []string{
  1502  		// recovery shim
  1503  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "bootx64.efi-c0437507ac094a7e9c699725cc0a4726cd10799af9eb79bbeaa136c2773163c80432295c2a04d3aa2ddd535ce8f1a12b"),
  1504  		// new recovery shim
  1505  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "bootx64.efi-cc0663cc7e6c7ada990261c3ff1d72da001dc02451558716422d3d2443b8789463363c9ff0cd1b853c6ced3e8e7dc39d"),
  1506  		// boot bootloader
  1507  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "grubx64.efi-0d0c6522fcc813770f2bb9ca68ad3b4f0ccc6b4bfbd2e8497030079e6146f92177ad8f6f83d96ab61d7d42f5228a4389"),
  1508  		// recovery bootloader
  1509  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "grubx64.efi-6c3e6fc78ade5aadc5f9f0603a127346cc174436eb5e0188e108a376c3ba4d8951c460a8f51674e797c06951f74cb10d"),
  1510  		// new recovery and boot bootloader
  1511  		filepath.Join(dirs.SnapBootAssetsDir, "grub", "grubx64.efi-f9554844308e89b565c1cdbcbdb9b09b8210dd2f1a11cb3b361de0a59f780ae3d4bd6941729a60e0f8ce15b2edef605d"),
  1512  	})
  1513  
  1514  	// and modeenv contents
  1515  	newM, err := boot.ReadModeenv("")
  1516  	c.Assert(err, IsNil)
  1517  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  1518  		"grubx64.efi": {
  1519  			// old hash
  1520  			"0d0c6522fcc813770f2bb9ca68ad3b4f0ccc6b4bfbd2e8497030079e6146f92177ad8f6f83d96ab61d7d42f5228a4389",
  1521  			// update
  1522  			"f9554844308e89b565c1cdbcbdb9b09b8210dd2f1a11cb3b361de0a59f780ae3d4bd6941729a60e0f8ce15b2edef605d",
  1523  		},
  1524  	})
  1525  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  1526  		"grubx64.efi": {
  1527  			// old hash
  1528  			"6c3e6fc78ade5aadc5f9f0603a127346cc174436eb5e0188e108a376c3ba4d8951c460a8f51674e797c06951f74cb10d",
  1529  			// update
  1530  			"f9554844308e89b565c1cdbcbdb9b09b8210dd2f1a11cb3b361de0a59f780ae3d4bd6941729a60e0f8ce15b2edef605d",
  1531  		},
  1532  		"bootx64.efi": {
  1533  			// old hash
  1534  			"c0437507ac094a7e9c699725cc0a4726cd10799af9eb79bbeaa136c2773163c80432295c2a04d3aa2ddd535ce8f1a12b",
  1535  			// update
  1536  			"cc0663cc7e6c7ada990261c3ff1d72da001dc02451558716422d3d2443b8789463363c9ff0cd1b853c6ced3e8e7dc39d",
  1537  		},
  1538  	})
  1539  
  1540  	// hiya, update failed, pretend we do a rollback, files on disk are as
  1541  	// if they were restored
  1542  
  1543  	res, err = obs.Observe(gadget.ContentRollback, mockRunBootStruct, bootDir, "EFI/boot/grubx64.efi",
  1544  		&gadget.ContentChange{})
  1545  	c.Assert(err, IsNil)
  1546  	c.Check(res, Equals, gadget.ChangeApply)
  1547  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, seedDir, "EFI/boot/grubx64.efi",
  1548  		&gadget.ContentChange{})
  1549  	c.Assert(err, IsNil)
  1550  	c.Check(res, Equals, gadget.ChangeApply)
  1551  	res, err = obs.Observe(gadget.ContentRollback, mockSeedStruct, seedDir, "EFI/boot/bootx64.efi",
  1552  		&gadget.ContentChange{})
  1553  	c.Assert(err, IsNil)
  1554  	c.Check(res, Equals, gadget.ChangeApply)
  1555  
  1556  	// modeenv is back to the initial state
  1557  	afterRollbackM, err := boot.ReadModeenv("")
  1558  	c.Assert(err, IsNil)
  1559  	c.Check(afterRollbackM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  1560  	c.Check(afterRollbackM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  1561  	// and cache is back to the same state as before
  1562  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "grub", "*"), cacheContentBefore)
  1563  }
  1564  
  1565  func (s *assetsSuite) TestUpdateObserverCanceledSimpleAfterBackupMocked(c *C) {
  1566  	d := c.MkDir()
  1567  	root := c.MkDir()
  1568  
  1569  	m := boot.Modeenv{
  1570  		Mode: "run",
  1571  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1572  			"asset": {"assethash"},
  1573  			"shim":  {"shimhash"},
  1574  		},
  1575  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1576  			"asset": {"recoveryhash"},
  1577  		},
  1578  	}
  1579  	err := m.WriteTo("")
  1580  	c.Assert(err, IsNil)
  1581  
  1582  	// mock some files in cache
  1583  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0755), IsNil)
  1584  	for _, name := range []string{
  1585  		"shim-shimhash",
  1586  		"asset-assethash",
  1587  		"asset-recoveryhash",
  1588  	} {
  1589  		err = ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "trusted", name), nil, 0644)
  1590  		c.Assert(err, IsNil)
  1591  	}
  1592  
  1593  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  1594  
  1595  	// we get an observer for UC20
  1596  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1597  
  1598  	data := []byte("foobar")
  1599  	// SHA3-384
  1600  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  1601  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
  1602  	c.Assert(err, IsNil)
  1603  	shim := []byte("shim")
  1604  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  1605  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
  1606  	c.Assert(err, IsNil)
  1607  
  1608  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  1609  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1610  	c.Assert(err, IsNil)
  1611  	c.Check(res, Equals, gadget.ChangeApply)
  1612  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
  1613  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1614  	c.Assert(err, IsNil)
  1615  	c.Check(res, Equals, gadget.ChangeApply)
  1616  	// observe the recovery struct
  1617  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
  1618  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1619  	c.Assert(err, IsNil)
  1620  	c.Check(res, Equals, gadget.ChangeApply)
  1621  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  1622  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1623  	c.Assert(err, IsNil)
  1624  	c.Check(res, Equals, gadget.ChangeApply)
  1625  	// files are in cache
  1626  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1627  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
  1628  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  1629  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-recoveryhash"),
  1630  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
  1631  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-shimhash"),
  1632  	})
  1633  	// check modeenv
  1634  	newM, err := boot.ReadModeenv("")
  1635  	c.Assert(err, IsNil)
  1636  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  1637  		"asset": {"assethash", dataHash},
  1638  		"shim":  {"shimhash", shimHash},
  1639  	})
  1640  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  1641  		"asset": {"recoveryhash", dataHash},
  1642  		"shim":  {shimHash},
  1643  	})
  1644  	resealCalls := 0
  1645  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1646  		resealCalls++
  1647  		return nil
  1648  	})
  1649  	defer restore()
  1650  
  1651  	// update is canceled
  1652  	err = obs.Canceled()
  1653  	c.Assert(err, IsNil)
  1654  	// modeenv is back to initial state
  1655  	afterCancelM, err := boot.ReadModeenv("")
  1656  	c.Assert(err, IsNil)
  1657  	c.Check(afterCancelM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  1658  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  1659  	// unused assets were dropped
  1660  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1661  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  1662  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-recoveryhash"),
  1663  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-shimhash"),
  1664  	})
  1665  
  1666  	c.Check(resealCalls, Equals, 1)
  1667  }
  1668  
  1669  func (s *assetsSuite) TestUpdateObserverCanceledPartiallyUsedMocked(c *C) {
  1670  	// cancel an update where one of the assets is already used and canceling does not remove it from the cache
  1671  
  1672  	d := c.MkDir()
  1673  	root := c.MkDir()
  1674  
  1675  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  1676  
  1677  	data := []byte("foobar")
  1678  	// SHA3-384
  1679  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  1680  	err := ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
  1681  	c.Assert(err, IsNil)
  1682  	shim := []byte("shim")
  1683  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  1684  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
  1685  	c.Assert(err, IsNil)
  1686  
  1687  	// mock some files in cache
  1688  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0755), IsNil)
  1689  	for _, name := range []string{
  1690  		"shim-shimhash",
  1691  		"asset-assethash",
  1692  		fmt.Sprintf("shim-%s", shimHash),
  1693  	} {
  1694  		err = ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "trusted", name), nil, 0644)
  1695  		c.Assert(err, IsNil)
  1696  	}
  1697  
  1698  	// we get an observer for UC20
  1699  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1700  
  1701  	m := boot.Modeenv{
  1702  		Mode: "run",
  1703  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1704  			"asset": {"assethash"},
  1705  			"shim":  {"shimhash"},
  1706  		},
  1707  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1708  			"shim": {shimHash},
  1709  		},
  1710  	}
  1711  	err = m.WriteTo("")
  1712  	c.Assert(err, IsNil)
  1713  
  1714  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  1715  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1716  	c.Assert(err, IsNil)
  1717  	c.Check(res, Equals, gadget.ChangeApply)
  1718  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
  1719  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1720  	c.Assert(err, IsNil)
  1721  	c.Check(res, Equals, gadget.ChangeApply)
  1722  	// observe the recovery struct
  1723  	// XXX: shim is not updated
  1724  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  1725  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  1726  	c.Assert(err, IsNil)
  1727  	c.Check(res, Equals, gadget.ChangeApply)
  1728  	// files are in cache
  1729  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1730  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
  1731  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  1732  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
  1733  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-shimhash"),
  1734  	})
  1735  	// check modeenv
  1736  	newM, err := boot.ReadModeenv("")
  1737  	c.Assert(err, IsNil)
  1738  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  1739  		"asset": {"assethash", dataHash},
  1740  		"shim":  {"shimhash", shimHash},
  1741  	})
  1742  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  1743  		"asset": {dataHash},
  1744  		"shim":  {shimHash},
  1745  	})
  1746  	// update is canceled
  1747  	err = obs.Canceled()
  1748  	c.Assert(err, IsNil)
  1749  	// modeenv is back to initial state
  1750  	afterCancelM, err := boot.ReadModeenv("")
  1751  	c.Assert(err, IsNil)
  1752  	c.Check(afterCancelM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  1753  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  1754  	// unused assets were dropped
  1755  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1756  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  1757  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
  1758  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-shimhash"),
  1759  	})
  1760  }
  1761  
  1762  func (s *assetsSuite) TestUpdateObserverCanceledNoActionsMocked(c *C) {
  1763  	// make sure that when no ContentUpdate actions were registered, or some
  1764  	// were registered for one bootloader, but not the other, is not
  1765  	// triggering unwanted behavior on cancel
  1766  
  1767  	d := c.MkDir()
  1768  	root := c.MkDir()
  1769  
  1770  	m := boot.Modeenv{
  1771  		Mode: "run",
  1772  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1773  			"asset": {"assethash"},
  1774  			"shim":  {"shimhash"},
  1775  		},
  1776  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1777  			"asset": {"recoveryhash"},
  1778  		},
  1779  	}
  1780  	err := m.WriteTo("")
  1781  	c.Assert(err, IsNil)
  1782  
  1783  	// mock the files in cache
  1784  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0755), IsNil)
  1785  	for _, name := range []string{
  1786  		"shim-shimhash",
  1787  		"asset-assethash",
  1788  		"asset-recoveryhash",
  1789  	} {
  1790  		err = ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "trusted", name), nil, 0644)
  1791  		c.Assert(err, IsNil)
  1792  	}
  1793  
  1794  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  1795  	// we get an observer for UC20
  1796  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1797  
  1798  	resealCalls := 0
  1799  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  1800  		resealCalls++
  1801  		return nil
  1802  	})
  1803  	defer restore()
  1804  
  1805  	// cancel the update
  1806  	err = obs.Canceled()
  1807  	c.Assert(err, IsNil)
  1808  	// modeenv is unchanged
  1809  	afterCancelM, err := boot.ReadModeenv("")
  1810  	c.Assert(err, IsNil)
  1811  	c.Check(afterCancelM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  1812  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  1813  	// unused assets were dropped
  1814  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1815  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  1816  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-recoveryhash"),
  1817  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-shimhash"),
  1818  	})
  1819  
  1820  	c.Check(resealCalls, Equals, 0)
  1821  
  1822  	err = ioutil.WriteFile(filepath.Join(d, "shim"), []byte("shim"), 0644)
  1823  	c.Assert(err, IsNil)
  1824  	// observe only recovery bootloader update, no action for run bootloader
  1825  	res, err := obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
  1826  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1827  	c.Assert(err, IsNil)
  1828  	c.Check(res, Equals, gadget.ChangeApply)
  1829  	// cancel again
  1830  	err = obs.Canceled()
  1831  	c.Assert(err, IsNil)
  1832  	afterCancelM, err = boot.ReadModeenv("")
  1833  	c.Assert(err, IsNil)
  1834  	c.Check(afterCancelM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  1835  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  1836  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1837  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  1838  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-recoveryhash"),
  1839  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-shimhash"),
  1840  	})
  1841  }
  1842  
  1843  func (s *assetsSuite) TestUpdateObserverCanceledEmptyModeenvAssets(c *C) {
  1844  	// cancel an update where the maps of trusted assets are nil/empty
  1845  	d := c.MkDir()
  1846  	root := c.MkDir()
  1847  	m := boot.Modeenv{
  1848  		Mode: "run",
  1849  	}
  1850  	err := m.WriteTo("")
  1851  	c.Assert(err, IsNil)
  1852  
  1853  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  1854  	// we get an observer for UC20
  1855  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1856  
  1857  	// trigger loading modeenv and bootloader information
  1858  	err = ioutil.WriteFile(filepath.Join(d, "shim"), []byte("shim"), 0644)
  1859  	c.Assert(err, IsNil)
  1860  	// observe an update only for the recovery bootloader, the run bootloader trusted assets remain empty
  1861  	res, err := obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
  1862  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1863  	c.Assert(err, IsNil)
  1864  	c.Check(res, Equals, gadget.ChangeApply)
  1865  
  1866  	// cancel the update
  1867  	err = obs.Canceled()
  1868  	c.Assert(err, IsNil)
  1869  	afterCancelM, err := boot.ReadModeenv("")
  1870  	c.Assert(err, IsNil)
  1871  	c.Check(afterCancelM.CurrentTrustedBootAssets, HasLen, 0)
  1872  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, HasLen, 0)
  1873  
  1874  	// get a new observer, and observe an update for run bootloader asset only
  1875  	obs, _ = s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1876  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
  1877  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1878  	c.Assert(err, IsNil)
  1879  	c.Check(res, Equals, gadget.ChangeApply)
  1880  	// cancel once more
  1881  	err = obs.Canceled()
  1882  	c.Assert(err, IsNil)
  1883  	afterCancelM, err = boot.ReadModeenv("")
  1884  	c.Assert(err, IsNil)
  1885  	c.Check(afterCancelM.CurrentTrustedBootAssets, HasLen, 0)
  1886  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, HasLen, 0)
  1887  }
  1888  
  1889  func (s *assetsSuite) TestUpdateObserverCanceledAfterRollback(c *C) {
  1890  	// pretend there are changed assets with hashes that are not listed in
  1891  	// modeenv
  1892  	d := c.MkDir()
  1893  	root := c.MkDir()
  1894  
  1895  	m := boot.Modeenv{
  1896  		Mode: "run",
  1897  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1898  			"asset": {"assethash"},
  1899  		},
  1900  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1901  			"asset": {"assethash"},
  1902  		},
  1903  	}
  1904  	err := m.WriteTo("")
  1905  	c.Assert(err, IsNil)
  1906  
  1907  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  1908  	// we get an observer for UC20
  1909  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1910  
  1911  	// trigger loading modeenv and bootloader information
  1912  	err = ioutil.WriteFile(filepath.Join(d, "shim"), []byte("shim"), 0644)
  1913  	c.Assert(err, IsNil)
  1914  	res, err := obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
  1915  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1916  	c.Assert(err, IsNil)
  1917  	c.Check(res, Equals, gadget.ChangeApply)
  1918  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
  1919  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1920  	c.Assert(err, IsNil)
  1921  	c.Check(res, Equals, gadget.ChangeApply)
  1922  
  1923  	// procure the desired state by:
  1924  	// injecting a changed asset for run bootloader
  1925  	recoveryAsset := true
  1926  	obs.InjectChangedAsset("trusted", "asset", "changehash", !recoveryAsset)
  1927  	// and a changed asset for recovery bootloader
  1928  	obs.InjectChangedAsset("trusted", "asset", "changehash", recoveryAsset)
  1929  	// completely unknown
  1930  	obs.InjectChangedAsset("trusted", "unknown", "somehash", !recoveryAsset)
  1931  
  1932  	// cancel the update
  1933  	err = obs.Canceled()
  1934  	c.Assert(err, IsNil)
  1935  	afterCancelM, err := boot.ReadModeenv("")
  1936  	c.Assert(err, IsNil)
  1937  	c.Check(afterCancelM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  1938  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  1939  }
  1940  
  1941  func (s *assetsSuite) TestUpdateObserverCanceledUnhappyCacheStillProceeds(c *C) {
  1942  	// make sure that trying to remove the file from cache will not break
  1943  	// the cancellation
  1944  
  1945  	if os.Geteuid() == 0 {
  1946  		c.Skip("the test cannot be executed by the root user")
  1947  	}
  1948  
  1949  	logBuf, restore := logger.MockLogger()
  1950  	defer restore()
  1951  
  1952  	d := c.MkDir()
  1953  	root := c.MkDir()
  1954  
  1955  	m := boot.Modeenv{
  1956  		Mode: "run",
  1957  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  1958  			"asset": {"assethash"},
  1959  		},
  1960  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  1961  			"asset": {"recoveryhash"},
  1962  		},
  1963  	}
  1964  	err := m.WriteTo("")
  1965  	c.Assert(err, IsNil)
  1966  
  1967  	// mock the files in cache
  1968  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0755), IsNil)
  1969  	for _, name := range []string{
  1970  		"asset-assethash",
  1971  		"asset-recoveryhash",
  1972  	} {
  1973  		err = ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "trusted", name), nil, 0644)
  1974  		c.Assert(err, IsNil)
  1975  	}
  1976  
  1977  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  1978  	// we get an observer for UC20
  1979  	obs, _ := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  1980  
  1981  	shim := []byte("shim")
  1982  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  1983  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
  1984  	c.Assert(err, IsNil)
  1985  	res, err := obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
  1986  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1987  	c.Assert(err, IsNil)
  1988  	c.Check(res, Equals, gadget.ChangeApply)
  1989  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
  1990  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  1991  	c.Assert(err, IsNil)
  1992  	c.Check(res, Equals, gadget.ChangeApply)
  1993  	// make sure that the cache directory state is as expected
  1994  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  1995  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  1996  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-recoveryhash"),
  1997  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
  1998  	})
  1999  	// and the file is added to the assets map
  2000  	newM, err := boot.ReadModeenv("")
  2001  	c.Assert(err, IsNil)
  2002  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  2003  		"asset": {"assethash"},
  2004  		"shim":  {shimHash},
  2005  	})
  2006  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  2007  		"asset": {"recoveryhash"},
  2008  		"shim":  {shimHash},
  2009  	})
  2010  
  2011  	// make cache directory read only and thus cache.Remove() fail
  2012  	c.Assert(os.Chmod(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0444), IsNil)
  2013  	defer os.Chmod(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0755)
  2014  
  2015  	// cancel should not fail, even though files cannot be removed from cache
  2016  	err = obs.Canceled()
  2017  	c.Assert(err, IsNil)
  2018  	afterCancelM, err := boot.ReadModeenv("")
  2019  	c.Assert(err, IsNil)
  2020  	c.Check(afterCancelM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  2021  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  2022  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  2023  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  2024  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-recoveryhash"),
  2025  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
  2026  	})
  2027  	c.Check(logBuf.String(), Matches, fmt.Sprintf(`.* cannot remove unused boot asset shim:%s: .* permission denied\n`, shimHash))
  2028  }
  2029  
  2030  func (s *assetsSuite) TestObserveSuccessfulBootNoTrusted(c *C) {
  2031  	// call to observe successful boot without any trusted assets
  2032  
  2033  	m := &boot.Modeenv{
  2034  		Mode: "run",
  2035  		// no trusted assets
  2036  	}
  2037  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2038  	c.Assert(err, IsNil)
  2039  	c.Check(drop, IsNil)
  2040  	c.Check(newM, DeepEquals, m)
  2041  }
  2042  
  2043  func (s *assetsSuite) TestObserveSuccessfulBootNoAssetsOnDisk(c *C) {
  2044  	// call to observe successful boot, but assets do not exist on disk
  2045  
  2046  	s.bootloaderWithTrustedAssets(c, []string{"asset"})
  2047  
  2048  	m := &boot.Modeenv{
  2049  		Mode: "run",
  2050  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2051  			"asset": {"assethash"},
  2052  		},
  2053  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2054  			"asset": {"assethash"},
  2055  		},
  2056  	}
  2057  
  2058  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2059  	c.Assert(err, IsNil)
  2060  	c.Check(drop, IsNil)
  2061  	// we booted without assets on disk nonetheless
  2062  	c.Check(newM.CurrentTrustedBootAssets, HasLen, 0)
  2063  	c.Check(newM.CurrentTrustedRecoveryBootAssets, HasLen, 0)
  2064  }
  2065  
  2066  func (s *assetsSuite) TestObserveSuccessfulBootAfterUpdate(c *C) {
  2067  	// call to observe successful boot
  2068  
  2069  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  2070  
  2071  	data := []byte("foobar")
  2072  	// SHA3-384
  2073  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  2074  	shim := []byte("shim")
  2075  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  2076  
  2077  	// only asset for ubuntu-boot
  2078  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
  2079  	// shim and asset for ubuntu-seed
  2080  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
  2081  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil)
  2082  
  2083  	m := &boot.Modeenv{
  2084  		Mode: "run",
  2085  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2086  			"asset": {"assethash", dataHash},
  2087  		},
  2088  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2089  			"asset": {"recoveryassethash", dataHash},
  2090  			"shim":  {"recoveryshimhash", shimHash},
  2091  		},
  2092  	}
  2093  
  2094  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2095  	c.Assert(err, IsNil)
  2096  	c.Assert(newM, NotNil)
  2097  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  2098  		"asset": {dataHash},
  2099  	})
  2100  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  2101  		"asset": {dataHash},
  2102  		"shim":  {shimHash},
  2103  	})
  2104  	c.Check(drop, HasLen, 3)
  2105  	for i, en := range []struct {
  2106  		assetName, hash string
  2107  	}{
  2108  		{"asset", "assethash"},
  2109  		{"asset", "recoveryassethash"},
  2110  		{"shim", "recoveryshimhash"},
  2111  	} {
  2112  		c.Check(drop[i].Equals("trusted", en.assetName, en.hash), IsNil)
  2113  	}
  2114  }
  2115  
  2116  func (s *assetsSuite) TestObserveSuccessfulBootWithUnexpected(c *C) {
  2117  	// call to observe successful boot, but the asset we booted with is unexpected
  2118  
  2119  	s.bootloaderWithTrustedAssets(c, []string{"asset"})
  2120  
  2121  	data := []byte("foobar")
  2122  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  2123  	unexpected := []byte("unexpected")
  2124  	unexpectedHash := "2c823b62c52e614e48faac7e8b1fbb8ff3aee4d06b6f7fe5bd7d64953162b6e9879ead4827fa19c8c9a514585ddac94c"
  2125  
  2126  	// asset for ubuntu-boot
  2127  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), unexpected, 0644), IsNil)
  2128  	// and for ubuntu-seed
  2129  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), unexpected, 0644), IsNil)
  2130  
  2131  	m := &boot.Modeenv{
  2132  		Mode: "run",
  2133  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2134  			"asset": {"assethash", dataHash},
  2135  		},
  2136  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2137  			"asset": {"recoveryassethash", dataHash},
  2138  		},
  2139  	}
  2140  
  2141  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2142  	c.Assert(err, ErrorMatches, fmt.Sprintf(`system booted with unexpected run mode bootloader asset "asset" hash %v`, unexpectedHash))
  2143  	c.Assert(newM, IsNil)
  2144  	c.Check(drop, HasLen, 0)
  2145  
  2146  	// make the run bootloader asset an expected one, we should still fail
  2147  	// on the recovery bootloader asset
  2148  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
  2149  
  2150  	newM, drop, err = boot.ObserveSuccessfulBootWithAssets(m)
  2151  	c.Assert(err, ErrorMatches, fmt.Sprintf(`system booted with unexpected recovery bootloader asset "asset" hash %v`, unexpectedHash))
  2152  	c.Assert(newM, IsNil)
  2153  	c.Check(drop, HasLen, 0)
  2154  }
  2155  
  2156  func (s *assetsSuite) TestObserveSuccessfulBootSingleEntries(c *C) {
  2157  	// call to observe successful boot
  2158  
  2159  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  2160  
  2161  	data := []byte("foobar")
  2162  	// SHA3-384
  2163  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  2164  	shim := []byte("shim")
  2165  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  2166  
  2167  	// only asset for ubuntu-boot
  2168  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
  2169  	// shim and asset for ubuntu-seed
  2170  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
  2171  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil)
  2172  
  2173  	m := &boot.Modeenv{
  2174  		Mode: "run",
  2175  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2176  			"asset": {dataHash},
  2177  		},
  2178  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2179  			"asset": {dataHash},
  2180  			"shim":  {shimHash},
  2181  		},
  2182  	}
  2183  
  2184  	// nothing is changed
  2185  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2186  	c.Assert(err, IsNil)
  2187  	c.Assert(newM, NotNil)
  2188  	c.Check(newM, DeepEquals, m)
  2189  	c.Check(drop, HasLen, 0)
  2190  }
  2191  
  2192  func (s *assetsSuite) TestObserveSuccessfulBootDropCandidateUsedByOtherBootloader(c *C) {
  2193  	// observe successful boot, an unused recovery asset of a recovery
  2194  	// bootloader is used by the ubuntu-boot bootloader, so it cannot be
  2195  	// dropped from cache
  2196  
  2197  	s.bootloaderWithTrustedAssets(c, []string{"asset"})
  2198  
  2199  	maybeDrop := []byte("maybe-drop")
  2200  	maybeDropHash := "08a99ce3af529ebbfb9a82df690007ac650635b165c3d1b416d471907fa3843270dce9cc001ea26f4afb4e0c5af05209"
  2201  	data := []byte("foobar")
  2202  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  2203  
  2204  	// ubuntu-boot booted with maybe-drop asset
  2205  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), maybeDrop, 0644), IsNil)
  2206  
  2207  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
  2208  
  2209  	m := &boot.Modeenv{
  2210  		Mode: "run",
  2211  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2212  			"asset": {maybeDropHash},
  2213  		},
  2214  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2215  			"asset": {maybeDropHash, dataHash},
  2216  		},
  2217  	}
  2218  
  2219  	// nothing is changed
  2220  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2221  	c.Assert(err, IsNil)
  2222  	c.Assert(newM, NotNil)
  2223  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  2224  		"asset": {maybeDropHash},
  2225  	})
  2226  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  2227  		"asset": {dataHash},
  2228  	})
  2229  	// nothing get dropped, maybe-drop asset is still used by the
  2230  	// ubuntu-boot bootloader
  2231  	c.Check(drop, HasLen, 0)
  2232  }
  2233  
  2234  func (s *assetsSuite) TestObserveSuccessfulBootParallelUpdate(c *C) {
  2235  	// call to observe successful boot
  2236  
  2237  	s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  2238  
  2239  	data := []byte("foobar")
  2240  	// SHA3-384
  2241  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  2242  	shim := []byte("shim")
  2243  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  2244  
  2245  	// only asset for ubuntu-boot
  2246  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
  2247  	// shim and asset for ubuntu-seed
  2248  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
  2249  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil)
  2250  
  2251  	m := &boot.Modeenv{
  2252  		Mode: "run",
  2253  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2254  			"asset": {"oldhash", dataHash},
  2255  		},
  2256  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2257  			"asset": {"oldhash", dataHash},
  2258  			"shim":  {shimHash},
  2259  		},
  2260  	}
  2261  
  2262  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2263  	c.Assert(err, IsNil)
  2264  	c.Assert(newM, NotNil)
  2265  	c.Check(newM.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
  2266  		"asset": {dataHash},
  2267  	})
  2268  	c.Check(newM.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
  2269  		"asset": {dataHash},
  2270  		"shim":  {shimHash},
  2271  	})
  2272  	// asset was updated in parallel on both partition from the same
  2273  	// oldhash that should be dropped now
  2274  	c.Check(drop, HasLen, 1)
  2275  	c.Check(drop[0].Equals("trusted", "asset", "oldhash"), IsNil)
  2276  }
  2277  
  2278  func (s *assetsSuite) TestObserveSuccessfulBootHashErr(c *C) {
  2279  	// call to observe successful boot
  2280  
  2281  	if os.Geteuid() == 0 {
  2282  		c.Skip("the test cannot be executed by the root user")
  2283  	}
  2284  
  2285  	s.bootloaderWithTrustedAssets(c, []string{"asset"})
  2286  
  2287  	data := []byte("foobar")
  2288  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  2289  
  2290  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0000), IsNil)
  2291  	c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0000), IsNil)
  2292  
  2293  	m := &boot.Modeenv{
  2294  		Mode: "run",
  2295  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2296  			"asset": {dataHash},
  2297  		},
  2298  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2299  			"asset": {dataHash},
  2300  		},
  2301  	}
  2302  
  2303  	// nothing is changed
  2304  	_, _, err := boot.ObserveSuccessfulBootWithAssets(m)
  2305  	c.Assert(err, ErrorMatches, "cannot calculate the digest of existing trusted asset: .*/asset: permission denied")
  2306  }
  2307  
  2308  func (s *assetsSuite) TestObserveSuccessfulBootDifferentMode(c *C) {
  2309  	s.bootloaderWithTrustedAssets(c, []string{"asset"})
  2310  
  2311  	m := &boot.Modeenv{
  2312  		Mode: "recover",
  2313  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2314  			"asset": {"hash-1", "hash-2"},
  2315  		},
  2316  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2317  			"asset": {"hash-3", "hash-4"},
  2318  		},
  2319  	}
  2320  
  2321  	// if we were in run mode, this would error out because the assets don't
  2322  	// exist, but we are not in run mode
  2323  	newM, drop, err := boot.ObserveSuccessfulBootWithAssets(m)
  2324  	c.Assert(err, IsNil)
  2325  	c.Assert(newM, DeepEquals, m)
  2326  	c.Assert(drop, IsNil)
  2327  }
  2328  
  2329  func (s *assetsSuite) TestCopyBootAssetsCacheHappy(c *C) {
  2330  	newRoot := c.MkDir()
  2331  	// does not fail when dir does not exist
  2332  	err := boot.CopyBootAssetsCacheToRoot(newRoot)
  2333  	c.Assert(err, IsNil)
  2334  
  2335  	// temporarily overide umask
  2336  	oldUmask := syscall.Umask(0000)
  2337  	defer syscall.Umask(oldUmask)
  2338  
  2339  	entries := []struct {
  2340  		name, content string
  2341  		mode          uint
  2342  	}{
  2343  		{"foo/bar", "1234", 0644},
  2344  		{"grub/grubx64.efi-1234", "grub content", 0622},
  2345  		{"top-level", "top level content", 0666},
  2346  		{"deeply/nested/content", "deeply nested content", 0611},
  2347  	}
  2348  
  2349  	for _, entry := range entries {
  2350  		p := filepath.Join(dirs.SnapBootAssetsDir, entry.name)
  2351  		err = os.MkdirAll(filepath.Dir(p), 0755)
  2352  		c.Assert(err, IsNil)
  2353  		err = ioutil.WriteFile(p, []byte(entry.content), os.FileMode(entry.mode))
  2354  		c.Assert(err, IsNil)
  2355  	}
  2356  
  2357  	err = boot.CopyBootAssetsCacheToRoot(newRoot)
  2358  	c.Assert(err, IsNil)
  2359  	for _, entry := range entries {
  2360  		p := filepath.Join(dirs.SnapBootAssetsDirUnder(newRoot), entry.name)
  2361  		c.Check(p, testutil.FileEquals, entry.content)
  2362  		fi, err := os.Stat(p)
  2363  		c.Assert(err, IsNil)
  2364  		c.Check(fi.Mode().Perm(), Equals, os.FileMode(entry.mode),
  2365  			Commentf("unexpected mode of copied file %q: %v", entry.name, fi.Mode().Perm()))
  2366  	}
  2367  }
  2368  
  2369  func (s *assetsSuite) TestCopyBootAssetsCacheUnhappy(c *C) {
  2370  	// non-file
  2371  	newRoot := c.MkDir()
  2372  	dirs.SnapBootAssetsDir = c.MkDir()
  2373  	p := filepath.Join(dirs.SnapBootAssetsDir, "fifo")
  2374  	syscall.Mkfifo(p, 0644)
  2375  	err := boot.CopyBootAssetsCacheToRoot(newRoot)
  2376  	c.Assert(err, ErrorMatches, `unsupported non-file entry "fifo" mode prw-.*`)
  2377  
  2378  	if os.Geteuid() == 0 {
  2379  		// the rest of the test cannot be executed by root user
  2380  		return
  2381  	}
  2382  
  2383  	// non-writable root
  2384  	newRoot = c.MkDir()
  2385  	nonWritableRoot := filepath.Join(newRoot, "non-writable")
  2386  	err = os.MkdirAll(nonWritableRoot, 0000)
  2387  	c.Assert(err, IsNil)
  2388  	dirs.SnapBootAssetsDir = c.MkDir()
  2389  	err = ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "file"), nil, 0644)
  2390  	c.Assert(err, IsNil)
  2391  	err = boot.CopyBootAssetsCacheToRoot(nonWritableRoot)
  2392  	c.Assert(err, ErrorMatches, `cannot create cache directory under new root: mkdir .*: permission denied`)
  2393  
  2394  	// file cannot be read
  2395  	newRoot = c.MkDir()
  2396  	dirs.SnapBootAssetsDir = c.MkDir()
  2397  	err = ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "file"), nil, 0000)
  2398  	c.Assert(err, IsNil)
  2399  	err = boot.CopyBootAssetsCacheToRoot(newRoot)
  2400  	c.Assert(err, ErrorMatches, `cannot copy boot asset cache file "file": failed to copy all: .*`)
  2401  
  2402  	// directory at destination cannot be recreated
  2403  	newRoot = c.MkDir()
  2404  	dirs.SnapBootAssetsDir = c.MkDir()
  2405  	// make a directory at destination non writable
  2406  	err = os.MkdirAll(dirs.SnapBootAssetsDirUnder(newRoot), 0755)
  2407  	c.Assert(err, IsNil)
  2408  	err = os.Chmod(dirs.SnapBootAssetsDirUnder(newRoot), 0000)
  2409  	c.Assert(err, IsNil)
  2410  	err = os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir, "dir"), 0755)
  2411  	c.Assert(err, IsNil)
  2412  	err = ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "dir", "file"), nil, 0000)
  2413  	c.Assert(err, IsNil)
  2414  	err = boot.CopyBootAssetsCacheToRoot(newRoot)
  2415  	c.Assert(err, ErrorMatches, `cannot recreate cache directory "dir": .*: permission denied`)
  2416  
  2417  }
  2418  
  2419  func (s *assetsSuite) TestUpdateObserverReseal(c *C) {
  2420  	// observe an update followed by reseal
  2421  
  2422  	d := c.MkDir()
  2423  	backups := c.MkDir()
  2424  	root := c.MkDir()
  2425  
  2426  	// try to arrange the backups like the updater would do it
  2427  	before := []byte("before")
  2428  	beforeHash := "2df0976fd45ba2392dc7985cdfb7c2d096c1ea4917929dd7a0e9bffae90a443271e702663fc6a4189c1f4ab3ce7daee3"
  2429  	err := ioutil.WriteFile(filepath.Join(backups, "asset.backup"), before, 0644)
  2430  	c.Assert(err, IsNil)
  2431  
  2432  	data := []byte("foobar")
  2433  	// SHA3-384
  2434  	dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
  2435  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
  2436  	c.Assert(err, IsNil)
  2437  	shim := []byte("shim")
  2438  	shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
  2439  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
  2440  	c.Assert(err, IsNil)
  2441  
  2442  	tab := s.bootloaderWithTrustedAssets(c, []string{
  2443  		"asset",
  2444  		"shim",
  2445  	})
  2446  
  2447  	// we get an observer for UC20
  2448  	obs, uc20model := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  2449  
  2450  	m := boot.Modeenv{
  2451  		Mode: "run",
  2452  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2453  			"asset": {beforeHash},
  2454  		},
  2455  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2456  			"asset": {beforeHash},
  2457  		},
  2458  		CurrentRecoverySystems: []string{"recovery-system-label"},
  2459  		CurrentKernels:         []string{"pc-kernel_500.snap"},
  2460  
  2461  		Model:          uc20model.Model(),
  2462  		BrandID:        uc20model.BrandID(),
  2463  		Grade:          string(uc20model.Grade()),
  2464  		ModelSignKeyID: uc20model.SignKeyID(),
  2465  	}
  2466  	err = m.WriteTo("")
  2467  	c.Assert(err, IsNil)
  2468  
  2469  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  2470  		&gadget.ContentChange{
  2471  			After: filepath.Join(d, "foobar"),
  2472  			// original content would get backed up by the updater
  2473  			Before: filepath.Join(backups, "asset.backup"),
  2474  		})
  2475  	c.Assert(err, IsNil)
  2476  	c.Check(res, Equals, gadget.ChangeApply)
  2477  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
  2478  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  2479  	c.Assert(err, IsNil)
  2480  	c.Check(res, Equals, gadget.ChangeApply)
  2481  	// observe the recovery struct
  2482  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
  2483  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  2484  	c.Assert(err, IsNil)
  2485  	c.Check(res, Equals, gadget.ChangeApply)
  2486  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  2487  		&gadget.ContentChange{
  2488  			After: filepath.Join(d, "foobar"),
  2489  			// original content
  2490  			Before: filepath.Join(backups, "asset.backup"),
  2491  		})
  2492  	c.Assert(err, IsNil)
  2493  	c.Check(res, Equals, gadget.ChangeApply)
  2494  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  2495  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)),
  2496  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", beforeHash)),
  2497  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)),
  2498  	})
  2499  
  2500  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  2501  		return uc20model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
  2502  	})
  2503  	defer restore()
  2504  
  2505  	// everything is set up, trigger a reseal
  2506  
  2507  	resealCalls := 0
  2508  	shimBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)), bootloader.RoleRecovery)
  2509  	assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRecovery)
  2510  	beforeAssetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", beforeHash)), bootloader.RoleRecovery)
  2511  	recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
  2512  	runKernelBf := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
  2513  
  2514  	tab.RecoveryBootChainList = []bootloader.BootFile{
  2515  		bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
  2516  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
  2517  		recoveryKernelBf,
  2518  	}
  2519  	tab.BootChainList = []bootloader.BootFile{
  2520  		bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
  2521  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
  2522  		runKernelBf,
  2523  	}
  2524  
  2525  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  2526  		resealCalls++
  2527  
  2528  		c.Assert(params.ModelParams, HasLen, 1)
  2529  		mp := params.ModelParams[0]
  2530  		c.Check(mp.Model.Model(), Equals, uc20model.Model())
  2531  		for _, ch := range mp.EFILoadChains {
  2532  			printChain(c, ch, "-")
  2533  		}
  2534  		switch resealCalls {
  2535  		case 1:
  2536  			c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
  2537  				secboot.NewLoadChain(shimBf,
  2538  					secboot.NewLoadChain(assetBf,
  2539  						secboot.NewLoadChain(recoveryKernelBf)),
  2540  					secboot.NewLoadChain(beforeAssetBf,
  2541  						secboot.NewLoadChain(recoveryKernelBf))),
  2542  				secboot.NewLoadChain(shimBf,
  2543  					secboot.NewLoadChain(assetBf,
  2544  						secboot.NewLoadChain(runKernelBf)),
  2545  					secboot.NewLoadChain(beforeAssetBf,
  2546  						secboot.NewLoadChain(runKernelBf))),
  2547  			})
  2548  		case 2:
  2549  			c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
  2550  				secboot.NewLoadChain(shimBf,
  2551  					secboot.NewLoadChain(assetBf,
  2552  						secboot.NewLoadChain(recoveryKernelBf)),
  2553  					secboot.NewLoadChain(beforeAssetBf,
  2554  						secboot.NewLoadChain(recoveryKernelBf))),
  2555  			})
  2556  		default:
  2557  			c.Errorf("unexpected additional call to secboot.ResealKey (call # %d)", resealCalls)
  2558  		}
  2559  		return nil
  2560  	})
  2561  	defer restore()
  2562  
  2563  	err = obs.BeforeWrite()
  2564  	c.Assert(err, IsNil)
  2565  	c.Check(resealCalls, Equals, 2)
  2566  }
  2567  
  2568  func (s *assetsSuite) TestUpdateObserverCanceledReseal(c *C) {
  2569  	// check that Canceled calls reseal when there were changes to the
  2570  	// trusted boot assets
  2571  	d := c.MkDir()
  2572  	root := c.MkDir()
  2573  
  2574  	// mock some files in cache
  2575  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapBootAssetsDir, "trusted"), 0755), IsNil)
  2576  	for _, name := range []string{
  2577  		"shim-shimhash",
  2578  		"asset-assethash",
  2579  		"asset-recoveryhash",
  2580  	} {
  2581  		err := ioutil.WriteFile(filepath.Join(dirs.SnapBootAssetsDir, "trusted", name), nil, 0644)
  2582  		c.Assert(err, IsNil)
  2583  	}
  2584  
  2585  	tab := s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"})
  2586  
  2587  	// we get an observer for UC20
  2588  	obs, uc20model := s.uc20UpdateObserverEncryptedSystemMockedBootloader(c)
  2589  
  2590  	m := boot.Modeenv{
  2591  		Mode: "run",
  2592  		CurrentTrustedBootAssets: boot.BootAssetsMap{
  2593  			"asset": {"assethash"},
  2594  			"shim":  {"shimhash"},
  2595  		},
  2596  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
  2597  			"asset": {"assethash"},
  2598  			"shim":  {"shimhash"},
  2599  		},
  2600  		CurrentRecoverySystems:    []string{"system"},
  2601  		CurrentKernels:            []string{"pc-kernel_1.snap"},
  2602  		CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"},
  2603  
  2604  		Model:          uc20model.Model(),
  2605  		BrandID:        uc20model.BrandID(),
  2606  		Grade:          string(uc20model.Grade()),
  2607  		ModelSignKeyID: uc20model.SignKeyID(),
  2608  	}
  2609  	err := m.WriteTo("")
  2610  	c.Assert(err, IsNil)
  2611  
  2612  	data := []byte("foobar")
  2613  	err = ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
  2614  	c.Assert(err, IsNil)
  2615  	shim := []byte("shim")
  2616  	err = ioutil.WriteFile(filepath.Join(d, "shim"), shim, 0644)
  2617  	c.Assert(err, IsNil)
  2618  
  2619  	// trigger a bunch of updates, so that we have things to cancel
  2620  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset",
  2621  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  2622  	c.Assert(err, IsNil)
  2623  	c.Check(res, Equals, gadget.ChangeApply)
  2624  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "shim",
  2625  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  2626  	c.Assert(err, IsNil)
  2627  	c.Check(res, Equals, gadget.ChangeApply)
  2628  	// observe the recovery struct
  2629  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "shim",
  2630  		&gadget.ContentChange{After: filepath.Join(d, "shim")})
  2631  	c.Assert(err, IsNil)
  2632  	c.Check(res, Equals, gadget.ChangeApply)
  2633  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset",
  2634  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  2635  	c.Assert(err, IsNil)
  2636  	c.Check(res, Equals, gadget.ChangeApply)
  2637  
  2638  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  2639  		return uc20model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
  2640  	})
  2641  	defer restore()
  2642  
  2643  	shimBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted/shim-shimhash"), bootloader.RoleRecovery)
  2644  	assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted/asset-assethash"), bootloader.RoleRecovery)
  2645  	recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
  2646  	runKernelBf := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode)
  2647  	tab.RecoveryBootChainList = []bootloader.BootFile{
  2648  		bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
  2649  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
  2650  		recoveryKernelBf,
  2651  	}
  2652  	tab.BootChainList = []bootloader.BootFile{
  2653  		bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
  2654  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
  2655  		runKernelBf,
  2656  	}
  2657  
  2658  	resealCalls := 0
  2659  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  2660  		resealCalls++
  2661  		c.Assert(params.ModelParams, HasLen, 1)
  2662  		mp := params.ModelParams[0]
  2663  		c.Check(mp.Model.Model(), Equals, uc20model.Model())
  2664  		for _, ch := range mp.EFILoadChains {
  2665  			printChain(c, ch, "-")
  2666  		}
  2667  		switch resealCalls {
  2668  		case 1:
  2669  			c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
  2670  				secboot.NewLoadChain(shimBf,
  2671  					secboot.NewLoadChain(assetBf,
  2672  						secboot.NewLoadChain(recoveryKernelBf))),
  2673  				secboot.NewLoadChain(shimBf,
  2674  					secboot.NewLoadChain(assetBf,
  2675  						secboot.NewLoadChain(runKernelBf))),
  2676  			})
  2677  		case 2:
  2678  			c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
  2679  				secboot.NewLoadChain(shimBf,
  2680  					secboot.NewLoadChain(assetBf,
  2681  						secboot.NewLoadChain(recoveryKernelBf))),
  2682  			})
  2683  		default:
  2684  			c.Errorf("unexpected additional call to secboot.ResealKey (call # %d)", resealCalls)
  2685  		}
  2686  		return nil
  2687  	})
  2688  	defer restore()
  2689  
  2690  	// update is canceled
  2691  	err = obs.Canceled()
  2692  	c.Assert(err, IsNil)
  2693  	// modeenv is back to initial state
  2694  	afterCancelM, err := boot.ReadModeenv("")
  2695  	c.Assert(err, IsNil)
  2696  	c.Check(afterCancelM.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
  2697  	c.Check(afterCancelM.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
  2698  	// unused assets were dropped
  2699  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
  2700  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-assethash"),
  2701  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-recoveryhash"),
  2702  		filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-shimhash"),
  2703  	})
  2704  
  2705  	c.Check(resealCalls, Equals, 2)
  2706  }
  2707  
  2708  func (s *assetsSuite) TestUpdateObserverUpdateMockedNonEncryption(c *C) {
  2709  	// observe an update on a system where encryption is not used
  2710  
  2711  	d := c.MkDir()
  2712  	backups := c.MkDir()
  2713  	root := c.MkDir()
  2714  
  2715  	// try to arrange the backups like the updater would do it
  2716  	data := []byte("foobar")
  2717  	err := ioutil.WriteFile(filepath.Join(d, "foobar"), data, 0644)
  2718  	c.Assert(err, IsNil)
  2719  
  2720  	m := boot.Modeenv{
  2721  		Mode: "run",
  2722  	}
  2723  	err = m.WriteTo("")
  2724  	c.Assert(err, IsNil)
  2725  
  2726  	tab := s.bootloaderWithTrustedAssets(c, []string{
  2727  		"asset",
  2728  	})
  2729  	tab.ManagedAssetsList = []string{
  2730  		"managed-asset",
  2731  	}
  2732  
  2733  	// we get an observer for UC20, bootloader is mocked
  2734  	obs, _ := s.uc20UpdateObserver(c, c.MkDir())
  2735  
  2736  	// asset is ignored, and the change is applied
  2737  	change := &gadget.ContentChange{
  2738  		After: filepath.Join(d, "foobar"),
  2739  		// original content would get backed up by the updater
  2740  		Before: filepath.Join(backups, "asset.backup"),
  2741  	}
  2742  	res, err := obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "asset", change)
  2743  	c.Assert(err, IsNil)
  2744  	c.Check(res, Equals, gadget.ChangeApply)
  2745  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "asset", change)
  2746  	c.Assert(err, IsNil)
  2747  	c.Check(res, Equals, gadget.ChangeApply)
  2748  	// trusted assets were asked for when setting up bootloader context
  2749  	c.Check(tab.TrustedAssetsCalls, Equals, 2)
  2750  	// but nothing is really tracked
  2751  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), nil)
  2752  	// check modeenv
  2753  	newM, err := boot.ReadModeenv("")
  2754  	c.Assert(err, IsNil)
  2755  	c.Check(newM.CurrentTrustedBootAssets, HasLen, 0)
  2756  	c.Check(newM.CurrentTrustedRecoveryBootAssets, HasLen, 0)
  2757  
  2758  	// verify that managed assets are to be preserved
  2759  	res, err = obs.Observe(gadget.ContentUpdate, mockRunBootStruct, root, "managed-asset",
  2760  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  2761  	c.Assert(err, IsNil)
  2762  	c.Check(res, Equals, gadget.ChangeIgnore)
  2763  	res, err = obs.Observe(gadget.ContentUpdate, mockSeedStruct, root, "managed-asset",
  2764  		&gadget.ContentChange{After: filepath.Join(d, "foobar")})
  2765  	c.Assert(err, IsNil)
  2766  	c.Check(res, Equals, gadget.ChangeIgnore)
  2767  
  2768  	// make sure that no reseal is triggered
  2769  	resealCalls := 0
  2770  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
  2771  		resealCalls++
  2772  		return nil
  2773  	})
  2774  	defer restore()
  2775  
  2776  	err = obs.BeforeWrite()
  2777  	c.Assert(err, IsNil)
  2778  	c.Check(resealCalls, Equals, 0)
  2779  
  2780  	err = obs.Canceled()
  2781  	c.Assert(err, IsNil)
  2782  	c.Check(resealCalls, Equals, 0)
  2783  }