github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/systems_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "os" 25 "path/filepath" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/boot" 31 "github.com/snapcore/snapd/boot/boottest" 32 "github.com/snapcore/snapd/bootloader" 33 "github.com/snapcore/snapd/bootloader/bootloadertest" 34 "github.com/snapcore/snapd/secboot" 35 "github.com/snapcore/snapd/seed" 36 "github.com/snapcore/snapd/snap" 37 "github.com/snapcore/snapd/testutil" 38 "github.com/snapcore/snapd/timings" 39 ) 40 41 type baseSystemsSuite struct { 42 baseBootenvSuite 43 } 44 45 func (s *baseSystemsSuite) SetUpTest(c *C) { 46 s.baseBootenvSuite.SetUpTest(c) 47 c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil) 48 c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil) 49 } 50 51 type systemsSuite struct { 52 baseSystemsSuite 53 54 uc20dev boot.Device 55 56 runKernelBf bootloader.BootFile 57 recoveryKernelBf bootloader.BootFile 58 seedKernelSnap *seed.Snap 59 seedGadgetSnap *seed.Snap 60 } 61 62 var _ = Suite(&systemsSuite{}) 63 64 func (s *systemsSuite) mockTrustedBootloaderWithAssetAndChains(c *C, runKernelBf, recoveryKernelBf bootloader.BootFile) *bootloadertest.MockTrustedAssetsBootloader { 65 mockAssetsCache(c, s.rootdir, "trusted", []string{ 66 "asset-asset-hash-1", 67 }) 68 69 mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets() 70 mtbl.TrustedAssetsList = []string{"asset-1"} 71 mtbl.StaticCommandLine = "static cmdline" 72 mtbl.BootChainList = []bootloader.BootFile{ 73 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 74 runKernelBf, 75 } 76 mtbl.RecoveryBootChainList = []bootloader.BootFile{ 77 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 78 recoveryKernelBf, 79 } 80 bootloader.Force(mtbl) 81 return mtbl 82 } 83 84 func (s *systemsSuite) SetUpTest(c *C) { 85 s.baseBootenvSuite.SetUpTest(c) 86 87 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil }) 88 s.AddCleanup(restore) 89 90 s.uc20dev = boottest.MockUC20Device("", nil) 91 92 // run kernel 93 s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap", 94 "kernel.efi", bootloader.RoleRunMode) 95 // seed (recovery) kernel 96 s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 97 "kernel.efi", bootloader.RoleRecovery) 98 99 s.seedKernelSnap = mockKernelSeedSnap(c, snap.R(1)) 100 s.seedGadgetSnap = mockGadgetSeedSnap(c, nil) 101 } 102 103 func (s *systemsSuite) TestSetTryRecoverySystemEncrypted(c *C) { 104 mockAssetsCache(c, s.rootdir, "trusted", []string{ 105 "asset-asset-hash-1", 106 }) 107 108 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 109 bootloader.Force(mtbl) 110 defer bootloader.Force(nil) 111 112 // system is encrypted 113 s.stampSealedKeys(c, s.rootdir) 114 115 model := s.uc20dev.Model() 116 117 modeenv := &boot.Modeenv{ 118 Mode: "run", 119 // keep this comment to make old gofmt happy 120 CurrentRecoverySystems: []string{"20200825"}, 121 GoodRecoverySystems: []string{"20200825"}, 122 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 123 "asset": []string{"asset-hash-1"}, 124 }, 125 CurrentTrustedBootAssets: boot.BootAssetsMap{ 126 "asset": []string{"asset-hash-1"}, 127 }, 128 CurrentKernels: []string{"pc-kernel_500.snap"}, 129 130 Model: model.Model(), 131 BrandID: model.BrandID(), 132 Grade: string(model.Grade()), 133 ModelSignKeyID: model.SignKeyID(), 134 } 135 c.Assert(modeenv.WriteTo(""), IsNil) 136 137 var readSeedSeenLabels []string 138 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 139 // the mock bootloader can only mock a single recovery boot 140 // chain, so pretend both seeds use the same kernel, but keep track of the labels 141 readSeedSeenLabels = append(readSeedSeenLabels, label) 142 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 143 }) 144 defer restore() 145 146 resealCalls := 0 147 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 148 resealCalls++ 149 // bootloader variables have already been modified 150 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 151 c.Assert(params, NotNil) 152 c.Assert(params.ModelParams, HasLen, 1) 153 switch resealCalls { 154 case 1: 155 c.Check(params.KeyFiles, DeepEquals, []string{ 156 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 157 }) 158 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 159 "snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline", 160 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 161 "snapd_recovery_mode=run static cmdline", 162 }) 163 return nil 164 case 2: 165 c.Check(params.KeyFiles, DeepEquals, []string{ 166 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 167 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 168 }) 169 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 170 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 171 }) 172 return nil 173 default: 174 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 175 return fmt.Errorf("unexpected call") 176 } 177 }) 178 defer restore() 179 180 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 181 c.Assert(err, IsNil) 182 183 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 184 c.Assert(err, IsNil) 185 c.Check(vars, DeepEquals, map[string]string{ 186 "try_recovery_system": "1234", 187 "recovery_system_status": "try", 188 }) 189 // run and recovery keys 190 c.Check(resealCalls, Equals, 2) 191 c.Check(readSeedSeenLabels, DeepEquals, []string{ 192 "20200825", "1234", // current recovery systems for run key 193 "20200825", // good recovery systems for recovery keys 194 }) 195 196 modeenvRead, err := boot.ReadModeenv("") 197 c.Assert(err, IsNil) 198 c.Check(modeenvRead.DeepEqual(&boot.Modeenv{ 199 Mode: "run", 200 // keep this comment to make old gofmt happy 201 CurrentRecoverySystems: []string{"20200825", "1234"}, 202 GoodRecoverySystems: []string{"20200825"}, 203 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 204 "asset": []string{"asset-hash-1"}, 205 }, 206 CurrentTrustedBootAssets: boot.BootAssetsMap{ 207 "asset": []string{"asset-hash-1"}, 208 }, 209 CurrentKernels: []string{"pc-kernel_500.snap"}, 210 211 Model: model.Model(), 212 BrandID: model.BrandID(), 213 Grade: string(model.Grade()), 214 ModelSignKeyID: model.SignKeyID(), 215 }), Equals, true) 216 } 217 218 func (s *systemsSuite) TestSetTryRecoverySystemRemodelEncrypted(c *C) { 219 mockAssetsCache(c, s.rootdir, "trusted", []string{ 220 "asset-asset-hash-1", 221 }) 222 223 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 224 bootloader.Force(mtbl) 225 defer bootloader.Force(nil) 226 227 // system is encrypted 228 s.stampSealedKeys(c, s.rootdir) 229 230 model := s.uc20dev.Model() 231 newModel := boottest.MakeMockUC20Model(map[string]interface{}{ 232 "model": "my-new-model", 233 }) 234 235 modeenv := &boot.Modeenv{ 236 Mode: "run", 237 // keep this comment to make old gofmt happy 238 CurrentRecoverySystems: []string{"20200825"}, 239 GoodRecoverySystems: []string{"20200825"}, 240 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 241 "asset": []string{"asset-hash-1"}, 242 }, 243 CurrentTrustedBootAssets: boot.BootAssetsMap{ 244 "asset": []string{"asset-hash-1"}, 245 }, 246 CurrentKernels: []string{"pc-kernel_500.snap"}, 247 248 Model: model.Model(), 249 BrandID: model.BrandID(), 250 Grade: string(model.Grade()), 251 ModelSignKeyID: model.SignKeyID(), 252 } 253 c.Assert(modeenv.WriteTo(""), IsNil) 254 255 var readSeedSeenLabels []string 256 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 257 // the mock bootloader can only mock a single recovery boot 258 // chain, so pretend both seeds use the same kernel, but keep track of the labels 259 readSeedSeenLabels = append(readSeedSeenLabels, label) 260 systemModel := model 261 if label == "1234" { 262 systemModel = newModel 263 } 264 return systemModel, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 265 }) 266 defer restore() 267 268 resealCalls := 0 269 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 270 resealCalls++ 271 // bootloader variables have already been modified 272 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 273 c.Assert(params, NotNil) 274 switch resealCalls { 275 case 1: 276 c.Assert(params.ModelParams, HasLen, 2) 277 c.Check(params.KeyFiles, DeepEquals, []string{ 278 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 279 }) 280 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 281 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 282 "snapd_recovery_mode=run static cmdline", 283 }) 284 c.Assert(params.ModelParams[1].KernelCmdlines, DeepEquals, []string{ 285 "snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline", 286 "snapd_recovery_mode=run static cmdline", 287 }) 288 return nil 289 case 2: 290 c.Assert(params.ModelParams, HasLen, 1) 291 c.Check(params.KeyFiles, DeepEquals, []string{ 292 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 293 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 294 }) 295 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 296 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 297 }) 298 return nil 299 default: 300 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 301 return fmt.Errorf("unexpected call") 302 } 303 }) 304 defer restore() 305 306 // a remodel will pass the new device 307 newUC20Device := boottest.MockUC20Device("run", newModel) 308 err := boot.SetTryRecoverySystem(newUC20Device, "1234") 309 c.Assert(err, IsNil) 310 311 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 312 c.Assert(err, IsNil) 313 c.Check(vars, DeepEquals, map[string]string{ 314 "try_recovery_system": "1234", 315 "recovery_system_status": "try", 316 }) 317 // run and recovery keys 318 c.Check(resealCalls, Equals, 2) 319 c.Check(readSeedSeenLabels, DeepEquals, []string{ 320 "20200825", "1234", // current recovery systems for run key and current model from modeenv 321 "20200825", "1234", // current recovery systems for run key and try model from modeenv 322 "20200825", // good recovery systems for recovery keys 323 }) 324 325 modeenvRead, err := boot.ReadModeenv("") 326 c.Assert(err, IsNil) 327 c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{ 328 Mode: "run", 329 // keep this comment to make old gofmt happy 330 CurrentRecoverySystems: []string{"20200825", "1234"}, 331 GoodRecoverySystems: []string{"20200825"}, 332 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 333 "asset": []string{"asset-hash-1"}, 334 }, 335 CurrentTrustedBootAssets: boot.BootAssetsMap{ 336 "asset": []string{"asset-hash-1"}, 337 }, 338 CurrentKernels: []string{"pc-kernel_500.snap"}, 339 340 Model: model.Model(), 341 BrandID: model.BrandID(), 342 Grade: string(model.Grade()), 343 ModelSignKeyID: model.SignKeyID(), 344 345 TryModel: newModel.Model(), 346 TryBrandID: newModel.BrandID(), 347 TryGrade: string(newModel.Grade()), 348 TryModelSignKeyID: newModel.SignKeyID(), 349 }) 350 } 351 352 func (s *systemsSuite) TestSetTryRecoverySystemSimple(c *C) { 353 mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets() 354 bootloader.Force(mtbl) 355 defer bootloader.Force(nil) 356 357 model := s.uc20dev.Model() 358 modeenv := &boot.Modeenv{ 359 Mode: "run", 360 // keep this comment to make old gofmt happy 361 CurrentRecoverySystems: []string{"20200825"}, 362 363 Model: model.Model(), 364 BrandID: model.BrandID(), 365 Grade: string(model.Grade()), 366 ModelSignKeyID: model.SignKeyID(), 367 } 368 c.Assert(modeenv.WriteTo(""), IsNil) 369 370 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 371 return fmt.Errorf("unexpected call") 372 }) 373 s.AddCleanup(restore) 374 375 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 376 c.Assert(err, IsNil) 377 378 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 379 c.Assert(err, IsNil) 380 c.Check(vars, DeepEquals, map[string]string{ 381 "try_recovery_system": "1234", 382 "recovery_system_status": "try", 383 }) 384 385 modeenvRead, err := boot.ReadModeenv("") 386 c.Assert(err, IsNil) 387 c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{ 388 Mode: "run", 389 // keep this comment to make old gofmt happy 390 CurrentRecoverySystems: []string{"20200825", "1234"}, 391 392 Model: model.Model(), 393 BrandID: model.BrandID(), 394 Grade: string(model.Grade()), 395 ModelSignKeyID: model.SignKeyID(), 396 }) 397 } 398 399 func (s *systemsSuite) TestSetTryRecoverySystemSetBootVarsErr(c *C) { 400 mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets() 401 bootloader.Force(mtbl) 402 defer bootloader.Force(nil) 403 404 modeenv := &boot.Modeenv{ 405 Mode: "run", 406 // keep this comment to make old gofmt happy 407 CurrentRecoverySystems: []string{"20200825"}, 408 } 409 c.Assert(modeenv.WriteTo(""), IsNil) 410 411 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 412 return fmt.Errorf("unexpected call") 413 }) 414 s.AddCleanup(restore) 415 416 mtbl.BootVars = map[string]string{ 417 "try_recovery_system": "mock", 418 "recovery_system_status": "mock", 419 } 420 mtbl.SetErrFunc = func() error { 421 switch mtbl.SetBootVarsCalls { 422 case 1: 423 return fmt.Errorf("set boot vars fails") 424 case 2: 425 // called during cleanup 426 return nil 427 default: 428 return fmt.Errorf("unexpected call") 429 } 430 } 431 432 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 433 c.Assert(err, ErrorMatches, "set boot vars fails") 434 435 // cleared 436 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 437 c.Assert(err, IsNil) 438 c.Check(vars, DeepEquals, map[string]string{ 439 "try_recovery_system": "", 440 "recovery_system_status": "", 441 }) 442 443 modeenvRead, err := boot.ReadModeenv("") 444 c.Assert(err, IsNil) 445 // modeenv is unchanged 446 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 447 } 448 449 func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorBeforeReseal(c *C) { 450 mockAssetsCache(c, s.rootdir, "trusted", []string{ 451 "asset-asset-hash-1", 452 }) 453 454 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 455 defer bootloader.Force(nil) 456 457 // system is encrypted 458 s.stampSealedKeys(c, s.rootdir) 459 460 model := s.uc20dev.Model() 461 462 modeenv := &boot.Modeenv{ 463 Mode: "run", 464 // keep this comment to make old gofmt happy 465 CurrentRecoverySystems: []string{"20200825"}, 466 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 467 "asset": []string{"asset-hash-1"}, 468 }, 469 470 CurrentTrustedBootAssets: boot.BootAssetsMap{ 471 "asset": []string{"asset-hash-1"}, 472 }, 473 474 Model: model.Model(), 475 BrandID: model.BrandID(), 476 Grade: string(model.Grade()), 477 ModelSignKeyID: model.SignKeyID(), 478 } 479 c.Assert(modeenv.WriteTo(""), IsNil) 480 481 readSeedCalls := 0 482 cleanupTriggered := false 483 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 484 readSeedCalls++ 485 // this is the reseal cleanup path 486 switch readSeedCalls { 487 case 1: 488 // called for the first system 489 c.Assert(label, Equals, "20200825") 490 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 491 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 492 case 2: 493 // called for the 'try' system 494 c.Assert(label, Equals, "1234") 495 // modeenv is updated first 496 modeenvRead, err := boot.ReadModeenv("") 497 c.Assert(err, IsNil) 498 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{ 499 "20200825", "1234", 500 }) 501 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 502 // we are triggering the cleanup by returning an error now 503 cleanupTriggered = true 504 return nil, nil, fmt.Errorf("seed read essential fails") 505 case 3: 506 // (cleanup) recovery boot chains for run key, called 507 // for the first system only 508 fallthrough 509 case 4: 510 // (cleanup) recovery boot chains for recovery keys 511 c.Assert(label, Equals, "20200825") 512 // boot variables already updated 513 c.Check(mtbl.SetBootVarsCalls, Equals, 2) 514 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 515 default: 516 return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls) 517 } 518 }) 519 defer restore() 520 521 resealCalls := 0 522 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 523 resealCalls++ 524 if cleanupTriggered { 525 return nil 526 } 527 return fmt.Errorf("unexpected call") 528 }) 529 defer restore() 530 531 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 532 c.Assert(err, ErrorMatches, ".*: seed read essential fails") 533 534 // failed after the call to read the 'try' system seed 535 c.Check(readSeedCalls, Equals, 4) 536 // called twice during cleanup for run and recovery keys 537 c.Check(resealCalls, Equals, 2) 538 539 modeenvRead, err := boot.ReadModeenv("") 540 c.Assert(err, IsNil) 541 // modeenv is unchanged 542 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 543 // bootloader variables have been cleared 544 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 545 c.Assert(err, IsNil) 546 c.Check(vars, DeepEquals, map[string]string{ 547 "try_recovery_system": "", 548 "recovery_system_status": "", 549 }) 550 } 551 552 func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorAfterReseal(c *C) { 553 mockAssetsCache(c, s.rootdir, "trusted", []string{ 554 "asset-asset-hash-1", 555 }) 556 557 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 558 bootloader.Force(mtbl) 559 defer bootloader.Force(nil) 560 561 // system is encrypted 562 s.stampSealedKeys(c, s.rootdir) 563 564 model := s.uc20dev.Model() 565 566 modeenv := &boot.Modeenv{ 567 Mode: "run", 568 // keep this comment to make old gofmt happy 569 CurrentRecoverySystems: []string{"20200825"}, 570 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 571 "asset": []string{"asset-hash-1"}, 572 }, 573 574 CurrentTrustedBootAssets: boot.BootAssetsMap{ 575 "asset": []string{"asset-hash-1"}, 576 }, 577 578 Model: model.Model(), 579 BrandID: model.BrandID(), 580 Grade: string(model.Grade()), 581 ModelSignKeyID: model.SignKeyID(), 582 } 583 c.Assert(modeenv.WriteTo(""), IsNil) 584 585 readSeedCalls := 0 586 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 587 readSeedCalls++ 588 // this is the reseal cleanup path 589 590 switch readSeedCalls { 591 case 1: 592 // called for the first system 593 c.Assert(label, Equals, "20200825") 594 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 595 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 596 case 2: 597 // called for the 'try' system 598 c.Assert(label, Equals, "1234") 599 // modeenv is updated first 600 modeenvRead, err := boot.ReadModeenv("") 601 c.Assert(err, IsNil) 602 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{ 603 "20200825", "1234", 604 }) 605 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 606 // still good 607 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 608 case 3: 609 // recovery boot chains for a good recovery system 610 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 611 fallthrough 612 case 4: 613 // (cleanup) recovery boot chains for run key, called 614 // for the first system only 615 fallthrough 616 case 5: 617 // (cleanup) recovery boot chains for recovery keys 618 c.Assert(label, Equals, "20200825") 619 // boot variables already updated 620 if readSeedCalls >= 4 { 621 c.Check(mtbl.SetBootVarsCalls, Equals, 2) 622 } 623 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 624 default: 625 return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls) 626 } 627 }) 628 defer restore() 629 630 resealCalls := 0 631 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 632 resealCalls++ 633 switch resealCalls { 634 case 1: 635 // attempt to reseal the run key 636 return fmt.Errorf("reseal fails") 637 case 2, 3: 638 // reseal of run and recovery keys 639 return nil 640 default: 641 return fmt.Errorf("unexpected call") 642 643 } 644 }) 645 defer restore() 646 647 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 648 c.Assert(err, ErrorMatches, "cannot reseal the encryption key: reseal fails") 649 650 // failed after the call to read the 'try' system seed 651 c.Check(readSeedCalls, Equals, 5) 652 // called 3 times, once when mocked failure occurs, twice during cleanup 653 // for run and recovery keys 654 c.Check(resealCalls, Equals, 3) 655 656 modeenvRead, err := boot.ReadModeenv("") 657 c.Assert(err, IsNil) 658 // modeenv is unchanged 659 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 660 // bootloader variables have been cleared 661 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 662 c.Assert(err, IsNil) 663 c.Check(vars, DeepEquals, map[string]string{ 664 "try_recovery_system": "", 665 "recovery_system_status": "", 666 }) 667 } 668 669 func (s *systemsSuite) TestSetTryRecoverySystemCleanupError(c *C) { 670 mockAssetsCache(c, s.rootdir, "trusted", []string{ 671 "asset-asset-hash-1", 672 }) 673 674 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 675 bootloader.Force(mtbl) 676 defer bootloader.Force(nil) 677 678 // system is encrypted 679 s.stampSealedKeys(c, s.rootdir) 680 681 model := s.uc20dev.Model() 682 modeenv := &boot.Modeenv{ 683 Mode: "run", 684 // keep this comment to make old gofmt happy 685 CurrentRecoverySystems: []string{"20200825"}, 686 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 687 "asset": []string{"asset-hash-1"}, 688 }, 689 690 CurrentTrustedBootAssets: boot.BootAssetsMap{ 691 "asset": []string{"asset-hash-1"}, 692 }, 693 694 Model: model.Model(), 695 BrandID: model.BrandID(), 696 Grade: string(model.Grade()), 697 ModelSignKeyID: model.SignKeyID(), 698 } 699 c.Assert(modeenv.WriteTo(""), IsNil) 700 701 readSeedCalls := 0 702 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 703 readSeedCalls++ 704 // this is the reseal cleanup path 705 c.Logf("call %v label %v", readSeedCalls, label) 706 switch readSeedCalls { 707 case 1: 708 // called for the first system 709 c.Assert(label, Equals, "20200825") 710 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 711 case 2: 712 // called for the 'try' system 713 c.Assert(label, Equals, "1234") 714 // still good 715 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 716 case 3: 717 // recovery boot chains for a good recovery system 718 fallthrough 719 case 4: 720 // (cleanup) recovery boot chains for run key, called 721 // for the first system only 722 fallthrough 723 case 5: 724 // (cleanup) recovery boot chains for recovery keys 725 c.Check(label, Equals, "20200825") 726 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 727 default: 728 return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls) 729 } 730 }) 731 defer restore() 732 733 resealCalls := 0 734 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 735 resealCalls++ 736 switch resealCalls { 737 case 1: 738 return fmt.Errorf("reseal fails") 739 case 2, 3: 740 // reseal of run and recovery keys 741 return fmt.Errorf("reseal in cleanup fails too") 742 default: 743 return fmt.Errorf("unexpected call") 744 745 } 746 }) 747 defer restore() 748 749 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 750 c.Assert(err, ErrorMatches, `cannot reseal the encryption key: reseal fails \(cleanup failed: cannot reseal the encryption key: reseal in cleanup fails too\)`) 751 752 // failed after the call to read the 'try' system seed 753 c.Check(readSeedCalls, Equals, 5) 754 // called twice, once when enabling the try system, once on cleanup 755 c.Check(resealCalls, Equals, 2) 756 757 modeenvRead, err := boot.ReadModeenv("") 758 c.Assert(err, IsNil) 759 // modeenv is unchanged 760 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 761 // bootloader variables have been cleared regardless of reseal failing 762 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 763 c.Assert(err, IsNil) 764 c.Check(vars, DeepEquals, map[string]string{ 765 "try_recovery_system": "", 766 "recovery_system_status": "", 767 }) 768 } 769 770 func (s *systemsSuite) testInspectRecoverySystemOutcomeHappy(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, expectedOutcome boot.TryRecoverySystemOutcome, expectedErr string) { 771 bootloader.Force(mtbl) 772 defer bootloader.Force(nil) 773 774 // system is encrypted 775 s.stampSealedKeys(c, s.rootdir) 776 777 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 778 return nil, nil, fmt.Errorf("unexpected call") 779 }) 780 defer restore() 781 782 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 783 return fmt.Errorf("unexpected call") 784 }) 785 defer restore() 786 787 outcome, label, err := boot.InspectTryRecoverySystemOutcome(s.uc20dev) 788 if expectedErr == "" { 789 c.Assert(err, IsNil) 790 } else { 791 c.Assert(err, ErrorMatches, expectedErr) 792 } 793 c.Check(outcome, Equals, expectedOutcome) 794 switch outcome { 795 case boot.TryRecoverySystemOutcomeSuccess, boot.TryRecoverySystemOutcomeFailure: 796 c.Check(label, Equals, "1234") 797 default: 798 c.Check(label, Equals, "") 799 } 800 } 801 802 func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappySuccess(c *C) { 803 triedVars := map[string]string{ 804 "recovery_system_status": "tried", 805 "try_recovery_system": "1234", 806 } 807 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 808 err := mtbl.SetBootVars(triedVars) 809 c.Assert(err, IsNil) 810 811 m := boot.Modeenv{ 812 Mode: boot.ModeRun, 813 // keep this comment to make old gofmt happy 814 CurrentRecoverySystems: []string{"29112019", "1234"}, 815 } 816 err = m.WriteTo("") 817 c.Assert(err, IsNil) 818 819 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeSuccess, "") 820 821 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 822 c.Assert(err, IsNil) 823 c.Check(vars, DeepEquals, triedVars) 824 } 825 826 func (s *systemsSuite) TestInspectRecoverySystemOutcomeFailureMissingSystemInModeenv(c *C) { 827 triedVars := map[string]string{ 828 "recovery_system_status": "tried", 829 "try_recovery_system": "1234", 830 } 831 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 832 err := mtbl.SetBootVars(triedVars) 833 c.Assert(err, IsNil) 834 835 m := boot.Modeenv{ 836 Mode: boot.ModeRun, 837 // we don't have the tried recovery system in the modeenv 838 CurrentRecoverySystems: []string{"29112019"}, 839 } 840 err = m.WriteTo("") 841 c.Assert(err, IsNil) 842 843 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, `recovery system "1234" was tried, but is not present in the modeenv CurrentRecoverySystems`) 844 845 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 846 c.Assert(err, IsNil) 847 c.Check(vars, DeepEquals, triedVars) 848 } 849 850 func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappyFailure(c *C) { 851 tryVars := map[string]string{ 852 "recovery_system_status": "try", 853 "try_recovery_system": "1234", 854 } 855 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 856 err := mtbl.SetBootVars(tryVars) 857 c.Assert(err, IsNil) 858 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, "") 859 860 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 861 c.Assert(err, IsNil) 862 c.Check(vars, DeepEquals, tryVars) 863 } 864 865 func (s *systemsSuite) TestInspectRecoverySystemOutcomeNotTried(c *C) { 866 notTriedVars := map[string]string{ 867 "recovery_system_status": "", 868 "try_recovery_system": "", 869 } 870 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 871 err := mtbl.SetBootVars(notTriedVars) 872 c.Assert(err, IsNil) 873 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeNoneTried, "") 874 } 875 876 func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBogusStatus(c *C) { 877 badVars := map[string]string{ 878 "recovery_system_status": "foo", 879 "try_recovery_system": "1234", 880 } 881 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 882 err := mtbl.SetBootVars(badVars) 883 c.Assert(err, IsNil) 884 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status "foo"`) 885 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 886 c.Assert(err, IsNil) 887 c.Check(vars, DeepEquals, badVars) 888 } 889 890 func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBadLabel(c *C) { 891 badVars := map[string]string{ 892 "recovery_system_status": "tried", 893 "try_recovery_system": "", 894 } 895 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 896 err := mtbl.SetBootVars(badVars) 897 c.Assert(err, IsNil) 898 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `try recovery system is unset but status is "tried"`) 899 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 900 c.Assert(err, IsNil) 901 c.Check(vars, DeepEquals, badVars) 902 } 903 904 func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentUnexpectedLabel(c *C) { 905 badVars := map[string]string{ 906 "recovery_system_status": "", 907 "try_recovery_system": "1234", 908 } 909 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 910 err := mtbl.SetBootVars(badVars) 911 c.Assert(err, IsNil) 912 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status ""`) 913 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 914 c.Assert(err, IsNil) 915 c.Check(vars, DeepEquals, badVars) 916 } 917 918 type clearRecoverySystemTestCase struct { 919 systemLabel string 920 tryModel *asserts.Model 921 resealErr error 922 expectedErr string 923 } 924 925 func (s *systemsSuite) testClearRecoverySystem(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, tc clearRecoverySystemTestCase) { 926 mockAssetsCache(c, s.rootdir, "trusted", []string{ 927 "asset-asset-hash-1", 928 }) 929 930 bootloader.Force(mtbl) 931 defer bootloader.Force(nil) 932 933 // system is encrypted 934 s.stampSealedKeys(c, s.rootdir) 935 936 model := s.uc20dev.Model() 937 938 modeenv := &boot.Modeenv{ 939 Mode: "run", 940 // keep this comment to make old gofmt happy 941 CurrentRecoverySystems: []string{"20200825"}, 942 GoodRecoverySystems: []string{"20200825"}, 943 CurrentKernels: []string{}, 944 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 945 "asset": []string{"asset-hash-1"}, 946 }, 947 948 CurrentTrustedBootAssets: boot.BootAssetsMap{ 949 "asset": []string{"asset-hash-1"}, 950 }, 951 952 Model: model.Model(), 953 BrandID: model.BrandID(), 954 Grade: string(model.Grade()), 955 ModelSignKeyID: model.SignKeyID(), 956 } 957 if tc.systemLabel != "" { 958 modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, tc.systemLabel) 959 } 960 if tc.tryModel != nil { 961 modeenv.TryModel = tc.tryModel.Model() 962 modeenv.TryBrandID = tc.tryModel.BrandID() 963 modeenv.TryGrade = string(tc.tryModel.Grade()) 964 modeenv.TryModelSignKeyID = tc.tryModel.SignKeyID() 965 } 966 c.Assert(modeenv.WriteTo(""), IsNil) 967 968 var readSeedSeenLabels []string 969 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 970 // the mock bootloader can only mock a single recovery boot 971 // chain, so pretend both seeds use the same kernel, but keep track of the labels 972 readSeedSeenLabels = append(readSeedSeenLabels, label) 973 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 974 }) 975 defer restore() 976 977 resealCalls := 0 978 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 979 resealCalls++ 980 c.Assert(params, NotNil) 981 c.Assert(params.ModelParams, HasLen, 1) 982 switch resealCalls { 983 case 1: 984 c.Check(params.KeyFiles, DeepEquals, []string{ 985 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 986 }) 987 return tc.resealErr 988 case 2: 989 c.Check(params.KeyFiles, DeepEquals, []string{ 990 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 991 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 992 }) 993 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 994 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 995 }) 996 return nil 997 default: 998 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 999 return fmt.Errorf("unexpected call") 1000 } 1001 }) 1002 defer restore() 1003 1004 err := boot.ClearTryRecoverySystem(s.uc20dev, tc.systemLabel) 1005 if tc.expectedErr == "" { 1006 c.Assert(err, IsNil) 1007 } else { 1008 c.Assert(err, ErrorMatches, tc.expectedErr) 1009 } 1010 1011 // only one seed system accessed 1012 c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"}) 1013 if tc.resealErr == nil { 1014 // called twice, for run and recovery keys 1015 c.Check(resealCalls, Equals, 2) 1016 } else { 1017 // fails on run key 1018 c.Check(resealCalls, Equals, 1) 1019 } 1020 1021 modeenvRead, err := boot.ReadModeenv("") 1022 c.Assert(err, IsNil) 1023 // modeenv systems list has one entry only 1024 c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{ 1025 Mode: "run", 1026 // keep this comment to make old gofmt happy 1027 CurrentRecoverySystems: []string{"20200825"}, 1028 GoodRecoverySystems: []string{"20200825"}, 1029 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 1030 "asset": []string{"asset-hash-1"}, 1031 }, 1032 1033 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1034 "asset": []string{"asset-hash-1"}, 1035 }, 1036 1037 Model: model.Model(), 1038 BrandID: model.BrandID(), 1039 Grade: string(model.Grade()), 1040 ModelSignKeyID: model.SignKeyID(), 1041 1042 // try model if set, has been cleared 1043 }) 1044 } 1045 1046 func (s *systemsSuite) TestClearRecoverySystemHappy(c *C) { 1047 setVars := map[string]string{ 1048 "recovery_system_status": "try", 1049 "try_recovery_system": "1234", 1050 } 1051 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1052 err := mtbl.SetBootVars(setVars) 1053 c.Assert(err, IsNil) 1054 1055 s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"}) 1056 // bootloader variables have been cleared 1057 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1058 c.Assert(err, IsNil) 1059 c.Check(vars, DeepEquals, map[string]string{ 1060 "try_recovery_system": "", 1061 "recovery_system_status": "", 1062 }) 1063 } 1064 1065 func (s *systemsSuite) TestClearRecoverySystemTriedHappy(c *C) { 1066 setVars := map[string]string{ 1067 "recovery_system_status": "tried", 1068 "try_recovery_system": "1234", 1069 } 1070 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1071 err := mtbl.SetBootVars(setVars) 1072 c.Assert(err, IsNil) 1073 1074 s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"}) 1075 // bootloader variables have been cleared 1076 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1077 c.Assert(err, IsNil) 1078 c.Check(vars, DeepEquals, map[string]string{ 1079 "try_recovery_system": "", 1080 "recovery_system_status": "", 1081 }) 1082 } 1083 1084 func (s *systemsSuite) TestClearRecoverySystemInconsistentStateHappy(c *C) { 1085 setVars := map[string]string{ 1086 "recovery_system_status": "foo", 1087 "try_recovery_system": "", 1088 } 1089 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1090 err := mtbl.SetBootVars(setVars) 1091 c.Assert(err, IsNil) 1092 1093 s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"}) 1094 // bootloader variables have been cleared 1095 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1096 c.Assert(err, IsNil) 1097 c.Check(vars, DeepEquals, map[string]string{ 1098 "try_recovery_system": "", 1099 "recovery_system_status": "", 1100 }) 1101 } 1102 1103 func (s *systemsSuite) TestClearRecoverySystemInconsistentNoLabelHappy(c *C) { 1104 setVars := map[string]string{ 1105 "recovery_system_status": "this-will-be-gone", 1106 "try_recovery_system": "this-too", 1107 } 1108 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1109 err := mtbl.SetBootVars(setVars) 1110 c.Assert(err, IsNil) 1111 1112 // clear without passing the system label, just clears the relevant boot 1113 // variables 1114 const noLabel = "" 1115 s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: noLabel}) 1116 // bootloader variables have been cleared 1117 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1118 c.Assert(err, IsNil) 1119 c.Check(vars, DeepEquals, map[string]string{ 1120 "try_recovery_system": "", 1121 "recovery_system_status": "", 1122 }) 1123 } 1124 1125 func (s *systemsSuite) TestClearRecoverySystemRemodelHappy(c *C) { 1126 setVars := map[string]string{ 1127 "recovery_system_status": "try", 1128 "try_recovery_system": "1234", 1129 } 1130 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1131 err := mtbl.SetBootVars(setVars) 1132 c.Assert(err, IsNil) 1133 1134 s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{ 1135 systemLabel: "1234", 1136 tryModel: boottest.MakeMockUC20Model(map[string]interface{}{ 1137 "tryModelodel": "my-new-model", 1138 }), 1139 }) 1140 // bootloader variables have been cleared 1141 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1142 c.Assert(err, IsNil) 1143 c.Check(vars, DeepEquals, map[string]string{ 1144 "try_recovery_system": "", 1145 "recovery_system_status": "", 1146 }) 1147 } 1148 1149 func (s *systemsSuite) TestClearRecoverySystemResealFails(c *C) { 1150 setVars := map[string]string{ 1151 "recovery_system_status": "try", 1152 "try_recovery_system": "1234", 1153 } 1154 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1155 err := mtbl.SetBootVars(setVars) 1156 c.Assert(err, IsNil) 1157 1158 s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{ 1159 systemLabel: "1234", 1160 resealErr: fmt.Errorf("reseal fails"), 1161 expectedErr: "cannot reseal the encryption key: reseal fails", 1162 }) 1163 // bootloader variables have been cleared 1164 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1165 c.Assert(err, IsNil) 1166 // variables were cleared 1167 c.Check(vars, DeepEquals, map[string]string{ 1168 "try_recovery_system": "", 1169 "recovery_system_status": "", 1170 }) 1171 } 1172 1173 func (s *systemsSuite) TestClearRecoverySystemSetBootVarsFails(c *C) { 1174 setVars := map[string]string{ 1175 "recovery_system_status": "try", 1176 "try_recovery_system": "1234", 1177 } 1178 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1179 err := mtbl.SetBootVars(setVars) 1180 c.Assert(err, IsNil) 1181 mtbl.SetErr = fmt.Errorf("set boot vars fails") 1182 1183 s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{ 1184 systemLabel: "1234", 1185 expectedErr: "set boot vars fails", 1186 }) 1187 } 1188 1189 func (s *systemsSuite) TestClearRecoverySystemReboot(c *C) { 1190 setVars := map[string]string{ 1191 "recovery_system_status": "try", 1192 "try_recovery_system": "1234", 1193 } 1194 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1195 err := mtbl.SetBootVars(setVars) 1196 c.Assert(err, IsNil) 1197 1198 mockAssetsCache(c, s.rootdir, "trusted", []string{ 1199 "asset-asset-hash-1", 1200 }) 1201 1202 bootloader.Force(mtbl) 1203 defer bootloader.Force(nil) 1204 1205 // system is encrypted 1206 s.stampSealedKeys(c, s.rootdir) 1207 1208 model := s.uc20dev.Model() 1209 1210 modeenv := &boot.Modeenv{ 1211 Mode: "run", 1212 // keep this comment to make old gofmt happy 1213 CurrentRecoverySystems: []string{"20200825", "1234"}, 1214 CurrentKernels: []string{}, 1215 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 1216 "asset": []string{"asset-hash-1"}, 1217 }, 1218 1219 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1220 "asset": []string{"asset-hash-1"}, 1221 }, 1222 1223 Model: model.Model(), 1224 BrandID: model.BrandID(), 1225 Grade: string(model.Grade()), 1226 ModelSignKeyID: model.SignKeyID(), 1227 } 1228 c.Assert(modeenv.WriteTo(""), IsNil) 1229 1230 var readSeedSeenLabels []string 1231 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1232 // the mock bootloader can only mock a single recovery boot 1233 // chain, so pretend both seeds use the same kernel, but keep track of the labels 1234 readSeedSeenLabels = append(readSeedSeenLabels, label) 1235 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 1236 }) 1237 defer restore() 1238 1239 resealCalls := 0 1240 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1241 resealCalls++ 1242 c.Assert(params, NotNil) 1243 c.Assert(params.ModelParams, HasLen, 1) 1244 switch resealCalls { 1245 case 1: 1246 c.Check(params.KeyFiles, DeepEquals, []string{ 1247 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1248 }) 1249 panic("reseal panic") 1250 case 2: 1251 c.Check(params.KeyFiles, DeepEquals, []string{ 1252 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1253 }) 1254 return nil 1255 case 3: 1256 c.Check(params.KeyFiles, DeepEquals, []string{ 1257 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1258 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1259 }) 1260 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1261 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1262 }) 1263 return nil 1264 default: 1265 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 1266 return fmt.Errorf("unexpected call") 1267 1268 } 1269 }) 1270 defer restore() 1271 1272 checkGoodState := func() { 1273 // modeenv was already written 1274 modeenvRead, err := boot.ReadModeenv("") 1275 c.Assert(err, IsNil) 1276 // modeenv systems list has one entry only 1277 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{ 1278 "20200825", 1279 }) 1280 // bootloader variables have been cleared already 1281 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1282 c.Assert(err, IsNil) 1283 // variables were cleared 1284 c.Check(vars, DeepEquals, map[string]string{ 1285 "try_recovery_system": "", 1286 "recovery_system_status": "", 1287 }) 1288 } 1289 1290 c.Assert(func() { 1291 boot.ClearTryRecoverySystem(s.uc20dev, "1234") 1292 }, PanicMatches, "reseal panic") 1293 // only one seed system accessed 1294 c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"}) 1295 // panicked on run key 1296 c.Check(resealCalls, Equals, 1) 1297 checkGoodState() 1298 1299 mtbl.SetErrFunc = func() error { 1300 panic("set boot vars panic") 1301 } 1302 c.Assert(func() { 1303 boot.ClearTryRecoverySystem(s.uc20dev, "1234") 1304 }, PanicMatches, "set boot vars panic") 1305 // we did not reach resealing yet 1306 c.Check(resealCalls, Equals, 1) 1307 checkGoodState() 1308 1309 mtbl.SetErrFunc = nil 1310 err = boot.ClearTryRecoverySystem(s.uc20dev, "1234") 1311 c.Assert(err, IsNil) 1312 checkGoodState() 1313 } 1314 1315 type recoverySystemGoodTestCase struct { 1316 systemLabelAddToCurrent bool 1317 systemLabelAddToGood bool 1318 triedSystems []string 1319 1320 resealRecoveryKeyErr error 1321 resealRecoveryKeyDuringCleanupErr error 1322 resealCalls int 1323 expectedErr string 1324 1325 readSeedSystems []string 1326 expectedCurrentSystemsList []string 1327 expectedGoodSystemsList []string 1328 } 1329 1330 func (s *systemsSuite) testPromoteTriedRecoverySystem(c *C, systemLabel string, tc recoverySystemGoodTestCase) { 1331 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1332 mockAssetsCache(c, s.rootdir, "trusted", []string{ 1333 "asset-asset-hash-1", 1334 }) 1335 1336 bootloader.Force(mtbl) 1337 defer bootloader.Force(nil) 1338 1339 // system is encrypted 1340 s.stampSealedKeys(c, s.rootdir) 1341 1342 model := s.uc20dev.Model() 1343 1344 modeenv := &boot.Modeenv{ 1345 Mode: "run", 1346 // keep this comment to make old gofmt happy 1347 CurrentRecoverySystems: []string{"20200825"}, 1348 GoodRecoverySystems: []string{"20200825"}, 1349 CurrentKernels: []string{}, 1350 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 1351 "asset": []string{"asset-hash-1"}, 1352 }, 1353 1354 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1355 "asset": []string{"asset-hash-1"}, 1356 }, 1357 1358 Model: model.Model(), 1359 BrandID: model.BrandID(), 1360 Grade: string(model.Grade()), 1361 ModelSignKeyID: model.SignKeyID(), 1362 } 1363 if tc.systemLabelAddToCurrent { 1364 modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel) 1365 } 1366 if tc.systemLabelAddToGood { 1367 modeenv.GoodRecoverySystems = append(modeenv.GoodRecoverySystems, systemLabel) 1368 } 1369 1370 c.Assert(modeenv.WriteTo(""), IsNil) 1371 1372 var readSeedSeenLabels []string 1373 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1374 // the mock bootloader can only mock a single recovery boot 1375 // chain, so pretend both seeds use the same kernel, but keep track of the labels 1376 readSeedSeenLabels = append(readSeedSeenLabels, label) 1377 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 1378 }) 1379 defer restore() 1380 1381 resealCalls := 0 1382 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1383 resealCalls++ 1384 c.Assert(params, NotNil) 1385 c.Assert(params.ModelParams, HasLen, 1) 1386 switch resealCalls { 1387 case 1: 1388 c.Check(params.KeyFiles, DeepEquals, []string{ 1389 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1390 }) 1391 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1392 fmt.Sprintf("snapd_recovery_mode=recover snapd_recovery_system=%s static cmdline", systemLabel), 1393 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1394 }) 1395 return nil 1396 case 2: 1397 c.Check(params.KeyFiles, DeepEquals, []string{ 1398 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1399 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1400 }) 1401 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1402 fmt.Sprintf("snapd_recovery_mode=recover snapd_recovery_system=%s static cmdline", systemLabel), 1403 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1404 }) 1405 return tc.resealRecoveryKeyErr 1406 case 3: 1407 // run key boot chain is unchanged, so only recovery key boot chain is resealed 1408 if tc.resealRecoveryKeyErr == nil { 1409 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 1410 return fmt.Errorf("unexpected call") 1411 } 1412 c.Check(params.KeyFiles, DeepEquals, []string{ 1413 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1414 }) 1415 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1416 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1417 }) 1418 return nil 1419 case 4: 1420 if tc.resealRecoveryKeyErr == nil { 1421 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 1422 return fmt.Errorf("unexpected call") 1423 } 1424 c.Check(params.KeyFiles, DeepEquals, []string{ 1425 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1426 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1427 }) 1428 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1429 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1430 }) 1431 return tc.resealRecoveryKeyDuringCleanupErr 1432 default: 1433 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 1434 return fmt.Errorf("unexpected call") 1435 } 1436 }) 1437 defer restore() 1438 1439 err := boot.PromoteTriedRecoverySystem(s.uc20dev, systemLabel, tc.triedSystems) 1440 if tc.expectedErr == "" { 1441 c.Assert(err, IsNil) 1442 } else { 1443 c.Assert(err, ErrorMatches, tc.expectedErr) 1444 } 1445 c.Check(readSeedSeenLabels, DeepEquals, tc.readSeedSystems) 1446 c.Check(resealCalls, Equals, tc.resealCalls) 1447 1448 modeenvRead, err := boot.ReadModeenv("") 1449 c.Assert(err, IsNil) 1450 c.Check(modeenvRead.GoodRecoverySystems, DeepEquals, tc.expectedGoodSystemsList) 1451 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, tc.expectedCurrentSystemsList) 1452 } 1453 1454 func (s *systemsSuite) TestPromoteTriedRecoverySystemHappy(c *C) { 1455 s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{ 1456 triedSystems: []string{"1234"}, 1457 1458 resealCalls: 2, 1459 1460 readSeedSystems: []string{ 1461 // run key 1462 "20200825", "1234", 1463 // recovery keys 1464 "20200825", "1234", 1465 }, 1466 1467 expectedCurrentSystemsList: []string{"20200825", "1234"}, 1468 expectedGoodSystemsList: []string{"20200825", "1234"}, 1469 }) 1470 } 1471 1472 func (s *systemsSuite) TestPromoteTriedRecoverySystemInCurrent(c *C) { 1473 s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{ 1474 triedSystems: []string{"1234"}, 1475 systemLabelAddToCurrent: true, 1476 resealCalls: 2, 1477 1478 readSeedSystems: []string{ 1479 // run key 1480 "20200825", "1234", 1481 // recovery keys 1482 "20200825", "1234", 1483 }, 1484 expectedCurrentSystemsList: []string{"20200825", "1234"}, 1485 expectedGoodSystemsList: []string{"20200825", "1234"}, 1486 }) 1487 } 1488 1489 func (s *systemsSuite) TestPromoteTriedRecoverySystemPresentEverywhere(c *C) { 1490 s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{ 1491 triedSystems: []string{"1234"}, 1492 systemLabelAddToCurrent: true, 1493 systemLabelAddToGood: true, 1494 1495 resealCalls: 2, 1496 1497 readSeedSystems: []string{ 1498 // run key 1499 "20200825", "1234", 1500 // recovery keys 1501 "20200825", "1234", 1502 }, 1503 expectedCurrentSystemsList: []string{"20200825", "1234"}, 1504 expectedGoodSystemsList: []string{"20200825", "1234"}, 1505 }) 1506 } 1507 1508 func (s *systemsSuite) TestPromoteTriedRecoverySystemResealFails(c *C) { 1509 s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{ 1510 triedSystems: []string{"1234"}, 1511 resealRecoveryKeyErr: fmt.Errorf("recovery key reseal mock failure"), 1512 // no failure during cleanup 1513 resealRecoveryKeyDuringCleanupErr: nil, 1514 1515 resealCalls: 4, 1516 1517 expectedErr: `cannot reseal the fallback encryption keys: recovery key reseal mock failure`, 1518 1519 readSeedSystems: []string{ 1520 // run key 1521 "20200825", "1234", 1522 // recovery keys 1523 "20200825", "1234", 1524 // cleanup run key reseal (the seed system is still in 1525 // current-recovery-systems) 1526 "20200825", 1527 // cleanup recovery keys 1528 "20200825", 1529 }, 1530 expectedCurrentSystemsList: []string{"20200825"}, 1531 expectedGoodSystemsList: []string{"20200825"}, 1532 }) 1533 } 1534 1535 func (s *systemsSuite) TestPromoteTriedRecoverySystemResealUndoFails(c *C) { 1536 s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{ 1537 triedSystems: []string{"1234"}, 1538 resealRecoveryKeyErr: fmt.Errorf("recovery key reseal mock failure"), 1539 resealRecoveryKeyDuringCleanupErr: fmt.Errorf("recovery key reseal mock fail in cleanup"), 1540 1541 resealCalls: 4, 1542 1543 expectedErr: `cannot reseal the fallback encryption keys: recovery key reseal mock failure \(cleanup failed: cannot reseal the fallback encryption keys: recovery key reseal mock fail in cleanup\)`, 1544 1545 readSeedSystems: []string{ 1546 // run key 1547 "20200825", "1234", 1548 // recovery keys 1549 "20200825", "1234", 1550 // cleanup run key reseal (the seed system is still in 1551 // current-recovery-systems) 1552 // cleanup run key 1553 "20200825", 1554 // cleanup recovery keys 1555 "20200825", 1556 }, 1557 expectedCurrentSystemsList: []string{"20200825"}, 1558 expectedGoodSystemsList: []string{"20200825"}, 1559 }) 1560 } 1561 1562 func (s *systemsSuite) TestPromoteTriedRecoverySystemNotTried(c *C) { 1563 s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{ 1564 triedSystems: []string{"not-here"}, 1565 1566 expectedErr: `system has not been successfully tried`, 1567 1568 expectedCurrentSystemsList: []string{"20200825"}, 1569 expectedGoodSystemsList: []string{"20200825"}, 1570 }) 1571 1572 // also works if tried systems list is nil 1573 s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{ 1574 triedSystems: nil, 1575 1576 expectedErr: `system has not been successfully tried`, 1577 1578 expectedCurrentSystemsList: []string{"20200825"}, 1579 expectedGoodSystemsList: []string{"20200825"}, 1580 }) 1581 } 1582 1583 type recoverySystemDropTestCase struct { 1584 systemLabelAddToCurrent bool 1585 systemLabelAddToGood bool 1586 1587 resealRecoveryKeyErr error 1588 resealCalls int 1589 expectedErr string 1590 1591 expectedCurrentSystemsList []string 1592 expectedGoodSystemsList []string 1593 } 1594 1595 func (s *systemsSuite) testDropRecoverySystem(c *C, systemLabel string, tc recoverySystemDropTestCase) { 1596 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 1597 mockAssetsCache(c, s.rootdir, "trusted", []string{ 1598 "asset-asset-hash-1", 1599 }) 1600 1601 bootloader.Force(mtbl) 1602 defer bootloader.Force(nil) 1603 1604 // system is encrypted 1605 s.stampSealedKeys(c, s.rootdir) 1606 1607 model := s.uc20dev.Model() 1608 1609 modeenv := &boot.Modeenv{ 1610 Mode: "run", 1611 // keep this comment to make old gofmt happy 1612 CurrentRecoverySystems: []string{"20200825"}, 1613 GoodRecoverySystems: []string{"20200825"}, 1614 CurrentKernels: []string{}, 1615 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 1616 "asset": []string{"asset-hash-1"}, 1617 }, 1618 1619 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1620 "asset": []string{"asset-hash-1"}, 1621 }, 1622 1623 Model: model.Model(), 1624 BrandID: model.BrandID(), 1625 Grade: string(model.Grade()), 1626 ModelSignKeyID: model.SignKeyID(), 1627 } 1628 if tc.systemLabelAddToCurrent { 1629 modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel) 1630 } 1631 if tc.systemLabelAddToGood { 1632 modeenv.GoodRecoverySystems = append(modeenv.GoodRecoverySystems, systemLabel) 1633 } 1634 1635 c.Assert(modeenv.WriteTo(""), IsNil) 1636 1637 var readSeedSeenLabels []string 1638 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1639 // the mock bootloader can only mock a single recovery boot 1640 // chain, so pretend both seeds use the same kernel, but keep track of the labels 1641 readSeedSeenLabels = append(readSeedSeenLabels, label) 1642 return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil 1643 }) 1644 defer restore() 1645 1646 resealCalls := 0 1647 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1648 resealCalls++ 1649 c.Assert(params, NotNil) 1650 c.Assert(params.ModelParams, HasLen, 1) 1651 switch resealCalls { 1652 case 1: 1653 c.Check(params.KeyFiles, DeepEquals, []string{ 1654 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1655 }) 1656 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1657 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1658 }) 1659 return nil 1660 case 2: 1661 c.Check(params.KeyFiles, DeepEquals, []string{ 1662 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1663 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1664 }) 1665 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1666 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1667 }) 1668 return tc.resealRecoveryKeyErr 1669 default: 1670 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 1671 return fmt.Errorf("unexpected call") 1672 } 1673 }) 1674 defer restore() 1675 1676 err := boot.DropRecoverySystem(s.uc20dev, systemLabel) 1677 if tc.expectedErr == "" { 1678 c.Assert(err, IsNil) 1679 } else { 1680 c.Assert(err, ErrorMatches, tc.expectedErr) 1681 } 1682 c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"}) 1683 c.Check(resealCalls, Equals, tc.resealCalls) 1684 1685 modeenvRead, err := boot.ReadModeenv("") 1686 c.Assert(err, IsNil) 1687 // current is unchanged 1688 c.Check(modeenvRead.GoodRecoverySystems, DeepEquals, tc.expectedCurrentSystemsList) 1689 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, tc.expectedGoodSystemsList) 1690 } 1691 1692 func (s *systemsSuite) TestDropRecoverySystemHappy(c *C) { 1693 s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{ 1694 systemLabelAddToCurrent: true, 1695 systemLabelAddToGood: true, 1696 resealCalls: 2, 1697 1698 expectedGoodSystemsList: []string{"20200825"}, 1699 expectedCurrentSystemsList: []string{"20200825"}, 1700 }) 1701 } 1702 1703 func (s *systemsSuite) TestDropRecoverySystemAlreadyGoneFromBoth(c *C) { 1704 s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{ 1705 systemLabelAddToCurrent: false, 1706 systemLabelAddToGood: false, 1707 resealCalls: 2, 1708 1709 expectedGoodSystemsList: []string{"20200825"}, 1710 expectedCurrentSystemsList: []string{"20200825"}, 1711 }) 1712 } 1713 1714 func (s *systemsSuite) TestDropRecoverySystemAlreadyGoneOne(c *C) { 1715 s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{ 1716 systemLabelAddToCurrent: true, 1717 systemLabelAddToGood: false, 1718 resealCalls: 2, 1719 1720 expectedGoodSystemsList: []string{"20200825"}, 1721 expectedCurrentSystemsList: []string{"20200825"}, 1722 }) 1723 } 1724 1725 func (s *systemsSuite) TestDropRecoverySystemResealErr(c *C) { 1726 s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{ 1727 systemLabelAddToCurrent: true, 1728 systemLabelAddToGood: false, 1729 resealCalls: 2, 1730 resealRecoveryKeyErr: fmt.Errorf("mocked error"), 1731 expectedErr: `cannot reseal the fallback encryption keys: mocked error`, 1732 1733 expectedGoodSystemsList: []string{"20200825"}, 1734 expectedCurrentSystemsList: []string{"20200825"}, 1735 }) 1736 } 1737 1738 type initramfsMarkTryRecoverySystemSuite struct { 1739 baseSystemsSuite 1740 1741 bl *bootloadertest.MockBootloader 1742 } 1743 1744 var _ = Suite(&initramfsMarkTryRecoverySystemSuite{}) 1745 1746 func (s *initramfsMarkTryRecoverySystemSuite) SetUpTest(c *C) { 1747 s.baseSystemsSuite.SetUpTest(c) 1748 1749 s.bl = bootloadertest.Mock("bootloader", s.bootdir) 1750 bootloader.Force(s.bl) 1751 s.AddCleanup(func() { bootloader.Force(nil) }) 1752 } 1753 1754 func (s *initramfsMarkTryRecoverySystemSuite) testMarkRecoverySystemForRun(c *C, outcome boot.TryRecoverySystemOutcome, expectingStatus string) { 1755 err := s.bl.SetBootVars(map[string]string{ 1756 "recovery_system_status": "try", 1757 "try_recovery_system": "1234", 1758 }) 1759 c.Assert(err, IsNil) 1760 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome) 1761 c.Assert(err, IsNil) 1762 1763 expectedVars := map[string]string{ 1764 "snapd_recovery_mode": "run", 1765 "snapd_recovery_system": "", 1766 1767 "recovery_system_status": expectingStatus, 1768 "try_recovery_system": "1234", 1769 } 1770 1771 vars, err := s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system", 1772 "recovery_system_status", "try_recovery_system") 1773 c.Assert(err, IsNil) 1774 c.Check(vars, DeepEquals, expectedVars) 1775 1776 err = s.bl.SetBootVars(map[string]string{ 1777 // the status is overwritten, even if it's completely bogus 1778 "recovery_system_status": "foobar", 1779 "try_recovery_system": "1234", 1780 }) 1781 c.Assert(err, IsNil) 1782 1783 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome) 1784 c.Assert(err, IsNil) 1785 1786 vars, err = s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system", 1787 "recovery_system_status", "try_recovery_system") 1788 c.Assert(err, IsNil) 1789 c.Check(vars, DeepEquals, expectedVars) 1790 } 1791 1792 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkTryRecoverySystemSuccess(c *C) { 1793 s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeSuccess, "tried") 1794 } 1795 1796 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemFailure(c *C) { 1797 s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeFailure, "try") 1798 } 1799 1800 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemBogus(c *C) { 1801 s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeInconsistent, "") 1802 } 1803 1804 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemErr(c *C) { 1805 s.bl.SetErr = fmt.Errorf("set fails") 1806 err := boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeSuccess) 1807 c.Assert(err, ErrorMatches, "set fails") 1808 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeFailure) 1809 c.Assert(err, ErrorMatches, "set fails") 1810 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeInconsistent) 1811 c.Assert(err, ErrorMatches, "set fails") 1812 } 1813 1814 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemUnset(c *C) { 1815 err := s.bl.SetBootVars(map[string]string{ 1816 "recovery_system_status": "try", 1817 // system is unset 1818 "try_recovery_system": "", 1819 }) 1820 c.Assert(err, IsNil) 1821 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1822 c.Assert(err, ErrorMatches, `try recovery system is unset but status is "try"`) 1823 c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true) 1824 c.Check(isTry, Equals, false) 1825 } 1826 1827 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemBogus(c *C) { 1828 err := s.bl.SetBootVars(map[string]string{ 1829 "recovery_system_status": "foobar", 1830 "try_recovery_system": "1234", 1831 }) 1832 c.Assert(err, IsNil) 1833 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1834 c.Assert(err, ErrorMatches, `unexpected recovery system status "foobar"`) 1835 c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true) 1836 c.Check(isTry, Equals, false) 1837 1838 // errors out even if try recovery system label is unset 1839 err = s.bl.SetBootVars(map[string]string{ 1840 "recovery_system_status": "no-label", 1841 "try_recovery_system": "", 1842 }) 1843 c.Assert(err, IsNil) 1844 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1845 c.Assert(err, ErrorMatches, `unexpected recovery system status "no-label"`) 1846 c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true) 1847 c.Check(isTry, Equals, false) 1848 } 1849 1850 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemNoTryingStatus(c *C) { 1851 err := s.bl.SetBootVars(map[string]string{ 1852 "recovery_system_status": "", 1853 "try_recovery_system": "", 1854 }) 1855 c.Assert(err, IsNil) 1856 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1857 c.Assert(err, IsNil) 1858 c.Check(isTry, Equals, false) 1859 1860 err = s.bl.SetBootVars(map[string]string{ 1861 // status is checked first 1862 "recovery_system_status": "", 1863 "try_recovery_system": "1234", 1864 }) 1865 c.Assert(err, IsNil) 1866 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1867 c.Assert(err, IsNil) 1868 c.Check(isTry, Equals, false) 1869 } 1870 1871 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemSameSystem(c *C) { 1872 // the usual scenario 1873 err := s.bl.SetBootVars(map[string]string{ 1874 "recovery_system_status": "try", 1875 "try_recovery_system": "1234", 1876 }) 1877 c.Assert(err, IsNil) 1878 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1879 c.Assert(err, IsNil) 1880 c.Check(isTry, Equals, true) 1881 1882 // pretend the system has already been tried 1883 err = s.bl.SetBootVars(map[string]string{ 1884 "recovery_system_status": "tried", 1885 "try_recovery_system": "1234", 1886 }) 1887 c.Assert(err, IsNil) 1888 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1889 c.Assert(err, IsNil) 1890 c.Check(isTry, Equals, true) 1891 } 1892 1893 func (s *initramfsMarkTryRecoverySystemSuite) TestRecoverySystemSuccessDifferent(c *C) { 1894 // other system 1895 err := s.bl.SetBootVars(map[string]string{ 1896 "recovery_system_status": "try", 1897 "try_recovery_system": "9999", 1898 }) 1899 c.Assert(err, IsNil) 1900 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1901 c.Assert(err, IsNil) 1902 c.Check(isTry, Equals, false) 1903 1904 // same when the other system has already been tried 1905 err = s.bl.SetBootVars(map[string]string{ 1906 "recovery_system_status": "tried", 1907 "try_recovery_system": "9999", 1908 }) 1909 c.Assert(err, IsNil) 1910 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1911 c.Assert(err, IsNil) 1912 c.Check(isTry, Equals, false) 1913 }