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