github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/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/timings" 38 ) 39 40 type baseSystemsSuite struct { 41 baseBootenvSuite 42 } 43 44 func (s *baseSystemsSuite) SetUpTest(c *C) { 45 s.baseBootenvSuite.SetUpTest(c) 46 c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil) 47 c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil) 48 } 49 50 type systemsSuite struct { 51 baseSystemsSuite 52 53 uc20dev boot.Device 54 55 runKernelBf bootloader.BootFile 56 recoveryKernelBf bootloader.BootFile 57 seedKernelSnap *seed.Snap 58 } 59 60 var _ = Suite(&systemsSuite{}) 61 62 func (s *systemsSuite) mockTrustedBootloaderWithAssetAndChains(c *C, runKernelBf, recoveryKernelBf bootloader.BootFile) *bootloadertest.MockTrustedAssetsBootloader { 63 mockAssetsCache(c, s.rootdir, "trusted", []string{ 64 "asset-asset-hash-1", 65 }) 66 67 mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets() 68 mtbl.TrustedAssetsList = []string{"asset-1"} 69 mtbl.StaticCommandLine = "static cmdline" 70 mtbl.BootChainList = []bootloader.BootFile{ 71 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 72 runKernelBf, 73 } 74 mtbl.RecoveryBootChainList = []bootloader.BootFile{ 75 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 76 recoveryKernelBf, 77 } 78 bootloader.Force(mtbl) 79 return mtbl 80 } 81 82 func (s *systemsSuite) SetUpTest(c *C) { 83 s.baseBootenvSuite.SetUpTest(c) 84 85 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil }) 86 s.AddCleanup(restore) 87 88 s.uc20dev = boottest.MockUC20Device("", nil) 89 90 // run kernel 91 s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap", 92 "kernel.efi", bootloader.RoleRunMode) 93 // seed (recovery) kernel 94 s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 95 "kernel.efi", bootloader.RoleRecovery) 96 97 s.seedKernelSnap = &seed.Snap{ 98 Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 99 SideInfo: &snap.SideInfo{ 100 RealName: "pc-kernel", 101 Revision: snap.Revision{N: 1}, 102 }, 103 } 104 } 105 106 func (s *systemsSuite) TestSetTryRecoverySystemEncrypted(c *C) { 107 mockAssetsCache(c, s.rootdir, "trusted", []string{ 108 "asset-asset-hash-1", 109 }) 110 111 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 112 bootloader.Force(mtbl) 113 defer bootloader.Force(nil) 114 115 // system is encrypted 116 s.stampSealedKeys(c, s.rootdir) 117 118 modeenv := &boot.Modeenv{ 119 Mode: "run", 120 // keep this comment to make old gofmt happy 121 CurrentRecoverySystems: []string{"20200825"}, 122 GoodRecoverySystems: []string{"20200825"}, 123 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 124 "asset": []string{"asset-hash-1"}, 125 }, 126 CurrentTrustedBootAssets: boot.BootAssetsMap{ 127 "asset": []string{"asset-hash-1"}, 128 }, 129 CurrentKernels: []string{"pc-kernel_500.snap"}, 130 } 131 c.Assert(modeenv.WriteTo(""), IsNil) 132 133 var readSeedSeenLabels []string 134 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 135 // the mock bootloader can only mock a single recovery boot 136 // chain, so pretend both seeds use the same kernel, but keep track of the labels 137 readSeedSeenLabels = append(readSeedSeenLabels, label) 138 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 139 }) 140 defer restore() 141 142 resealCalls := 0 143 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 144 resealCalls++ 145 // bootloader variables have already been modified 146 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 147 c.Assert(params, NotNil) 148 c.Assert(params.ModelParams, HasLen, 1) 149 switch resealCalls { 150 case 1: 151 c.Check(params.KeyFiles, DeepEquals, []string{ 152 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 153 }) 154 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 155 "snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline", 156 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 157 "snapd_recovery_mode=run static cmdline", 158 }) 159 return nil 160 case 2: 161 c.Check(params.KeyFiles, DeepEquals, []string{ 162 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 163 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 164 }) 165 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 166 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 167 }) 168 return nil 169 default: 170 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 171 return fmt.Errorf("unexpected call") 172 } 173 }) 174 defer restore() 175 176 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 177 c.Assert(err, IsNil) 178 179 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 180 c.Assert(err, IsNil) 181 c.Check(vars, DeepEquals, map[string]string{ 182 "try_recovery_system": "1234", 183 "recovery_system_status": "try", 184 }) 185 // run and recovery keys 186 c.Check(resealCalls, Equals, 2) 187 c.Check(readSeedSeenLabels, DeepEquals, []string{ 188 "20200825", "1234", // current recovery systems for run key 189 "20200825", // good recovery systems for recovery keys 190 }) 191 192 modeenvRead, err := boot.ReadModeenv("") 193 c.Assert(err, IsNil) 194 c.Check(modeenvRead.DeepEqual(&boot.Modeenv{ 195 Mode: "run", 196 // keep this comment to make old gofmt happy 197 CurrentRecoverySystems: []string{"20200825", "1234"}, 198 GoodRecoverySystems: []string{"20200825"}, 199 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 200 "asset": []string{"asset-hash-1"}, 201 }, 202 CurrentTrustedBootAssets: boot.BootAssetsMap{ 203 "asset": []string{"asset-hash-1"}, 204 }, 205 CurrentKernels: []string{"pc-kernel_500.snap"}, 206 }), Equals, true) 207 } 208 209 func (s *systemsSuite) TestSetTryRecoverySystemSimple(c *C) { 210 mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets() 211 bootloader.Force(mtbl) 212 defer bootloader.Force(nil) 213 214 modeenv := &boot.Modeenv{ 215 Mode: "run", 216 // keep this comment to make old gofmt happy 217 CurrentRecoverySystems: []string{"20200825"}, 218 } 219 c.Assert(modeenv.WriteTo(""), IsNil) 220 221 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 222 return fmt.Errorf("unexpected call") 223 }) 224 s.AddCleanup(restore) 225 226 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 227 c.Assert(err, IsNil) 228 229 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 230 c.Assert(err, IsNil) 231 c.Check(vars, DeepEquals, map[string]string{ 232 "try_recovery_system": "1234", 233 "recovery_system_status": "try", 234 }) 235 236 modeenvRead, err := boot.ReadModeenv("") 237 c.Assert(err, IsNil) 238 c.Check(modeenvRead.DeepEqual(&boot.Modeenv{ 239 Mode: "run", 240 // keep this comment to make old gofmt happy 241 CurrentRecoverySystems: []string{"20200825", "1234"}, 242 }), Equals, true) 243 } 244 245 func (s *systemsSuite) TestSetTryRecoverySystemSetBootVarsErr(c *C) { 246 mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets() 247 bootloader.Force(mtbl) 248 defer bootloader.Force(nil) 249 250 modeenv := &boot.Modeenv{ 251 Mode: "run", 252 // keep this comment to make old gofmt happy 253 CurrentRecoverySystems: []string{"20200825"}, 254 } 255 c.Assert(modeenv.WriteTo(""), IsNil) 256 257 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 258 return fmt.Errorf("unexpected call") 259 }) 260 s.AddCleanup(restore) 261 262 mtbl.BootVars = map[string]string{ 263 "try_recovery_system": "mock", 264 "recovery_system_status": "mock", 265 } 266 mtbl.SetErrFunc = func() error { 267 switch mtbl.SetBootVarsCalls { 268 case 1: 269 return fmt.Errorf("set boot vars fails") 270 case 2: 271 // called during cleanup 272 return nil 273 default: 274 return fmt.Errorf("unexpected call") 275 } 276 } 277 278 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 279 c.Assert(err, ErrorMatches, "set boot vars fails") 280 281 // cleared 282 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 283 c.Assert(err, IsNil) 284 c.Check(vars, DeepEquals, map[string]string{ 285 "try_recovery_system": "", 286 "recovery_system_status": "", 287 }) 288 289 modeenvRead, err := boot.ReadModeenv("") 290 c.Assert(err, IsNil) 291 // modeenv is unchanged 292 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 293 } 294 295 func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorBeforeReseal(c *C) { 296 mockAssetsCache(c, s.rootdir, "trusted", []string{ 297 "asset-asset-hash-1", 298 }) 299 300 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 301 defer bootloader.Force(nil) 302 303 // system is encrypted 304 s.stampSealedKeys(c, s.rootdir) 305 306 modeenv := &boot.Modeenv{ 307 Mode: "run", 308 // keep this comment to make old gofmt happy 309 CurrentRecoverySystems: []string{"20200825"}, 310 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 311 "asset": []string{"asset-hash-1"}, 312 }, 313 314 CurrentTrustedBootAssets: boot.BootAssetsMap{ 315 "asset": []string{"asset-hash-1"}, 316 }, 317 } 318 c.Assert(modeenv.WriteTo(""), IsNil) 319 320 readSeedCalls := 0 321 cleanupTriggered := false 322 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 323 readSeedCalls++ 324 // this is the reseal cleanup path 325 switch readSeedCalls { 326 case 1: 327 // called for the first system 328 c.Assert(label, Equals, "20200825") 329 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 330 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 331 case 2: 332 // called for the 'try' system 333 c.Assert(label, Equals, "1234") 334 // modeenv is updated first 335 modeenvRead, err := boot.ReadModeenv("") 336 c.Assert(err, IsNil) 337 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{ 338 "20200825", "1234", 339 }) 340 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 341 // we are triggering the cleanup by returning an error now 342 cleanupTriggered = true 343 return nil, nil, fmt.Errorf("seed read essential fails") 344 case 3: 345 // (cleanup) recovery boot chains for run key, called 346 // for the first system only 347 fallthrough 348 case 4: 349 // (cleanup) recovery boot chains for recovery keys 350 c.Assert(label, Equals, "20200825") 351 // boot variables already updated 352 c.Check(mtbl.SetBootVarsCalls, Equals, 2) 353 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 354 default: 355 return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls) 356 } 357 }) 358 defer restore() 359 360 resealCalls := 0 361 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 362 resealCalls++ 363 if cleanupTriggered { 364 return nil 365 } 366 return fmt.Errorf("unexpected call") 367 }) 368 defer restore() 369 370 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 371 c.Assert(err, ErrorMatches, ".*: seed read essential fails") 372 373 // failed after the call to read the 'try' system seed 374 c.Check(readSeedCalls, Equals, 4) 375 // called twice during cleanup for run and recovery keys 376 c.Check(resealCalls, Equals, 2) 377 378 modeenvRead, err := boot.ReadModeenv("") 379 c.Assert(err, IsNil) 380 // modeenv is unchanged 381 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 382 // bootloader variables have been cleared 383 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 384 c.Assert(err, IsNil) 385 c.Check(vars, DeepEquals, map[string]string{ 386 "try_recovery_system": "", 387 "recovery_system_status": "", 388 }) 389 } 390 391 func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorAfterReseal(c *C) { 392 mockAssetsCache(c, s.rootdir, "trusted", []string{ 393 "asset-asset-hash-1", 394 }) 395 396 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 397 bootloader.Force(mtbl) 398 defer bootloader.Force(nil) 399 400 // system is encrypted 401 s.stampSealedKeys(c, s.rootdir) 402 403 modeenv := &boot.Modeenv{ 404 Mode: "run", 405 // keep this comment to make old gofmt happy 406 CurrentRecoverySystems: []string{"20200825"}, 407 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 408 "asset": []string{"asset-hash-1"}, 409 }, 410 411 CurrentTrustedBootAssets: boot.BootAssetsMap{ 412 "asset": []string{"asset-hash-1"}, 413 }, 414 } 415 c.Assert(modeenv.WriteTo(""), IsNil) 416 417 readSeedCalls := 0 418 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 419 readSeedCalls++ 420 // this is the reseal cleanup path 421 422 switch readSeedCalls { 423 case 1: 424 // called for the first system 425 c.Assert(label, Equals, "20200825") 426 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 427 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 428 case 2: 429 // called for the 'try' system 430 c.Assert(label, Equals, "1234") 431 // modeenv is updated first 432 modeenvRead, err := boot.ReadModeenv("") 433 c.Assert(err, IsNil) 434 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{ 435 "20200825", "1234", 436 }) 437 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 438 // still good 439 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 440 case 3: 441 // recovery boot chains for a good recovery system 442 c.Check(mtbl.SetBootVarsCalls, Equals, 1) 443 fallthrough 444 case 4: 445 // (cleanup) recovery boot chains for run key, called 446 // for the first system only 447 fallthrough 448 case 5: 449 // (cleanup) recovery boot chains for recovery keys 450 c.Assert(label, Equals, "20200825") 451 // boot variables already updated 452 if readSeedCalls >= 4 { 453 c.Check(mtbl.SetBootVarsCalls, Equals, 2) 454 } 455 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 456 default: 457 return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls) 458 } 459 }) 460 defer restore() 461 462 resealCalls := 0 463 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 464 resealCalls++ 465 switch resealCalls { 466 case 1: 467 // attempt to reseal the run key 468 return fmt.Errorf("reseal fails") 469 case 2, 3: 470 // reseal of run and recovery keys 471 return nil 472 default: 473 return fmt.Errorf("unexpected call") 474 475 } 476 }) 477 defer restore() 478 479 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 480 c.Assert(err, ErrorMatches, "cannot reseal the encryption key: reseal fails") 481 482 // failed after the call to read the 'try' system seed 483 c.Check(readSeedCalls, Equals, 5) 484 // called 3 times, once when mocked failure occurs, twice during cleanup 485 // for run and recovery keys 486 c.Check(resealCalls, Equals, 3) 487 488 modeenvRead, err := boot.ReadModeenv("") 489 c.Assert(err, IsNil) 490 // modeenv is unchanged 491 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 492 // bootloader variables have been cleared 493 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 494 c.Assert(err, IsNil) 495 c.Check(vars, DeepEquals, map[string]string{ 496 "try_recovery_system": "", 497 "recovery_system_status": "", 498 }) 499 } 500 501 func (s *systemsSuite) TestSetTryRecoverySystemCleanupError(c *C) { 502 mockAssetsCache(c, s.rootdir, "trusted", []string{ 503 "asset-asset-hash-1", 504 }) 505 506 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 507 bootloader.Force(mtbl) 508 defer bootloader.Force(nil) 509 510 // system is encrypted 511 s.stampSealedKeys(c, s.rootdir) 512 513 modeenv := &boot.Modeenv{ 514 Mode: "run", 515 // keep this comment to make old gofmt happy 516 CurrentRecoverySystems: []string{"20200825"}, 517 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 518 "asset": []string{"asset-hash-1"}, 519 }, 520 521 CurrentTrustedBootAssets: boot.BootAssetsMap{ 522 "asset": []string{"asset-hash-1"}, 523 }, 524 } 525 c.Assert(modeenv.WriteTo(""), IsNil) 526 527 readSeedCalls := 0 528 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 529 readSeedCalls++ 530 // this is the reseal cleanup path 531 switch readSeedCalls { 532 case 1: 533 // called for the first system 534 c.Assert(label, Equals, "20200825") 535 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 536 case 2: 537 // called for the 'try' system 538 c.Assert(label, Equals, "1234") 539 // still good 540 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 541 case 3: 542 // recovery boot chains for a good recovery system 543 fallthrough 544 case 4: 545 // (cleanup) recovery boot chains for run key, called 546 // for the first system only 547 fallthrough 548 case 5: 549 // (cleanup) recovery boot chains for recovery keys 550 c.Assert(label, Equals, "20200825") 551 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 552 default: 553 return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls) 554 } 555 }) 556 defer restore() 557 558 resealCalls := 0 559 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 560 resealCalls++ 561 switch resealCalls { 562 case 1: 563 return fmt.Errorf("reseal fails") 564 case 2, 3: 565 // reseal of run and recovery keys 566 return fmt.Errorf("reseal in cleanup fails too") 567 default: 568 return fmt.Errorf("unexpected call") 569 570 } 571 }) 572 defer restore() 573 574 err := boot.SetTryRecoverySystem(s.uc20dev, "1234") 575 c.Assert(err, ErrorMatches, `cannot reseal the encryption key: reseal fails \(cleanup failed: cannot reseal the encryption key: reseal in cleanup fails too\)`) 576 577 // failed after the call to read the 'try' system seed 578 c.Check(readSeedCalls, Equals, 5) 579 // called twice, once when enabling the try system, once on cleanup 580 c.Check(resealCalls, Equals, 2) 581 582 modeenvRead, err := boot.ReadModeenv("") 583 c.Assert(err, IsNil) 584 // modeenv is unchanged 585 c.Check(modeenvRead.DeepEqual(modeenv), Equals, true) 586 // bootloader variables have been cleared regardless of reseal failing 587 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 588 c.Assert(err, IsNil) 589 c.Check(vars, DeepEquals, map[string]string{ 590 "try_recovery_system": "", 591 "recovery_system_status": "", 592 }) 593 } 594 595 func (s *systemsSuite) testInspectRecoverySystemOutcomeHappy(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, expectedOutcome boot.TryRecoverySystemOutcome, expectedErr string) { 596 bootloader.Force(mtbl) 597 defer bootloader.Force(nil) 598 599 // system is encrypted 600 s.stampSealedKeys(c, s.rootdir) 601 602 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 603 return nil, nil, fmt.Errorf("unexpected call") 604 }) 605 defer restore() 606 607 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 608 return fmt.Errorf("unexpected call") 609 }) 610 defer restore() 611 612 outcome, label, err := boot.InspectTryRecoverySystemOutcome(s.uc20dev) 613 if expectedErr == "" { 614 c.Assert(err, IsNil) 615 } else { 616 c.Assert(err, ErrorMatches, expectedErr) 617 } 618 c.Check(outcome, Equals, expectedOutcome) 619 switch outcome { 620 case boot.TryRecoverySystemOutcomeSuccess, boot.TryRecoverySystemOutcomeFailure: 621 c.Check(label, Equals, "1234") 622 default: 623 c.Check(label, Equals, "") 624 } 625 } 626 627 func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappySuccess(c *C) { 628 triedVars := map[string]string{ 629 "recovery_system_status": "tried", 630 "try_recovery_system": "1234", 631 } 632 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 633 err := mtbl.SetBootVars(triedVars) 634 c.Assert(err, IsNil) 635 636 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeSuccess, "") 637 638 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 639 c.Assert(err, IsNil) 640 c.Check(vars, DeepEquals, triedVars) 641 } 642 643 func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappyFailure(c *C) { 644 tryVars := map[string]string{ 645 "recovery_system_status": "try", 646 "try_recovery_system": "1234", 647 } 648 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 649 err := mtbl.SetBootVars(tryVars) 650 c.Assert(err, IsNil) 651 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, "") 652 653 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 654 c.Assert(err, IsNil) 655 c.Check(vars, DeepEquals, tryVars) 656 } 657 658 func (s *systemsSuite) TestInspectRecoverySystemOutcomeNotTried(c *C) { 659 notTriedVars := map[string]string{ 660 "recovery_system_status": "", 661 "try_recovery_system": "", 662 } 663 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 664 err := mtbl.SetBootVars(notTriedVars) 665 c.Assert(err, IsNil) 666 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeNoneTried, "") 667 } 668 669 func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBogusStatus(c *C) { 670 badVars := map[string]string{ 671 "recovery_system_status": "foo", 672 "try_recovery_system": "1234", 673 } 674 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 675 err := mtbl.SetBootVars(badVars) 676 c.Assert(err, IsNil) 677 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status "foo"`) 678 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 679 c.Assert(err, IsNil) 680 c.Check(vars, DeepEquals, badVars) 681 } 682 683 func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBadLabel(c *C) { 684 badVars := map[string]string{ 685 "recovery_system_status": "tried", 686 "try_recovery_system": "", 687 } 688 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 689 err := mtbl.SetBootVars(badVars) 690 c.Assert(err, IsNil) 691 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `try recovery system is unset but status is "tried"`) 692 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 693 c.Assert(err, IsNil) 694 c.Check(vars, DeepEquals, badVars) 695 } 696 697 func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentUnexpectedLabel(c *C) { 698 badVars := map[string]string{ 699 "recovery_system_status": "", 700 "try_recovery_system": "1234", 701 } 702 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 703 err := mtbl.SetBootVars(badVars) 704 c.Assert(err, IsNil) 705 s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status ""`) 706 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 707 c.Assert(err, IsNil) 708 c.Check(vars, DeepEquals, badVars) 709 } 710 711 func (s *systemsSuite) testClearRecoverySystem(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, systemLabel string, resealErr error, expectedErr string) { 712 mockAssetsCache(c, s.rootdir, "trusted", []string{ 713 "asset-asset-hash-1", 714 }) 715 716 bootloader.Force(mtbl) 717 defer bootloader.Force(nil) 718 719 // system is encrypted 720 s.stampSealedKeys(c, s.rootdir) 721 722 modeenv := &boot.Modeenv{ 723 Mode: "run", 724 // keep this comment to make old gofmt happy 725 CurrentRecoverySystems: []string{"20200825"}, 726 GoodRecoverySystems: []string{"20200825"}, 727 CurrentKernels: []string{}, 728 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 729 "asset": []string{"asset-hash-1"}, 730 }, 731 732 CurrentTrustedBootAssets: boot.BootAssetsMap{ 733 "asset": []string{"asset-hash-1"}, 734 }, 735 } 736 if systemLabel != "" { 737 modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel) 738 } 739 c.Assert(modeenv.WriteTo(""), IsNil) 740 741 var readSeedSeenLabels []string 742 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 743 // the mock bootloader can only mock a single recovery boot 744 // chain, so pretend both seeds use the same kernel, but keep track of the labels 745 readSeedSeenLabels = append(readSeedSeenLabels, label) 746 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 747 }) 748 defer restore() 749 750 resealCalls := 0 751 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 752 resealCalls++ 753 c.Assert(params, NotNil) 754 c.Assert(params.ModelParams, HasLen, 1) 755 switch resealCalls { 756 case 1: 757 c.Check(params.KeyFiles, DeepEquals, []string{ 758 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 759 }) 760 return resealErr 761 case 2: 762 c.Check(params.KeyFiles, DeepEquals, []string{ 763 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 764 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 765 }) 766 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 767 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 768 }) 769 return nil 770 default: 771 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 772 return fmt.Errorf("unexpected call") 773 } 774 }) 775 defer restore() 776 777 err := boot.ClearTryRecoverySystem(s.uc20dev, systemLabel) 778 if expectedErr == "" { 779 c.Assert(err, IsNil) 780 } else { 781 c.Assert(err, ErrorMatches, expectedErr) 782 } 783 784 // only one seed system accessed 785 c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"}) 786 if resealErr == nil { 787 // called twice, for run and recovery keys 788 c.Check(resealCalls, Equals, 2) 789 } else { 790 // fails on run key 791 c.Check(resealCalls, Equals, 1) 792 } 793 794 modeenvRead, err := boot.ReadModeenv("") 795 c.Assert(err, IsNil) 796 // modeenv systems list has one entry only 797 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{ 798 "20200825", 799 }) 800 } 801 802 func (s *systemsSuite) TestClearRecoverySystemHappy(c *C) { 803 setVars := map[string]string{ 804 "recovery_system_status": "try", 805 "try_recovery_system": "1234", 806 } 807 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 808 err := mtbl.SetBootVars(setVars) 809 c.Assert(err, IsNil) 810 811 s.testClearRecoverySystem(c, mtbl, "1234", nil, "") 812 // bootloader variables have been cleared 813 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 814 c.Assert(err, IsNil) 815 c.Check(vars, DeepEquals, map[string]string{ 816 "try_recovery_system": "", 817 "recovery_system_status": "", 818 }) 819 } 820 821 func (s *systemsSuite) TestClearRecoverySystemTriedHappy(c *C) { 822 setVars := map[string]string{ 823 "recovery_system_status": "tried", 824 "try_recovery_system": "1234", 825 } 826 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 827 err := mtbl.SetBootVars(setVars) 828 c.Assert(err, IsNil) 829 830 s.testClearRecoverySystem(c, mtbl, "1234", nil, "") 831 // bootloader variables have been cleared 832 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 833 c.Assert(err, IsNil) 834 c.Check(vars, DeepEquals, map[string]string{ 835 "try_recovery_system": "", 836 "recovery_system_status": "", 837 }) 838 } 839 840 func (s *systemsSuite) TestClearRecoverySystemInconsistentStateHappy(c *C) { 841 setVars := map[string]string{ 842 "recovery_system_status": "foo", 843 "try_recovery_system": "", 844 } 845 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 846 err := mtbl.SetBootVars(setVars) 847 c.Assert(err, IsNil) 848 849 s.testClearRecoverySystem(c, mtbl, "1234", nil, "") 850 // bootloader variables have been cleared 851 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 852 c.Assert(err, IsNil) 853 c.Check(vars, DeepEquals, map[string]string{ 854 "try_recovery_system": "", 855 "recovery_system_status": "", 856 }) 857 } 858 859 func (s *systemsSuite) TestClearRecoverySystemInconsistentNoLabelHappy(c *C) { 860 setVars := map[string]string{ 861 "recovery_system_status": "this-will-be-gone", 862 "try_recovery_system": "this-too", 863 } 864 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 865 err := mtbl.SetBootVars(setVars) 866 c.Assert(err, IsNil) 867 868 // clear without passing the system label, just clears the relevant boot 869 // variables 870 const noLabel = "" 871 s.testClearRecoverySystem(c, mtbl, noLabel, nil, "") 872 // bootloader variables have been cleared 873 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 874 c.Assert(err, IsNil) 875 c.Check(vars, DeepEquals, map[string]string{ 876 "try_recovery_system": "", 877 "recovery_system_status": "", 878 }) 879 } 880 881 func (s *systemsSuite) TestClearRecoverySystemResealFails(c *C) { 882 setVars := map[string]string{ 883 "recovery_system_status": "try", 884 "try_recovery_system": "1234", 885 } 886 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 887 err := mtbl.SetBootVars(setVars) 888 c.Assert(err, IsNil) 889 890 s.testClearRecoverySystem(c, mtbl, "1234", fmt.Errorf("reseal fails"), "cannot reseal the encryption key: reseal fails") 891 // bootloader variables have been cleared 892 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 893 c.Assert(err, IsNil) 894 // variables were cleared 895 c.Check(vars, DeepEquals, map[string]string{ 896 "try_recovery_system": "", 897 "recovery_system_status": "", 898 }) 899 } 900 901 func (s *systemsSuite) TestClearRecoverySystemSetBootVarsFails(c *C) { 902 setVars := map[string]string{ 903 "recovery_system_status": "try", 904 "try_recovery_system": "1234", 905 } 906 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 907 err := mtbl.SetBootVars(setVars) 908 c.Assert(err, IsNil) 909 mtbl.SetErr = fmt.Errorf("set boot vars fails") 910 911 s.testClearRecoverySystem(c, mtbl, "1234", nil, "set boot vars fails") 912 } 913 914 func (s *systemsSuite) TestClearRecoverySystemReboot(c *C) { 915 setVars := map[string]string{ 916 "recovery_system_status": "try", 917 "try_recovery_system": "1234", 918 } 919 mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf) 920 err := mtbl.SetBootVars(setVars) 921 c.Assert(err, IsNil) 922 923 mockAssetsCache(c, s.rootdir, "trusted", []string{ 924 "asset-asset-hash-1", 925 }) 926 927 bootloader.Force(mtbl) 928 defer bootloader.Force(nil) 929 930 // system is encrypted 931 s.stampSealedKeys(c, s.rootdir) 932 933 modeenv := &boot.Modeenv{ 934 Mode: "run", 935 // keep this comment to make old gofmt happy 936 CurrentRecoverySystems: []string{"20200825", "1234"}, 937 CurrentKernels: []string{}, 938 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 939 "asset": []string{"asset-hash-1"}, 940 }, 941 942 CurrentTrustedBootAssets: boot.BootAssetsMap{ 943 "asset": []string{"asset-hash-1"}, 944 }, 945 } 946 c.Assert(modeenv.WriteTo(""), IsNil) 947 948 var readSeedSeenLabels []string 949 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 950 // the mock bootloader can only mock a single recovery boot 951 // chain, so pretend both seeds use the same kernel, but keep track of the labels 952 readSeedSeenLabels = append(readSeedSeenLabels, label) 953 return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil 954 }) 955 defer restore() 956 957 resealCalls := 0 958 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 959 resealCalls++ 960 c.Assert(params, NotNil) 961 c.Assert(params.ModelParams, HasLen, 1) 962 switch resealCalls { 963 case 1: 964 c.Check(params.KeyFiles, DeepEquals, []string{ 965 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 966 }) 967 panic("reseal panic") 968 case 2: 969 c.Check(params.KeyFiles, DeepEquals, []string{ 970 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 971 }) 972 return nil 973 case 3: 974 c.Check(params.KeyFiles, DeepEquals, []string{ 975 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 976 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 977 }) 978 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 979 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 980 }) 981 return nil 982 default: 983 c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls) 984 return fmt.Errorf("unexpected call") 985 986 } 987 }) 988 defer restore() 989 990 checkGoodState := func() { 991 // modeenv was already written 992 modeenvRead, err := boot.ReadModeenv("") 993 c.Assert(err, IsNil) 994 // modeenv systems list has one entry only 995 c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{ 996 "20200825", 997 }) 998 // bootloader variables have been cleared already 999 vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status") 1000 c.Assert(err, IsNil) 1001 // variables were cleared 1002 c.Check(vars, DeepEquals, map[string]string{ 1003 "try_recovery_system": "", 1004 "recovery_system_status": "", 1005 }) 1006 } 1007 1008 c.Assert(func() { 1009 boot.ClearTryRecoverySystem(s.uc20dev, "1234") 1010 }, PanicMatches, "reseal panic") 1011 // only one seed system accessed 1012 c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"}) 1013 // panicked on run key 1014 c.Check(resealCalls, Equals, 1) 1015 checkGoodState() 1016 1017 mtbl.SetErrFunc = func() error { 1018 panic("set boot vars panic") 1019 } 1020 c.Assert(func() { 1021 boot.ClearTryRecoverySystem(s.uc20dev, "1234") 1022 }, PanicMatches, "set boot vars panic") 1023 // we did not reach resealing yet 1024 c.Check(resealCalls, Equals, 1) 1025 checkGoodState() 1026 1027 mtbl.SetErrFunc = nil 1028 err = boot.ClearTryRecoverySystem(s.uc20dev, "1234") 1029 c.Assert(err, IsNil) 1030 checkGoodState() 1031 } 1032 1033 type initramfsMarkTryRecoverySystemSuite struct { 1034 baseSystemsSuite 1035 1036 bl *bootloadertest.MockBootloader 1037 } 1038 1039 var _ = Suite(&initramfsMarkTryRecoverySystemSuite{}) 1040 1041 func (s *initramfsMarkTryRecoverySystemSuite) SetUpTest(c *C) { 1042 s.baseSystemsSuite.SetUpTest(c) 1043 1044 s.bl = bootloadertest.Mock("bootloader", s.bootdir) 1045 bootloader.Force(s.bl) 1046 s.AddCleanup(func() { bootloader.Force(nil) }) 1047 } 1048 1049 func (s *initramfsMarkTryRecoverySystemSuite) testMarkRecoverySystemForRun(c *C, outcome boot.TryRecoverySystemOutcome, expectingStatus string) { 1050 err := s.bl.SetBootVars(map[string]string{ 1051 "recovery_system_status": "try", 1052 "try_recovery_system": "1234", 1053 }) 1054 c.Assert(err, IsNil) 1055 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome) 1056 c.Assert(err, IsNil) 1057 1058 expectedVars := map[string]string{ 1059 "snapd_recovery_mode": "run", 1060 "snapd_recovery_system": "", 1061 1062 "recovery_system_status": expectingStatus, 1063 "try_recovery_system": "1234", 1064 } 1065 1066 vars, err := s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system", 1067 "recovery_system_status", "try_recovery_system") 1068 c.Assert(err, IsNil) 1069 c.Check(vars, DeepEquals, expectedVars) 1070 1071 err = s.bl.SetBootVars(map[string]string{ 1072 // the status is overwritten, even if it's completely bogus 1073 "recovery_system_status": "foobar", 1074 "try_recovery_system": "1234", 1075 }) 1076 c.Assert(err, IsNil) 1077 1078 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome) 1079 c.Assert(err, IsNil) 1080 1081 vars, err = s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system", 1082 "recovery_system_status", "try_recovery_system") 1083 c.Assert(err, IsNil) 1084 c.Check(vars, DeepEquals, expectedVars) 1085 } 1086 1087 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkTryRecoverySystemSuccess(c *C) { 1088 s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeSuccess, "tried") 1089 } 1090 1091 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemFailure(c *C) { 1092 s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeFailure, "try") 1093 } 1094 1095 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemBogus(c *C) { 1096 s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeInconsistent, "") 1097 } 1098 1099 func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemErr(c *C) { 1100 s.bl.SetErr = fmt.Errorf("set fails") 1101 err := boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeSuccess) 1102 c.Assert(err, ErrorMatches, "set fails") 1103 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeFailure) 1104 c.Assert(err, ErrorMatches, "set fails") 1105 err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeInconsistent) 1106 c.Assert(err, ErrorMatches, "set fails") 1107 } 1108 1109 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemUnset(c *C) { 1110 err := s.bl.SetBootVars(map[string]string{ 1111 "recovery_system_status": "try", 1112 // system is unset 1113 "try_recovery_system": "", 1114 }) 1115 c.Assert(err, IsNil) 1116 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1117 c.Assert(err, ErrorMatches, `try recovery system is unset but status is "try"`) 1118 c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true) 1119 c.Check(isTry, Equals, false) 1120 } 1121 1122 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemBogus(c *C) { 1123 err := s.bl.SetBootVars(map[string]string{ 1124 "recovery_system_status": "foobar", 1125 "try_recovery_system": "1234", 1126 }) 1127 c.Assert(err, IsNil) 1128 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1129 c.Assert(err, ErrorMatches, `unexpected recovery system status "foobar"`) 1130 c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true) 1131 c.Check(isTry, Equals, false) 1132 1133 // errors out even if try recovery system label is unset 1134 err = s.bl.SetBootVars(map[string]string{ 1135 "recovery_system_status": "no-label", 1136 "try_recovery_system": "", 1137 }) 1138 c.Assert(err, IsNil) 1139 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1140 c.Assert(err, ErrorMatches, `unexpected recovery system status "no-label"`) 1141 c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true) 1142 c.Check(isTry, Equals, false) 1143 } 1144 1145 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemNoTryingStatus(c *C) { 1146 err := s.bl.SetBootVars(map[string]string{ 1147 "recovery_system_status": "", 1148 "try_recovery_system": "", 1149 }) 1150 c.Assert(err, IsNil) 1151 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1152 c.Assert(err, IsNil) 1153 c.Check(isTry, Equals, false) 1154 1155 err = s.bl.SetBootVars(map[string]string{ 1156 // status is checked first 1157 "recovery_system_status": "", 1158 "try_recovery_system": "1234", 1159 }) 1160 c.Assert(err, IsNil) 1161 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1162 c.Assert(err, IsNil) 1163 c.Check(isTry, Equals, false) 1164 } 1165 1166 func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemSameSystem(c *C) { 1167 // the usual scenario 1168 err := s.bl.SetBootVars(map[string]string{ 1169 "recovery_system_status": "try", 1170 "try_recovery_system": "1234", 1171 }) 1172 c.Assert(err, IsNil) 1173 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1174 c.Assert(err, IsNil) 1175 c.Check(isTry, Equals, true) 1176 1177 // pretend the system has already been tried 1178 err = s.bl.SetBootVars(map[string]string{ 1179 "recovery_system_status": "tried", 1180 "try_recovery_system": "1234", 1181 }) 1182 c.Assert(err, IsNil) 1183 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1184 c.Assert(err, IsNil) 1185 c.Check(isTry, Equals, true) 1186 } 1187 1188 func (s *initramfsMarkTryRecoverySystemSuite) TestRecoverySystemSuccessDifferent(c *C) { 1189 // other system 1190 err := s.bl.SetBootVars(map[string]string{ 1191 "recovery_system_status": "try", 1192 "try_recovery_system": "9999", 1193 }) 1194 c.Assert(err, IsNil) 1195 isTry, err := boot.InitramfsIsTryingRecoverySystem("1234") 1196 c.Assert(err, IsNil) 1197 c.Check(isTry, Equals, false) 1198 1199 // same when the other system has already been tried 1200 err = s.bl.SetBootVars(map[string]string{ 1201 "recovery_system_status": "tried", 1202 "try_recovery_system": "9999", 1203 }) 1204 c.Assert(err, IsNil) 1205 isTry, err = boot.InitramfsIsTryingRecoverySystem("1234") 1206 c.Assert(err, IsNil) 1207 c.Check(isTry, Equals, false) 1208 }