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