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